From 664c56ccf0a233c4832c0a995f4fe054b0104801 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 9 Jun 2023 09:35:27 -0700 Subject: [PATCH 001/150] src/sage/features/sagemath.py: Expand documentation on # optional - sagemath-... --- src/sage/features/sagemath.py | 236 ++++++++++++++++++++++++++++++---- 1 file changed, 210 insertions(+), 26 deletions(-) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 7e545404de2..5f2dee3661b 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -3,8 +3,8 @@ """ # ***************************************************************************** -# Copyright (C) 2021 Matthias Koeppe -# 2021 Kwankyu Lee +# Copyright (C) 2021-2023 Matthias Koeppe +# 2021 Kwankyu Lee # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -22,10 +22,19 @@ class sagemath_doc_html(StaticFile): A :class:`Feature` which describes the presence of the documentation of the Sage library in HTML format. - EXAMPLES:: + Developers often use ``make build`` instead of ``make`` to avoid the + long time it takes to compile the documentation. Although commands + such as ``make ptest`` build the documentation before testing, other + test commands such as ``make ptestlong-nodoc`` or ``./sage -t --all`` + do not. + + All doctests that refer to the built documentation need to be marked + ``# optional - sagemath_doc_html``. + + TESTS:: sage: from sage.features.sagemath import sagemath_doc_html - sage: sagemath_doc_html().is_present() # optional - sagemath_doc_html + sage: sagemath_doc_html().is_present() # optional - sagemath_doc_html FeatureTestResult('sagemath_doc_html', True) """ def __init__(self): @@ -47,10 +56,53 @@ class sage__combinat(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.combinat`. - EXAMPLES:: + EXAMPLES: + + Python modules that provide elementary combinatorial objects such as :mod:`sage.combinat.subset`, + :mod:`sage.combinat.composition`, :mod:`sage.combinat.permutation` are always available; + there is no need for an ``# optional`` annotation:: + + sage: Permutation([1,2,3]).is_even() + True + sage: Permutation([6,1,4,5,2,3]).bruhat_inversions() + [[0, 1], [0, 2], [0, 3], [2, 4], [2, 5], [3, 4], [3, 5]] + + Use ``# optional - sage.combinat`` for doctests that use any other Python modules + from :mod:`sage.combinat`, for example :mod:`sage.combinat.tableau_tuple`:: + + sage: TableauTuple([[[7,8,9]],[],[[1,2,3],[4,5],[6]]]).shape() # optional - sage.combinat + ([3], [], [3, 2, 1]) + + Doctests that use Python modules from :mod:`sage.combinat` that involve trees, + graphs, hypergraphs, posets, quivers, combinatorial designs, + finite state machines etc. should be marked ``# optional - sage.combinat sage.graphs``:: + + sage: L = Poset({0: [1], 1: [2], 2:[3], 3:[4]}) # optional - sage.combinat sage.graphs + sage: L.is_chain() # optional - sage.combinat sage.graphs + True + + Doctests that use combinatorial modules/algebras, or root systems should use the annotation + ``# optional - sage.combinat sage.modules``:: + + sage: A = SchurAlgebra(QQ, 2, 3) # optional - sage.combinat sage.modules + sage: a = A.an_element(); a # optional - sage.combinat sage.modules + 2*S((1, 1, 1), (1, 1, 1)) + 2*S((1, 1, 1), (1, 1, 2)) + + 3*S((1, 1, 1), (1, 2, 2)) + sage: L = RootSystem(['A',3,1]).root_lattice() # optional - sage.combinat sage.modules + sage: PIR = L.positive_imaginary_roots(); PIR # optional - sage.combinat sage.modules + Positive imaginary roots of type ['A', 3, 1] + + Doctests that use lattices, semilattices, or Dynkin diagrams should use the annotation + ``# optional - sage.combinat sage.graphs sage.modules``:: + + sage: L = LatticePoset({0: [1,2], 1: [3], 2: [3,4], 3: [5], 4: [5]}) # optional - sage.combinat sage.graphs sage.modules + sage: L.meet_irreducibles() # optional - sage.combinat sage.graphs sage.modules + [1, 3, 4] + + TESTS:: sage: from sage.features.sagemath import sage__combinat - sage: sage__combinat().is_present() # optional - sage.combinat + sage: sage__combinat().is_present() # optional - sage.combinat FeatureTestResult('sage.combinat', True) """ def __init__(self): @@ -72,10 +124,25 @@ class sage__geometry__polyhedron(PythonModule): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.geometry.polyhedron`. - EXAMPLES:: + EXAMPLES: + + Doctests that use polyhedra, cones, geometric complexes, triangulations, etc. should use + the annotations ``# optional - sage.geometry.polyhedron``. + + sage: co = polytopes.truncated_tetrahedron() # optional - sage.geometry.polyhedron + sage: co.volume() # optional - sage.geometry.polyhedron + 184/3 + + Some constructions of polyhedra require additional annotations:: + + sage: perm_a3_reg_nf = polytopes.generalized_permutahedron( # optional - sage.combinat sage.geometry.polyhedron sage.rings.number_field + ....: ['A',3], regular=True, backend='number_field'); perm_a3_reg_nf + A 3-dimensional polyhedron in AA^3 defined as the convex hull of 24 vertices + + TESTS:: sage: from sage.features.sagemath import sage__geometry__polyhedron - sage: sage__geometry__polyhedron().is_present() # optional - sage.geometry.polyhedron + sage: sage__geometry__polyhedron().is_present() # optional - sage.geometry.polyhedron FeatureTestResult('sage.geometry.polyhedron', True) """ @@ -94,7 +161,20 @@ class sage__graphs(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.graphs`. - EXAMPLES:: + EXAMPLES: + + + Matroids are not implemented as posets in Sage but are instead closely tied to + linear algebra over fields; hence use ``# optional - sage.modules``:: + + sage: M = Matroid(Matrix(QQ, [[1, 0, 0, 0, 1, 1, 1], # optional - sage.modules + ....: [0, 1, 0, 1, 0, 1, 1], + ....: [0, 0, 1, 1, 1, 0, 1]])) + sage: N = M / [2] \ [3, 4] # optional - sage.modules + sage: sorted(N.groundset()) # optional - sage.modules + [0, 1, 5, 6] + + TESTS:: sage: from sage.features.sagemath import sage__graphs sage: sage__graphs().is_present() # optional - sage.graphs @@ -116,10 +196,20 @@ class sage__groups(JoinFeature): r""" A :class:`sage.features.Feature` describing the presence of ``sage.groups``. - EXAMPLES:: + EXAMPLES: + + Permutations and sets of permutations are always available, but permutation groups are + implemented in Sage using the :ref:`GAP ` system and require the annotation + ``# optional - sage.groups``:: + + sage: p = Permutation([2,1,4,3]) + sage: p.to_permutation_group_element() # optional - sage.groups + (1,2)(3,4) + + TESTS:: sage: from sage.features.sagemath import sage__groups - sage: sage__groups().is_present() # optional - sage.groups + sage: sage__groups().is_present() # optional - sage.groups FeatureTestResult('sage.groups', True) """ def __init__(self): @@ -138,10 +228,24 @@ class sage__libs__pari(JoinFeature): r""" A :class:`sage.features.Feature` describing the presence of :mod:`sage.libs.pari`. + SageMath uses the :ref:`PARI ` library (via :ref:`cypari2 `) for numerous purposes. + Doctests that involves such features should be marked ``# optional - sage.libs.pari``. + + In addition to the modularization purposes that this annotation serves, it also provides attribution + to the upstream project. + EXAMPLES:: + sage: R. = QQ[] + sage: S. = R[] + sage: f = x^2 + a; g = x^3 + a + sage: r = f.resultant(g); r # optional - sage.libs.pari + a^3 + a^2 + + TESTS:: + sage: from sage.features.sagemath import sage__libs__pari - sage: sage__libs__pari().is_present() # optional - sage.libs.pari + sage: sage__libs__pari().is_present() # optional - sage.libs.pari FeatureTestResult('sage.libs.pari', True) """ def __init__(self): @@ -160,7 +264,7 @@ class sage__modules(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.modules`. - EXAMPLES:: + TESTS:: sage: from sage.features.sagemath import sage__modules sage: sage__modules().is_present() # optional - sage.modules @@ -182,7 +286,7 @@ class sage__plot(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.plot`. - EXAMPLES:: + TESTS:: sage: from sage.features.sagemath import sage__plot sage: sage__plot().is_present() # optional - sage.plot @@ -203,12 +307,12 @@ def __init__(self): class sage__rings__finite_rings(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.rings.finite_rings`; - specifically, the element implementations using PARI. + specifically, the element implementations using the :ref:`PARI ` library. - EXAMPLES:: + TESTS:: sage: from sage.features.sagemath import sage__rings__finite_rings - sage: sage__rings__finite_rings().is_present() # optional - sage.rings.finite_rings + sage: sage__rings__finite_rings().is_present() # optional - sage.rings.finite_rings FeatureTestResult('sage.rings.finite_rings', True) """ def __init__(self): @@ -227,10 +331,30 @@ class sage__rings__function_field(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.rings.function_field`. - EXAMPLES:: + EXAMPLES: + + Rational function fields are always available:: + + sage: K. = FunctionField(QQ) + sage: K.maximal_order() + Maximal order of Rational function field in x over Rational Field + + Use the annotation ``# optional - sage.rings.function_field`` whenever extensions + of function fields (by adjoining a root of a univariate polynomial) come into play. + + sage: R. = K[] + sage: L. = K.extension(y^5 - (x^3 + 2*x*y + 1/x)); L # optional - sage.rings.function_field + Function field in y defined by y^5 - 2*x*y + (-x^4 - 1)/x + + Such extensions of function fields are implemented using Gr\"obner bases of polynomial rings; + Sage makes essential use of the :ref:`Singular ` system for this. + (It is not necessary to use the annotation ``# optional - sage.libs.singular``; it is + implied by ``# optional - sage.rings.function_field``.) + + TESTS:: sage: from sage.features.sagemath import sage__rings__function_field - sage: sage__rings__function_field().is_present() # optional - sage.rings.function_field + sage: sage__rings__function_field().is_present() # optional - sage.rings.function_field FeatureTestResult('sage.rings.function_field', True) """ def __init__(self): @@ -250,10 +374,48 @@ class sage__rings__number_field(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.rings.number_field`. - EXAMPLES:: + Number fields are implemented in Sage using a complicated mixture of various libraries, + including :ref:`arb `, :ref:`FLINT `, :ref:`GAP `, + :ref:`MPFI `, :ref:`NTL `, and :ref:`PARI `. + + EXAMPLES: + + Rational numbers are, of course, always available:: + + sage: QQ in NumberFields() + True + + Doctests that construct algebraic number fields should be marked ``# optional - sage.rings.number_field``:: + + sage: K. = NumberField(x^3 - 2) # optional - sage.rings.number_field + sage: L. = K.extension(x^3 - 3) # optional - sage.rings.number_field + sage: S. = L.extension(x^2 - 2); S # optional - sage.rings.number_field + Number Field in sqrt2 with defining polynomial x^2 - 2 over its base field + + sage: K. = CyclotomicField(15) # optional - sage.rings.number_field + sage: CC(zeta) # optional - sage.rings.number_field + 0.913545457642601 + 0.406736643075800*I + + Doctests that make use of the Algebraic Field ``QQbar``, the Algebraic Real Field ``AA``, + or the Universal Cyclotomic Field should be marked likewise:: + + sage: AA(-1)^(1/3) # optional - sage.rings.number_field + -1 + sage: QQbar(-1)^(1/3) # optional - sage.rings.number_field + 0.500000000000000? + 0.866025403784439?*I + + sage: UCF = UniversalCyclotomicField(); UCF # optional - sage.rings.number_field + Universal Cyclotomic Field + sage: E = UCF.gen # optional - sage.rings.number_field + sage: f = E(2) + E(3); f # optional - sage.rings.number_field + 2*E(3) + E(3)^2 + sage: f.galois_conjugates() # optional - sage.rings.number_field + [2*E(3) + E(3)^2, E(3) + 2*E(3)^2] + + TESTS:: sage: from sage.features.sagemath import sage__rings__number_field - sage: sage__rings__number_field().is_present() # optional - sage.rings.number_field + sage: sage__rings__number_field().is_present() # optional - sage.rings.number_field FeatureTestResult('sage.rings.number_field', True) """ def __init__(self): @@ -272,7 +434,7 @@ class sage__rings__padics(JoinFeature): r""" A :class:`sage.features.Feature` describing the presence of ``sage.rings.padics``. - EXAMPLES:: + TESTS:: sage: from sage.features.sagemath import sage__rings__padics sage: sage__rings__padics().is_present() # optional - sage.rings.padics @@ -294,7 +456,17 @@ class sage__rings__real_double(PythonModule): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.rings.real_double`. - EXAMPLES:: + EXAMPLES: + + The Real Double Field is basically always available, and no ``# optional`` annotation is needed. + + sage: RDF.characteristic() + 0 + + The feature exists for use in doctests of Python modules that are shipped by the + most fundamental distributions. + + TESTS:: sage: from sage.features.sagemath import sage__rings__real_double sage: sage__rings__real_double().is_present() # optional - sage.rings.real_double @@ -315,7 +487,7 @@ class sage__rings__real_mpfr(PythonModule): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.rings.real_mpfr`. - EXAMPLES:: + TESTS:: sage: from sage.features.sagemath import sage__rings__real_mpfr sage: sage__rings__real_mpfr().is_present() # optional - sage.rings.real_mpfr @@ -336,10 +508,22 @@ class sage__symbolic(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.symbolic`. - EXAMPLES:: + EXAMPLES: + + The symbolic subsystem of Sage is provided by the distribution + :ref:`sagemath-symbolics `. If it is not installed, + Sage is able to provide installation advice:: + + sage: from sage.features.sagemath import sage__symbolic + sage: print(sage__symbolic().resolution()) # optional - sage_spkg + ...To install sagemath_symbolics...you can try to run... + pip install sagemath-symbolics + ... + + TESTS:: sage: from sage.features.sagemath import sage__symbolic - sage: sage__symbolic().is_present() # optional - sage.symbolic + sage: sage__symbolic().is_present() # optional - sage.symbolic FeatureTestResult('sage.symbolic', True) """ def __init__(self): From d7ecd4d50918caf5ddcb4086a5f284b73f0db426 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 9 Jun 2023 11:29:07 -0700 Subject: [PATCH 002/150] src/doc/en/developer/coding_basics.rst: Add style guide for formatting doctests, # optional --- src/doc/en/developer/coding_basics.rst | 62 ++++++++++++++++++++++++++ src/sage/features/sagemath.py | 2 +- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index b323a7eaee4..5d2eab5a156 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -958,6 +958,68 @@ written. 5 7 +- **Wrap long doctest lines:** Note that all doctests in EXAMPLES blocks + get formatted as part of our HTML and PDF reference manuals. Our HTML manuals + are formatted using the responsive design provided by the + :ref:`Furo theme `. Even when the browser window is expanded to + make use of the full width of a wide desktop screen, the style will not + allow code boxes to grow arbitrarily wide. + + It is best to wrap long lines when possible so that readers do not have to + scroll horizontally (back and forth) to follow an example. + + - Try to wrap long lines somewhere around columns 80 to 88 + and try to never exceed column 95 in the source file. + (Columns numbers are from the left margin in the source file; + these rules work no matter how deep the docstring may be nested + because also the formatted output will be nested.) + + - If you have to break an expression at a place that is not already + nested in parentheses, wrap it in parentheses:: + + sage: (length(list(Permutations(['a', 'b', 'c', 'd', 'e', 'f', 'g']))) + ....: == length(list(Permutations(7)))) + + - If the output in your only example is very wide and cannot be reasonably + reformatted to fit (for example, large symbolic matrices or numbers with many digits), + consider showing a smaller example first. + + - No need to wrap long ``import`` statements. Typically, the ``import`` statements + are not the interesting parts of the doctests. Users only need to be able to + copy-paste them into a Sage session or source file:: + + sage: from sage.rings.polynomial.multi_polynomial_ring import MPolynomialRing_polydict, MPolynomialRing_polydict_domain # this is fine + + - If there is no whitespace in the doctest output where you could wrap the line, + do not add such whitespace. Just don't wrap the line. + + - Wrap and indent long output to maximize readability in source and in the HTML output. + But do not wrap strings:: + + sage: from sage.schemes.generic.algebraic_scheme import AlgebraicScheme_quasi + sage: P. = ProjectiveSpace(2, ZZ) + sage: S = P.subscheme([]) + sage: T = P.subscheme([x - y]) + sage: U = AlgebraicScheme_quasi(S, T); U + Quasi-projective subscheme X - Y of Projective Space of dimension 2 + over Integer Ring, + where X is defined by: (no polynomials) + and Y is defined by: x - y + sage: U._repr_() # this is fine + 'Quasi-projective subscheme X - Y of Projective Space of dimension 2 over Integer Ring, where X is defined by:\n (no polynomials)\nand Y is defined by:\n x - y' + + - Annotations for modularization purposes such as ``# optional - sagemath-combinat sagemath-modules`` + (see :ref:`section-further_conventions`) should be aligned at column 88. + Clean lines from consistent alignment help reduce visual clutter. + Moreover, at the maximum window width, only the word fragment ``# option`` will be + visible without horizontal scrolling, striking a thoughtfully chosen balance between presenting + the information and reducing visual clutter. (How much can be seen may be + browser-dependent, of course.) In visually dense doctests, you can try to sculpt out visual space to separate + the test commands from the annotation. + + - Annotations for doctests depending on optional packages such as ``# optional - pynormaliz``, on the other hand, + should be aligned so that they are visible without having to scroll horizontally. + - **Python3 print:** Python3 syntax for print must be used in Sage code and doctests. If you use an old-style print in doctests, it will raise a SyntaxError:: diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 5f2dee3661b..ad011d72196 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -511,7 +511,7 @@ class sage__symbolic(JoinFeature): EXAMPLES: The symbolic subsystem of Sage is provided by the distribution - :ref:`sagemath-symbolics `. If it is not installed, + sagemath-symbolics. If it is not installed, Sage is able to provide installation advice:: sage: from sage.features.sagemath import sage__symbolic From e7173cd555a44cc1567e74af80a1a268b7b36e9a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 9 Jun 2023 13:15:48 -0700 Subject: [PATCH 003/150] src/sage/features/sagemath.py: Fix markup, expand examples --- src/doc/en/developer/coding_basics.rst | 4 +-- src/sage/features/sagemath.py | 44 +++++++++++++++----------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 5d2eab5a156..7eafc142fb9 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -993,8 +993,8 @@ written. - If there is no whitespace in the doctest output where you could wrap the line, do not add such whitespace. Just don't wrap the line. - - Wrap and indent long output to maximize readability in source and in the HTML output. - But do not wrap strings:: + - Wrap and indent long output to maximize readability in the source code + and in the HTML output. But do not wrap strings:: sage: from sage.schemes.generic.algebraic_scheme import AlgebraicScheme_quasi sage: P. = ProjectiveSpace(2, ZZ) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index ad011d72196..82b32bda6a9 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -127,7 +127,7 @@ class sage__geometry__polyhedron(PythonModule): EXAMPLES: Doctests that use polyhedra, cones, geometric complexes, triangulations, etc. should use - the annotations ``# optional - sage.geometry.polyhedron``. + the annotations ``# optional - sage.geometry.polyhedron``:: sage: co = polytopes.truncated_tetrahedron() # optional - sage.geometry.polyhedron sage: co.volume() # optional - sage.geometry.polyhedron @@ -164,20 +164,29 @@ class sage__graphs(JoinFeature): EXAMPLES: - Matroids are not implemented as posets in Sage but are instead closely tied to - linear algebra over fields; hence use ``# optional - sage.modules``:: + Matroids are not implemented as posets in Sage but are instead closely tied to + linear algebra over fields; hence use ``# optional - sage.modules``:: - sage: M = Matroid(Matrix(QQ, [[1, 0, 0, 0, 1, 1, 1], # optional - sage.modules - ....: [0, 1, 0, 1, 0, 1, 1], - ....: [0, 0, 1, 1, 1, 0, 1]])) - sage: N = M / [2] \ [3, 4] # optional - sage.modules - sage: sorted(N.groundset()) # optional - sage.modules - [0, 1, 5, 6] + sage: M = Matroid(Matrix(QQ, [[1, 0, 0, 0, 1, 1, 1], # optional - sage.modules + ....: [0, 1, 0, 1, 0, 1, 1], + ....: [0, 0, 1, 1, 1, 0, 1]])) + sage: N = M / [2] \ [3, 4] # optional - sage.modules + sage: sorted(N.groundset()) # optional - sage.modules + [0, 1, 5, 6] + + However, many constructions (and some methods) of matroids do involve graphs:: + + sage: W = matroids.Wheel(3) # despite the name, not created via graphs # optional - sage.modules + sage: W.is_isomorphic(N) # goes through a graph isomorphism test # optional - sage.graphs sage.modules + False + sage: K4 = matroids.CompleteGraphic(4) # this one is created via graphs # optional - sage.graphs sage.modules + sage: K4.is_isomorphic(W) # optional - sage.graphs sage.modules + True TESTS:: sage: from sage.features.sagemath import sage__graphs - sage: sage__graphs().is_present() # optional - sage.graphs + sage: sage__graphs().is_present() # optional - sage.graphs FeatureTestResult('sage.graphs', True) """ def __init__(self): @@ -340,7 +349,7 @@ class sage__rings__function_field(JoinFeature): Maximal order of Rational function field in x over Rational Field Use the annotation ``# optional - sage.rings.function_field`` whenever extensions - of function fields (by adjoining a root of a univariate polynomial) come into play. + of function fields (by adjoining a root of a univariate polynomial) come into play:: sage: R. = K[] sage: L. = K.extension(y^5 - (x^3 + 2*x*y + 1/x)); L # optional - sage.rings.function_field @@ -458,7 +467,7 @@ class sage__rings__real_double(PythonModule): EXAMPLES: - The Real Double Field is basically always available, and no ``# optional`` annotation is needed. + The Real Double Field is basically always available, and no ``# optional`` annotation is needed:: sage: RDF.characteristic() 0 @@ -510,12 +519,12 @@ class sage__symbolic(JoinFeature): EXAMPLES: - The symbolic subsystem of Sage is provided by the distribution - sagemath-symbolics. If it is not installed, - Sage is able to provide installation advice:: + The symbolics subsystem of Sage will be provided by the distribution + sagemath-symbolics, in preparation at :issue:`35095`. If it is not installed, + Sage will be able to provide installation advice:: sage: from sage.features.sagemath import sage__symbolic - sage: print(sage__symbolic().resolution()) # optional - sage_spkg + sage: print(sage__symbolic().resolution()) # optional - sage_spkg, not tested ...To install sagemath_symbolics...you can try to run... pip install sagemath-symbolics ... @@ -535,8 +544,7 @@ def __init__(self): True """ JoinFeature.__init__(self, 'sage.symbolic', - [PythonModule('sage.symbolic.expression')], - spkg="sagemath_symbolics") + [PythonModule('sage.symbolic.expression')]) def all_features(): From 3aacb73ed931639814c955971288414283aa34c1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 9 Jun 2023 18:13:18 -0700 Subject: [PATCH 004/150] src/sage/features/sagemath.py: Add module-level doc --- src/sage/features/sagemath.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 82b32bda6a9..4ce12efba82 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -1,5 +1,32 @@ r""" Features for testing the presence of Python modules in the Sage library + +All of these features are present in a monolithic installation of the Sage library, +such as the one made by the SageMath distribution. + +The features are defined for the purpose of separately testing modularized +distributions such as :ref:`sagemath-categories ` +and :ref:`sagemath-repl `. + +Often, doctests in a module of the Sage library illustrate the +interplay with a range of different objects; this is a form of integration testing. +These objects may come from modules shipped in +other distributions. For example, :mod:`sage.structure.element` +(shipped by :ref:`sagemath-objects `, +one of the most fundamental distributions) contains the +doctest:: + + sage: G = SymmetricGroup(4) # optional - sage.groups + sage: g = G([2, 3, 4, 1]) # optional - sage.groups + sage: g.powers(4) # optional - sage.groups + [(), (1,2,3,4), (1,3)(2,4), (1,4,3,2)] + +This test cannot pass when the distribution :ref:`sagemath-objects ` +is tested separately (in a virtual environment): In this situation, +:class:`SymmetricGroup` is not defined anywhere (and thus not present +in the top-level namespace). +Hence, we conditionalize this doctest on the presence of the feature +:class:`sage.groups `. """ # ***************************************************************************** From bfb9a527e3cf755f909c0bd91ba39957e175df6e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 9 Jun 2023 19:54:12 -0700 Subject: [PATCH 005/150] Fix markup --- src/sage/features/sagemath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 4ce12efba82..70afedcd087 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -21,7 +21,7 @@ sage: g.powers(4) # optional - sage.groups [(), (1,2,3,4), (1,3)(2,4), (1,4,3,2)] -This test cannot pass when the distribution :ref:`sagemath-objects ` +This test cannot pass when the distribution :ref:`sagemath-objects ` is tested separately (in a virtual environment): In this situation, :class:`SymmetricGroup` is not defined anywhere (and thus not present in the top-level namespace). From 4b697560098c7d908dc91b81938acd66da500b33 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 9 Jun 2023 21:41:12 -0700 Subject: [PATCH 006/150] src/sage/features/sagemath.py: Expand on sage.graphs --- src/sage/features/sagemath.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 70afedcd087..fe9b9c53250 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -190,9 +190,20 @@ class sage__graphs(JoinFeature): EXAMPLES: + Doctests that use anything from :mod:`sage.graphs` (:class:`Graph`, :class:`DiGraph`, ...) + should be marked ``# optional - sage.graphs``. The same applies to any doctest that + uses a :class:`~sage.combinat.posets.posets.Poset`, cluster algebra quiver, finite + state machines, abelian sandpiles, or Dynkin diagrams. - Matroids are not implemented as posets in Sage but are instead closely tied to - linear algebra over fields; hence use ``# optional - sage.modules``:: + Also any use of tree classes defined in :mod:`sage.combinat` (:class:`BinaryTree`, + :class:`RootedTree`, ...) in doctests should be marked the same. + + By way of generalization, any use of :class:`SimplicialComplex` or other abstract complexes from + :mod:`sage.topology`, hypergraphs, and combinatorial designs, should be marked + ``# optional - sage.graphs`` as well. + + On the other hand, matroids are not implemented as posets in Sage but are instead + closely tied to linear algebra over fields; hence use ``# optional - sage.modules`` instead:: sage: M = Matroid(Matrix(QQ, [[1, 0, 0, 0, 1, 1, 1], # optional - sage.modules ....: [0, 1, 0, 1, 0, 1, 1], From d46fb19d2e3c61e4f871f407861ae3560820897a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 9 Jun 2023 23:27:11 -0700 Subject: [PATCH 007/150] Expand on sage.graphs, sage.modules --- src/sage/features/sagemath.py | 38 +++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index fe9b9c53250..ae531f38d3c 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -46,7 +46,7 @@ class sagemath_doc_html(StaticFile): r""" - A :class:`Feature` which describes the presence of the documentation + A :class:`~sage.features.Feature` which describes the presence of the documentation of the Sage library in HTML format. Developers often use ``make build`` instead of ``make`` to avoid the @@ -195,6 +195,10 @@ class sage__graphs(JoinFeature): uses a :class:`~sage.combinat.posets.posets.Poset`, cluster algebra quiver, finite state machines, abelian sandpiles, or Dynkin diagrams. + sage: g = graphs.PetersenGraph() # optional - sage.graphs + sage: r, s = g.is_weakly_chordal(certificate=True); r # optional - sage.graphs + False + Also any use of tree classes defined in :mod:`sage.combinat` (:class:`BinaryTree`, :class:`RootedTree`, ...) in doctests should be marked the same. @@ -202,6 +206,13 @@ class sage__graphs(JoinFeature): :mod:`sage.topology`, hypergraphs, and combinatorial designs, should be marked ``# optional - sage.graphs`` as well. + sage: X = SimplicialComplex([[0,1,2], [1,2,3]]) # optional - sage.graphs + sage: X.link(Simplex([0])) # optional - sage.graphs + Simplicial complex with vertex set (1, 2) and facets {(1, 2)} + + sage: IncidenceStructure([[1,2,3],[1,4]]).degrees(2) # optional - sage.graphs + {(1, 2): 1, (1, 3): 1, (1, 4): 1, (2, 3): 1, (2, 4): 0, (3, 4): 0} + On the other hand, matroids are not implemented as posets in Sage but are instead closely tied to linear algebra over fields; hence use ``# optional - sage.modules`` instead:: @@ -215,7 +226,7 @@ class sage__graphs(JoinFeature): However, many constructions (and some methods) of matroids do involve graphs:: sage: W = matroids.Wheel(3) # despite the name, not created via graphs # optional - sage.modules - sage: W.is_isomorphic(N) # goes through a graph isomorphism test # optional - sage.graphs sage.modules + sage: W.is_isomorphic(N) # goes through a graph isomorphism test # optional - sage.graphs sage.modules False sage: K4 = matroids.CompleteGraphic(4) # this one is created via graphs # optional - sage.graphs sage.modules sage: K4.is_isomorphic(W) # optional - sage.graphs sage.modules @@ -241,7 +252,7 @@ def __init__(self): class sage__groups(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of ``sage.groups``. + A :class:`~sage.features.Feature` describing the presence of ``sage.groups``. EXAMPLES: @@ -273,7 +284,7 @@ def __init__(self): class sage__libs__pari(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of :mod:`sage.libs.pari`. + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.libs.pari`. SageMath uses the :ref:`PARI ` library (via :ref:`cypari2 `) for numerous purposes. Doctests that involves such features should be marked ``# optional - sage.libs.pari``. @@ -311,6 +322,21 @@ class sage__modules(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.modules`. + EXAMPLES: + + All uses of implementations of vector spaces / free modules in SageMath, whether + :class:`sage.modules.free_module.FreeModule`, + :class:`sage.combinat.free_module.CombinatorialFreeModule`, + :class:`sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule`, or + additive abelian groups, should be marked ``# optional - sage.modules``. + + The same holds for matrices, tensors, algebras, quadratic forms, + point lattices, root systems, matrix/affine/Weyl/Coxeter groups, matroids, + and ring derivations. + + Likewise, all uses of :mod:`sage.coding`, :mod:`sage.crypto`, and :mod:`sage.homology` + in doctests should be marked ``# optional - sage.modules``. + TESTS:: sage: from sage.features.sagemath import sage__modules @@ -393,7 +419,7 @@ class sage__rings__function_field(JoinFeature): sage: L. = K.extension(y^5 - (x^3 + 2*x*y + 1/x)); L # optional - sage.rings.function_field Function field in y defined by y^5 - 2*x*y + (-x^4 - 1)/x - Such extensions of function fields are implemented using Gr\"obner bases of polynomial rings; + Such extensions of function fields are implemented using Gröbner bases of polynomial rings; Sage makes essential use of the :ref:`Singular ` system for this. (It is not necessary to use the annotation ``# optional - sage.libs.singular``; it is implied by ``# optional - sage.rings.function_field``.) @@ -479,7 +505,7 @@ def __init__(self): class sage__rings__padics(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of ``sage.rings.padics``. + A :class:`~sage.features.Feature` describing the presence of ``sage.rings.padics``. TESTS:: From 5dc088f23e27a9cabcf1cdf1205cac760adf9297 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 10 Jun 2023 12:07:44 -0700 Subject: [PATCH 008/150] src/doc/en/developer/coding_basics.rst: Fix doctest output --- src/doc/en/developer/coding_basics.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 7eafc142fb9..e12c02388f5 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -979,6 +979,7 @@ written. sage: (length(list(Permutations(['a', 'b', 'c', 'd', 'e', 'f', 'g']))) ....: == length(list(Permutations(7)))) + True - If the output in your only example is very wide and cannot be reasonably reformatted to fit (for example, large symbolic matrices or numbers with many digits), From 1ad27764813e0a55a48db6b46fc3770292f38ede Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 10 Jun 2023 12:21:07 -0700 Subject: [PATCH 009/150] Fix markup --- src/sage/features/sagemath.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index ae531f38d3c..c8e21a9cfc2 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -193,7 +193,7 @@ class sage__graphs(JoinFeature): Doctests that use anything from :mod:`sage.graphs` (:class:`Graph`, :class:`DiGraph`, ...) should be marked ``# optional - sage.graphs``. The same applies to any doctest that uses a :class:`~sage.combinat.posets.posets.Poset`, cluster algebra quiver, finite - state machines, abelian sandpiles, or Dynkin diagrams. + state machines, abelian sandpiles, or Dynkin diagrams:: sage: g = graphs.PetersenGraph() # optional - sage.graphs sage: r, s = g.is_weakly_chordal(certificate=True); r # optional - sage.graphs @@ -204,7 +204,7 @@ class sage__graphs(JoinFeature): By way of generalization, any use of :class:`SimplicialComplex` or other abstract complexes from :mod:`sage.topology`, hypergraphs, and combinatorial designs, should be marked - ``# optional - sage.graphs`` as well. + ``# optional - sage.graphs`` as well:: sage: X = SimplicialComplex([[0,1,2], [1,2,3]]) # optional - sage.graphs sage: X.link(Simplex([0])) # optional - sage.graphs From eef016f97d10b2d149d169df43e9643764d64c22 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 10 Jun 2023 20:48:11 -0700 Subject: [PATCH 010/150] src/doc/en/developer/coding_basics.rst: Fix doctest --- src/doc/en/developer/coding_basics.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index e12c02388f5..44882e965eb 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -977,8 +977,8 @@ written. - If you have to break an expression at a place that is not already nested in parentheses, wrap it in parentheses:: - sage: (length(list(Permutations(['a', 'b', 'c', 'd', 'e', 'f', 'g']))) - ....: == length(list(Permutations(7)))) + sage: (len(list(Permutations(['a', 'b', 'c', 'd', 'e', 'f', 'g']))) + ....: == len(list(Permutations(7)))) True - If the output in your only example is very wide and cannot be reasonably From 2440c0bdd87ffeaadce6ff3d4ac6366d220bde8a Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Wed, 14 Jun 2023 16:23:32 +0900 Subject: [PATCH 011/150] Add persistent optional tag --- src/sage/doctest/parsing.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 2e39bee5704..8f1e9283192 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -522,7 +522,7 @@ def parse(self, string, *args): - A list consisting of strings and :class:`doctest.Example` instances. There will be at least one string between - successive examples (exactly one unless or long or optional + successive examples (exactly one unless long or optional tests are removed), and it will begin and end with a string. EXAMPLES:: @@ -571,7 +571,7 @@ def parse(self, string, *args): of the current line should be joined to the next line. This feature allows for breaking large integers over multiple lines but is not standard for Python doctesting. It's not - guaranteed to persist, but works in Sage 5.5:: + guaranteed to persist:: sage: n = 1234\ ....: 5678 @@ -587,6 +587,22 @@ def parse(self, string, *args): sage: print(m) 87654321 + Optional tags at the start of an example block persists to the end of the block:: + + sage: # long time + sage: QQbar(I)^10000 + 1 + sage: QQbar(I)^10000 # not tested + I + + sage: # optional - sage.rings.finite_rings + sage: GF(7) + Finite Field of size 7 + sage: GF(10) # not tested + Traceback (most recent call last): + ... + ValueError: the order of a finite field must be a prime power + Test that :trac:`26575` is resolved:: sage: example3 = 'sage: Zp(5,4,print_mode="digits")(5)\n...00010' @@ -630,9 +646,14 @@ def parse(self, string, *args): string = find_sage_continuation.sub(r"\1...", string) res = doctest.DocTestParser.parse(self, string, *args) filtered = [] + persistent_optional_tags = [] for item in res: if isinstance(item, doctest.Example): optional_tags = parse_optional_tags(item.source) + if item.source.startswith("sage: ") and item.source[6:].lstrip().startswith('#'): + persistent_optional_tags = optional_tags + continue + optional_tags = optional_tags.union(persistent_optional_tags) if optional_tags: for tag in optional_tags: self.optionals[tag] += 1 @@ -654,6 +675,7 @@ def parse(self, string, *args): elif self.optional_only: self.optionals['sage'] += 1 continue + if replace_ellipsis: item.want = item.want.replace(ellipsis_tag, "...") if item.exc_msg is not None: @@ -664,6 +686,9 @@ def parse(self, string, *args): if item.sage_source.lstrip().startswith('#'): continue item.source = preparse(item.sage_source) + else: + if item == '\n': + persistent_optional_tags = [] filtered.append(item) return filtered From 647380c793a39a4895c9ae00f41e75320a75225e Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Fri, 16 Jun 2023 20:02:42 +0900 Subject: [PATCH 012/150] Fix a typo --- src/sage/doctest/parsing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 8f1e9283192..254249b8173 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -587,7 +587,8 @@ def parse(self, string, *args): sage: print(m) 87654321 - Optional tags at the start of an example block persists to the end of the block:: + Optional tag at the start of an example block persists to the end of + the block:: sage: # long time sage: QQbar(I)^10000 From 2bd55ab98538d17140e89ad353ec807705fa192f Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Sat, 17 Jun 2023 09:38:30 +0900 Subject: [PATCH 013/150] Allow needs for optional --- src/sage/doctest/parsing.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 254249b8173..f24dca90922 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -109,6 +109,8 @@ def parse_optional_tags(string): ['bar', 'foo'] sage: parse_optional_tags("sage: #optional -- foo.bar, baz") {'foo.bar'} + sage: parse_optional_tags("sage: #needs foo.bar, baz") + {'foo.bar'} sage: sorted(list(parse_optional_tags(" sage: factor(10^(10^10) + 1) # LoNg TiME, NoT TeSTED; OptioNAL -- P4cka9e"))) ['long time', 'not tested', 'p4cka9e'] sage: parse_optional_tags(" sage: raise RuntimeError # known bug") @@ -137,17 +139,18 @@ def parse_optional_tags(string): # strip_string_literals replaces comments comment = "#" + (literals[comment]).lower() - optional_regex = re.compile(r'(arb216|arb218|py2|long time|not implemented|not tested|known bug)|([^ a-z]\s*optional\s*[:-]*((\s|\w|[.])*))') + optional_regex = re.compile(r'arb216|arb218|py2|long time|not implemented|not tested|known bug' \ + r'|[^ a-z]\s*(optional|needs)\s*[:-]*((?:\s|\w|[.])*)') tags = [] for m in optional_regex.finditer(comment): - cmd = m.group(1) + cmd = m.group(0) if cmd == 'known bug': tags.append('bug') # so that such tests will be run by sage -t ... -only-optional=bug + elif m.group(1) is not None: + tags.extend(m.group(2).split() or [""]) elif cmd: tags.append(cmd) - else: - tags.extend(m.group(3).split() or [""]) return set(tags) From 9485097388a54fc17f1f51e8f15af0d73bdd5fdc Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Sat, 17 Jun 2023 09:41:24 +0900 Subject: [PATCH 014/150] Block ends with a blank line --- src/sage/doctest/parsing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index f24dca90922..f6f27e33b64 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -591,7 +591,7 @@ def parse(self, string, *args): 87654321 Optional tag at the start of an example block persists to the end of - the block:: + the block (delimited by a blank line):: sage: # long time sage: QQbar(I)^10000 From 6dd4afdadc2c0372cae2e097860e28d50cb2d1c5 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Sat, 17 Jun 2023 10:55:11 +0900 Subject: [PATCH 015/150] Use needs in the doctest --- src/sage/doctest/parsing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index f6f27e33b64..5f5b1c99d39 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -590,19 +590,19 @@ def parse(self, string, *args): sage: print(m) 87654321 - Optional tag at the start of an example block persists to the end of + Optional tags at the start of an example block persist to the end of the block (delimited by a blank line):: - sage: # long time + sage: # needs sage.rings.number_field, long time sage: QQbar(I)^10000 1 sage: QQbar(I)^10000 # not tested I - sage: # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings sage: GF(7) Finite Field of size 7 - sage: GF(10) # not tested + sage: GF(10) Traceback (most recent call last): ... ValueError: the order of a finite field must be a prime power From f7152bdf263b3237764f40bd331339588969672a Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Sat, 17 Jun 2023 11:05:16 +0900 Subject: [PATCH 016/150] Remove redundant backslash --- src/sage/doctest/parsing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 5f5b1c99d39..91e5b1843eb 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -139,7 +139,7 @@ def parse_optional_tags(string): # strip_string_literals replaces comments comment = "#" + (literals[comment]).lower() - optional_regex = re.compile(r'arb216|arb218|py2|long time|not implemented|not tested|known bug' \ + optional_regex = re.compile(r'arb216|arb218|py2|long time|not implemented|not tested|known bug' r'|[^ a-z]\s*(optional|needs)\s*[:-]*((?:\s|\w|[.])*)') tags = [] From 87f5e1534b69a5d98480f48666c5380c3fa19f11 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Sat, 17 Jun 2023 16:43:15 +0900 Subject: [PATCH 017/150] Use formal needs: --- src/sage/doctest/parsing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 91e5b1843eb..dddb21cbfc0 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -109,7 +109,7 @@ def parse_optional_tags(string): ['bar', 'foo'] sage: parse_optional_tags("sage: #optional -- foo.bar, baz") {'foo.bar'} - sage: parse_optional_tags("sage: #needs foo.bar, baz") + sage: parse_optional_tags("sage: #needs: foo.bar, baz") {'foo.bar'} sage: sorted(list(parse_optional_tags(" sage: factor(10^(10^10) + 1) # LoNg TiME, NoT TeSTED; OptioNAL -- P4cka9e"))) ['long time', 'not tested', 'p4cka9e'] @@ -140,7 +140,7 @@ def parse_optional_tags(string): comment = "#" + (literals[comment]).lower() optional_regex = re.compile(r'arb216|arb218|py2|long time|not implemented|not tested|known bug' - r'|[^ a-z]\s*(optional|needs)\s*[:-]*((?:\s|\w|[.])*)') + r'|[^ a-z]\s*(optional\s*[:-]*|needs:\s*)((?:\s|\w|[.])*)') tags = [] for m in optional_regex.finditer(comment): @@ -593,13 +593,13 @@ def parse(self, string, *args): Optional tags at the start of an example block persist to the end of the block (delimited by a blank line):: - sage: # needs sage.rings.number_field, long time + sage: # needs: sage.rings.number_field, long time sage: QQbar(I)^10000 1 sage: QQbar(I)^10000 # not tested I - sage: # needs sage.rings.finite_rings + sage: # needs: sage.rings.finite_rings sage: GF(7) Finite Field of size 7 sage: GF(10) From bdc4232472cc8177fcd07764e9c9a0e872d142e8 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Sat, 17 Jun 2023 22:09:22 +0900 Subject: [PATCH 018/150] Revert to needs revising the doctest --- src/sage/doctest/parsing.py | 8 ++++---- src/sage/dynamics/arithmetic_dynamics/projective_ds.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index dddb21cbfc0..91e5b1843eb 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -109,7 +109,7 @@ def parse_optional_tags(string): ['bar', 'foo'] sage: parse_optional_tags("sage: #optional -- foo.bar, baz") {'foo.bar'} - sage: parse_optional_tags("sage: #needs: foo.bar, baz") + sage: parse_optional_tags("sage: #needs foo.bar, baz") {'foo.bar'} sage: sorted(list(parse_optional_tags(" sage: factor(10^(10^10) + 1) # LoNg TiME, NoT TeSTED; OptioNAL -- P4cka9e"))) ['long time', 'not tested', 'p4cka9e'] @@ -140,7 +140,7 @@ def parse_optional_tags(string): comment = "#" + (literals[comment]).lower() optional_regex = re.compile(r'arb216|arb218|py2|long time|not implemented|not tested|known bug' - r'|[^ a-z]\s*(optional\s*[:-]*|needs:\s*)((?:\s|\w|[.])*)') + r'|[^ a-z]\s*(optional|needs)\s*[:-]*((?:\s|\w|[.])*)') tags = [] for m in optional_regex.finditer(comment): @@ -593,13 +593,13 @@ def parse(self, string, *args): Optional tags at the start of an example block persist to the end of the block (delimited by a blank line):: - sage: # needs: sage.rings.number_field, long time + sage: # needs sage.rings.number_field, long time sage: QQbar(I)^10000 1 sage: QQbar(I)^10000 # not tested I - sage: # needs: sage.rings.finite_rings + sage: # needs sage.rings.finite_rings sage: GF(7) Finite Field of size 7 sage: GF(10) diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index a6220f27801..91648e11962 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -5979,7 +5979,7 @@ def reduced_form(self, **kwds): sage: f = DynamicalSystem_projective([x^3 + x*y^2, y^3]) sage: m = matrix(QQ, 2, 2, [-201221, -1, 1, 0]) sage: f = f.conjugate(m) - sage: f.reduced_form(prec=50, smallest_coeffs=False) #needs 2 periodic + sage: f.reduced_form(prec=50, smallest_coeffs=False) # this needs 2 periodic Traceback (most recent call last): ... ValueError: accuracy of Newton's root not within tolerance(0.000066... > 1e-06), increase precision @@ -5997,7 +5997,7 @@ def reduced_form(self, **kwds): :: sage: PS. = ProjectiveSpace(ZZ, 1) - sage: f = DynamicalSystem_projective([x^2+ x*y, y^2]) #needs 3 periodic + sage: f = DynamicalSystem_projective([x^2+ x*y, y^2]) # this needs 3 periodic sage: m = matrix(QQ, 2, 2, [-221, -1, 1, 0]) sage: f = f.conjugate(m) sage: f.reduced_form(prec=200, smallest_coeffs=False) From 131e1f9da0d21a0f31b32ecdc047c942dc974646 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 23 Jun 2023 14:19:34 -0700 Subject: [PATCH 019/150] sage -fixdoctests: New options --venv, --environment, --full-tracebacks; add # optional on ModuleNotFoundError and 'was only set only in doctest marked' warning --- src/bin/sage-fixdoctests | 42 ++++++++++++++++++++++++++++++++----- src/sage/doctest/parsing.py | 21 +++++++++++++++++++ 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 3cba3464a3e..2e7cf9e6b93 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -15,13 +15,19 @@ AUTHORS:: Added doctest to sage.tests.cmdline """ import os +import re import subprocess from argparse import ArgumentParser, FileType from sage.misc.temporary_file import tmp_filename +from sage.doctest.parsing import update_optional_tags parser = ArgumentParser(description="Given an input file with doctests, this creates a modified file that passes the doctests (modulo any raised exceptions). By default, the input file is modified. You can also name an output file.") parser.add_argument('-l', '--long', dest='long', action="store_true", default=False) +parser.add_argument("--venv", type=str, default='', help="directory name of a venv where 'sage -t' is to be run") +parser.add_argument("--environment", type=str, default="sage.repl.ipython_kernel.all_jupyter", help="name of a module that provides the global environment for tests") +parser.add_argument("--full-tracebacks", default=False, action="store_true", + help="include full tracebacks rather than '...'") parser.add_argument("input", help="input filename", type=FileType('r')) parser.add_argument('output', nargs='?', help="output filename", @@ -36,8 +42,11 @@ test_file.close() # put the output of the test into sage's temporary directory doc_file = tmp_filename() -os.system('sage -t %s %s > %s' % ('--long' if args.long else '', - test_file.name, doc_file)) +os.system('%s -t %s %s %s > %s' % ( + f'{args.venv}/bin/sage' if args.venv else 'sage', + '--long' if args.long else '', + f'--environment {args.environment}' if args.environment else '', + test_file.name, doc_file)) with open(doc_file, 'r') as doc: doc_out = doc.read() sep = "**********************************************************************\n" @@ -54,7 +63,7 @@ for block in doctests: comma = block.find(', in ') # we try to extract the line number which gives the test failure if line == -1 or comma == -1: continue # but if something goes wrong we give up - line_num = eval(block[line + 5:comma]) + line_num = first_line_num = eval(block[line + 5:comma]) # Take care of multiline examples. if 'Expected' in block: @@ -72,13 +81,36 @@ for block in doctests: elif 'Exception raised' in block: exp = block.find('Exception raised') block = '\nGot:\n' + block[exp + 17:] + elif (m := re.search(r"referenced here was set only in doctest marked '([^;']*)")): + optional = m.group(1) + src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=[optional]) + continue else: continue # Error testing. + if m := re.search(r"ModuleNotFoundError: No module named '([^']*)'", block): + optional = m.group(1) + # FIXME: map module name to feature name + src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=[optional]) + continue + if 'Traceback (most recent call last):' in block: expected, got = block.split('\nGot:\n') - got = got.splitlines() - got = [' Traceback (most recent call last):', ' ...', got[-1]] + if args.full_tracebacks: + if re.fullmatch(' *\n', got): + got = got[re.end(0):] + # don't show doctester internals (anything before first "" frame + if m := re.search('(Traceback.*\n *)(?s:.*?)(^ *File "]*)>', got, re.MULTILINE): + got = m.group(1) + '...\n' + m.group(2) + '...' + got[m.end(3):] + while m := re.search(' *File "]*)>', got): + got = got[:m.start(1)] + '...' + got[m.end(1):] + # simplify filenames shown in backtrace + while m := re.search('"([-a-zA-Z0-9._/]*/site-packages)/sage/', got): + got = got[:m.start(1)] + '...' + got[m.end(1):] + got = got.splitlines() + else: + got = got.splitlines() + got = [' Traceback (most recent call last):', ' ...', got[-1]] elif block[-21:] == 'Got:\n \n': expected = block[:-22] got = [''] diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 579768d6a06..21907998a18 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -185,6 +185,27 @@ def unparse_optional_tags(tags): return '' +optional_tag_columns = [88, 100, 120] +standard_tag_columns = [64, 72, 80, 84] + + +def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None): + if not (m := re.match(' *sage: [^#]*', line)): + raise ValueError(f'line must start with a sage: prompt, got: {line}') + if tags is not None or remove_tags: + raise NotImplementedError + if not add_tags: + return line + # FIXME: parse existing tags, merge with given tags; remove them from line, keeping non-tag comments + tags = add_tags + for column in optional_tag_columns: + if len(line) < column - 2: + line += ' ' * (column - 2 - len(line)) + break + line += ' ' + unparse_optional_tags(tags) + return line + + def parse_tolerance(source, want): r""" Return a version of ``want`` marked up with the tolerance tags From d9d190091de18b13887c4170a15d8fb97451ef0b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 23 Jun 2023 20:49:10 -0700 Subject: [PATCH 020/150] sage -fixdoctests: Add option --keep-both --- src/bin/sage-fixdoctests | 82 ++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 2e7cf9e6b93..dcc2d9187df 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -28,6 +28,8 @@ parser.add_argument("--venv", type=str, default='', help="directory name of a ve parser.add_argument("--environment", type=str, default="sage.repl.ipython_kernel.all_jupyter", help="name of a module that provides the global environment for tests") parser.add_argument("--full-tracebacks", default=False, action="store_true", help="include full tracebacks rather than '...'") +parser.add_argument("--keep-both", default=False, action="store_true", + help="do not replace test results; duplicate the test instead and mark both copies # optional") parser.add_argument("input", help="input filename", type=FileType('r')) parser.add_argument('output', nargs='?', help="output filename", @@ -63,44 +65,54 @@ for block in doctests: comma = block.find(', in ') # we try to extract the line number which gives the test failure if line == -1 or comma == -1: continue # but if something goes wrong we give up - line_num = first_line_num = eval(block[line + 5:comma]) - - # Take care of multiline examples. - if 'Expected' in block: - i1 = block.index('Failed example') - i2 = block.index('Expected') - example_len = block[i1:i2].count('\n') - 1 - line_num += example_len - 1 - - if 'Expected nothing' in block: - exp = block.find('Expected nothing') - block = '\n' + block[exp + 17:] # so that split('\nGot:\n') does not fail below - elif 'Expected:' in block: - exp = block.find('Expected:\n') - block = block[exp + 10:] - elif 'Exception raised' in block: - exp = block.find('Exception raised') - block = '\nGot:\n' + block[exp + 17:] + first_line_num = line_num = int(block[line + 5:comma]) # 1-based line number of the first line of the example + + if m2 := re.search('(Expected:|Expected nothing|Exception raised:)\n', block): + m1 = re.search('Failed example:\n', block) + line_num += block[m1.end() : m2.start()].count('\n') - 1 + # Now line_num is the 1-based line number of the last line of the example + + if m2.group(1) == 'Expected nothing': + expected = '' + block = '\n' + block[m2.end():] # so that split('\nGot:\n') does not fail below + elif m2.group(1) == 'Exception raised:': + # In this case, the doctester does not show the expected output, + # so we do not know how many lines it spans; so we check for the next prompt or + # docstring end. + expected = [] + indentation = ' ' * (len(src_in_lines[line_num - 1]) - len(src_in_lines[line_num - 1].lstrip())) + i = line_num + while ((not src_in_lines[i].rstrip() or src_in_lines[i].startswith(indentation)) + and not re.match(' *(sage:|""")', src_in_lines[i])): + expected.append(src_in_lines[i]) + i += 1 + block = '\n'.join(expected) + '\nGot:\n' + block[m2.end():] + else: + block = block[m2.end():] elif (m := re.search(r"referenced here was set only in doctest marked '([^;']*)")): optional = m.group(1) - src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=[optional]) + src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], + add_tags=[optional]) continue else: continue + # Error testing. if m := re.search(r"ModuleNotFoundError: No module named '([^']*)'", block): optional = m.group(1) # FIXME: map module name to feature name - src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=[optional]) + src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], + add_tags=[optional]) continue if 'Traceback (most recent call last):' in block: + expected, got = block.split('\nGot:\n') if args.full_tracebacks: if re.fullmatch(' *\n', got): got = got[re.end(0):] # don't show doctester internals (anything before first "" frame - if m := re.search('(Traceback.*\n *)(?s:.*?)(^ *File "]*)>', got, re.MULTILINE): + if m := re.search('( *Traceback.*\n *)(?s:.*?)(^ *File "]*)>', got, re.MULTILINE): got = m.group(1) + '...\n' + m.group(2) + '...' + got[m.end(3):] while m := re.search(' *File "]*)>', got): got = got[:m.start(1)] + '...' + got[m.end(1):] @@ -110,7 +122,7 @@ for block in doctests: got = got.splitlines() else: got = got.splitlines() - got = [' Traceback (most recent call last):', ' ...', got[-1]] + got = ['Traceback (most recent call last):', '...', got[-1].lstrip()] elif block[-21:] == 'Got:\n \n': expected = block[:-22] got = [''] @@ -118,21 +130,35 @@ for block in doctests: expected, got = block.split('\nGot:\n') got = got.splitlines() # got can't be the empty string + expected = expected.splitlines() + + print(f'{expected=}, {got=}') + + if args.keep_both: + test_lines = ([update_optional_tags(src_in_lines[first_line_num - 1], add_tags=['GOT'])] + + src_in_lines[first_line_num : line_num]) + src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], + add_tags=['EXPECTED']) + indent = (len(src_in_lines[line_num - 1]) - len(src_in_lines[line_num - 1].lstrip())) + line_num += len(expected) # skip to the last line of the expected output + src_in_lines[line_num - 1] += '\n'.join([''] + test_lines) # 2nd copy of the test + # now line_num is the last line of the 2nd copy of the test + expected = [] + # If we expected nothing, and got something, then we need to insert the line before line_num # and match indentation with line number line_num-1 - if expected == '': - indent = (len(src_in_lines[line_num - 1]) - len(src_in_lines[line_num - 1].lstrip())) + if not expected: + indent = (len(src_in_lines[first_line_num - 1]) - len(src_in_lines[first_line_num - 1].lstrip())) src_in_lines[line_num - 1] += '\n' + '\n'.join('%s%s' % (' ' * indent, line.lstrip()) for line in got) continue - # Guess how much extra indenting got needs to match with the indentation + # Guess how much extra indenting ``got`` needs to match with the indentation # of src_in_lines - we match the indentation with the line in ``got`` which # has the smallest indentation after lstrip(). Note that the amount of indentation # required could be negative if the ``got`` block is indented. In this case # ``indent`` is set to zero. - indent = max(0, (len(src_in_lines[line_num]) - len(src_in_lines[line_num].lstrip()) - min(len(got[j]) - len(got[j].lstrip()) for j in range(len(got))))) - - expected = expected.splitlines() + indent = max(0, (len(src_in_lines[line_num]) - len(src_in_lines[line_num].lstrip()) + - min(len(got[j]) - len(got[j].lstrip()) for j in range(len(got))))) # Double check that what was expected was indeed in the source file and if # it is not then then print a warning for the user which contains the From dda8c281ddc276882e6c931ce50499e984d72aa9 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 23 Jun 2023 21:44:35 -0700 Subject: [PATCH 021/150] sage -fixdoctests: Handle why/explain annotation --- src/bin/sage-fixdoctests | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index dcc2d9187df..1a7caebc552 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -103,7 +103,11 @@ for block in doctests: # FIXME: map module name to feature name src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=[optional]) - continue + if not re.search('#.*(why|explain)', src_in_lines[first_line_num - 1]): + # When no explanation has been demanded, + # we just mark the doctest with the feature + continue + # Otherwise, continue and show the backtrace as 'GOT' if 'Traceback (most recent call last):' in block: @@ -132,8 +136,6 @@ for block in doctests: expected = expected.splitlines() - print(f'{expected=}, {got=}') - if args.keep_both: test_lines = ([update_optional_tags(src_in_lines[first_line_num - 1], add_tags=['GOT'])] + src_in_lines[first_line_num : line_num]) From e9f38c02989b90e9cc218cfb27696cb2fe4f7402 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 23 Jun 2023 22:46:30 -0700 Subject: [PATCH 022/150] sage -fixdoctests: Shorter annotations for top-level NameError --- src/bin/sage-fixdoctests | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 1a7caebc552..332cb2fc2db 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -56,6 +56,8 @@ sep = "**********************************************************************\n" doctests = doc_out.split(sep) src_in_lines = src_in.splitlines() +venv_explainer = f' (with venv={args.venv})' if args.venv else '' + for block in doctests: if 'Failed example:' not in block: continue # sanity checking, but shouldn't happen @@ -123,6 +125,13 @@ for block in doctests: # simplify filenames shown in backtrace while m := re.search('"([-a-zA-Z0-9._/]*/site-packages)/sage/', got): got = got[:m.start(1)] + '...' + got[m.end(1):] + + last_frame = got.rfind('File "') + if last_frame >= 0 and got.rfind("NameError:") >= 0 and got[last_frame:].startswith('File " Date: Fri, 23 Jun 2023 23:40:20 -0700 Subject: [PATCH 023/150] sage -fixdoctests: Add option --only-tags --- src/bin/sage-fixdoctests | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 332cb2fc2db..f9fb7540a1a 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -28,6 +28,8 @@ parser.add_argument("--venv", type=str, default='', help="directory name of a ve parser.add_argument("--environment", type=str, default="sage.repl.ipython_kernel.all_jupyter", help="name of a module that provides the global environment for tests") parser.add_argument("--full-tracebacks", default=False, action="store_true", help="include full tracebacks rather than '...'") +parser.add_argument("--only-tags", default=False, action="store_true", + help="only add '# optional' tags where needed, ignore other failures") parser.add_argument("--keep-both", default=False, action="store_true", help="do not replace test results; duplicate the test instead and mark both copies # optional") parser.add_argument("input", help="input filename", @@ -59,9 +61,6 @@ src_in_lines = src_in.splitlines() venv_explainer = f' (with venv={args.venv})' if args.venv else '' for block in doctests: - if 'Failed example:' not in block: - continue # sanity checking, but shouldn't happen - # Extract the line, what was expected, and was got. line = block.find('line ') # block should contain: 'line ##, in ...', where ## is an integer comma = block.find(', in ') # we try to extract the line number which gives the test failure @@ -69,6 +68,11 @@ for block in doctests: continue # but if something goes wrong we give up first_line_num = line_num = int(block[line + 5:comma]) # 1-based line number of the first line of the example + if m := re.search(r"referenced here was set only in doctest marked '([^;']*)", block): + optional = m.group(1) + src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], + add_tags=[optional]) + if m2 := re.search('(Expected:|Expected nothing|Exception raised:)\n', block): m1 = re.search('Failed example:\n', block) line_num += block[m1.end() : m2.start()].count('\n') - 1 @@ -91,11 +95,6 @@ for block in doctests: block = '\n'.join(expected) + '\nGot:\n' + block[m2.end():] else: block = block[m2.end():] - elif (m := re.search(r"referenced here was set only in doctest marked '([^;']*)")): - optional = m.group(1) - src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], - add_tags=[optional]) - continue else: continue @@ -111,6 +110,9 @@ for block in doctests: continue # Otherwise, continue and show the backtrace as 'GOT' + if args.only_tags: + continue + if 'Traceback (most recent call last):' in block: expected, got = block.split('\nGot:\n') From 6e25da2980380348e25dba35df63ecc0c62394f3 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 24 Jun 2023 12:29:28 -0700 Subject: [PATCH 024/150] sage --fixdoctests: New option --distribution, defaults --- src/bin/sage-fixdoctests | 83 +++++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index f9fb7540a1a..22dc8b4e014 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -16,16 +16,26 @@ AUTHORS:: """ import os import re +import shlex import subprocess + from argparse import ArgumentParser, FileType -from sage.misc.temporary_file import tmp_filename +from pathlib import Path + from sage.doctest.parsing import update_optional_tags +from sage.env import SAGE_ROOT +from sage.misc.temporary_file import tmp_filename + parser = ArgumentParser(description="Given an input file with doctests, this creates a modified file that passes the doctests (modulo any raised exceptions). By default, the input file is modified. You can also name an output file.") parser.add_argument('-l', '--long', dest='long', action="store_true", default=False) -parser.add_argument("--venv", type=str, default='', help="directory name of a venv where 'sage -t' is to be run") -parser.add_argument("--environment", type=str, default="sage.repl.ipython_kernel.all_jupyter", help="name of a module that provides the global environment for tests") +parser.add_argument("--distribution", type=str, default='', + help="distribution package to test, e.g., 'sagemath-graphs', 'sagemath-combinat[modules]'; sets defaults for --venv and --environment") +parser.add_argument("--venv", type=str, default='', + help="directory name of a venv where 'sage -t' is to be run") +parser.add_argument("--environment", type=str, default='', + help="name of a module that provides the global environment for tests; implies --keep-both and --full-tracebacks") parser.add_argument("--full-tracebacks", default=False, action="store_true", help="include full tracebacks rather than '...'") parser.add_argument("--only-tags", default=False, action="store_true", @@ -44,13 +54,71 @@ test_file = args.input src_in = test_file.read() test_file.close() +def default_venv_environment_from_distribution(): + if args.distribution: + # shortcuts / variants + args.distribution = args.distribution.replace('_', '-') + if not (args.distribution.startswith('sagemath-') + or args.distribution.startswith('sage-')): + args.distribution = f'sagemath-{args.distribution}' + # extras + m = re.fullmatch(r'([^[]*)(\[([^]]*)\])?', args.distribution) + plain_distribution, extras = m.group(1), m.group(3) + tox_env_name = 'sagepython-sagewheels-nopypi-norequirements' + if extras: + tox_env_name += '-' + extras.replace(',', '-') + default_venv = os.path.join(SAGE_ROOT, 'pkgs', plain_distribution, '.tox', tox_env_name) + default_environment = 'sage.all__' + plain_distribution.replace('-', '_') + else: + default_venv = '' + default_environment = "sage.repl.ipython_kernel.all_jupyter" + return default_venv, default_environment + +default_venv, default_environment = default_venv_environment_from_distribution() + +if not args.venv: + args.venv = default_venv +if not args.environment: + args.environment = default_environment + +if args.environment: + args.keep_both = args.full_tracebacks = True + +venv_explainers = [] + +if args.venv: + if m := re.search(f'pkgs/(sage[^/]*)/[.]tox/((sagepython|sagewheels|nopypi|norequirements)-*)*([^/]*)$', + args.venv): + args.distribution, extras = m.group(1), m.group(4) + if extras: + args.distribution += '[' + extras.replace('-', ',') + ']' + default_venv_given_distribution, default_environment_given_distribution = default_venv_environment_from_distribution() + + if (Path(args.venv).resolve() == Path(default_venv_given_distribution).resolve() + or args.environment == default_environment_given_distribution): + venv_explainers.append(f'--distribution {shlex.quote(args.distribution)}') + default_venv, default_environment = default_venv_given_distribution, default_environment_given_distribution + +if Path(args.venv).resolve() != Path(default_venv).resolve(): + venv_explainers.append(f'--venv {shlex.quote(args.venv)}') +if args.environment != default_environment: + venv_explainers.append(f'--environment {args.environment}') + +if venv_explainers: + venv_explainer = ' (with ' + ' '.join(venv_explainers) + ')' +else: + venv_explainer = '' + # put the output of the test into sage's temporary directory doc_file = tmp_filename() -os.system('%s -t %s %s %s > %s' % ( - f'{args.venv}/bin/sage' if args.venv else 'sage', +cmdline = '%s -t %s %s %s > %s' % ( + shlex.quote(f'{args.venv}/bin/sage') if args.venv else 'sage', '--long' if args.long else '', f'--environment {args.environment}' if args.environment else '', - test_file.name, doc_file)) + test_file.name, doc_file) +print(f'Running {cmdline}') +os.system(cmdline) + with open(doc_file, 'r') as doc: doc_out = doc.read() sep = "**********************************************************************\n" @@ -58,8 +126,6 @@ sep = "**********************************************************************\n" doctests = doc_out.split(sep) src_in_lines = src_in.splitlines() -venv_explainer = f' (with venv={args.venv})' if args.venv else '' - for block in doctests: # Extract the line, what was expected, and was got. line = block.find('line ') # block should contain: 'line ##, in ...', where ## is an integer @@ -215,7 +281,6 @@ test_output.close() if args.output: print('The fixed doctests have been saved as {0}.'.format(test_output.name)) else: - from sage.env import SAGE_ROOT relative = os.path.relpath(test_output.name, SAGE_ROOT) print(relative) if relative.startswith('..'): From a14580e13e1c5e88cedb6e6b441322351143c66a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 24 Jun 2023 13:32:37 -0700 Subject: [PATCH 025/150] sage --fixdoctests: Use git --no-pager --- src/bin/sage-fixdoctests | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 22dc8b4e014..112213912a5 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -54,6 +54,8 @@ test_file = args.input src_in = test_file.read() test_file.close() +runtest_default_environment = "sage.repl.ipython_kernel.all_jupyter" + def default_venv_environment_from_distribution(): if args.distribution: # shortcuts / variants @@ -71,7 +73,7 @@ def default_venv_environment_from_distribution(): default_environment = 'sage.all__' + plain_distribution.replace('-', '_') else: default_venv = '' - default_environment = "sage.repl.ipython_kernel.all_jupyter" + default_environment = runtest_default_environment return default_venv, default_environment default_venv, default_environment = default_venv_environment_from_distribution() @@ -114,7 +116,7 @@ doc_file = tmp_filename() cmdline = '%s -t %s %s %s > %s' % ( shlex.quote(f'{args.venv}/bin/sage') if args.venv else 'sage', '--long' if args.long else '', - f'--environment {args.environment}' if args.environment else '', + f'--environment {args.environment}' if args.environment != runtest_default_environment else '', test_file.name, doc_file) print(f'Running {cmdline}') os.system(cmdline) @@ -286,4 +288,4 @@ else: if relative.startswith('..'): print('Fixed source file is not part of Sage.') else: - subprocess.call(['git', 'diff', relative], cwd=SAGE_ROOT) + subprocess.call(['git', '--no-pager', 'diff', relative], cwd=SAGE_ROOT) From 48b166031bbd33d7ff513c5588c7857f06d0cdbf Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 24 Jun 2023 16:39:31 -0700 Subject: [PATCH 026/150] sage --fixdoctests: Rewrite # optional properly --- src/bin/sage-fixdoctests | 4 +- src/sage/doctest/forker.py | 6 +-- src/sage/doctest/parsing.py | 73 +++++++++++++++++++++++++++---------- 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 112213912a5..30d7911027d 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -136,7 +136,7 @@ for block in doctests: continue # but if something goes wrong we give up first_line_num = line_num = int(block[line + 5:comma]) # 1-based line number of the first line of the example - if m := re.search(r"referenced here was set only in doctest marked '([^;']*)", block): + if m := re.search(r"referenced here was set only in doctest marked '# optional - ([^;']*)", block): optional = m.group(1) src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=[optional]) @@ -172,6 +172,8 @@ for block in doctests: # FIXME: map module name to feature name src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=[optional]) + if '- #' in src_in_lines[first_line_num - 1]: + breakpoint() if not re.search('#.*(why|explain)', src_in_lines[first_line_num - 1]): # When no explanation has been demanded, # we just mark the doctest with the feature diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 6a62fdccb83..187dcaa8d73 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -769,13 +769,13 @@ def compiler(example): self.total_walltime += example.walltime + check_duration # Report the outcome. + if example.warnings: + for warning in example.warnings: + out(self._failure_header(test, example, f'Warning: {warning}')) if outcome is SUCCESS: if self.options.warn_long > 0 and example.walltime + check_duration > self.options.warn_long: self.report_overtime(out, test, example, got, check_duration=check_duration) - elif example.warnings: - for warning in example.warnings: - out(self._failure_header(test, example, f'Warning: {warning}')) elif not quiet: self.report_success(out, test, example, got, check_duration=check_duration) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 21907998a18..83a74625d6d 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -80,11 +80,11 @@ def fake_RIFtol(*args): ansi_escape_sequence = re.compile(r'(\x1b[@-Z\\-~]|\x1b\[.*?[@-~]|\x9b.*?[@-~])') special_optional_regex = 'arb216|arb218|py2|long time|not implemented|not tested|known bug' -optional_regex = re.compile(fr'({special_optional_regex})|([^ a-z]\s*optional\s*[:-]*((\s|\w|[.])*))') -special_optional_regex = re.compile(special_optional_regex) +optional_regex = re.compile(fr'({special_optional_regex})|([^ a-z]\s*optional\s*[:-]*((\s|\w|[.])*))', re.IGNORECASE) +special_optional_regex = re.compile(special_optional_regex, re.IGNORECASE) -def parse_optional_tags(string): +def parse_optional_tags(string, *, return_string_sans_tags=False): """ Return a set consisting of the optional tags from the following set that occur in a comment on the first line of the input string. @@ -98,6 +98,14 @@ def parse_optional_tags(string): - 'arb218' - 'optional: PKG_NAME' -- the set will just contain 'PKG_NAME' + INPUT: + + - ``string`` -- a string + + - ``return_string_sans_tags`` -- (boolean, default ``False``); whether to + additionally return ``string`` with the optional tags removed but other + comments kept. + EXAMPLES:: sage: from sage.doctest.parsing import parse_optional_tags @@ -133,13 +141,20 @@ def parse_optional_tags(string): set() """ safe, literals, state = strip_string_literals(string) - first_line = safe.split('\n', 1)[0] - if '#' not in first_line: - return set() - comment = first_line[first_line.find('#') + 1:] - comment = comment[comment.index('(') + 1: comment.rindex(')')] - # strip_string_literals replaces comments - comment = "#" + (literals[comment]).lower() + split = safe.split('\n', 1) + if len(split) > 1: + first_line, rest = split + else: + first_line, rest = split[0], '' + + sharp_index = first_line.find('#') + if sharp_index <= 0: # no comment + if return_string_sans_tags: + return set(), string + else: + return set() + + first_line, comment = first_line[:sharp_index] % literals, first_line[sharp_index:] % literals tags = [] for m in optional_regex.finditer(comment): @@ -150,7 +165,12 @@ def parse_optional_tags(string): tags.append(cmd) else: tags.extend(m.group(3).split() or [""]) - return set(tags) + + if return_string_sans_tags: + # FIXME: Keep non-tag comments + return set(tags), first_line + rest%literals + else: + return set(tags) def unparse_optional_tags(tags): @@ -189,20 +209,33 @@ def unparse_optional_tags(tags): standard_tag_columns = [64, 72, 80, 84] -def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None): - if not (m := re.match(' *sage: [^#]*', line)): +def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, force_rewrite=False): + + if not (m := re.match('( *sage: *)(.*)', line)): raise ValueError(f'line must start with a sage: prompt, got: {line}') - if tags is not None or remove_tags: - raise NotImplementedError - if not add_tags: + + current_tags, line_sans_tags = parse_optional_tags(line, return_string_sans_tags=True) + + new_tags = set(current_tags) + + if tags is not None: + new_tags = tags + + if add_tags is not None: + new_tags.update(add_tags) + + if remove_tags is not None: + new_tags.difference_update(remove_tags) + + if not force_rewrite and new_tags == current_tags: return line - # FIXME: parse existing tags, merge with given tags; remove them from line, keeping non-tag comments - tags = add_tags + + line = line_sans_tags.rstrip() for column in optional_tag_columns: - if len(line) < column - 2: + if len(line) <= column - 2: line += ' ' * (column - 2 - len(line)) break - line += ' ' + unparse_optional_tags(tags) + line += ' ' + unparse_optional_tags(new_tags) return line From 58d857e6df76c811fe59709ffd3fddf961e70707 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 24 Jun 2023 17:24:19 -0700 Subject: [PATCH 027/150] sage --fixdoctests: More detail on NameError --- src/bin/sage-fixdoctests | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 30d7911027d..ab9b7952973 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -199,10 +199,16 @@ for block in doctests: got = got[:m.start(1)] + '...' + got[m.end(1):] last_frame = got.rfind('File "') - if last_frame >= 0 and got.rfind("NameError:") >= 0 and got[last_frame:].startswith('File "= 0 + and (index_NameError := got.rfind("NameError:")) >= 0 + and got[last_frame:].startswith('File " Date: Sat, 24 Jun 2023 17:29:27 -0700 Subject: [PATCH 028/150] sage --fixdoctests: Remove debugging code --- src/bin/sage-fixdoctests | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index ab9b7952973..e91ec77bac2 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -172,8 +172,6 @@ for block in doctests: # FIXME: map module name to feature name src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=[optional]) - if '- #' in src_in_lines[first_line_num - 1]: - breakpoint() if not re.search('#.*(why|explain)', src_in_lines[first_line_num - 1]): # When no explanation has been demanded, # we just mark the doctest with the feature From 52fb3cc468cb05f33c49148111f2683fe93d5a2a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 24 Jun 2023 18:30:16 -0700 Subject: [PATCH 029/150] sage --fixdoctests: Fix up 'why' --- src/bin/sage-fixdoctests | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index e91ec77bac2..5b050286937 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -169,10 +169,11 @@ for block in doctests: # Error testing. if m := re.search(r"ModuleNotFoundError: No module named '([^']*)'", block): optional = m.group(1) + asked_why = re.search('#.*(why|explain)', src_in_lines[first_line_num - 1]) # FIXME: map module name to feature name src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=[optional]) - if not re.search('#.*(why|explain)', src_in_lines[first_line_num - 1]): + if not asked_why: # When no explanation has been demanded, # we just mark the doctest with the feature continue From 28019f404602c7f1e3abf39028f0550c652f0a42 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 24 Jun 2023 20:05:05 -0700 Subject: [PATCH 030/150] sage --fixdoctests: Handle multiple files --- src/bin/sage-fixdoctests | 119 ++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 57 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 5b050286937..d2b46316d33 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -42,17 +42,15 @@ parser.add_argument("--only-tags", default=False, action="store_true", help="only add '# optional' tags where needed, ignore other failures") parser.add_argument("--keep-both", default=False, action="store_true", help="do not replace test results; duplicate the test instead and mark both copies # optional") -parser.add_argument("input", help="input filename", - type=FileType('r')) -parser.add_argument('output', nargs='?', help="output filename", - type=FileType('w')) +parser.add_argument("--overwrite", default=False, action="store_true", + help="never interpret a second filename as OUTPUT; overwrite the source files") +parser.add_argument("--no-overwrite", default=False, action="store_true", + help="never interpret a second filename as OUTPUT; output goes to files named INPUT.fixed") +parser.add_argument("filename", nargs='*', help="input filenames; or INPUT_FILENAME OUTPUT_FILENAME if exactly two filenames are given and neither --overwrite nor --no-overwrite is present", + type=str) args = parser.parse_args() -# set input and output files -test_file = args.input -src_in = test_file.read() -test_file.close() runtest_default_environment = "sage.repl.ipython_kernel.all_jupyter" @@ -111,30 +109,13 @@ if venv_explainers: else: venv_explainer = '' -# put the output of the test into sage's temporary directory -doc_file = tmp_filename() -cmdline = '%s -t %s %s %s > %s' % ( - shlex.quote(f'{args.venv}/bin/sage') if args.venv else 'sage', - '--long' if args.long else '', - f'--environment {args.environment}' if args.environment != runtest_default_environment else '', - test_file.name, doc_file) -print(f'Running {cmdline}') -os.system(cmdline) - -with open(doc_file, 'r') as doc: - doc_out = doc.read() -sep = "**********************************************************************\n" - -doctests = doc_out.split(sep) -src_in_lines = src_in.splitlines() -for block in doctests: +def process_block(block, src_in_lines): # Extract the line, what was expected, and was got. - line = block.find('line ') # block should contain: 'line ##, in ...', where ## is an integer - comma = block.find(', in ') # we try to extract the line number which gives the test failure - if line == -1 or comma == -1: - continue # but if something goes wrong we give up - first_line_num = line_num = int(block[line + 5:comma]) # 1-based line number of the first line of the example + if not (m := re.match('File "([^"]*)", line ([0-9]+), in ', block)): + return + filename = m.group(1) + first_line_num = line_num = int(m.group(2)) # 1-based line number of the first line of the example if m := re.search(r"referenced here was set only in doctest marked '# optional - ([^;']*)", block): optional = m.group(1) @@ -164,7 +145,7 @@ for block in doctests: else: block = block[m2.end():] else: - continue + return # Error testing. if m := re.search(r"ModuleNotFoundError: No module named '([^']*)'", block): @@ -176,11 +157,11 @@ for block in doctests: if not asked_why: # When no explanation has been demanded, # we just mark the doctest with the feature - continue + return # Otherwise, continue and show the backtrace as 'GOT' if args.only_tags: - continue + return if 'Traceback (most recent call last):' in block: @@ -208,7 +189,7 @@ for block in doctests: name = "" src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=[f'NameError{name}{venv_explainer}']) - continue + return got = got.splitlines() else: got = got.splitlines() @@ -239,7 +220,7 @@ for block in doctests: if not expected: indent = (len(src_in_lines[first_line_num - 1]) - len(src_in_lines[first_line_num - 1].lstrip())) src_in_lines[line_num - 1] += '\n' + '\n'.join('%s%s' % (' ' * indent, line.lstrip()) for line in got) - continue + return # Guess how much extra indenting ``got`` needs to match with the indentation # of src_in_lines - we match the indentation with the line in ``got`` which @@ -258,7 +239,7 @@ for block in doctests: txt = "Did not manage to replace\n%s\n%s\n%s\nwith\n%s\n%s\n%s" warnings.warn(txt % ('>' * 40, '\n'.join(expected), '>' * 40, '<' * 40, '\n'.join(got), '<' * 40)) - continue + return # If we got something when we expected nothing then we delete the line from the # output, otherwise, add all of what we `got` onto the end of src_in_lines[line_num] @@ -272,27 +253,51 @@ for block in doctests: for i in range(1, len(expected)): src_in_lines[line_num + i] = None -# Overwrite the source (or output file specified on the command line) -if args.output: - test_output = args.output -else: - test_output = open(test_file.name, 'w') - -for line in src_in_lines: - if line is None: - continue - test_output.write(line) - test_output.write('\n') - -test_output.close() -# Show summary of changes -if args.output: - print('The fixed doctests have been saved as {0}.'.format(test_output.name)) +# set input and output files +if len(args.filename) == 2 and not args.overwrite and not args.no_overwrite: + inputs, outputs = [args.filename[0]], [args.filename[1]] +elif args.no_overwrite: + inputs, outputs = args.filename, [input + ".fixed" for input in args.filename] else: - relative = os.path.relpath(test_output.name, SAGE_ROOT) - print(relative) - if relative.startswith('..'): - print('Fixed source file is not part of Sage.') + inputs = outputs = args.filename + +for input, output in zip(inputs, outputs): + with open(input, 'r') as test_file: + src_in = test_file.read() + + # Run doctester, putting the output of the test into sage's temporary directory + doc_file = tmp_filename() + cmdline = '%s -t %s %s %s > %s' % ( + shlex.quote(f'{args.venv}/bin/sage') if args.venv else 'sage', + '--long' if args.long else '', + f'--environment {args.environment}' if args.environment != runtest_default_environment else '', + test_file.name, doc_file) + print(f'Running "{cmdline}"') + os.system(cmdline) + + with open(doc_file, 'r') as doc: + doc_out = doc.read() + + sep = "**********************************************************************\n" + doctests = doc_out.split(sep) + src_in_lines = src_in.splitlines() + + for block in doctests: + process_block(block, src_in_lines) + + with open(output, 'w') as test_output: + for line in src_in_lines: + if line is None: + continue + test_output.write(line) + test_output.write('\n') + + # Show summary of changes + if input != output: + print("The fixed doctests have been saved as '{0}'.".format(output)) else: - subprocess.call(['git', '--no-pager', 'diff', relative], cwd=SAGE_ROOT) + relative = os.path.relpath(output, SAGE_ROOT) + print(f"The input file '{output}' has been overwritten.") + if not relative.startswith('..'): + subprocess.call(['git', '--no-pager', 'diff', relative], cwd=SAGE_ROOT) From 65212a3f7b6162f9ae5d387a90fb337227996e1a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 25 Jun 2023 11:27:11 -0700 Subject: [PATCH 031/150] sage --fixdoctests: Exit early if the doctester does not work --- src/bin/sage-fixdoctests | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index d2b46316d33..525b58308e1 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -18,6 +18,7 @@ import os import re import shlex import subprocess +import sys from argparse import ArgumentParser, FileType from pathlib import Path @@ -262,17 +263,23 @@ elif args.no_overwrite: else: inputs = outputs = args.filename +# Test the doctester, putting the output of the test into sage's temporary directory +executable = f'{args.venv}/bin/sage' if args.venv else 'sage' +environment_args = f'--environment {args.environment}' if args.environment != runtest_default_environment else '' +doc_file = tmp_filename() +input = os.path.join(SAGE_ROOT, 'src', 'sage', 'version.py') +cmdline = f'{shlex.quote(executable)} -t {environment_args} {shlex.quote(input)} > {shlex.quote(doc_file)}' +print(f'Running "{cmdline}"') +if status := os.waitstatus_to_exitcode(os.system(cmdline)): + print(f'Doctester exited with error status {status}') + sys.exit(status) + for input, output in zip(inputs, outputs): with open(input, 'r') as test_file: src_in = test_file.read() - # Run doctester, putting the output of the test into sage's temporary directory - doc_file = tmp_filename() - cmdline = '%s -t %s %s %s > %s' % ( - shlex.quote(f'{args.venv}/bin/sage') if args.venv else 'sage', - '--long' if args.long else '', - f'--environment {args.environment}' if args.environment != runtest_default_environment else '', - test_file.name, doc_file) + # Run the doctester, putting the output of the test into sage's temporary directory + cmdline = f'{shlex.quote(executable)} -t {environment_args} {shlex.quote(input)} > {shlex.quote(doc_file)}' print(f'Running "{cmdline}"') os.system(cmdline) From 66f1a7ad343fe75830de53beba41db4b1e3cd6ba Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 25 Jun 2023 11:40:02 -0700 Subject: [PATCH 032/150] src/sage/doctest/parsing.py (unparse_optional_tags): Take spkg_type into account for sorting --- src/sage/doctest/parsing.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 83a74625d6d..03f57df220b 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -23,12 +23,15 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -import re import doctest +import re + from collections import defaultdict -from sage.repl.preparse import preparse, strip_string_literals from functools import reduce +from sage.misc.cachefunc import cached_function +from sage.repl.preparse import preparse, strip_string_literals + from .external import available_software _RIFtol = None @@ -173,6 +176,22 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): return set(tags) +@cached_function +def _standard_tags(): + from sage.features.all import all_features + return frozenset(feature.name for feature in all_features() + if feature._spkg_type() == 'standard') + + +def _tag_key(tag): + if tag.startswith('sage.'): + return 2, tag + elif tag in _standard_tags(): + return 1, tag + else: + return 0, tag + + def unparse_optional_tags(tags): r""" Return a comment string that sets ``tags``. @@ -195,8 +214,7 @@ def unparse_optional_tags(tags): """ tags = set(tags) special_tags = set(tag for tag in tags if special_optional_regex.fullmatch(tag)) - optional_tags = sorted(tags - special_tags, - key=lambda tag: (tag.startswith('sage.'), tag)) + optional_tags = sorted(tags - special_tags, key=_tag_key) tags = sorted(special_tags) if optional_tags: tags.append('optional - ' + " ".join(optional_tags)) From f6d9acd36ad30e46a22980749235b8a988a7772a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 25 Jun 2023 13:16:26 -0700 Subject: [PATCH 033/150] sage --fixdoctests: Remove line-level # optional tags that duplicate file tags --- src/bin/sage-fixdoctests | 21 ++++++++++++++++++--- src/sage/doctest/parsing.py | 31 +++++++++++++++++++++---------- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 525b58308e1..edc0ba6e281 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -23,7 +23,8 @@ import sys from argparse import ArgumentParser, FileType from pathlib import Path -from sage.doctest.parsing import update_optional_tags +from sage.doctest.control import skipfile +from sage.doctest.parsing import parse_optional_tags, update_optional_tags from sage.env import SAGE_ROOT from sage.misc.temporary_file import tmp_filename @@ -275,8 +276,13 @@ if status := os.waitstatus_to_exitcode(os.system(cmdline)): sys.exit(status) for input, output in zip(inputs, outputs): - with open(input, 'r') as test_file: - src_in = test_file.read() + if skipfile_result := skipfile(input, False): + if skipfile_result is True: + print(f"Skipping {input} because it is marked 'nodoctest'") + continue + file_optional_tags = parse_optional_tags('#' + skipfile_result) + else: + file_optional_tags = set() # Run the doctester, putting the output of the test into sage's temporary directory cmdline = f'{shlex.quote(executable)} -t {environment_args} {shlex.quote(input)} > {shlex.quote(doc_file)}' @@ -288,8 +294,17 @@ for input, output in zip(inputs, outputs): sep = "**********************************************************************\n" doctests = doc_out.split(sep) + + with open(input, 'r') as test_file: + src_in = test_file.read() src_in_lines = src_in.splitlines() + # First remove duplicate optional tags + if file_optional_tags: + for i, line in enumerate(src_in_lines): + if re.match('( *sage: *)(.*)#', line): + src_in_lines[i] = update_optional_tags(line, remove_tags=file_optional_tags) + for block in doctests: process_block(block, src_in_lines) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 03f57df220b..7dc4e2e8005 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -151,7 +151,7 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): first_line, rest = split[0], '' sharp_index = first_line.find('#') - if sharp_index <= 0: # no comment + if sharp_index < 0: # no comment if return_string_sans_tags: return set(), string else: @@ -223,8 +223,8 @@ def unparse_optional_tags(tags): return '' -optional_tag_columns = [88, 100, 120] -standard_tag_columns = [64, 72, 80, 84] +optional_tag_columns = [48, 56, 64, 72, 80, 84] +standard_tag_columns = [88, 100, 120, 160] def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, force_rewrite=False): @@ -232,7 +232,7 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo if not (m := re.match('( *sage: *)(.*)', line)): raise ValueError(f'line must start with a sage: prompt, got: {line}') - current_tags, line_sans_tags = parse_optional_tags(line, return_string_sans_tags=True) + current_tags, line_sans_tags = parse_optional_tags(line.rstrip(), return_string_sans_tags=True) new_tags = set(current_tags) @@ -248,12 +248,23 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo if not force_rewrite and new_tags == current_tags: return line - line = line_sans_tags.rstrip() - for column in optional_tag_columns: - if len(line) <= column - 2: - line += ' ' * (column - 2 - len(line)) - break - line += ' ' + unparse_optional_tags(new_tags) + if not new_tags: + return line_sans_tags.rstrip() + + tag_columns = optional_tag_columns if any(_tag_key(tag)[0] < 1 for tag in new_tags) else standard_tag_columns + + if len(line) in tag_columns and line[-2:] == ' ': + # keep alignment + pass + else: + # realign + line = line_sans_tags.rstrip() + for column in tag_columns: + if len(line) <= column - 2: + line += ' ' * (column - 2 - len(line)) + break + line += ' ' + line += unparse_optional_tags(new_tags) return line From f4076530ce55c4e7af36a01f7c22f791e8509f57 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 26 Jun 2023 23:35:32 -0700 Subject: [PATCH 034/150] sage -t: Add option --probe --- src/bin/sage-runtests | 3 +++ src/sage/doctest/control.py | 21 +++++++++++++++++++++ src/sage/doctest/forker.py | 24 +++++++++++++++++++++++- src/sage/doctest/parsing.py | 16 +++++++++++++--- src/sage/doctest/sources.py | 4 +++- 5 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/bin/sage-runtests b/src/bin/sage-runtests index 4131161647d..779d7f762a7 100755 --- a/src/bin/sage-runtests +++ b/src/bin/sage-runtests @@ -56,6 +56,9 @@ if __name__ == "__main__": help='run tests pretending that the software listed in FEATURES (separated by commas) is not installed; ' 'if "all" is listed, will also hide features corresponding to all optional or experimental packages; ' 'if "optional" is listed, will also hide features corresponding to optional packages.') + parser.add_argument("--probe", metavar="FEATURES", default="", + help='run tests that would not be run because one of the given FEATURES (separated by commas) is not installed; ' + 'report the tests that pass nevertheless') parser.add_argument("--randorder", type=int, metavar="SEED", help="randomize order of tests") parser.add_argument("--random-seed", dest="random_seed", type=int, metavar="SEED", help="random seed (integer) for fuzzing doctests", default=os.environ.get("SAGE_DOCTEST_RANDOM_SEED")) diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 5aae3f020c6..4ca575b8e20 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -137,6 +137,7 @@ def __init__(self, **kwds): # the auto_optional_tags there. self.optional = set(['sage']) | auto_optional_tags self.hide = '' + self.probe = '' # > 0: always run GC before every test # < 0: disable GC @@ -458,6 +459,23 @@ def __init__(self, options, args): options.optional |= auto_optional_tags options.optional -= options.disabled_optional + if isinstance(options.probe, str): + if options.probe == 'none': + options.probe = '' + s = options.probe.lower() + if not s: + options.probe = set() + else: + options.probe = set(s.split(',')) + if "all" in options.probe: + # Special case to probe all features that are not present + options.probe = True + else: + # Check that all tags are valid + for o in options.probe: + if not optionaltag_regex.search(o): + raise ValueError('invalid optional tag {!r}'.format(o)) + self.options = options self.files = args @@ -1462,6 +1480,9 @@ def run(self): self.log("Features to be detected: " + ','.join(available_software.detectable())) if self.options.hidden_features: self.log("Hidden features: " + ','.join([f.name for f in self.options.hidden_features])) + if self.options.probe: + self.log("Features to be probed: " + ('all' if self.options.probe is True + else ','.join(self.options.probe))) self.add_files() self.expand_files_into_sources() self.filter_sources() diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 187dcaa8d73..d1ed5d0bf30 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -59,7 +59,7 @@ from .sources import DictAsObject from .parsing import OriginalSource, reduce_hex, unparse_optional_tags from sage.structure.sage_object import SageObject -from .parsing import SageOutputChecker, pre_hash, get_source +from .parsing import SageOutputChecker, pre_hash, get_source, unparse_optional_tags from sage.repl.user_globals import set_globals from sage.cpython.atexit import restore_atexit from sage.cpython.string import bytes_to_str, str_to_bytes @@ -716,10 +716,18 @@ def compiler(example): outcome = FAILURE # guilty until proved innocent or insane + probed_tags = getattr(example, 'probed_tags', False) + # If the example executed without raising any exceptions, # verify its output. if exception is None: if check(example.want, got, self.optionflags): + if probed_tags: + example.warnings.append( + f"The annotation '{unparse_optional_tags(probed_tags)}' " + f"may no longer be needed; these features are not present, " + f"but we ran the doctest anyway as requested by --probing, " + f"and it succeeded.") outcome = SUCCESS # The example raised an exception: check if it was expected. @@ -755,6 +763,12 @@ def compiler(example): # We expected an exception: see whether it matches. elif check(example.exc_msg, exc_msg, self.optionflags): + if probed_tags: + example.warnings.append( + f"The annotation '{unparse_optional_tags(example.probed_tags)}' " + f"may no longer be needed; these features are not present, " + f"but we ran the doctest anyway as requested by --probing, " + f"and it succeeded (raised the expected exception).") outcome = SUCCESS # Another chance if they didn't care about the detail. @@ -763,6 +777,12 @@ def compiler(example): m2 = re.match(r'(?:[^:]*\.)?([^:]*:)', exc_msg) if m1 and m2 and check(m1.group(1), m2.group(1), self.optionflags): + if probed_tags: + example.warnings.append( + f"The annotation '{unparse_optional_tags(example.probed_tags)}' " + f"may no longer be needed; these features are not present, " + f"but we ran the doctest anyway as requested by --probing, " + f"and it succeeded (raised an exception as expected).") outcome = SUCCESS check_duration = walltime(check_starttime) @@ -779,6 +799,8 @@ def compiler(example): elif not quiet: self.report_success(out, test, example, got, check_duration=check_duration) + elif probed_tags: + pass elif outcome is FAILURE: if not quiet: self.report_failure(out, test, example, got, test.globs) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 7dc4e2e8005..983c606156f 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -32,7 +32,7 @@ from sage.misc.cachefunc import cached_function from sage.repl.preparse import preparse, strip_string_literals -from .external import available_software +from .external import available_software, external_software _RIFtol = None @@ -559,13 +559,14 @@ class SageDocTestParser(doctest.DocTestParser): A version of the standard doctest parser which handles Sage's custom options and tolerances in floating point arithmetic. """ - def __init__(self, optional_tags=(), long=False): + def __init__(self, optional_tags=(), long=False, *, probed_tags=()): r""" INPUT: - ``optional_tags`` -- a list or tuple of strings. - ``long`` -- boolean, whether to run doctests marked as taking a long time. + - ``probed_tags`` -- a list or tuple of strings. EXAMPLES:: @@ -594,6 +595,7 @@ def __init__(self, optional_tags=(), long=False): self.optional_tags.remove('sage') else: self.optional_only = True + self.probed_tags = probed_tags def __eq__(self, other): """ @@ -751,6 +753,7 @@ def parse(self, string, *args): if isinstance(item, doctest.Example): optional_tags = parse_optional_tags(item.source) item.optional_tags = frozenset(optional_tags) + item.probed_tags = set() if optional_tags: for tag in optional_tags: self.optionals[tag] += 1 @@ -767,7 +770,14 @@ def parse(self, string, *args): if self.optional_tags is not True: extra = optional_tags - self.optional_tags # set difference if extra: - if not available_software.issuperset(extra): + for tag in extra: + if tag not in available_software: + if (self.probed_tags is True or tag in self.probed_tags + and tag not in external_software): + item.probed_tags.add(tag) + else: + break + if not item.probed_tags: continue elif self.optional_only: self.optionals['sage'] += 1 diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index 15115a245b3..e6c139fb741 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -225,6 +225,7 @@ def _process_doc(self, doctests, doc, namespace, start): sigon = doctest.Example(sig_on_count_doc_doctest, "0\n", lineno=docstring.count("\n")) sigon.sage_source = sig_on_count_doc_doctest sigon.optional_tags = frozenset() + sigon.probed_tags = frozenset() dt.examples.append(sigon) doctests.append(dt) @@ -274,7 +275,8 @@ def _create_doctests(self, namespace, tab_okay=None): self._init() self.line_shift = 0 self.parser = SageDocTestParser(self.options.optional, - self.options.long) + self.options.long, + probed_tags=self.options.probe) self.linking = False doctests = [] in_docstring = False From f7d7e5b3d6b424195f1fedb0892e0215596cd810 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 26 Jun 2023 23:36:06 -0700 Subject: [PATCH 035/150] sage -fixdoctests: Use --probe --- src/bin/sage-fixdoctests | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index edc0ba6e281..97ef42634ee 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -42,6 +42,8 @@ parser.add_argument("--full-tracebacks", default=False, action="store_true", help="include full tracebacks rather than '...'") parser.add_argument("--only-tags", default=False, action="store_true", help="only add '# optional' tags where needed, ignore other failures") +parser.add_argument("--probe", metavar="FEATURES", type=str, default='all', + help="check whether '# optional - FEATURES' tags are still needed, remove these") parser.add_argument("--keep-both", default=False, action="store_true", help="do not replace test results; duplicate the test instead and mark both copies # optional") parser.add_argument("--overwrite", default=False, action="store_true", @@ -124,6 +126,11 @@ def process_block(block, src_in_lines): src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=[optional]) + if m := re.search(r"annotation '# optional - ([^;']*)' may no longer be needed", block): + optional = m.group(1) + src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], + remove_tags=[optional]) + if m2 := re.search('(Expected:|Expected nothing|Exception raised:)\n', block): m1 = re.search('Failed example:\n', block) line_num += block[m1.end() : m2.start()].count('\n') - 1 @@ -266,10 +273,11 @@ else: # Test the doctester, putting the output of the test into sage's temporary directory executable = f'{args.venv}/bin/sage' if args.venv else 'sage' -environment_args = f'--environment {args.environment}' if args.environment != runtest_default_environment else '' +environment_args = f'--environment {args.environment} ' if args.environment != runtest_default_environment else '' +probe_args = f'--probe {shlex.quote(args.probe)} ' if args.probe else '' doc_file = tmp_filename() input = os.path.join(SAGE_ROOT, 'src', 'sage', 'version.py') -cmdline = f'{shlex.quote(executable)} -t {environment_args} {shlex.quote(input)} > {shlex.quote(doc_file)}' +cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{shlex.quote(input)} > {shlex.quote(doc_file)}' print(f'Running "{cmdline}"') if status := os.waitstatus_to_exitcode(os.system(cmdline)): print(f'Doctester exited with error status {status}') @@ -285,7 +293,7 @@ for input, output in zip(inputs, outputs): file_optional_tags = set() # Run the doctester, putting the output of the test into sage's temporary directory - cmdline = f'{shlex.quote(executable)} -t {environment_args} {shlex.quote(input)} > {shlex.quote(doc_file)}' + cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{shlex.quote(input)} > {shlex.quote(doc_file)}' print(f'Running "{cmdline}"') os.system(cmdline) From c890919aecbc928dc89ba854db861bba9eb9e6f9 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Jun 2023 09:08:10 -0700 Subject: [PATCH 036/150] sage.misc.sagedoc: If terminal is too narrow, cut away # optional for present features --- src/sage/misc/sagedoc.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index 5a69dbcb0cc..25987568812 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -41,6 +41,7 @@ # **************************************************************************** import os import re +import shutil import sys import pydoc from sage.misc.temporary_file import tmp_dir @@ -589,6 +590,26 @@ def process_mathtt(s): return s +def process_optional_annotations(s): + + lines = s.split('\n') + columns = shutil.get_terminal_size().columns + extra_indent = 3 # extra indent used by IPython after title "Class docstring:" etc. + if not any(len(line) + 3 >= columns for line in lines): + # fast path + return s + + from sage.doctest.external import available_software + from sage.doctest.parsing import parse_optional_tags, update_optional_tags + + for i, line in enumerate(lines): + if re.match(' *sage: .*#', line): + tags = parse_optional_tags(line) + lines[i] = update_optional_tags(line, remove_tags=[tag for tag in tags + if tag in available_software]) + return '\n'.join(lines) + + def format(s, embedded=False): r"""noreplace Format Sage documentation ``s`` for viewing with IPython. @@ -768,6 +789,10 @@ def format(s, embedded=False): s = process_mathtt(s) s = process_extlinks(s, embedded=embedded) s = detex(s, embedded=embedded) + + if not embedded: + s = process_optional_annotations(s) + return s From 7870c3d8519c001f947b40a560c09d1530002d49 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Jun 2023 12:12:34 -0700 Subject: [PATCH 037/150] sage.features.sagemath: Add more modules to the JoinFeatures --- src/sage/features/sagemath.py | 43 ++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index ab1ce95884d..098fa7f1a6b 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -146,11 +146,13 @@ def __init__(self): # Some modules providing basic combinatorics are already included in sagemath-categories. # Hence, we test a Python module within the package. JoinFeature.__init__(self, 'sage.combinat', - [PythonModule('sage.combinat.tableau')], + [PythonModule('sage.combinat'), # namespace package + PythonModule('sage.combinat.tableau'), # representative + ], spkg='sagemath_combinat', type="standard") -class sage__geometry__polyhedron(PythonModule): +class sage__geometry__polyhedron(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.geometry.polyhedron`. @@ -184,8 +186,13 @@ def __init__(self): sage: isinstance(sage__geometry__polyhedron(), sage__geometry__polyhedron) True """ - PythonModule.__init__(self, 'sage.geometry.polyhedron', - spkg='sagemath_polyhedra', type="standard") + JoinFeature.__init__(self, 'sage.geometry.polyhedron', + [PythonModule('sage.geometry'), # namespace package + PythonModule('sage.geometry.polyhedron'), # representative + PythonModule('sage.schemes.toric'), # namespace package + PythonModule('sage.schemes.toric.variety'), # representative + ], + spkg='sagemath_polyhedra', type="standard") class sage__graphs(JoinFeature): @@ -251,7 +258,20 @@ def __init__(self): True """ JoinFeature.__init__(self, 'sage.graphs', - [PythonModule('sage.graphs.graph')], + # These lists of modules are an (incomplete) duplication + # of information in the distribution's MANIFEST. + # But at least as long as the monolithic Sage library is + # around, we need this information here for use by + # sage-fixdoctests. + [PythonModule('sage.graphs'), # namespace package + PythonModule('sage.graphs.graph'), # representative + PythonModule('sage.combinat.designs'), # namespace package + PythonModule('sage.combinat.designs.block_design'), # representative + PythonModule('sage.combinat.posets'), # namespace package + PythonModule('sage.combinat.posets.posets'), # representative + PythonModule('sage.topology'), # namespace package + PythonModule('sage.topology.simplicial_complex'), # representative + ], spkg='sagemath_graphs', type="standard") @@ -431,7 +451,18 @@ def __init__(self): True """ JoinFeature.__init__(self, 'sage.modules', - [PythonModule('sage.modules.free_module')], + [PythonModule('sage.modules'), # namespace package + PythonModule('sage.modules.free_module'), # representative + PythonModule('sage.matrix'), # namespace package + PythonModule('sage.matrix.matrix2'), # representative + PythonModule('sage.combinat.free_module'), + PythonModule('sage.quadratic_forms'), # namespace package + PythonModule('sage.quadratic_forms.quadratic_form'), # representative + PythonModule('sage.groups.affine_gps'), # namespace package + PythonModule('sage.groups.affine_gps.affine_group'), # representative + PythonModule('sage.groups.matrix_gps'), # namespace package + PythonModule('sage.groups.matrix_gps.named_group'), # representative + ], spkg='sagemath_modules', type='standard') From 6f230ffa605440bb6642b316832dac95dd7c3333 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Jun 2023 12:13:12 -0700 Subject: [PATCH 038/150] sage -fixdoctests: Map module names to feature names --- src/bin/sage-fixdoctests | 41 +++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 97ef42634ee..f1d8c75ada4 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -14,6 +14,7 @@ AUTHORS:: situations when either the expected output or computed output are empty. Added doctest to sage.tests.cmdline """ +import itertools import os import re import shlex @@ -26,6 +27,8 @@ from pathlib import Path from sage.doctest.control import skipfile from sage.doctest.parsing import parse_optional_tags, update_optional_tags from sage.env import SAGE_ROOT +from sage.features import PythonModule +from sage.features.all import all_features from sage.misc.temporary_file import tmp_filename @@ -114,6 +117,25 @@ else: venv_explainer = '' +def module_feature(module_name): + r""" + Find a top-level :class:`Feature` that provides the Python module of the given ``module_name``. + + OUTPUT: a :class:`Feature` or ``None``. + """ + longest_prefix = '' + longest_prefix_feature = None + for feature in all_features(): + for joined in itertools.chain([feature], feature.joined_features()): + if joined.name == module_name: + return feature + if (joined.name + '.').startswith(longest_prefix): + if (module_name + '.').startswith(joined.name + '.'): + longest_prefix = feature.name + '.' + longest_prefix_feature = feature + return longest_prefix_feature + + def process_block(block, src_in_lines): # Extract the line, what was expected, and was got. if not (m := re.match('File "([^"]*)", line ([0-9]+), in ', block)): @@ -158,16 +180,17 @@ def process_block(block, src_in_lines): # Error testing. if m := re.search(r"ModuleNotFoundError: No module named '([^']*)'", block): - optional = m.group(1) + module = m.group(1) asked_why = re.search('#.*(why|explain)', src_in_lines[first_line_num - 1]) - # FIXME: map module name to feature name - src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], - add_tags=[optional]) - if not asked_why: - # When no explanation has been demanded, - # we just mark the doctest with the feature - return - # Otherwise, continue and show the backtrace as 'GOT' + optional = module_feature(module) + if optional: + src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], + add_tags=[optional.name]) + if not asked_why: + # When no explanation has been demanded, + # we just mark the doctest with the feature + return + # Otherwise, continue and show the backtrace as 'GOT' if args.only_tags: return From 40d44fe4d09cce54fb2de94866236cc862525dc4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Jun 2023 13:19:36 -0700 Subject: [PATCH 039/150] sage -fixdoctests: Do not remove comments that precede annotations --- src/sage/doctest/parsing.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 983c606156f..34d470ebe99 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -159,6 +159,17 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): first_line, comment = first_line[:sharp_index] % literals, first_line[sharp_index:] % literals + if return_string_sans_tags: + # skip non-tag comments that precede the first tag comment + if m := optional_regex.search(comment): + sharp_index = comment[:m.start(0)].rfind('#') + if sharp_index >= 0: + first_line += comment[:sharp_index] + comment = comment[sharp_index:] + else: + # no non-tag comment + return set(), string + tags = [] for m in optional_regex.finditer(comment): cmd = m.group(1) From 7400851d7093f6769ad77e7e2165e50e5a488b63 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Jun 2023 18:26:51 -0700 Subject: [PATCH 040/150] sage.misc.sagedoc: Unconditionally remove "# optional" tags of present features --- src/sage/misc/sagedoc.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index 25987568812..00d39507524 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -591,13 +591,10 @@ def process_mathtt(s): def process_optional_annotations(s): - + r""" + Remove ``# optional`` annotations for present features from docstring ``s``. + """ lines = s.split('\n') - columns = shutil.get_terminal_size().columns - extra_indent = 3 # extra indent used by IPython after title "Class docstring:" etc. - if not any(len(line) + 3 >= columns for line in lines): - # fast path - return s from sage.doctest.external import available_software from sage.doctest.parsing import parse_optional_tags, update_optional_tags From c54d3d102569e1638fa48dbb7304f0f81a425daf Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Jun 2023 19:09:02 -0700 Subject: [PATCH 041/150] src/sage/doctest/parsing.py: Repair parse_optional_tags --- src/sage/doctest/parsing.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 34d470ebe99..41076c7c335 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -140,8 +140,17 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): UTF-8 works:: - sage: parse_optional_tags("'ěščřžýáíéďĎ'") - set() + sage: parse_optional_tags("'ěščřžýáíéďĎ'") + set() + + With ``return_string_sans_tags=True``:: + + sage: parse_optional_tags("sage: print(1) # very important 1 # optional - foo", # optional - EXPECTED + ....: return_string_sans_tags=True) + sage: parse_optional_tags("sage: print(1) # very important 1 # optional - foo", # optional - GOT + ....: return_string_sans_tags=True) + ({'foo'}, 'sage: print(1) # very important 1 ') + """ safe, literals, state = strip_string_literals(string) split = safe.split('\n', 1) @@ -162,7 +171,7 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): if return_string_sans_tags: # skip non-tag comments that precede the first tag comment if m := optional_regex.search(comment): - sharp_index = comment[:m.start(0)].rfind('#') + sharp_index = comment[:m.start(0) + 1].rfind('#') if sharp_index >= 0: first_line += comment[:sharp_index] comment = comment[sharp_index:] @@ -173,12 +182,16 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): tags = [] for m in optional_regex.finditer(comment): cmd = m.group(1) - if cmd == 'known bug': + if cmd and cmd.lower() == 'known bug': tags.append('bug') # so that such tests will be run by sage -t ... -only-optional=bug elif cmd: - tags.append(cmd) + tags.append(cmd.lower()) else: - tags.extend(m.group(3).split() or [""]) + words = m.group(3).split() + if words: + tags.extend([s.lower() for s in words]) + else: + tags.append("") if return_string_sans_tags: # FIXME: Keep non-tag comments From 231886dd8769b49495ac8a88c955a237e51d2b03 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Jun 2023 19:50:15 -0700 Subject: [PATCH 042/150] sage.features.sagemath: Add even more modules to the JoinFeatures --- src/sage/features/sagemath.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 098fa7f1a6b..b563f41c5a0 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -458,10 +458,16 @@ def __init__(self): PythonModule('sage.combinat.free_module'), PythonModule('sage.quadratic_forms'), # namespace package PythonModule('sage.quadratic_forms.quadratic_form'), # representative + PythonModule('sage.groups.additive_abelian'), # namespace package + PythonModule('sage.groups.additive_abelian.qmodnz'), # representative PythonModule('sage.groups.affine_gps'), # namespace package PythonModule('sage.groups.affine_gps.affine_group'), # representative PythonModule('sage.groups.matrix_gps'), # namespace package PythonModule('sage.groups.matrix_gps.named_group'), # representative + PythonModule('sage.homology'), # namespace package + PythonModule('sage.homology.chain_complex'), # representative + PythonModule('sage.matroids'), # namespace package + PythonModule('sage.matroids.matroid'), # representative ], spkg='sagemath_modules', type='standard') @@ -695,7 +701,7 @@ def __init__(self): PythonModule.__init__(self, 'sage.rings.real_double') -class sage__rings__real_mpfr(PythonModule): +class sage__rings__real_mpfr(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.rings.real_mpfr`. @@ -713,8 +719,11 @@ def __init__(self): sage: isinstance(sage__rings__real_mpfr(), sage__rings__real_mpfr) True """ - PythonModule.__init__(self, 'sage.rings.real_mpfr', - spkg='sagemath_modules') + JoinFeature.__init__(self, 'sage.rings.real_mpfr', + [PythonModule('sage.rings.real_mpfr'), + PythonModule('sage.rings.complex_mpfr'), + ], + spkg='sagemath_modules') class sage__schemes(JoinFeature): From 793ec77101e9fdcfe90e92317086e5acd4f0c831 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Jun 2023 20:28:26 -0700 Subject: [PATCH 043/150] sage -fixdoctests: Do not add tags that are already file-level tags --- src/bin/sage-fixdoctests | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index f1d8c75ada4..52f75c88fd7 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -136,7 +136,7 @@ def module_feature(module_name): return longest_prefix_feature -def process_block(block, src_in_lines): +def process_block(block, src_in_lines, file_optional_tags): # Extract the line, what was expected, and was got. if not (m := re.match('File "([^"]*)", line ([0-9]+), in ', block)): return @@ -183,7 +183,7 @@ def process_block(block, src_in_lines): module = m.group(1) asked_why = re.search('#.*(why|explain)', src_in_lines[first_line_num - 1]) optional = module_feature(module) - if optional: + if optional and optional.name not in file_optional_tags: src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=[optional.name]) if not asked_why: @@ -337,7 +337,7 @@ for input, output in zip(inputs, outputs): src_in_lines[i] = update_optional_tags(line, remove_tags=file_optional_tags) for block in doctests: - process_block(block, src_in_lines) + process_block(block, src_in_lines, file_optional_tags) with open(output, 'w') as test_output: for line in src_in_lines: From 747b3d4905e672aeeb935089c1a9fb30ac0d75bd Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Jun 2023 20:29:14 -0700 Subject: [PATCH 044/150] sage -fixdoctests: Only check for a functional doctester when venv or environment is set --- src/bin/sage-fixdoctests | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 52f75c88fd7..edeab5de785 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -299,12 +299,13 @@ executable = f'{args.venv}/bin/sage' if args.venv else 'sage' environment_args = f'--environment {args.environment} ' if args.environment != runtest_default_environment else '' probe_args = f'--probe {shlex.quote(args.probe)} ' if args.probe else '' doc_file = tmp_filename() -input = os.path.join(SAGE_ROOT, 'src', 'sage', 'version.py') -cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{shlex.quote(input)} > {shlex.quote(doc_file)}' -print(f'Running "{cmdline}"') -if status := os.waitstatus_to_exitcode(os.system(cmdline)): - print(f'Doctester exited with error status {status}') - sys.exit(status) +if args.venv or environment_args: + input = os.path.join(SAGE_ROOT, 'src', 'sage', 'version.py') + cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{shlex.quote(input)} > {shlex.quote(doc_file)}' + print(f'Running "{cmdline}"') + if status := os.waitstatus_to_exitcode(os.system(cmdline)): + print(f'Doctester exited with error status {status}') + sys.exit(status) for input, output in zip(inputs, outputs): if skipfile_result := skipfile(input, False): From f78ce644c1ab6fdd974082eed5b8399c7ab00008 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Jun 2023 23:16:09 -0700 Subject: [PATCH 045/150] sage.doctest: Restore 'variable referenced here was set only' warnings, fix logic --- src/sage/doctest/forker.py | 2 ++ src/sage/doctest/parsing.py | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index d1ed5d0bf30..49eda002c6a 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -796,6 +796,8 @@ def compiler(example): if self.options.warn_long > 0 and example.walltime + check_duration > self.options.warn_long: self.report_overtime(out, test, example, got, check_duration=check_duration) + elif example.warnings: + pass elif not quiet: self.report_success(out, test, example, got, check_duration=check_duration) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 41076c7c335..b9772794a97 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -792,16 +792,19 @@ def parse(self, string, *args): continue if self.optional_tags is not True: - extra = optional_tags - self.optional_tags # set difference + extra = set(tag + for tag in optional_tags + if (tag not in self.optional_tags + and tag not in available_software)) if extra: - for tag in extra: - if tag not in available_software: - if (self.probed_tags is True or tag in self.probed_tags - and tag not in external_software): - item.probed_tags.add(tag) - else: - break - if not item.probed_tags: + if any(tag in external_software for tag in extra): + # never probe "external" software + continue + if self.probed_tags is True: + item.probed_tags = extra + elif all(tag in self.probed_tags for tag in extra): + item.probed_tags = extra + else: continue elif self.optional_only: self.optionals['sage'] += 1 From 2d72755e066fa26847255336cd7bdef0c63adc19 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Jun 2023 09:50:38 -0700 Subject: [PATCH 046/150] sage.doctest: Make 'variable referenced here was set only' warnings deterministic --- src/sage/doctest/forker.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 49eda002c6a..dd1698a8083 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -57,7 +57,7 @@ from sage.misc.misc import walltime from .util import Timer, RecordingDict, count_noun from .sources import DictAsObject -from .parsing import OriginalSource, reduce_hex, unparse_optional_tags +from .parsing import OriginalSource, reduce_hex from sage.structure.sage_object import SageObject from .parsing import SageOutputChecker, pre_hash, get_source, unparse_optional_tags from sage.repl.user_globals import set_globals @@ -1135,10 +1135,12 @@ def compile_and_execute(self, example, compiler, globs): for name in globs.got: setters_dict = self.setters.get(name) # setter_optional_tags -> setter if setters_dict: + was_set = False for setter_optional_tags, setter in setters_dict.items(): - if setter_optional_tags.issubset(example.optional_tags): + if setter_optional_tags.issubset(example.optional_tags): # was set in a less constrained doctest + was_set = True example.predecessors.append(setter) - if not example.predecessors: + if not was_set: f_setter_optional_tags = "; ".join("'" + unparse_optional_tags(setter_optional_tags) + "'" From 87ce0db099bad9989e011193fdb33363fd1eb28d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Jun 2023 13:21:19 -0700 Subject: [PATCH 047/150] sage -t: New option --only-lib; log when directly specified files are skipped --- src/bin/sage-runtests | 1 + src/sage/doctest/control.py | 58 ++++++++++++++++++++++++++++--------- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/bin/sage-runtests b/src/bin/sage-runtests index 779d7f762a7..35591632155 100755 --- a/src/bin/sage-runtests +++ b/src/bin/sage-runtests @@ -69,6 +69,7 @@ if __name__ == "__main__": parser.add_argument("-i", "--initial", action="store_true", default=False, help="only show the first failure in each file") parser.add_argument("--exitfirst", action="store_true", default=False, help="end the test run immediately after the first failure or unexpected exception") parser.add_argument("--force_lib", "--force-lib", action="store_true", default=False, help="do not import anything from the tested file(s)") + parser.add_argument("--only-lib", action="store_true", default=False, help="skip Python/Cython files that are not installed as modules") parser.add_argument("--abspath", action="store_true", default=False, help="print absolute paths rather than relative paths") parser.add_argument("--verbose", action="store_true", default=False, help="print debugging output during the test") parser.add_argument("-d", "--debug", action="store_true", default=False, help="drop into a python debugger when an unexpected error is raised") diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 4ca575b8e20..593b1c20758 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -20,6 +20,7 @@ # https://www.gnu.org/licenses/ # **************************************************************************** +import importlib import random import os import sys @@ -35,7 +36,7 @@ from sage.misc.temporary_file import tmp_dir from cysignals.signals import AlarmInterrupt, init_cysignals -from .sources import FileDocTestSource, DictAsObject +from .sources import FileDocTestSource, DictAsObject, get_basename from .forker import DocTestDispatcher from .reporting import DocTestReporter from .util import Timer, count_noun, dict_difference @@ -114,6 +115,7 @@ def __init__(self, **kwds): self.initial = False self.exitfirst = False self.force_lib = False + self.only_lib = False self.abspath = True # sage-runtests default is False self.verbose = False self.debug = False @@ -214,18 +216,22 @@ def skipdir(dirname): return True return False -def skipfile(filename, tested_optional_tags=False): +def skipfile(filename, tested_optional_tags=False, *, only_lib=False, log=None): """ - Return True if and only if the file ``filename`` should not be - doctested. + Return ``True`` if and only if the file ``filename`` should not be doctested. INPUT: - - ``filename`` - name of a file + - ``filename`` -- name of a file - - ``tested_optional_tags`` - a list or tuple or set of optional tags to test, + - ``tested_optional_tags`` -- a list or tuple or set of optional tags to test, or ``False`` (no optional test) or ``True`` (all optional tests) + - ``only_lib`` -- (boolean, default ``False``) whether to skip Python/Cython files + that are not installed as modules + + - ``log`` -- function to call with log messages, or ``None`` + If ``filename`` contains a line of the form ``"# sage.doctest: optional - xyz")``, then this will return ``False`` if "xyz" is in ``tested_optional_tags``. Otherwise, it returns the matching tag @@ -263,25 +269,46 @@ def skipfile(filename, tested_optional_tags=False): base, ext = os.path.splitext(filename) # .rst.txt appear in the installed documentation in subdirectories named "_sources" if ext not in ('.py', '.pyx', '.pxd', '.pxi', '.sage', '.spyx', '.rst', '.tex', '.rst.txt'): + if log: + log(f"Skipping '{filename}' because it is does one have one of the recognized file name extensions") return True - # These files are created by the jupyter-sphinx extension for internal use and should not be tested if "jupyter_execute" in filename: + if log: + log(f"Skipping '{filename}' because it is created by the jupyter-sphinx extension for internal use and should not be tested") return True + if only_lib and ext in ('.py', '.pyx', '.pxd'): + module_name = get_basename(filename) + try: + if not importlib.util.find_spec(module_name): # tries to import the containing package + if log: + log(f"Skipping '{filename}' because module {module_name} is not present in the venv") + return True + except ModuleNotFoundError as e: + if log: + log(f"Skipping '{filename}' because module {e.name} cannot be imported") + return True with open(filename) as F: line_count = 0 for line in F: if nodoctest_regex.match(line): + if log: + log(f"Skipping '{filename}' because it is marked 'nodoctest'") return True if tested_optional_tags is not True: # Adapted from code in SageDocTestParser.parse m = optionalfiledirective_regex.match(line) if m: + file_tag_string = m.group(2) if tested_optional_tags is False: - return m.group(2) - optional_tags = parse_optional_tags('#' + m.group(2)) + if log: + log(f"Skipping '{filename}' because it is marked '# {file_tag_string}'") + return file_tag_string + optional_tags = parse_optional_tags('#' + file_tag_string) extra = optional_tags - set(tested_optional_tags) if extra: - return m.group(2) + if log: + log(f"Skipping '{filename}' because it is marked '# {file_tag_string}'") + return file_tag_string line_count += 1 if line_count >= 10: break @@ -911,7 +938,8 @@ def all_doc_sources(): and (filename.endswith(".py") or filename.endswith(".pyx") or filename.endswith(".rst")) - and not skipfile(opj(SAGE_ROOT, filename), self.options.optional)): + and not skipfile(opj(SAGE_ROOT, filename), self.options.optional, + only_lib=self.options.only_lib)): self.files.append(os.path.relpath(opj(SAGE_ROOT, filename))) def expand_files_into_sources(self): @@ -971,11 +999,13 @@ def expand(): if dir[0] == "." or skipdir(os.path.join(root,dir)): dirs.remove(dir) for file in files: - if not skipfile(os.path.join(root, file), self.options.optional): + if not skipfile(os.path.join(root, file), self.options.optional, + only_lib=self.options.only_lib): yield os.path.join(root, file) else: - # the user input this file explicitly, so we don't skip it - yield path + if not skipfile(path, self.options.optional, + only_lib=self.options.only_lib, log=self.log): # log when directly specified filenames are skipped + yield path self.sources = [FileDocTestSource(path, self.options) for path in expand()] def filter_sources(self): From bb873c2bc4d71117768d6bdde68465aca46202b5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Jun 2023 13:22:43 -0700 Subject: [PATCH 048/150] sage -fixdoctests: Use sage -t --only-lib, pass through 'Skipping ... because' messages --- src/bin/sage-fixdoctests | 85 ++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index edeab5de785..69c7f61fd03 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -295,63 +295,72 @@ else: inputs = outputs = args.filename # Test the doctester, putting the output of the test into sage's temporary directory -executable = f'{args.venv}/bin/sage' if args.venv else 'sage' +executable = f'{os.path.relpath(args.venv)}/bin/sage' if args.venv else 'sage' environment_args = f'--environment {args.environment} ' if args.environment != runtest_default_environment else '' probe_args = f'--probe {shlex.quote(args.probe)} ' if args.probe else '' +lib_args = f'--only-lib ' if args.venv else '' doc_file = tmp_filename() if args.venv or environment_args: - input = os.path.join(SAGE_ROOT, 'src', 'sage', 'version.py') - cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{shlex.quote(input)} > {shlex.quote(doc_file)}' + input = os.path.join(os.path.relpath(SAGE_ROOT), 'src', 'sage', 'version.py') + cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{lib_args}{shlex.quote(input)}' print(f'Running "{cmdline}"') - if status := os.waitstatus_to_exitcode(os.system(cmdline)): + if status := os.waitstatus_to_exitcode(os.system(f'{cmdline} > {shlex.quote(doc_file)}')): print(f'Doctester exited with error status {status}') sys.exit(status) for input, output in zip(inputs, outputs): - if skipfile_result := skipfile(input, False): + if skipfile_result := skipfile(input, True, log=print): if skipfile_result is True: - print(f"Skipping {input} because it is marked 'nodoctest'") continue file_optional_tags = parse_optional_tags('#' + skipfile_result) else: file_optional_tags = set() # Run the doctester, putting the output of the test into sage's temporary directory - cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{shlex.quote(input)} > {shlex.quote(doc_file)}' + cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{lib_args}{shlex.quote(input)}' print(f'Running "{cmdline}"') - os.system(cmdline) + os.system(f'{cmdline} > {shlex.quote(doc_file)}') with open(doc_file, 'r') as doc: doc_out = doc.read() - sep = "**********************************************************************\n" - doctests = doc_out.split(sep) - - with open(input, 'r') as test_file: - src_in = test_file.read() - src_in_lines = src_in.splitlines() - - # First remove duplicate optional tags - if file_optional_tags: - for i, line in enumerate(src_in_lines): - if re.match('( *sage: *)(.*)#', line): - src_in_lines[i] = update_optional_tags(line, remove_tags=file_optional_tags) - - for block in doctests: - process_block(block, src_in_lines, file_optional_tags) - - with open(output, 'w') as test_output: - for line in src_in_lines: - if line is None: - continue - test_output.write(line) - test_output.write('\n') - - # Show summary of changes - if input != output: - print("The fixed doctests have been saved as '{0}'.".format(output)) + # echo control messages + for m in re.finditer('^Skipping .*', doc_out, re.MULTILINE): + print('sage-runtest: ' + m.group(0)) + break else: - relative = os.path.relpath(output, SAGE_ROOT) - print(f"The input file '{output}' has been overwritten.") - if not relative.startswith('..'): - subprocess.call(['git', '--no-pager', 'diff', relative], cwd=SAGE_ROOT) + sep = "**********************************************************************\n" + doctests = doc_out.split(sep) + + with open(input, 'r') as test_file: + src_in = test_file.read() + src_in_lines = src_in.splitlines() + shallow_copy_of_src_in_lines = list(src_in_lines) + + # First remove duplicate optional tags + if file_optional_tags: + for i, line in enumerate(src_in_lines): + if re.match('( *sage: *)(.*)#', line): + src_in_lines[i] = update_optional_tags(line, remove_tags=file_optional_tags) + + for block in doctests: + process_block(block, src_in_lines, file_optional_tags) + + if src_in_lines != shallow_copy_of_src_in_lines: + with open(output, 'w') as test_output: + for line in src_in_lines: + if line is None: + continue + test_output.write(line) + test_output.write('\n') + + # Show summary of changes + if input != output: + print("The fixed doctests have been saved as '{0}'.".format(output)) + else: + relative = os.path.relpath(output, SAGE_ROOT) + print(f"The input file '{output}' has been overwritten.") + if not relative.startswith('..'): + subprocess.call(['git', '--no-pager', 'diff', relative], cwd=SAGE_ROOT) + else: + print(f"No fixes made in '{input}'") From 499d68fd13f21ec6c591093a8d130f310a283f76 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Jun 2023 15:14:52 -0700 Subject: [PATCH 049/150] src/doc/en/developer/doctesting.rst: Add section 'Options for testing in virtual environments' --- src/doc/en/developer/doctesting.rst | 46 +++++++++++++++++++ .../en/developer/packaging_sage_library.rst | 2 + 2 files changed, 48 insertions(+) diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index 2bc8f4765bf..41c69bda829 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -1344,3 +1344,49 @@ utilized most efficiently):: Sorting sources by runtime so that slower doctests are run first.... Doctesting 2067 files using 2 threads. ... + +.. _section-doctesting-venv: + +Options for testing in virtual environments +------------------------------------------- + +The distribution packages of the modularized Sage library can be tested in virtual environments. +Sage has infrastructure to create such virtual environments using ``tox``, which is explained +in detail in :ref:`section-modularized-doctesting`. Our examples in this section +refer to this setting, but it applies the same to any user-created virtual environments. + +The virtual environments, set up in directories such as +``pkgs/sagemath-standard/.tox/sagepython-sagewheels-nopypi-norequirements`` +contain installations of built (non-editable) wheels. + +To test all modules of Sage that are installed in a virtual environment, +use the option ``--installed`` (instead of ``--all``):: + + [mkoeppe@sage sage]$ pkgs/sagemath-standard/.tox/sagepython-sagewheels-.../sage -tp4 --installed + +This tests against the doctests as they appear in the installed copies of the files +(in ``site-packages/sage/...``). +Note that these installed copies should never be edited, as they can +be overwritten without warning. + +When testing a modularized distribution package other than sagemath-standard, +the top-level module :mod:`sage.all` is not available. Use the option ``--environment`` +to select an appropriate top-level module:: + + [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -tp4 --environment sage.all__sagemath_categories --installed + +To test the installed modules against the doctests as they appear in the source +tree (``src/sage/...``):: + + [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -tp4 --environment sage.all__sagemath_categories src/sage/structure + +Note that testing all doctests as they appear in the source tree does not make sense +because many of the source files may not be installed in the virtual environment. +Use the option ``--only-lib`` to skip the source files of all Python/Cython modules +that are not installed in the virtual environment. + + [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -tp4 --environment sage.all__sagemath_categories --only-lib src/sage/schemes + +This option can also be combined with ``--all``: + + [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -tp4 --environment sage.all__sagemath_categories --only-lib --all diff --git a/src/doc/en/developer/packaging_sage_library.rst b/src/doc/en/developer/packaging_sage_library.rst index 83f883dd53e..93bb2ca8e6c 100644 --- a/src/doc/en/developer/packaging_sage_library.rst +++ b/src/doc/en/developer/packaging_sage_library.rst @@ -536,6 +536,8 @@ Not shown in the diagram are build dependencies and optional dependencies for te the Sage doctester (:mod:`sage.doctest`), and some related modules from :mod:`sage.misc`. +.. _section-modularized-doctesting: + Testing distribution packages ============================= From 7d291cd5dfe3d2efe33cd53bdb52ea19eb66f635 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Jun 2023 15:18:36 -0700 Subject: [PATCH 050/150] src/doc/en/developer/coding_basics.rst: Update description of file-level # optional --- src/doc/en/developer/coding_basics.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 44882e965eb..158509fa92f 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -1215,8 +1215,8 @@ framework. Here is a comprehensive list: then that file will be skipped unless the ``--optional=keyword`` flag is passed to ``sage -t``. - This does not apply to files which are explicitly given - as command line arguments: those are always tested. + When a file is skipped that was explicitly given as a command line argument, + a warning is displayed. If you add such a line to a file, you are strongly encouraged to add a note to the module-level documentation, saying that From 6410b89b8b64fa5c465be786175cac3428ffa588 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Jun 2023 16:21:20 -0700 Subject: [PATCH 051/150] src/doc/en/developer/doctesting.rst: Document sage -fixdoctests --- src/doc/en/developer/doctesting.rst | 92 +++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 5 deletions(-) diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index 41c69bda829..100a3abe5b1 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -869,9 +869,7 @@ Run optional doctests You can run tests that require optional packages by using the ``--optional`` flag. Obviously, you need to have installed the -necessary optional packages in order for these tests to succeed. See -http://www.sagemath.org/packages/optional/ in order to download -optional packages. +necessary optional packages in order for these tests to succeed. By default, Sage only runs doctests that are not marked with the ``optional`` tag. This is equivalent to running :: @@ -1383,10 +1381,94 @@ tree (``src/sage/...``):: Note that testing all doctests as they appear in the source tree does not make sense because many of the source files may not be installed in the virtual environment. Use the option ``--only-lib`` to skip the source files of all Python/Cython modules -that are not installed in the virtual environment. +that are not installed in the virtual environment:: [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -tp4 --environment sage.all__sagemath_categories --only-lib src/sage/schemes -This option can also be combined with ``--all``: +This option can also be combined with ``--all``:: [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -tp4 --environment sage.all__sagemath_categories --only-lib --all + + +.. _section-fixdoctests: + +The doctest fixer +================= + +Sage provides a development tool that assists with updating doctests. + + +Updating doctest outputs +------------------------ + +By default, ``./sage --fixdoctests`` runs the doctester and replaces the expected outputs +of all examples by the actual outputs from the current version of Sage:: + + [mkoeppe@sage sage]$ ./sage --fixdoctests \ + --overwrite src/sage/geometry/cone.py + +As this command edits the source file, it may be a good practice to first use ``git commit`` +to save any changes made in the file. + +After running the doctest fixer, it is a good idea to use ``git diff`` to check +all edits that the automated tool made. + +An alternative to this workflow is to use the option ``--keep-both``. When expected and +actual output of an example differ, it duplicates the example, marking the two copies +``# optional - EXPECTED`` and ``# optional - GOT``. (Thus, when re-running the doctester, +neither of the two copies is run; this makes ``./sage --fixdoctests`` idempotent.) + +When exceptions are expected by an example, it is standard practice to abbreviate +the tracebacks using ``...``. The doctest fixer uses this abbreviation automatically +when formatting the actual output. To disable it so that the details of the exception +can be inspected, use the option ``--full-tracebacks``. This is particularly useful +in combination with ``--keep-both``:: + + [mkoeppe@sage sage]$ ./sage --fixdoctests --keep-both --full-tracebacks \ + --overwrite src/sage/geometry/cone.py + +To make sure that all doctests are updated, you may have to use the option ``--long``:: + + [mkoeppe@sage sage]$ ./sage --fixdoctests --long \ + --overwrite src/sage/geometry/cone.py + +If you are not comfortable with allowing this tool to edit your source files, you can use +the option ``--no-overwrite``, which will create a new file with the extension ``.fixed`` +instead of overwriting the source file:: + + [mkoeppe@sage sage]$ ./sage --fixdoctests \ + --no-overwrite src/sage/geometry/cone.py + + +Managing ``# optional`` tags +---------------------------- + +When a file uses a ``# sage.doctest: optional - FEATURE`` directive, the +doctest fixer automatically removes these tags from ``# optional - FEATURE`` +annotations on individual doctests. + +In places where the doctester issues a doctest dataflow warning +(``Variable ... referenced here was set only in doctest marked '# optional - FEATURE'``), +the doctest fixer automatically adds the missing ``# optional`` annotations. + + +Use in virtual environments +--------------------------- + +The doctest fixer can also run tests using the Sage doctester installed in +a virtual environment:: + + [mkoeppe@sage sage]$ ./sage --fixdoctests \ + --distribution sagemath-categories \ + src/sage/geometry/schemes/generic/*.py + +This command, using ``--distribution``, is equivalent to a command +that uses the more specific options ``--venv`` and ``--environment``:: + + [mkoeppe@sage sage]$ ./sage --fixdoctests \ + --venv pkgs/sagemath-categories/.tox/sagepython-... \ + --environment sage.all__sagemath_categories + src/sage/geometry/schemes/generic/*.py + +Either way, the options ``--keep-both``, ``--full-tracebacks``, and +``--only-lib`` are implied. From dc2f81ece12cec4a00eb30ede29e545039eb9fc0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Jun 2023 16:34:54 -0700 Subject: [PATCH 052/150] src/doc/en/developer/doctesting.rst: Document --probe --- src/doc/en/developer/doctesting.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index 100a3abe5b1..787d51b3ae0 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -1451,6 +1451,16 @@ In places where the doctester issues a doctest dataflow warning (``Variable ... referenced here was set only in doctest marked '# optional - FEATURE'``), the doctest fixer automatically adds the missing ``# optional`` annotations. +Sometimes code changes can make existing ``# optional - FEATURE`` annotations unnecessary. +In an installation or virtual environment where FEATURE is not available, +you can invoke the doctest fixer with the option ``--probe FEATURE``. +Then it will run examples marked ``# optional - FEATURE`` silently, and if the example +turns out to work anyway, the annotation is automatically removed. + +To have the doctest fixer take care of the ``# optional`` annotations, +but not change the expected results of examples, use the option ``--only-tags``. +This mode is suitable for unattended runs on many files. + Use in virtual environments --------------------------- From eb089fdefc2e390df26bb5957c5d954ad60e83e6 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Jun 2023 20:07:37 -0700 Subject: [PATCH 053/150] src/sage/doctest/control.py: Fix grammar in message --- src/sage/doctest/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 593b1c20758..432eac88085 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -270,7 +270,7 @@ def skipfile(filename, tested_optional_tags=False, *, only_lib=False, log=None): # .rst.txt appear in the installed documentation in subdirectories named "_sources" if ext not in ('.py', '.pyx', '.pxd', '.pxi', '.sage', '.spyx', '.rst', '.tex', '.rst.txt'): if log: - log(f"Skipping '{filename}' because it is does one have one of the recognized file name extensions") + log(f"Skipping '{filename}' because it does not have one of the recognized file name extensions") return True if "jupyter_execute" in filename: if log: From 88451cd15504c29fa51a45585bc5dd12022ad1a4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Jun 2023 20:29:57 -0700 Subject: [PATCH 054/150] sage -fixdoctest: Change default for --probe --- src/bin/sage-fixdoctests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 69c7f61fd03..8717cf00536 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -45,7 +45,7 @@ parser.add_argument("--full-tracebacks", default=False, action="store_true", help="include full tracebacks rather than '...'") parser.add_argument("--only-tags", default=False, action="store_true", help="only add '# optional' tags where needed, ignore other failures") -parser.add_argument("--probe", metavar="FEATURES", type=str, default='all', +parser.add_argument("--probe", metavar="FEATURES", type=str, default='', help="check whether '# optional - FEATURES' tags are still needed, remove these") parser.add_argument("--keep-both", default=False, action="store_true", help="do not replace test results; duplicate the test instead and mark both copies # optional") From b405d87cb983aaf01b8a81582a6c8938c64abdda Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Jun 2023 20:30:38 -0700 Subject: [PATCH 055/150] sage -t: Do not forget to discover available software when using skipfile --- src/sage/doctest/control.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 432eac88085..6a2ba48c082 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -304,7 +304,10 @@ def skipfile(filename, tested_optional_tags=False, *, only_lib=False, log=None): log(f"Skipping '{filename}' because it is marked '# {file_tag_string}'") return file_tag_string optional_tags = parse_optional_tags('#' + file_tag_string) - extra = optional_tags - set(tested_optional_tags) + extra = set(tag + for tag in optional_tags + if (tag not in tested_optional_tags + and tag not in available_software)) if extra: if log: log(f"Skipping '{filename}' because it is marked '# {file_tag_string}'") From b5679517cfec73706560dc622088eec5f5c1327b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Jun 2023 22:20:13 -0700 Subject: [PATCH 056/150] sage -fixdoctest: Do do duplicate tags --- src/bin/sage-fixdoctests | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 8717cf00536..cfc2e458acc 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -144,9 +144,9 @@ def process_block(block, src_in_lines, file_optional_tags): first_line_num = line_num = int(m.group(2)) # 1-based line number of the first line of the example if m := re.search(r"referenced here was set only in doctest marked '# optional - ([^;']*)", block): - optional = m.group(1) + optional = m.group(1).split() src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], - add_tags=[optional]) + add_tags=optional) if m := re.search(r"annotation '# optional - ([^;']*)' may no longer be needed", block): optional = m.group(1) From 80d15a3e0b23b61905b28132f0fd3f9408e9f1c2 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 11:13:08 -0700 Subject: [PATCH 057/150] sage -fixdoctest: Fix default of --keep-both --- src/bin/sage-fixdoctests | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index cfc2e458acc..7bf15f277aa 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -81,6 +81,9 @@ def default_venv_environment_from_distribution(): default_environment = runtest_default_environment return default_venv, default_environment +if args.environment: + args.keep_both = args.full_tracebacks = True + default_venv, default_environment = default_venv_environment_from_distribution() if not args.venv: @@ -88,9 +91,6 @@ if not args.venv: if not args.environment: args.environment = default_environment -if args.environment: - args.keep_both = args.full_tracebacks = True - venv_explainers = [] if args.venv: From 843b479d317449c91f18bc727fcf88b8539bd4c7 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 11:13:44 -0700 Subject: [PATCH 058/150] src/sage/doctest/parsing.py: Fix up merge --- src/sage/doctest/parsing.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 44bf6f2808e..6a2b4db0e0d 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -186,8 +186,6 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): cmd = m.group(1) if cmd and cmd.lower() == 'known bug': tags.append('bug') # so that such tests will be run by sage -t ... -only-optional=bug - elif m.group(1) is not None: - tags.extend(m.group(2).split() or [""]) elif cmd: tags.append(cmd.lower()) else: From 5a47c7936b27d3359c69fa43d14a06ad08a7d9e3 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 11:16:09 -0700 Subject: [PATCH 059/150] src/sage/doctest/parsing.py (unparse_optional_tags): Implement # optional / # needs distinction --- src/sage/doctest/parsing.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 6a2b4db0e0d..723676e3136 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -209,13 +209,15 @@ def _standard_tags(): if feature._spkg_type() == 'standard') -def _tag_key(tag): +def _tag_group(tag): if tag.startswith('sage.'): - return 2, tag + return 'sage' elif tag in _standard_tags(): - return 1, tag + return 'standard' + elif not special_optional_regex.fullmatch(tag): + return 'optional' else: - return 0, tag + return 'special' def unparse_optional_tags(tags): @@ -233,17 +235,22 @@ def unparse_optional_tags(tags): '' sage: unparse_optional_tags({'magma'}) '# optional - magma' - sage: unparse_optional_tags(['zipp', 'sage.rings.number_field', 'foo']) - '# optional - foo zipp sage.rings.number_field' + sage: unparse_optional_tags(['fictional_optional', 'sage.rings.number_field', + ....: 'scipy', 'bliss']) + '# optional - bliss fictional_optional, needs scipy sage.rings.number_field' sage: unparse_optional_tags(['long time', 'not tested', 'p4cka9e']) '# long time, not tested, optional - p4cka9e' """ - tags = set(tags) - special_tags = set(tag for tag in tags if special_optional_regex.fullmatch(tag)) - optional_tags = sorted(tags - special_tags, key=_tag_key) - tags = sorted(special_tags) - if optional_tags: - tags.append('optional - ' + " ".join(optional_tags)) + group = defaultdict(set) + for tag in tags: + group[_tag_group(tag)].add(tag) + tags = sorted(group.pop('special', [])) + if 'optional' in group: + tags.append('optional - ' + " ".join(sorted(group.pop('optional')))) + if 'standard' in group or 'sage' in group: + tags.append('needs ' + " ".join(sorted(group.pop('standard', [])) + + sorted(group.pop('sage', [])))) + assert not group if tags: return '# ' + ', '.join(tags) return '' @@ -277,7 +284,8 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo if not new_tags: return line_sans_tags.rstrip() - tag_columns = optional_tag_columns if any(_tag_key(tag)[0] < 1 for tag in new_tags) else standard_tag_columns + tag_columns = optional_tag_columns if any(_tag_group(tag) in ['optional', 'special'] + for tag in new_tags) else standard_tag_columns if len(line) in tag_columns and line[-2:] == ' ': # keep alignment From 7708ced7e48ba56018739217100cb4e55d093ba9 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 11:32:26 -0700 Subject: [PATCH 060/150] sage -fixdoctest: Rewrite # optional to # needs --- src/bin/sage-fixdoctests | 10 +++++----- src/sage/doctest/parsing.py | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 7bf15f277aa..8d444210700 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -337,11 +337,11 @@ for input, output in zip(inputs, outputs): src_in_lines = src_in.splitlines() shallow_copy_of_src_in_lines = list(src_in_lines) - # First remove duplicate optional tags - if file_optional_tags: - for i, line in enumerate(src_in_lines): - if re.match('( *sage: *)(.*)#', line): - src_in_lines[i] = update_optional_tags(line, remove_tags=file_optional_tags) + # First remove duplicate optional tags and rewrite all '# optional' that should be '# needs' + for i, line in enumerate(src_in_lines): + if re.match('( *sage: *)(.*)#', line): + src_in_lines[i] = update_optional_tags(line, remove_tags=file_optional_tags, + force_rewrite='standard') for block in doctests: process_block(block, src_in_lines, file_optional_tags) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 723676e3136..91a76b60aac 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -284,6 +284,12 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo if not new_tags: return line_sans_tags.rstrip() + if (force_rewrite == 'standard' + and new_tags == current_tags + and not any(_tag_group(tag) in ['standard', 'sage'] + for tag in new_tags)): + return line + tag_columns = optional_tag_columns if any(_tag_group(tag) in ['optional', 'special'] for tag in new_tags) else standard_tag_columns From c9e338afdcd0534e0930508259fd6c115fb805aa Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 12:36:38 -0700 Subject: [PATCH 061/150] src/sage/doctest/parsing.py: Add doctests, recognize is_persistent in parse_optional_tags --- src/sage/doctest/parsing.py | 110 +++++++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 32 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 91a76b60aac..4413aa365b9 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -107,7 +107,7 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): - ``return_string_sans_tags`` -- (boolean, default ``False``); whether to additionally return ``string`` with the optional tags removed but other - comments kept. + comments kept and a boolean ``is_persistent`` EXAMPLES:: @@ -147,11 +147,18 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): With ``return_string_sans_tags=True``:: - sage: parse_optional_tags("sage: print(1) # very important 1 # optional - foo", # optional - EXPECTED + sage: parse_optional_tags("sage: print(1) # very important 1 # optional - foo", ....: return_string_sans_tags=True) - sage: parse_optional_tags("sage: print(1) # very important 1 # optional - foo", # optional - GOT + ({'foo'}, 'sage: print(1) # very important 1 \n', False) + sage: parse_optional_tags("sage: print( # very important too # optional - foo\n....: 2)", ....: return_string_sans_tags=True) - ({'foo'}, 'sage: print(1) # very important 1 ') + ({'foo'}, 'sage: print( # very important too \n....: 2)', False) + sage: parse_optional_tags("sage: #this is persistent #needs scipy", + ....: return_string_sans_tags=True) + ({'scipy'}, 'sage: #this is persistent \n', True) + sage: parse_optional_tags("sage: #this is not #needs scipy\n....: import scipy", + ....: return_string_sans_tags=True) + ({'scipy'}, 'sage: #this is not \n....: import scipy', False) """ safe, literals, state = strip_string_literals(string) @@ -164,22 +171,22 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): sharp_index = first_line.find('#') if sharp_index < 0: # no comment if return_string_sans_tags: - return set(), string + return set(), string, False else: return set() - first_line, comment = first_line[:sharp_index] % literals, first_line[sharp_index:] % literals + first_line_sans_comments, comment = first_line[:sharp_index] % literals, first_line[sharp_index:] % literals if return_string_sans_tags: # skip non-tag comments that precede the first tag comment if m := optional_regex.search(comment): sharp_index = comment[:m.start(0) + 1].rfind('#') if sharp_index >= 0: - first_line += comment[:sharp_index] + first_line = first_line_sans_comments + comment[:sharp_index] comment = comment[sharp_index:] else: - # no non-tag comment - return set(), string + # no tag comment + return set(), string, False tags = [] for m in optional_regex.finditer(comment): @@ -196,8 +203,8 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): tags.append("") if return_string_sans_tags: - # FIXME: Keep non-tag comments - return set(tags), first_line + rest%literals + is_persistent = tags and first_line_sans_comments.strip() == 'sage:' and not rest # persistent (block-scoped) annotation + return set(tags), first_line + '\n' + rest%literals, is_persistent else: return set(tags) @@ -261,11 +268,46 @@ def unparse_optional_tags(tags): def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, force_rewrite=False): + r""" + Return the doctest ``line`` with tags changed. + + EXAMPLES:: + sage: from sage.doctest.parsing import update_optional_tags + sage: update_optional_tags(' sage: nothing_to_be_seen_here()') + ' sage: nothing_to_be_seen_here()' + sage: update_optional_tags(' sage: nothing_to_be_seen_here()', + ....: tags=['scipy', 'bliss', 'long time']) + ' sage: nothing_to_be_seen_here() # long time, optional - bliss, needs scipy' + sage: update_optional_tags(' sage: ntbsh() # abbrv for above#optional:bliss', + ....: add_tags=['scipy', 'long time']) + ' sage: ntbsh() # abbrv for above # long time, optional - bliss, needs scipy' + sage: update_optional_tags(' sage: something() # optional - latte_int', + ....: remove_tags=['latte_int', 'wasnt_even_there']) + ' sage: something()' + sage: update_optional_tags(' sage: something() # optional - latte_int', + ....: add_tags=['latte_int']) + ' sage: something() # optional - latte_int' + sage: update_optional_tags(' sage: something()#optional- latte_int', + ....: force_rewrite=True) + ' sage: something() # optional - latte_int' + + Forcing a rewrite whenever standard tags are involved:: + + sage: update_optional_tags(' sage: something_else() # optional - scipy', + ....: force_rewrite='standard') + ' sage: something_else() # needs scipy' + + Rewriting a persistent (block-scoped) annotation:: + + sage: update_optional_tags(' sage: #optional:magma', + ....: force_rewrite=True) + ' sage: # optional - magma' + """ if not (m := re.match('( *sage: *)(.*)', line)): raise ValueError(f'line must start with a sage: prompt, got: {line}') - current_tags, line_sans_tags = parse_optional_tags(line.rstrip(), return_string_sans_tags=True) + current_tags, line_sans_tags, is_persistent = parse_optional_tags(line.rstrip(), return_string_sans_tags=True) new_tags = set(current_tags) @@ -290,20 +332,24 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo for tag in new_tags)): return line - tag_columns = optional_tag_columns if any(_tag_group(tag) in ['optional', 'special'] - for tag in new_tags) else standard_tag_columns - - if len(line) in tag_columns and line[-2:] == ' ': - # keep alignment - pass + if is_persistent: + line = line_sans_tags.rstrip() + ' ' else: - # realign - line = line_sans_tags.rstrip() - for column in tag_columns: - if len(line) <= column - 2: - line += ' ' * (column - 2 - len(line)) - break - line += ' ' + tag_columns = optional_tag_columns if any(_tag_group(tag) in ['optional', 'special'] + for tag in new_tags) else standard_tag_columns + + if len(line) in tag_columns and line[-2:] == ' ': + # keep alignment + pass + else: + # realign + line = line_sans_tags.rstrip() + for column in tag_columns: + if len(line) <= column - 2: + line += ' ' * (column - 2 - len(line)) + break + line += ' ' + line += unparse_optional_tags(new_tags) return line @@ -749,7 +795,7 @@ def parse(self, string, *args): Optional tags at the start of an example block persist to the end of the block (delimited by a blank line):: - sage: # needs sage.rings.number_field, long time + sage: # long time, needs sage.rings.number_field sage: QQbar(I)^10000 1 sage: QQbar(I)^10000 # not tested @@ -809,8 +855,8 @@ def parse(self, string, *args): persistent_optional_tags = [] for item in res: if isinstance(item, doctest.Example): - optional_tags = parse_optional_tags(item.source) - if item.source.startswith("sage: ") and item.source[6:].lstrip().startswith('#'): + optional_tags, source_sans_tags, is_persistent = parse_optional_tags(item.source, return_string_sans_tags=True) + if is_persistent: persistent_optional_tags = optional_tags continue optional_tags.update(persistent_optional_tags) @@ -945,11 +991,11 @@ def add_tolerance(self, wantval, want): sage: want_tol = MarkedOutput().update(tol=0.0001) sage: want_abs = MarkedOutput().update(abs_tol=0.0001) sage: want_rel = MarkedOutput().update(rel_tol=0.0001) - sage: OC.add_tolerance(RIF(pi.n(64)), want_tol).endpoints() + sage: OC.add_tolerance(RIF(pi.n(64)), want_tol).endpoints() # needs sage.symbolic (3.14127849432443, 3.14190681285516) - sage: OC.add_tolerance(RIF(pi.n(64)), want_abs).endpoints() + sage: OC.add_tolerance(RIF(pi.n(64)), want_abs).endpoints() # needs sage.symbolic (3.14149265358979, 3.14169265358980) - sage: OC.add_tolerance(RIF(pi.n(64)), want_rel).endpoints() + sage: OC.add_tolerance(RIF(pi.n(64)), want_rel).endpoints() # needs sage.symbolic (3.14127849432443, 3.14190681285516) sage: OC.add_tolerance(RIF(1e1000), want_tol) 1.000?e1000 @@ -1059,7 +1105,7 @@ def check_output(self, want, got, optionflags): More explicit tolerance checks:: - sage: _ = x # rel tol 1e10 + sage: _ = x # rel tol 1e10 # needs sage.symbolic sage: raise RuntimeError # rel tol 1e10 Traceback (most recent call last): ... From 6ac8b55c89db53cdf010d1f082f50b9bccbcf268 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 12:52:42 -0700 Subject: [PATCH 062/150] src/sage/doctest/parsing.py: No trailing newline in string_sans_tags --- src/sage/doctest/parsing.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 4413aa365b9..c9feddea161 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -149,13 +149,13 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): sage: parse_optional_tags("sage: print(1) # very important 1 # optional - foo", ....: return_string_sans_tags=True) - ({'foo'}, 'sage: print(1) # very important 1 \n', False) + ({'foo'}, 'sage: print(1) # very important 1 ', False) sage: parse_optional_tags("sage: print( # very important too # optional - foo\n....: 2)", ....: return_string_sans_tags=True) ({'foo'}, 'sage: print( # very important too \n....: 2)', False) sage: parse_optional_tags("sage: #this is persistent #needs scipy", ....: return_string_sans_tags=True) - ({'scipy'}, 'sage: #this is persistent \n', True) + ({'scipy'}, 'sage: #this is persistent ', True) sage: parse_optional_tags("sage: #this is not #needs scipy\n....: import scipy", ....: return_string_sans_tags=True) ({'scipy'}, 'sage: #this is not \n....: import scipy', False) @@ -166,7 +166,7 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): if len(split) > 1: first_line, rest = split else: - first_line, rest = split[0], '' + first_line, rest = split[0], None sharp_index = first_line.find('#') if sharp_index < 0: # no comment @@ -204,7 +204,8 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): if return_string_sans_tags: is_persistent = tags and first_line_sans_comments.strip() == 'sage:' and not rest # persistent (block-scoped) annotation - return set(tags), first_line + '\n' + rest%literals, is_persistent + return set(tags), (first_line + '\n' + rest%literals if rest is not None + else first_line), is_persistent else: return set(tags) From a19bdd36200e2b638f8468897d63b866d15bd9df Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 13:29:30 -0700 Subject: [PATCH 063/150] sage -fixdoctest: Remove line-level # optional tags that are covered by persistent tags --- src/bin/sage-fixdoctests | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 8d444210700..f1f0c3c3f94 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -338,10 +338,23 @@ for input, output in zip(inputs, outputs): shallow_copy_of_src_in_lines = list(src_in_lines) # First remove duplicate optional tags and rewrite all '# optional' that should be '# needs' + persistent_optional_tags = set() for i, line in enumerate(src_in_lines): if re.match('( *sage: *)(.*)#', line): - src_in_lines[i] = update_optional_tags(line, remove_tags=file_optional_tags, - force_rewrite='standard') + tags, line_sans_tags, is_persistent = parse_optional_tags(line, return_string_sans_tags=True) + if is_persistent: + persistent_optional_tags = tags - file_optional_tags + if persistent_optional_tags: + line = update_optional_tags(line, tags=persistent_optional_tags, force_rewrite='standard') + if re.fullmatch('( *sage: *)', line): + # persistent (block scoped) annotation was removed, so remove the whole line + line = None + else: + line = update_optional_tags(line, remove_tags=file_optional_tags.union(persistent_optional_tags), + force_rewrite='standard') + src_in_lines[i] = line + elif line.strip() in ['', '"""', "'''"]: # Blank line or end of docstring + persistent_optional_tags = set() for block in doctests: process_block(block, src_in_lines, file_optional_tags) From f2fd9346582c20460592acc726248fb096c8740b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 18:38:34 -0700 Subject: [PATCH 064/150] src/bin/sage-fixdoctests: Handle the new 'needs' keyword --- src/bin/sage-fixdoctests | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index f1f0c3c3f94..17e164b2847 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -143,13 +143,13 @@ def process_block(block, src_in_lines, file_optional_tags): filename = m.group(1) first_line_num = line_num = int(m.group(2)) # 1-based line number of the first line of the example - if m := re.search(r"referenced here was set only in doctest marked '# optional - ([^;']*)", block): - optional = m.group(1).split() + if m := re.search(r"referenced here was set only in doctest marked '# (optional|needs)[-: ]*([^;']*)", block): + optional = m.group(2).split() src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=optional) - if m := re.search(r"annotation '# optional - ([^;']*)' may no longer be needed", block): - optional = m.group(1) + if m := re.search(r"annotation '# (optional|needs)[-: ]([^;']*)' may no longer be needed", block): + optional = m.group(2) src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], remove_tags=[optional]) From 1d544b0425cbeb9d3be9f12d51e5d1f672e3c2eb Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 19:33:25 -0700 Subject: [PATCH 065/150] sage.doctest: Do not try to detect available software outside of runners --- src/sage/doctest/control.py | 12 +++++++++++- src/sage/doctest/parsing.py | 3 ++- src/sage/doctest/sources.py | 18 +++++++++++++++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 6a2ba48c082..d2de22374d0 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -216,7 +216,8 @@ def skipdir(dirname): return True return False -def skipfile(filename, tested_optional_tags=False, *, only_lib=False, log=None): +def skipfile(filename, tested_optional_tags=False, *, + only_lib=False, log=None, detect_available_software=False): """ Return ``True`` if and only if the file ``filename`` should not be doctested. @@ -232,6 +233,8 @@ def skipfile(filename, tested_optional_tags=False, *, only_lib=False, log=None): - ``log`` -- function to call with log messages, or ``None`` + - ``detect_available_software`` -- whether it is allowed to use feature tests + If ``filename`` contains a line of the form ``"# sage.doctest: optional - xyz")``, then this will return ``False`` if "xyz" is in ``tested_optional_tags``. Otherwise, it returns the matching tag @@ -287,6 +290,12 @@ def skipfile(filename, tested_optional_tags=False, *, only_lib=False, log=None): if log: log(f"Skipping '{filename}' because module {e.name} cannot be imported") return True + + if detect_available_software: + detectable_tags = frozenset(available_software.detectable()) + else: + detectable_tags = frozenset(available_software.seen()) + with open(filename) as F: line_count = 0 for line in F: @@ -307,6 +316,7 @@ def skipfile(filename, tested_optional_tags=False, *, only_lib=False, log=None): extra = set(tag for tag in optional_tags if (tag not in tested_optional_tags + and tag in detectable_tags and tag not in available_software)) if extra: if log: diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index c9feddea161..33b96a20016 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -646,7 +646,7 @@ class SageDocTestParser(doctest.DocTestParser): A version of the standard doctest parser which handles Sage's custom options and tolerances in floating point arithmetic. """ - def __init__(self, optional_tags=(), long=False, *, probed_tags=()): + def __init__(self, optional_tags=(), long=False, *, probed_tags=(), file_optional_tags=()): r""" INPUT: @@ -654,6 +654,7 @@ def __init__(self, optional_tags=(), long=False, *, probed_tags=()): - ``long`` -- boolean, whether to run doctests marked as taking a long time. - ``probed_tags`` -- a list or tuple of strings. + - ``file_optional_tags`` -- a list or tuple of strings. EXAMPLES:: diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index e6c139fb741..ee86bce6811 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -118,7 +118,7 @@ class DocTestSource(): - ``options`` -- a :class:`sage.doctest.control.DocTestDefaults` instance or equivalent. """ - def __init__(self, options): + def __init__(self, options, *, file_optional_tags=()): """ Initialization. @@ -133,6 +133,7 @@ def __init__(self, options): sage: TestSuite(FDS).run() """ self.options = options + self.file_optional_tags = file_optional_tags def __eq__(self, other): """ @@ -276,7 +277,8 @@ def _create_doctests(self, namespace, tab_okay=None): self.line_shift = 0 self.parser = SageDocTestParser(self.options.optional, self.options.long, - probed_tags=self.options.probe) + probed_tags=self.options.probe, + file_optional_tags=self.file_optional_tags) self.linking = False doctests = [] in_docstring = False @@ -526,8 +528,18 @@ def __init__(self, path, options): sage: FDS.options.randorder 0 """ + from .external import available_software + from .control import skipfile + from .parsing import parse_optional_tags + # FIXME: skipfile should be refactored + file_tag_string = skipfile(path, False, only_lib=options.only_lib) + if isinstance(file_tag_string, str): + file_optional_tags = parse_optional_tags('#' + file_tag_string) + else: + file_optional_tags = () + self.path = path - DocTestSource.__init__(self, options) + DocTestSource.__init__(self, options, file_optional_tags=file_optional_tags) if path.endswith('.rst.txt'): ext = '.rst.txt' else: From feca1dcae3a7a8405d4392afb18f4091dafc3e8a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 21:30:45 -0700 Subject: [PATCH 066/150] sage -fixdoctests: Also rewrite a file-level annotation --- src/bin/sage-fixdoctests | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 17e164b2847..f2daa22c931 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -25,7 +25,7 @@ from argparse import ArgumentParser, FileType from pathlib import Path from sage.doctest.control import skipfile -from sage.doctest.parsing import parse_optional_tags, update_optional_tags +from sage.doctest.parsing import parse_optional_tags, unparse_optional_tags, update_optional_tags from sage.env import SAGE_ROOT from sage.features import PythonModule from sage.features.all import all_features @@ -145,6 +145,9 @@ def process_block(block, src_in_lines, file_optional_tags): if m := re.search(r"referenced here was set only in doctest marked '# (optional|needs)[-: ]*([^;']*)", block): optional = m.group(2).split() + if src_in_lines[first_line_num - 1].strip() in ['"""', "'''"]: + # This happens due to a virtual doctest in src/sage/repl/user_globals.py + return src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=optional) @@ -309,12 +312,8 @@ if args.venv or environment_args: sys.exit(status) for input, output in zip(inputs, outputs): - if skipfile_result := skipfile(input, True, log=print): - if skipfile_result is True: - continue - file_optional_tags = parse_optional_tags('#' + skipfile_result) - else: - file_optional_tags = set() + if (skipfile_result := skipfile(input, True, log=print)) is True: + continue # Run the doctester, putting the output of the test into sage's temporary directory cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{lib_args}{shlex.quote(input)}' @@ -326,7 +325,7 @@ for input, output in zip(inputs, outputs): # echo control messages for m in re.finditer('^Skipping .*', doc_out, re.MULTILINE): - print('sage-runtest: ' + m.group(0)) + print('sage-runtests: ' + m.group(0)) break else: sep = "**********************************************************************\n" @@ -338,21 +337,31 @@ for input, output in zip(inputs, outputs): shallow_copy_of_src_in_lines = list(src_in_lines) # First remove duplicate optional tags and rewrite all '# optional' that should be '# needs' + file_optional_tags = set() persistent_optional_tags = set() for i, line in enumerate(src_in_lines): - if re.match('( *sage: *)(.*)#', line): - tags, line_sans_tags, is_persistent = parse_optional_tags(line, return_string_sans_tags=True) + if m := re.match(' *sage: *(.*)#', line): + tags, line_sans_tags, is_persistent = parse_optional_tags('#' + line, return_string_sans_tags=True) if is_persistent: persistent_optional_tags = tags - file_optional_tags if persistent_optional_tags: line = update_optional_tags(line, tags=persistent_optional_tags, force_rewrite='standard') - if re.fullmatch('( *sage: *)', line): - # persistent (block scoped) annotation was removed, so remove the whole line + if not line.rstrip(): + # persistent (block-scoped or file-scoped) annotation was removed, so remove the whole line line = None else: line = update_optional_tags(line, remove_tags=file_optional_tags.union(persistent_optional_tags), force_rewrite='standard') src_in_lines[i] = line + elif m := re.match('([#.]* *sage[.]doctest:[ #]*)([^(]*)(.*)', line): + # file-scoped annotation + prefix = m.group(1) + file_optional_tags.update(parse_optional_tags('#' + m.group(2))) + suffix = m.group(3) + line = unparse_optional_tags(file_optional_tags) + if line: + line = line[2:] # strip '# ' + src_in_lines[i] = prefix + line + suffix elif line.strip() in ['', '"""', "'''"]: # Blank line or end of docstring persistent_optional_tags = set() From 9413da0ed35f4f2d192f7e40d54164e11bfe2c3f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 21:43:47 -0700 Subject: [PATCH 067/150] src/sage/doctest/parsing.py: Fix markup --- src/sage/doctest/parsing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 33b96a20016..110ced2635a 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -88,7 +88,7 @@ def fake_RIFtol(*args): def parse_optional_tags(string, *, return_string_sans_tags=False): - """ + r""" Return a set consisting of the optional tags from the following set that occur in a comment on the first line of the input string. From 038bb555b50b3e8c73a00c45ded8288d62ae66c0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 22:01:16 -0700 Subject: [PATCH 068/150] src/bin/sage-fixdoctests: Fix handling of multiple sage.doctest directives --- src/bin/sage-fixdoctests | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index f2daa22c931..8964589aed5 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -356,11 +356,14 @@ for input, output in zip(inputs, outputs): elif m := re.match('([#.]* *sage[.]doctest:[ #]*)([^(]*)(.*)', line): # file-scoped annotation prefix = m.group(1) - file_optional_tags.update(parse_optional_tags('#' + m.group(2))) + tags = parse_optional_tags('#' + m.group(2)) suffix = m.group(3) - line = unparse_optional_tags(file_optional_tags) + file_optional_tags.update(tags) + line = unparse_optional_tags(tags) if line: line = line[2:] # strip '# ' + if suffix: + suffix = ' ' + suffix src_in_lines[i] = prefix + line + suffix elif line.strip() in ['', '"""', "'''"]: # Blank line or end of docstring persistent_optional_tags = set() From b86ffb358fb99e055262137db932dc680b389e48 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 22:31:10 -0700 Subject: [PATCH 069/150] src/sage/doctest/parsing.py: Add doctests --- src/sage/doctest/parsing.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 110ced2635a..a0e45de19a6 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -212,12 +212,44 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): @cached_function def _standard_tags(): + r""" + Return the set of the names of all standard features. + + EXAMPLES:: + + sage: from sage.doctest.parsing import _standard_tags + sage: sorted(_standard_tags()) + [..., 'numpy', ..., 'sage.rings.finite_rings', ...] + """ from sage.features.all import all_features return frozenset(feature.name for feature in all_features() if feature._spkg_type() == 'standard') def _tag_group(tag): + r""" + Classify a doctest tag as belonging to one of 4 groups. + + INPUT: + + - ``tag`` -- string + + OUTPUT: + + a string; one of ``'special'``, ``'optional'``, ``'standard'``, ``'sage'`` + + EXAMPLES:: + + sage: from sage.doctest.parsing import _tag_group + sage: _tag_group('scipy') + 'standard' + sage: _tag_group('sage.numerical.mip') + 'sage' + sage: _tag_group('bliss') + 'optional' + sage: _tag_group('not tested') + 'special' + """ if tag.startswith('sage.'): return 'sage' elif tag in _standard_tags(): From 57c5c7d8ec5699d5bceeecc050c59a77540abe62 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 30 Jun 2023 09:22:50 -0700 Subject: [PATCH 070/150] sage.doctest.{parsing,sources}: Handle file_optional_tags properly --- src/sage/doctest/parsing.py | 5 +++-- src/sage/doctest/sources.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index a0e45de19a6..bf051ad0ecf 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -716,6 +716,7 @@ def __init__(self, optional_tags=(), long=False, *, probed_tags=(), file_optiona else: self.optional_only = True self.probed_tags = probed_tags + self.file_optional_tags = file_optional_tags def __eq__(self, other): """ @@ -886,7 +887,7 @@ def parse(self, string, *args): string = find_sage_continuation.sub(r"\1...", string) res = doctest.DocTestParser.parse(self, string, *args) filtered = [] - persistent_optional_tags = [] + persistent_optional_tags = self.file_optional_tags for item in res: if isinstance(item, doctest.Example): optional_tags, source_sans_tags, is_persistent = parse_optional_tags(item.source, return_string_sans_tags=True) @@ -940,7 +941,7 @@ def parse(self, string, *args): item.source = preparse(item.sage_source) else: if item == '\n': - persistent_optional_tags = [] + persistent_optional_tags = self.file_optional_tags filtered.append(item) return filtered diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index ee86bce6811..16bc6a60a22 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -536,7 +536,7 @@ def __init__(self, path, options): if isinstance(file_tag_string, str): file_optional_tags = parse_optional_tags('#' + file_tag_string) else: - file_optional_tags = () + file_optional_tags = set() self.path = path DocTestSource.__init__(self, options, file_optional_tags=file_optional_tags) From d75181efeb7ede6be991a0c29a4d8a2c185a12d1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 30 Jun 2023 11:11:03 -0700 Subject: [PATCH 071/150] sage.doctest: Refactor skipfile, defer 2nd read of file annotation to create_doctests time --- src/bin/sage-fixdoctests | 4 +-- src/sage/doctest/control.py | 65 +++++++++++++-------------------- src/sage/doctest/parsing.py | 59 +++++++++++++++++++++++++++--- src/sage/doctest/sources.py | 71 ++++++++++++++++++++++++++----------- 4 files changed, 130 insertions(+), 69 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 8964589aed5..2601a07b942 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -359,9 +359,7 @@ for input, output in zip(inputs, outputs): tags = parse_optional_tags('#' + m.group(2)) suffix = m.group(3) file_optional_tags.update(tags) - line = unparse_optional_tags(tags) - if line: - line = line[2:] # strip '# ' + line = unparse_optional_tags(tags, prefix='') if suffix: suffix = ' ' + suffix src_in_lines[i] = prefix + line + suffix diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index d2de22374d0..e4628cb2abb 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -41,11 +41,9 @@ from .reporting import DocTestReporter from .util import Timer, count_noun, dict_difference from .external import available_software -from .parsing import parse_optional_tags +from .parsing import parse_optional_tags, parse_file_optional_tags, unparse_optional_tags, \ + nodoctest_regex, optionaltag_regex, optionalfiledirective_regex -nodoctest_regex = re.compile(r'\s*(#+|%+|r"+|"+|\.\.)\s*nodoctest') -optionaltag_regex = re.compile(r'^(\w|[.])+$') -optionalfiledirective_regex = re.compile(r'\s*(#+|%+|r"+|"+|\.\.)\s*sage\.doctest: (.*)') # Optional tags which are always automatically added @@ -217,7 +215,7 @@ def skipdir(dirname): return False def skipfile(filename, tested_optional_tags=False, *, - only_lib=False, log=None, detect_available_software=False): + only_lib=False, log=None): """ Return ``True`` if and only if the file ``filename`` should not be doctested. @@ -233,8 +231,6 @@ def skipfile(filename, tested_optional_tags=False, *, - ``log`` -- function to call with log messages, or ``None`` - - ``detect_available_software`` -- whether it is allowed to use feature tests - If ``filename`` contains a line of the form ``"# sage.doctest: optional - xyz")``, then this will return ``False`` if "xyz" is in ``tested_optional_tags``. Otherwise, it returns the matching tag @@ -291,40 +287,29 @@ def skipfile(filename, tested_optional_tags=False, *, log(f"Skipping '{filename}' because module {e.name} cannot be imported") return True - if detect_available_software: - detectable_tags = frozenset(available_software.detectable()) - else: - detectable_tags = frozenset(available_software.seen()) - with open(filename) as F: - line_count = 0 - for line in F: - if nodoctest_regex.match(line): - if log: - log(f"Skipping '{filename}' because it is marked 'nodoctest'") - return True - if tested_optional_tags is not True: - # Adapted from code in SageDocTestParser.parse - m = optionalfiledirective_regex.match(line) - if m: - file_tag_string = m.group(2) - if tested_optional_tags is False: - if log: - log(f"Skipping '{filename}' because it is marked '# {file_tag_string}'") - return file_tag_string - optional_tags = parse_optional_tags('#' + file_tag_string) - extra = set(tag - for tag in optional_tags - if (tag not in tested_optional_tags - and tag in detectable_tags - and tag not in available_software)) - if extra: - if log: - log(f"Skipping '{filename}' because it is marked '# {file_tag_string}'") - return file_tag_string - line_count += 1 - if line_count >= 10: - break + file_optional_tags = parse_file_optional_tags(enumerate(F)) + + if 'not tested' in file_optional_tags: + if log: + log(f"Skipping '{filename}' because it is marked 'nodoctest'") + return True + + if tested_optional_tags is False: + if file_optional_tags: + file_tag_string = unparse_optional_tags(file_optional_tags, prefix='') + if log: + log(f"Skipping '{filename}' because it is marked '# {file_tag_string}'") + return file_tag_string + + elif tested_optional_tags is not True: + extra = file_optional_tags - set(tested_optional_tags) + if extra: + file_tag_string = unparse_optional_tags(file_optional_tags, prefix='') + if log: + log(f"Skipping '{filename}' because it is marked '{file_tag_string}'") + return file_tag_string + return False diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index bf051ad0ecf..220df3503d9 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -86,6 +86,10 @@ def fake_RIFtol(*args): optional_regex = re.compile(fr'({special_optional_regex})|[^ a-z]\s*(optional|needs)\s*[:-]*((?:\s|\w|[.])*)', re.IGNORECASE) special_optional_regex = re.compile(special_optional_regex, re.IGNORECASE) +nodoctest_regex = re.compile(r'\s*(#+|%+|r"+|"+|\.\.)\s*nodoctest') +optionaltag_regex = re.compile(r'^(\w|[.])+$') +optionalfiledirective_regex = re.compile(r'\s*(#+|%+|r"+|"+|\.\.)\s*sage\.doctest: (.*)') + def parse_optional_tags(string, *, return_string_sans_tags=False): r""" @@ -210,6 +214,49 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): return set(tags) +def parse_file_optional_tags(lines): + r""" + Scan the first few lines for file-level doctest directives. + + INPUT: + + - ``lines`` -- iterable of pairs ``(lineno, line)``. + + OUTPUT: + + a set of strings (tags) + + EXAMPLES:: + + sage: from sage.doctest.parsing import parse_file_optional_tags + sage: filename = tmp_filename(ext=".pyx") + sage: with open(filename, "r") as f: + ....: parse_file_optional_tags(enumerate(f)) + set() + sage: with open(filename, "w") as f: + ....: _ = f.write("# nodoctest") + sage: with open(filename, "r") as f: + ....: parse_file_optional_tags(enumerate(f)) + {'not tested'} + sage: with open(filename, "w") as f: + ....: _ = f.write("# sage.doctest: " # broken in two source lines to avoid the pattern + ....: "optional - xyz") # of relint (multiline_doctest_comment) + sage: with open(filename, "r") as f: + ....: parse_file_optional_tags(enumerate(f)) + {'xyz'} + """ + tags = set() + for line_count, line in lines: + if nodoctest_regex.match(line): + tags.add('not tested') + if m := optionalfiledirective_regex.match(line): + file_tag_string = m.group(2) + tags.update(parse_optional_tags('#' + file_tag_string)) + if line_count >= 10: + break + return tags + + @cached_function def _standard_tags(): r""" @@ -260,7 +307,7 @@ def _tag_group(tag): return 'special' -def unparse_optional_tags(tags): +def unparse_optional_tags(tags, prefix='# '): r""" Return a comment string that sets ``tags``. @@ -268,6 +315,8 @@ def unparse_optional_tags(tags): - ``tags`` -- iterable of tags, as output by :func:`parse_optional_tags` + - ``prefix`` -- to be put before a nonempty string + EXAMPLES:: sage: from sage.doctest.parsing import unparse_optional_tags @@ -278,8 +327,8 @@ def unparse_optional_tags(tags): sage: unparse_optional_tags(['fictional_optional', 'sage.rings.number_field', ....: 'scipy', 'bliss']) '# optional - bliss fictional_optional, needs scipy sage.rings.number_field' - sage: unparse_optional_tags(['long time', 'not tested', 'p4cka9e']) - '# long time, not tested, optional - p4cka9e' + sage: unparse_optional_tags(['long time', 'not tested', 'p4cka9e'], prefix='') + 'long time, not tested, optional - p4cka9e' """ group = defaultdict(set) for tag in tags: @@ -292,7 +341,7 @@ def unparse_optional_tags(tags): + sorted(group.pop('sage', [])))) assert not group if tags: - return '# ' + ', '.join(tags) + return prefix + ', '.join(tags) return '' @@ -716,7 +765,7 @@ def __init__(self, optional_tags=(), long=False, *, probed_tags=(), file_optiona else: self.optional_only = True self.probed_tags = probed_tags - self.file_optional_tags = file_optional_tags + self.file_optional_tags = set(file_optional_tags) def __eq__(self, other): """ diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index 16bc6a60a22..326889f2aa3 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -118,7 +118,7 @@ class DocTestSource(): - ``options`` -- a :class:`sage.doctest.control.DocTestDefaults` instance or equivalent. """ - def __init__(self, options, *, file_optional_tags=()): + def __init__(self, options): """ Initialization. @@ -133,7 +133,6 @@ def __init__(self, options, *, file_optional_tags=()): sage: TestSuite(FDS).run() """ self.options = options - self.file_optional_tags = file_optional_tags def __eq__(self, other): """ @@ -230,6 +229,26 @@ def _process_doc(self, doctests, doc, namespace, start): dt.examples.append(sigon) doctests.append(dt) + @lazy_attribute + def file_optional_tags(self): + """ + Return the set of tags that should apply to all doctests in this source. + + This default implementation just returns the empty set. + + EXAMPLES:: + + sage: from sage.doctest.control import DocTestDefaults + sage: from sage.doctest.sources import StringDocTestSource, PythonSource + sage: from sage.structure.dynamic_class import dynamic_class + sage: s = "'''\n sage: 2 + 2\n 4\n'''" + sage: PythonStringSource = dynamic_class('PythonStringSource', (StringDocTestSource, PythonSource)) + sage: PSS = PythonStringSource('', s, DocTestDefaults(), 'runtime') + sage: PSS.file_optional_tags + set() + """ + return set() + def _create_doctests(self, namespace, tab_okay=None): """ Creates a list of doctests defined in this source. @@ -265,7 +284,7 @@ def _create_doctests(self, namespace, tab_okay=None): sage: FDS.qualified_name = NestedName('sage.doctest.sources') sage: doctests, extras = FDS._create_doctests({}) sage: len(doctests) - 41 + 43 sage: extras['tab'] False sage: extras['line_number'] @@ -528,18 +547,8 @@ def __init__(self, path, options): sage: FDS.options.randorder 0 """ - from .external import available_software - from .control import skipfile - from .parsing import parse_optional_tags - # FIXME: skipfile should be refactored - file_tag_string = skipfile(path, False, only_lib=options.only_lib) - if isinstance(file_tag_string, str): - file_optional_tags = parse_optional_tags('#' + file_tag_string) - else: - file_optional_tags = set() - self.path = path - DocTestSource.__init__(self, options, file_optional_tags=file_optional_tags) + DocTestSource.__init__(self, options) if path.endswith('.rst.txt'): ext = '.rst.txt' else: @@ -700,6 +709,25 @@ def in_lib(self): return (self.options.force_lib or is_package_or_sage_namespace_package_dir(os.path.dirname(self.path))) + @lazy_attribute + def file_optional_tags(self): + """ + Return the set of tags that should apply to all doctests in this source. + + EXAMPLES:: + + sage: from sage.doctest.control import DocTestDefaults + sage: from sage.doctest.sources import FileDocTestSource + sage: from sage.env import SAGE_SRC + sage: import os + sage: filename = os.path.join(SAGE_SRC, 'sage', 'repl', 'user_globals.py') + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) + sage: FDS.file_optional_tags + {'sage.modules'} + """ + from .parsing import parse_file_optional_tags + return parse_file_optional_tags(self) + def create_doctests(self, namespace): r""" Return a list of doctests for this file. @@ -724,16 +752,16 @@ def create_doctests(self, namespace): sage: FDS = FileDocTestSource(filename,DocTestDefaults()) sage: doctests, extras = FDS.create_doctests(globals()) sage: len(doctests) - 41 + 43 sage: extras['tab'] False We give a self referential example:: - sage: doctests[18].name + sage: doctests[20].name 'sage.doctest.sources.FileDocTestSource.create_doctests' - sage: doctests[18].examples[10].source - 'doctests[Integer(18)].examples[Integer(10)].source\n' + sage: doctests[20].examples[10].source + 'doctests[Integer(20)].examples[Integer(10)].source\n' TESTS: @@ -742,11 +770,12 @@ def create_doctests(self, namespace): sage: import sys sage: bitness = '64' if sys.maxsize > (1 << 32) else '32' - sage: gp.get_precision() == 38 + sage: gp.get_precision() == 38 # needs sage.libs.pari False # 32-bit True # 64-bit - sage: ex = doctests[18].examples[13] - sage: (bitness == '64' and ex.want == 'True \n') or (bitness == '32' and ex.want == 'False \n') + sage: ex = doctests[20].examples[13] + sage: ((bitness == '64' and ex.want == 'True \n') # needs sage.libs.pari + ....: or (bitness == '32' and ex.want == 'False \n')) True We check that lines starting with a # aren't doctested:: From 7765125cc8a1708bb40f35c71ea5dd4eecaba1bf Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 30 Jun 2023 11:55:22 -0700 Subject: [PATCH 072/150] sage -fixdoctest: Fix default of --keep-both and --full-tracebacks again --- src/bin/sage-fixdoctests | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 2601a07b942..a89fb119ee0 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -81,9 +81,6 @@ def default_venv_environment_from_distribution(): default_environment = runtest_default_environment return default_venv, default_environment -if args.environment: - args.keep_both = args.full_tracebacks = True - default_venv, default_environment = default_venv_environment_from_distribution() if not args.venv: @@ -91,6 +88,9 @@ if not args.venv: if not args.environment: args.environment = default_environment +if args.distribution or args.venv != default_venv or args.environment != default_environment: + args.keep_both = args.full_tracebacks = True + venv_explainers = [] if args.venv: From 84004a63142bf3e7e2320af6f5eb9e37dd964fe0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 30 Jun 2023 12:14:02 -0700 Subject: [PATCH 073/150] src/bin/sage-fixdoctests: Fix reading of block-level tags --- src/bin/sage-fixdoctests | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index a89fb119ee0..70ba25f33fc 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -341,11 +341,10 @@ for input, output in zip(inputs, outputs): persistent_optional_tags = set() for i, line in enumerate(src_in_lines): if m := re.match(' *sage: *(.*)#', line): - tags, line_sans_tags, is_persistent = parse_optional_tags('#' + line, return_string_sans_tags=True) + tags, line_sans_tags, is_persistent = parse_optional_tags(line, return_string_sans_tags=True) if is_persistent: persistent_optional_tags = tags - file_optional_tags - if persistent_optional_tags: - line = update_optional_tags(line, tags=persistent_optional_tags, force_rewrite='standard') + line = update_optional_tags(line, tags=persistent_optional_tags, force_rewrite='standard') if not line.rstrip(): # persistent (block-scoped or file-scoped) annotation was removed, so remove the whole line line = None From bbd8270656853c16a5c0dc1cf8ba50ee3249c81c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 30 Jun 2023 13:55:41 -0700 Subject: [PATCH 074/150] sage.doctest: Remove use of skipfile with list of tags --- src/sage/doctest/control.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index e4628cb2abb..a6a738bd4a6 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -936,7 +936,8 @@ def all_doc_sources(): and (filename.endswith(".py") or filename.endswith(".pyx") or filename.endswith(".rst")) - and not skipfile(opj(SAGE_ROOT, filename), self.options.optional, + and not skipfile(opj(SAGE_ROOT, filename), + True if self.options.optional else False, only_lib=self.options.only_lib)): self.files.append(os.path.relpath(opj(SAGE_ROOT, filename))) @@ -997,11 +998,12 @@ def expand(): if dir[0] == "." or skipdir(os.path.join(root,dir)): dirs.remove(dir) for file in files: - if not skipfile(os.path.join(root, file), self.options.optional, + if not skipfile(os.path.join(root, file), + True if self.options.optional else False, only_lib=self.options.only_lib): yield os.path.join(root, file) else: - if not skipfile(path, self.options.optional, + if not skipfile(path, True if self.options.optional else False, only_lib=self.options.only_lib, log=self.log): # log when directly specified filenames are skipped yield path self.sources = [FileDocTestSource(path, self.options) for path in expand()] From 4ffec03463e7d4d226ec76ee42f85e9372d838a6 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 30 Jun 2023 14:35:07 -0700 Subject: [PATCH 075/150] src/doc/en/developer/doctesting.rst: Add warning --- src/doc/en/developer/doctesting.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index 787d51b3ae0..46fb4625752 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -1459,7 +1459,13 @@ turns out to work anyway, the annotation is automatically removed. To have the doctest fixer take care of the ``# optional`` annotations, but not change the expected results of examples, use the option ``--only-tags``. -This mode is suitable for unattended runs on many files. +This mode is suitable for mostly unattended runs on many files. + +.. warning:: + + While the doctest fixer guarantees to preserve any comments that + appear before ``# optional``, any comments that may be mixed with + the feature annotations will be lost. Use in virtual environments From 80e088d43e230a408064860d496995d3252f122f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 30 Jun 2023 15:25:11 -0700 Subject: [PATCH 076/150] src/sage/doctest: Fix markup --- src/sage/doctest/sources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index 326889f2aa3..a4ba02c1d4a 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -231,7 +231,7 @@ def _process_doc(self, doctests, doc, namespace, start): @lazy_attribute def file_optional_tags(self): - """ + r""" Return the set of tags that should apply to all doctests in this source. This default implementation just returns the empty set. From f0d851a8dc07c271a5a6cc57e74738d8370ed37c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 30 Jun 2023 18:31:45 -0700 Subject: [PATCH 077/150] src/sage/doctest/forker.py: Fix wording of warning message --- src/sage/doctest/forker.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index dd1698a8083..f68aecebcbc 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -726,7 +726,7 @@ def compiler(example): example.warnings.append( f"The annotation '{unparse_optional_tags(probed_tags)}' " f"may no longer be needed; these features are not present, " - f"but we ran the doctest anyway as requested by --probing, " + f"but we ran the doctest anyway as requested by --probe, " f"and it succeeded.") outcome = SUCCESS @@ -767,7 +767,7 @@ def compiler(example): example.warnings.append( f"The annotation '{unparse_optional_tags(example.probed_tags)}' " f"may no longer be needed; these features are not present, " - f"but we ran the doctest anyway as requested by --probing, " + f"but we ran the doctest anyway as requested by --probe, " f"and it succeeded (raised the expected exception).") outcome = SUCCESS @@ -781,7 +781,7 @@ def compiler(example): example.warnings.append( f"The annotation '{unparse_optional_tags(example.probed_tags)}' " f"may no longer be needed; these features are not present, " - f"but we ran the doctest anyway as requested by --probing, " + f"but we ran the doctest anyway as requested by --probe, " f"and it succeeded (raised an exception as expected).") outcome = SUCCESS From 7b79b47b5c0c911ebbe5f7215b3cba5d3820e548 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 1 Jul 2023 12:31:10 -0700 Subject: [PATCH 078/150] src/sage/doctest: Use dicts, not sets, for optional tags --- src/bin/sage-fixdoctests | 15 +++-- src/sage/doctest/control.py | 3 +- src/sage/doctest/forker.py | 10 +-- src/sage/doctest/parsing.py | 120 ++++++++++++++++++++---------------- src/sage/doctest/sources.py | 2 +- 5 files changed, 85 insertions(+), 65 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 70ba25f33fc..83a1f373eb3 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -337,20 +337,25 @@ for input, output in zip(inputs, outputs): shallow_copy_of_src_in_lines = list(src_in_lines) # First remove duplicate optional tags and rewrite all '# optional' that should be '# needs' - file_optional_tags = set() + file_optional_tags = {} persistent_optional_tags = set() for i, line in enumerate(src_in_lines): if m := re.match(' *sage: *(.*)#', line): tags, line_sans_tags, is_persistent = parse_optional_tags(line, return_string_sans_tags=True) if is_persistent: - persistent_optional_tags = tags - file_optional_tags + persistent_optional_tags = {tag: explanation + for tag, explanation in tags.items() + if explanation or tag not in file_optional_tags} line = update_optional_tags(line, tags=persistent_optional_tags, force_rewrite='standard') if not line.rstrip(): # persistent (block-scoped or file-scoped) annotation was removed, so remove the whole line line = None else: - line = update_optional_tags(line, remove_tags=file_optional_tags.union(persistent_optional_tags), - force_rewrite='standard') + tags = {tag: explanation + for tag, explanation in tags.items() + if explanation or (tag not in file_optional_tags + and tag not in persistent_optional_tags)} + line = update_optional_tags(line, tags=tags, force_rewrite='standard') src_in_lines[i] = line elif m := re.match('([#.]* *sage[.]doctest:[ #]*)([^(]*)(.*)', line): # file-scoped annotation @@ -363,7 +368,7 @@ for input, output in zip(inputs, outputs): suffix = ' ' + suffix src_in_lines[i] = prefix + line + suffix elif line.strip() in ['', '"""', "'''"]: # Blank line or end of docstring - persistent_optional_tags = set() + persistent_optional_tags = {} for block in doctests: process_block(block, src_in_lines, file_optional_tags) diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index a6a738bd4a6..f2e581037b6 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -303,7 +303,8 @@ def skipfile(filename, tested_optional_tags=False, *, return file_tag_string elif tested_optional_tags is not True: - extra = file_optional_tags - set(tested_optional_tags) + extra = set(tag for tag in file_optional_tags + if tag not in tested_optional_tags) if extra: file_tag_string = unparse_optional_tags(file_optional_tags, prefix='') if log: diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index f68aecebcbc..ccf62ef6bb6 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -132,10 +132,10 @@ def init_sage(controller=None): Check that SymPy equation pretty printer is limited in doctest mode to default width (80 chars):: - sage: from sympy import sympify # optional - sage.symbolic - sage: from sympy.printing.pretty.pretty import PrettyPrinter # optional - sage.symbolic - sage: s = sympify('+x^'.join(str(i) for i in range(30))) # optional - sage.symbolic - sage: print(PrettyPrinter(settings={'wrap_line': True}).doprint(s)) # optional - sage.symbolic + sage: from sympy import sympify # needs sympy + sage: from sympy.printing.pretty.pretty import PrettyPrinter # needs sympy + sage: s = sympify('+x^'.join(str(i) for i in range(30))) # needs sympy + sage: print(PrettyPrinter(settings={'wrap_line': True}).doprint(s)) # needs sympy 29 28 27 26 25 24 23 22 21 20 19 18 17 x + x + x + x + x + x + x + x + x + x + x + x + x + @@ -2160,7 +2160,7 @@ def run(self): TESTS:: - sage: run_doctests(sage.symbolic.units) # indirect doctest # optional - sage.symbolic + sage: run_doctests(sage.symbolic.units) # indirect doctest # needs sage.symbolic Running doctests with ID ... Doctesting 1 file. sage -t .../sage/symbolic/units.py diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 220df3503d9..88456a065fe 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -23,6 +23,7 @@ # https://www.gnu.org/licenses/ # **************************************************************************** +import collections.abc import doctest import re @@ -93,17 +94,20 @@ def fake_RIFtol(*args): def parse_optional_tags(string, *, return_string_sans_tags=False): r""" - Return a set consisting of the optional tags from the following + Return a dictionary whose keys are optional tags from the following set that occur in a comment on the first line of the input string. - - 'long time' - - 'not implemented' - - 'not tested' - - 'known bug' - - 'py2' - - 'arb216' - - 'arb218' - - 'optional: PKG_NAME' -- the set will just contain 'PKG_NAME' + - ``'long time'`` + - ``'not implemented'`` + - ``'not tested'`` + - ``'known bug'`` + - ``'py2'`` + - ``'arb216'`` + - ``'arb218'`` + - ``'optional - PKG_NAME...'`` -- the dictionary will just have the key ``'PKG_NAME'`` + + The values, if non-``None``, are strings with optional explanations + for a tag, which may appear in parentheses after the tag in ``string``. INPUT: @@ -117,52 +121,52 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): sage: from sage.doctest.parsing import parse_optional_tags sage: parse_optional_tags("sage: magma('2 + 2')# optional: magma") - {'magma'} + {'magma': None} sage: parse_optional_tags("sage: #optional -- mypkg") - {'mypkg'} + {'mypkg': None} sage: parse_optional_tags("sage: print(1) # parentheses are optional here") - set() + {} sage: parse_optional_tags("sage: print(1) # optional") - {''} + {} sage: sorted(list(parse_optional_tags("sage: #optional -- foo bar, baz"))) ['bar', 'foo'] sage: parse_optional_tags("sage: #optional -- foo.bar, baz") - {'foo.bar'} + {'foo.bar': None} sage: parse_optional_tags("sage: #needs foo.bar, baz") - {'foo.bar'} + {'foo.bar': None} sage: sorted(list(parse_optional_tags(" sage: factor(10^(10^10) + 1) # LoNg TiME, NoT TeSTED; OptioNAL -- P4cka9e"))) ['long time', 'not tested', 'p4cka9e'] sage: parse_optional_tags(" sage: raise RuntimeError # known bug") - {'bug'} + {'bug': None} sage: sorted(list(parse_optional_tags(" sage: determine_meaning_of_life() # long time, not implemented"))) ['long time', 'not implemented'] We don't parse inside strings:: sage: parse_optional_tags(" sage: print(' # long time')") - set() + {} sage: parse_optional_tags(" sage: print(' # long time') # not tested") - {'not tested'} + {'not tested': None} UTF-8 works:: sage: parse_optional_tags("'ěščřžýáíéďĎ'") - set() + {} With ``return_string_sans_tags=True``:: sage: parse_optional_tags("sage: print(1) # very important 1 # optional - foo", ....: return_string_sans_tags=True) - ({'foo'}, 'sage: print(1) # very important 1 ', False) + ({'foo': None}, 'sage: print(1) # very important 1 ', False) sage: parse_optional_tags("sage: print( # very important too # optional - foo\n....: 2)", ....: return_string_sans_tags=True) - ({'foo'}, 'sage: print( # very important too \n....: 2)', False) + ({'foo': None}, 'sage: print( # very important too \n....: 2)', False) sage: parse_optional_tags("sage: #this is persistent #needs scipy", ....: return_string_sans_tags=True) - ({'scipy'}, 'sage: #this is persistent ', True) + ({'scipy': None}, 'sage: #this is persistent ', True) sage: parse_optional_tags("sage: #this is not #needs scipy\n....: import scipy", ....: return_string_sans_tags=True) - ({'scipy'}, 'sage: #this is not \n....: import scipy', False) + ({'scipy': None}, 'sage: #this is not \n....: import scipy', False) """ safe, literals, state = strip_string_literals(string) @@ -175,9 +179,9 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): sharp_index = first_line.find('#') if sharp_index < 0: # no comment if return_string_sans_tags: - return set(), string, False + return {}, string, False else: - return set() + return {} first_line_sans_comments, comment = first_line[:sharp_index] % literals, first_line[sharp_index:] % literals @@ -190,28 +194,26 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): comment = comment[sharp_index:] else: # no tag comment - return set(), string, False + return {}, string, False - tags = [] + tags = {} for m in optional_regex.finditer(comment): cmd = m.group(1) if cmd and cmd.lower() == 'known bug': - tags.append('bug') # so that such tests will be run by sage -t ... -only-optional=bug + tags['bug'] = None # so that such tests will be run by sage -t ... -only-optional=bug elif cmd: - tags.append(cmd.lower()) + tags[cmd.lower()] = None else: words = m.group(3).split() if words: - tags.extend([s.lower() for s in words]) - else: - tags.append("") + tags.update({s.lower(): None for s in words}) if return_string_sans_tags: is_persistent = tags and first_line_sans_comments.strip() == 'sage:' and not rest # persistent (block-scoped) annotation - return set(tags), (first_line + '\n' + rest%literals if rest is not None - else first_line), is_persistent + return tags, (first_line + '\n' + rest%literals if rest is not None + else first_line), is_persistent else: - return set(tags) + return tags def parse_file_optional_tags(lines): @@ -224,7 +226,7 @@ def parse_file_optional_tags(lines): OUTPUT: - a set of strings (tags) + a dictionary whose keys are strings (tags); see :func:`parse_optional_tags` EXAMPLES:: @@ -232,23 +234,23 @@ def parse_file_optional_tags(lines): sage: filename = tmp_filename(ext=".pyx") sage: with open(filename, "r") as f: ....: parse_file_optional_tags(enumerate(f)) - set() + {} sage: with open(filename, "w") as f: ....: _ = f.write("# nodoctest") sage: with open(filename, "r") as f: ....: parse_file_optional_tags(enumerate(f)) - {'not tested'} + {'not tested': None} sage: with open(filename, "w") as f: ....: _ = f.write("# sage.doctest: " # broken in two source lines to avoid the pattern ....: "optional - xyz") # of relint (multiline_doctest_comment) sage: with open(filename, "r") as f: ....: parse_file_optional_tags(enumerate(f)) - {'xyz'} + {'xyz': None} """ - tags = set() + tags = {} for line_count, line in lines: if nodoctest_regex.match(line): - tags.add('not tested') + tags['not tested'] = None if m := optionalfiledirective_regex.match(line): file_tag_string = m.group(2) tags.update(parse_optional_tags('#' + file_tag_string)) @@ -313,26 +315,33 @@ def unparse_optional_tags(tags, prefix='# '): INPUT: - - ``tags`` -- iterable of tags, as output by :func:`parse_optional_tags` + - ``tags`` -- dict or iterable of tags, as output by :func:`parse_optional_tags` - ``prefix`` -- to be put before a nonempty string EXAMPLES:: sage: from sage.doctest.parsing import unparse_optional_tags - sage: unparse_optional_tags(set()) + sage: unparse_optional_tags({}) '' - sage: unparse_optional_tags({'magma'}) + sage: unparse_optional_tags({'magma': None}) '# optional - magma' - sage: unparse_optional_tags(['fictional_optional', 'sage.rings.number_field', - ....: 'scipy', 'bliss']) - '# optional - bliss fictional_optional, needs scipy sage.rings.number_field' + sage: unparse_optional_tags({'fictional_optional': None, + ....: 'sage.rings.number_field': None, + ....: 'scipy': 'just because', + ....: 'bliss': None}) + '# optional - bliss fictional_optional, needs scipy (just because) sage.rings.number_field' sage: unparse_optional_tags(['long time', 'not tested', 'p4cka9e'], prefix='') 'long time, not tested, optional - p4cka9e' """ group = defaultdict(set) - for tag in tags: - group[_tag_group(tag)].add(tag) + if isinstance(tags, collections.abc.Mapping): + for tag, explanation in tags.items(): + group[_tag_group(tag)].add(f'{tag} ({explanation})' if explanation else tag) + else: + for tag in tags: + group[_tag_group(tag)].add(tag) + tags = sorted(group.pop('special', [])) if 'optional' in group: tags.append('optional - ' + " ".join(sorted(group.pop('optional')))) @@ -391,16 +400,20 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo current_tags, line_sans_tags, is_persistent = parse_optional_tags(line.rstrip(), return_string_sans_tags=True) - new_tags = set(current_tags) + new_tags = dict(current_tags) if tags is not None: new_tags = tags if add_tags is not None: - new_tags.update(add_tags) + if isinstance(add_tags, collections.abc.Mapping): + new_tags.update(add_tags) + else: + new_tags.update({tag: None for tag in add_tags}) if remove_tags is not None: - new_tags.difference_update(remove_tags) + for tag in remove_tags: + new_tags.pop(tag, None) if not force_rewrite and new_tags == current_tags: return line @@ -735,7 +748,7 @@ def __init__(self, optional_tags=(), long=False, *, probed_tags=(), file_optiona - ``long`` -- boolean, whether to run doctests marked as taking a long time. - ``probed_tags`` -- a list or tuple of strings. - - ``file_optional_tags`` -- a list or tuple of strings. + - ``file_optional_tags`` -- an iterable of strings. EXAMPLES:: @@ -940,6 +953,7 @@ def parse(self, string, *args): for item in res: if isinstance(item, doctest.Example): optional_tags, source_sans_tags, is_persistent = parse_optional_tags(item.source, return_string_sans_tags=True) + optional_tags = set(optional_tags) if is_persistent: persistent_optional_tags = optional_tags continue diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index a4ba02c1d4a..c7317f3568b 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -723,7 +723,7 @@ def file_optional_tags(self): sage: filename = os.path.join(SAGE_SRC, 'sage', 'repl', 'user_globals.py') sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS.file_optional_tags - {'sage.modules'} + {'sage.modules': None} """ from .parsing import parse_file_optional_tags return parse_file_optional_tags(self) From ebfd16435e225ec91530a60beedef04bf56fd8ff Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 1 Jul 2023 15:18:32 -0700 Subject: [PATCH 079/150] src/sage/doctest/parsing.py (update_optional_tags): Alignment fixes, add doctests --- src/bin/sage-fixdoctests | 2 +- src/sage/doctest/parsing.py | 136 +++++++++++++++++++++++++++--------- 2 files changed, 103 insertions(+), 35 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 83a1f373eb3..387c05b2cf3 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -338,7 +338,7 @@ for input, output in zip(inputs, outputs): # First remove duplicate optional tags and rewrite all '# optional' that should be '# needs' file_optional_tags = {} - persistent_optional_tags = set() + persistent_optional_tags = {} for i, line in enumerate(src_in_lines): if m := re.match(' *sage: *(.*)#', line): tags, line_sans_tags, is_persistent = parse_optional_tags(line, return_string_sans_tags=True) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 88456a065fe..389771a3753 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -337,9 +337,13 @@ def unparse_optional_tags(tags, prefix='# '): group = defaultdict(set) if isinstance(tags, collections.abc.Mapping): for tag, explanation in tags.items(): + if tag == 'bug': + tag = 'known bug' group[_tag_group(tag)].add(f'{tag} ({explanation})' if explanation else tag) else: for tag in tags: + if tag == 'bug': + tag = 'known bug' group[_tag_group(tag)].add(tag) tags = sorted(group.pop('special', [])) @@ -364,46 +368,90 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo EXAMPLES:: - sage: from sage.doctest.parsing import update_optional_tags - sage: update_optional_tags(' sage: nothing_to_be_seen_here()') - ' sage: nothing_to_be_seen_here()' - sage: update_optional_tags(' sage: nothing_to_be_seen_here()', - ....: tags=['scipy', 'bliss', 'long time']) - ' sage: nothing_to_be_seen_here() # long time, optional - bliss, needs scipy' - sage: update_optional_tags(' sage: ntbsh() # abbrv for above#optional:bliss', - ....: add_tags=['scipy', 'long time']) - ' sage: ntbsh() # abbrv for above # long time, optional - bliss, needs scipy' - sage: update_optional_tags(' sage: something() # optional - latte_int', - ....: remove_tags=['latte_int', 'wasnt_even_there']) - ' sage: something()' - sage: update_optional_tags(' sage: something() # optional - latte_int', - ....: add_tags=['latte_int']) - ' sage: something() # optional - latte_int' - sage: update_optional_tags(' sage: something()#optional- latte_int', - ....: force_rewrite=True) - ' sage: something() # optional - latte_int' - - Forcing a rewrite whenever standard tags are involved:: - - sage: update_optional_tags(' sage: something_else() # optional - scipy', - ....: force_rewrite='standard') - ' sage: something_else() # needs scipy' + sage: from sage.doctest.parsing import update_optional_tags, optional_tag_columns, standard_tag_columns + sage: ruler = '' + sage: for column in optional_tag_columns: + ....: ruler += ' ' * (column - len(ruler)) + 'V' + sage: for column in standard_tag_columns: + ....: ruler += ' ' * (column - len(ruler)) + 'v' + sage: def print_with_ruler(lines): + ....: print('|' + ruler) + ....: for line in lines: + ....: print('|' + line) + sage: print_with_ruler([ + ....: update_optional_tags(' sage: something() # optional - latte_int', + ....: remove_tags=['latte_int', 'wasnt_even_there']), + ....: update_optional_tags(' sage: nothing_to_be_seen_here()', + ....: tags=['scipy', 'long time']), + ....: update_optional_tags(' sage: nothing_to_be_seen_here(honestly=True)', + ....: add_tags=['scipy', 'long time']), + ....: update_optional_tags(' sage: nothing_to_be_seen_here(honestly=True, very=True)', + ....: add_tags=['scipy', 'long time']), + ....: update_optional_tags(' sage: no_there_is_absolutely_nothing_to_be_seen_here_i_am_serious()#optional:bliss', + ....: add_tags=['scipy', 'long time']), + ....: update_optional_tags(' sage: ntbsh() # abbrv for above#optional:bliss', + ....: add_tags={'scipy': None, 'long time': '30s on the highest setting'}), + ....: update_optional_tags(' sage: no_there_is_absolutely_nothing_to_be_seen_here_i_am_serious() # really, you can trust me here', + ....: add_tags=['scipy']), + ....: ]) + | V V V V V V v v v v + | sage: something() + | sage: nothing_to_be_seen_here() # long time # needs scipy + | sage: nothing_to_be_seen_here(honestly=True) # long time # needs scipy + | sage: nothing_to_be_seen_here(honestly=True, very=True) # long time # needs scipy + | sage: no_there_is_absolutely_nothing_to_be_seen_here_i_am_serious() # long time, optional - bliss, needs scipy + | sage: ntbsh() # abbrv for above # long time (30s on the highest setting), optional - bliss, needs scipy + | sage: no_there_is_absolutely_nothing_to_be_seen_here_i_am_serious() # really, you can trust me here # needs scipy + + When no tags are changed, by default, the unchanged input is returned. + We can force a rewrite; unconditionally or whenever standard tags are involved. + But even when forced, if comments are already aligned at one of the standard alignment columns, + this alignment is kept even if we would normally realign farther to the left. + + sage: print_with_ruler([ + ....: update_optional_tags(' sage: unforced() # optional - latte_int'), + ....: update_optional_tags(' sage: unforced() # optional - latte_int', + ....: add_tags=['latte_int']), + ....: update_optional_tags(' sage: forced()#optional- latte_int', + ....: force_rewrite=True), + ....: update_optional_tags(' sage: forced() # optional - scipy', + ....: force_rewrite='standard'), + ....: update_optional_tags(' sage: aligned_with_below() # optional - 4ti2', + ....: force_rewrite=True), + ....: update_optional_tags(' sage: aligned_with_above() # optional - 4ti2', + ....: force_rewrite=True), + ....: update_optional_tags(' sage: also_already_aligned() # needs scipy', + ....: force_rewrite='standard'), + ....: ]) + | V V V V V V v v v v + | sage: unforced() # optional - latte_int + | sage: unforced() # optional - latte_int + | sage: forced() # optional - latte_int + | sage: forced() # needs scipy + | sage: aligned_with_below() # optional - 4ti2 + | sage: aligned_with_above() # optional - 4ti2 + | sage: also_already_aligned() # needs scipy Rewriting a persistent (block-scoped) annotation:: - sage: update_optional_tags(' sage: #optional:magma', - ....: force_rewrite=True) - ' sage: # optional - magma' + sage: print_with_ruler([ + ....: update_optional_tags(' sage: #optional:magma sage.symbolic', + ....: force_rewrite=True), + ....: ]) + | V V V V V V v v v v + | sage: # optional - magma, needs sage.symbolic """ if not (m := re.match('( *sage: *)(.*)', line)): raise ValueError(f'line must start with a sage: prompt, got: {line}') current_tags, line_sans_tags, is_persistent = parse_optional_tags(line.rstrip(), return_string_sans_tags=True) - new_tags = dict(current_tags) - - if tags is not None: - new_tags = tags + if isinstance(tags, collections.abc.Mapping): + new_tags = dict(tags) + elif tags is not None: + new_tags = {tag: None for tag in tags} + else: + new_tags = dict(current_tags) if add_tags is not None: if isinstance(add_tags, collections.abc.Mapping): @@ -430,11 +478,15 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo if is_persistent: line = line_sans_tags.rstrip() + ' ' else: - tag_columns = optional_tag_columns if any(_tag_group(tag) in ['optional', 'special'] - for tag in new_tags) else standard_tag_columns + group = defaultdict(set) + for tag in new_tags: + group[_tag_group(tag)].add(tag) + tag_columns = (optional_tag_columns if group['optional'] or group['special'] + else standard_tag_columns) - if len(line) in tag_columns and line[-2:] == ' ': + if len(line_sans_tags) in tag_columns and line_sans_tags[-2:] == ' ': # keep alignment + line = line_sans_tags pass else: # realign @@ -445,6 +497,22 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo break line += ' ' + if (group['optional'] or group['special']) and (group['standard'] or group['sage']): + # Try if two-column mode works better + first_part = unparse_optional_tags({tag: explanation + for tag, explanation in new_tags.items() + if (tag in group['optional'] + or tag in group['special'])}) + column = standard_tag_columns[0] + if len(line + first_part) + 8 <= column: + line += first_part + line += ' ' * (column - len(line)) + line += unparse_optional_tags({tag: explanation + for tag, explanation in new_tags.items() + if not (tag in group['optional'] + or tag in group['special'])}) + return line.rstrip() + line += unparse_optional_tags(new_tags) return line From 93c11f14e5d68efcca8676b40fa84d1a1e0f7a7e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 1 Jul 2023 15:56:07 -0700 Subject: [PATCH 080/150] src/sage/doctest/parsing.py: Hide tags from relint --- src/sage/doctest/parsing.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 389771a3753..fb816f6571e 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -378,8 +378,8 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo ....: print('|' + ruler) ....: for line in lines: ....: print('|' + line) - sage: print_with_ruler([ - ....: update_optional_tags(' sage: something() # optional - latte_int', + sage: print_with_ruler([ # the tags are obscured in the source file to avoid relint warnings + ....: update_optional_tags(' sage: something() # opt' 'ional - latte_int', ....: remove_tags=['latte_int', 'wasnt_even_there']), ....: update_optional_tags(' sage: nothing_to_be_seen_here()', ....: tags=['scipy', 'long time']), @@ -387,9 +387,9 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo ....: add_tags=['scipy', 'long time']), ....: update_optional_tags(' sage: nothing_to_be_seen_here(honestly=True, very=True)', ....: add_tags=['scipy', 'long time']), - ....: update_optional_tags(' sage: no_there_is_absolutely_nothing_to_be_seen_here_i_am_serious()#optional:bliss', + ....: update_optional_tags(' sage: no_there_is_absolutely_nothing_to_be_seen_here_i_am_serious()#opt' 'ional:bliss', ....: add_tags=['scipy', 'long time']), - ....: update_optional_tags(' sage: ntbsh() # abbrv for above#optional:bliss', + ....: update_optional_tags(' sage: ntbsh() # abbrv for above#opt' 'ional:bliss', ....: add_tags={'scipy': None, 'long time': '30s on the highest setting'}), ....: update_optional_tags(' sage: no_there_is_absolutely_nothing_to_be_seen_here_i_am_serious() # really, you can trust me here', ....: add_tags=['scipy']), @@ -409,18 +409,18 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo this alignment is kept even if we would normally realign farther to the left. sage: print_with_ruler([ - ....: update_optional_tags(' sage: unforced() # optional - latte_int'), - ....: update_optional_tags(' sage: unforced() # optional - latte_int', + ....: update_optional_tags(' sage: unforced() # opt' 'ional - latte_int'), + ....: update_optional_tags(' sage: unforced() # opt' 'ional - latte_int', ....: add_tags=['latte_int']), - ....: update_optional_tags(' sage: forced()#optional- latte_int', + ....: update_optional_tags(' sage: forced()#opt' 'ional- latte_int', ....: force_rewrite=True), - ....: update_optional_tags(' sage: forced() # optional - scipy', + ....: update_optional_tags(' sage: forced() # opt' 'ional - scipy', ....: force_rewrite='standard'), - ....: update_optional_tags(' sage: aligned_with_below() # optional - 4ti2', + ....: update_optional_tags(' sage: aligned_with_below() # opt' 'ional - 4ti2', ....: force_rewrite=True), - ....: update_optional_tags(' sage: aligned_with_above() # optional - 4ti2', + ....: update_optional_tags(' sage: aligned_with_above() # opt' 'ional - 4ti2', ....: force_rewrite=True), - ....: update_optional_tags(' sage: also_already_aligned() # needs scipy', + ....: update_optional_tags(' sage: also_already_aligned() # ne' 'eds scipy', ....: force_rewrite='standard'), ....: ]) | V V V V V V v v v v @@ -435,7 +435,7 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo Rewriting a persistent (block-scoped) annotation:: sage: print_with_ruler([ - ....: update_optional_tags(' sage: #optional:magma sage.symbolic', + ....: update_optional_tags(' sage: #opt' 'ional:magma sage.symbolic', ....: force_rewrite=True), ....: ]) | V V V V V V v v v v From 1130c262adefea8d152de2a4e0f2686773ae8c52 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 1 Jul 2023 17:22:12 -0700 Subject: [PATCH 081/150] sage --fixdoctests: Add option --no-test --- src/bin/sage-fixdoctests | 44 ++++++++++++++++------------- src/doc/en/developer/doctesting.rst | 4 +++ 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 387c05b2cf3..d458d0e8ebd 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -41,10 +41,12 @@ parser.add_argument("--venv", type=str, default='', help="directory name of a venv where 'sage -t' is to be run") parser.add_argument("--environment", type=str, default='', help="name of a module that provides the global environment for tests; implies --keep-both and --full-tracebacks") +parser.add_argument("--no-test", type=str, default='', + help="do not run the doctester, only rewrite '# optional/needs' tags; implies --only-tags") parser.add_argument("--full-tracebacks", default=False, action="store_true", help="include full tracebacks rather than '...'") parser.add_argument("--only-tags", default=False, action="store_true", - help="only add '# optional' tags where needed, ignore other failures") + help="only add '# optional/needs' tags where needed, ignore other failures") parser.add_argument("--probe", metavar="FEATURES", type=str, default='', help="check whether '# optional - FEATURES' tags are still needed, remove these") parser.add_argument("--keep-both", default=False, action="store_true", @@ -298,30 +300,34 @@ else: inputs = outputs = args.filename # Test the doctester, putting the output of the test into sage's temporary directory -executable = f'{os.path.relpath(args.venv)}/bin/sage' if args.venv else 'sage' -environment_args = f'--environment {args.environment} ' if args.environment != runtest_default_environment else '' -probe_args = f'--probe {shlex.quote(args.probe)} ' if args.probe else '' -lib_args = f'--only-lib ' if args.venv else '' -doc_file = tmp_filename() -if args.venv or environment_args: - input = os.path.join(os.path.relpath(SAGE_ROOT), 'src', 'sage', 'version.py') - cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{lib_args}{shlex.quote(input)}' - print(f'Running "{cmdline}"') - if status := os.waitstatus_to_exitcode(os.system(f'{cmdline} > {shlex.quote(doc_file)}')): - print(f'Doctester exited with error status {status}') - sys.exit(status) +if not args.no_test: + executable = f'{os.path.relpath(args.venv)}/bin/sage' if args.venv else 'sage' + environment_args = f'--environment {args.environment} ' if args.environment != runtest_default_environment else '' + probe_args = f'--probe {shlex.quote(args.probe)} ' if args.probe else '' + lib_args = f'--only-lib ' if args.venv else '' + doc_file = tmp_filename() + if args.venv or environment_args: + input = os.path.join(os.path.relpath(SAGE_ROOT), 'src', 'sage', 'version.py') + cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{lib_args}{shlex.quote(input)}' + print(f'Running "{cmdline}"') + if status := os.waitstatus_to_exitcode(os.system(f'{cmdline} > {shlex.quote(doc_file)}')): + print(f'Doctester exited with error status {status}') + sys.exit(status) for input, output in zip(inputs, outputs): if (skipfile_result := skipfile(input, True, log=print)) is True: continue - # Run the doctester, putting the output of the test into sage's temporary directory - cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{lib_args}{shlex.quote(input)}' - print(f'Running "{cmdline}"') - os.system(f'{cmdline} > {shlex.quote(doc_file)}') + if args.no_test: + doc_out = '' + else: + # Run the doctester, putting the output of the test into sage's temporary directory + cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{lib_args}{shlex.quote(input)}' + print(f'Running "{cmdline}"') + os.system(f'{cmdline} > {shlex.quote(doc_file)}') - with open(doc_file, 'r') as doc: - doc_out = doc.read() + with open(doc_file, 'r') as doc: + doc_out = doc.read() # echo control messages for m in re.finditer('^Skipping .*', doc_out, re.MULTILINE): diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index 46fb4625752..ca1d420ed1d 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -1467,6 +1467,10 @@ This mode is suitable for mostly unattended runs on many files. appear before ``# optional``, any comments that may be mixed with the feature annotations will be lost. +If you don't want to update any doctests, you can use the +option ``--no-test``. In this mode, the doctest fixer does not run +the doctester and only normalizes the style of the ``# optional`` annotations. + Use in virtual environments --------------------------- From 514d1f79f0b5bb126fa7d47098b1bf9c6924daee Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 1 Jul 2023 18:02:27 -0700 Subject: [PATCH 082/150] sage --fixdoctests: Do not add tags that duplicate file-scoped tags --- src/bin/sage-fixdoctests | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index d458d0e8ebd..a7c7d55fccc 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -150,6 +150,7 @@ def process_block(block, src_in_lines, file_optional_tags): if src_in_lines[first_line_num - 1].strip() in ['"""', "'''"]: # This happens due to a virtual doctest in src/sage/repl/user_globals.py return + optional = set(optional) - set(file_optional_tags) src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=optional) From 65e69857736958d9b2c0c23b2e6c960aaca6c597 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 1 Jul 2023 18:12:29 -0700 Subject: [PATCH 083/150] sage --fixdoctests: Add option --no-test (fixup) --- src/bin/sage-fixdoctests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index a7c7d55fccc..8db990a47eb 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -41,7 +41,7 @@ parser.add_argument("--venv", type=str, default='', help="directory name of a venv where 'sage -t' is to be run") parser.add_argument("--environment", type=str, default='', help="name of a module that provides the global environment for tests; implies --keep-both and --full-tracebacks") -parser.add_argument("--no-test", type=str, default='', +parser.add_argument("--no-test", default=False, action="store_true", help="do not run the doctester, only rewrite '# optional/needs' tags; implies --only-tags") parser.add_argument("--full-tracebacks", default=False, action="store_true", help="include full tracebacks rather than '...'") From 5934fe7fff8b855fb528eb253f2abd099da1b3bf Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 1 Jul 2023 19:59:12 -0700 Subject: [PATCH 084/150] sage --fixdoctests: Handle the case of multiple tags in 'may no longer be needed' --- src/bin/sage-fixdoctests | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 8db990a47eb..bdda8640006 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -155,9 +155,9 @@ def process_block(block, src_in_lines, file_optional_tags): add_tags=optional) if m := re.search(r"annotation '# (optional|needs)[-: ]([^;']*)' may no longer be needed", block): - optional = m.group(2) + optional = m.group(2).split() src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], - remove_tags=[optional]) + remove_tags=optional) if m2 := re.search('(Expected:|Expected nothing|Exception raised:)\n', block): m1 = re.search('Failed example:\n', block) From 51d406d700ab1c287aff05520777bcabc75c6da2 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 1 Jul 2023 20:15:47 -0700 Subject: [PATCH 085/150] sage -t, sage --fixdoctests: Don't probe pure file-level tags --- src/sage/doctest/parsing.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index fb816f6571e..930db5b3bbb 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -1050,6 +1050,10 @@ def parse(self, string, *args): if any(tag in external_software for tag in extra): # never probe "external" software continue + if all(tag in self.file_optional_tags for tag in extra): + # don't probe if test is only conditional + # on file-level tags + continue if self.probed_tags is True: item.probed_tags = extra elif all(tag in self.probed_tags for tag in extra): From 6f2ffd0ebdd2c735da4bb921e28c1ed9f1437c91 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 1 Jul 2023 22:17:11 -0700 Subject: [PATCH 086/150] src/sage/repl/user_globals.py: Add # needs --- src/sage/repl/user_globals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/repl/user_globals.py b/src/sage/repl/user_globals.py index 92de7d87e0c..73c13c44690 100644 --- a/src/sage/repl/user_globals.py +++ b/src/sage/repl/user_globals.py @@ -1,3 +1,4 @@ +# sage.doctest: needs sage.modules r""" User-interface globals From 914dd032987619dc68f6fb6ab8f3417fa3568234 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 1 Jul 2023 23:22:13 -0700 Subject: [PATCH 087/150] sage -t: While probing, do not issue "was set only in doctest marked" warnings --- src/sage/doctest/forker.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index e979cf6e836..179663ea065 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -722,7 +722,7 @@ def compiler(example): # verify its output. if exception is None: if check(example.want, got, self.optionflags): - if probed_tags: + if probed_tags and probed_tags is not True: example.warnings.append( f"The annotation '{unparse_optional_tags(probed_tags)}' " f"may no longer be needed; these features are not present, " @@ -763,7 +763,7 @@ def compiler(example): # We expected an exception: see whether it matches. elif check(example.exc_msg, exc_msg, self.optionflags): - if probed_tags: + if probed_tags and probed_tags is not True: example.warnings.append( f"The annotation '{unparse_optional_tags(example.probed_tags)}' " f"may no longer be needed; these features are not present, " @@ -777,7 +777,7 @@ def compiler(example): m2 = re.match(r'(?:[^:]*\.)?([^:]*:)', exc_msg) if m1 and m2 and check(m1.group(1), m2.group(1), self.optionflags): - if probed_tags: + if probed_tags and probed_tags is not True: example.warnings.append( f"The annotation '{unparse_optional_tags(example.probed_tags)}' " f"may no longer be needed; these features are not present, " @@ -1141,12 +1141,18 @@ def compile_and_execute(self, example, compiler, globs): was_set = True example.predecessors.append(setter) if not was_set: - f_setter_optional_tags = "; ".join("'" - + unparse_optional_tags(setter_optional_tags) - + "'" - for setter_optional_tags in setters_dict) - example.warnings.append(f"Variable '{name}' referenced here " - f"was set only in doctest marked {f_setter_optional_tags}") + if example.probed_tags: + # Probing confusion. + # Do not issue the "was set only in doctest marked" warning; + # and also do not issue the "may no longer be needed" notice + example.probed_tags = True + else: + f_setter_optional_tags = "; ".join("'" + + unparse_optional_tags(setter_optional_tags) + + "'" + for setter_optional_tags in setters_dict) + example.warnings.append(f"Variable '{name}' referenced here " + f"was set only in doctest marked {f_setter_optional_tags}") for name in globs.set: self.setters[name][example.optional_tags] = example else: From ce84e707f5ee3c6978904e86cbcad7becd9edc87 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 2 Jul 2023 01:04:04 -0700 Subject: [PATCH 088/150] sage -t, sage --fixdoctests: Don't probe pure file-level/block-level tags --- src/sage/doctest/parsing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 930db5b3bbb..048e26241fe 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -1050,9 +1050,9 @@ def parse(self, string, *args): if any(tag in external_software for tag in extra): # never probe "external" software continue - if all(tag in self.file_optional_tags for tag in extra): + if all(tag in persistent_optional_tags for tag in extra): # don't probe if test is only conditional - # on file-level tags + # on file-level or block-level tags continue if self.probed_tags is True: item.probed_tags = extra From c999202c905efc3ad6c58b95f3e9d9ca7a1aab93 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 2 Jul 2023 14:07:21 -0700 Subject: [PATCH 089/150] sage --fixdoctests: Handle top-level NameErrors that resolve in sage.all --- src/bin/sage-fixdoctests | 45 +++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index bdda8640006..17f6a875ddf 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -29,9 +29,9 @@ from sage.doctest.parsing import parse_optional_tags, unparse_optional_tags, upd from sage.env import SAGE_ROOT from sage.features import PythonModule from sage.features.all import all_features +from sage.misc.dev_tools import find_object_modules from sage.misc.temporary_file import tmp_filename - parser = ArgumentParser(description="Given an input file with doctests, this creates a modified file that passes the doctests (modulo any raised exceptions). By default, the input file is modified. You can also name an output file.") parser.add_argument('-l', '--long', dest='long', action="store_true", default=False) @@ -138,6 +138,29 @@ def module_feature(module_name): return longest_prefix_feature +def name_feature(name, toplevel=None): + r""" + Find a top-level :class:`Feature` that provides the top-level ``name``. + + OUTPUT: a :class:`Feature` or ``None``. + """ + if toplevel is None: + try: + import sage.all as toplevel + except ImportError: + return None + try: + obj = getattr(toplevel, name) + except AttributeError: + return None + + for module, names in find_object_modules(obj).items(): + if name in names and (feature := module_feature(module)): + return feature + + return None + + def process_block(block, src_in_lines, file_optional_tags): # Extract the line, what was expected, and was got. if not (m := re.match('File "([^"]*)", line ([0-9]+), in ', block)): @@ -198,9 +221,6 @@ def process_block(block, src_in_lines, file_optional_tags): return # Otherwise, continue and show the backtrace as 'GOT' - if args.only_tags: - return - if 'Traceback (most recent call last):' in block: expected, got = block.split('\nGot:\n') @@ -222,11 +242,19 @@ def process_block(block, src_in_lines, file_optional_tags): and got[last_frame:].startswith('File " Date: Mon, 3 Jul 2023 10:34:16 -0700 Subject: [PATCH 090/150] src/doc/en/developer, src/sage/features/sagemath.py: 'annotation' -> 'tag', 'optional' -> 'needs' --- src/doc/en/developer/doctesting.rst | 66 ++++- .../en/developer/packaging_sage_library.rst | 20 +- src/sage/features/sagemath.py | 264 ++++++++++++------ 3 files changed, 244 insertions(+), 106 deletions(-) diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index ca1d420ed1d..2722a0cf11b 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -1440,36 +1440,72 @@ instead of overwriting the source file:: --no-overwrite src/sage/geometry/cone.py -Managing ``# optional`` tags ----------------------------- - -When a file uses a ``# sage.doctest: optional - FEATURE`` directive, the -doctest fixer automatically removes these tags from ``# optional - FEATURE`` -annotations on individual doctests. +.. _section-fixdoctests-optional-needs: + +Managing ``# optional`` and ``# needs`` tags +-------------------------------------------- + +When a file uses a ``# sage.doctest: optional/needs FEATURE`` directive, the +doctest fixer automatically removes the redundant ``# optional/needs FEATURE`` +tags from all ``sage:`` lines. Likewise, when a codeblock-scoped tag +``sage: # optional/needs FEATURE`` is used, then the doctest fixer removes +redundant tags from all doctests in this scope. For example:: + + | # sage.doctest: optional - sirocco, needs sage.rings.number_field + | r""" + | ... + | + | EXAMPLES:: + | + | sage: # needs sage.modules sage.rings.number_field + | sage: Q5 = QuadraticField(5) + | sage: V = Q5^42 # needs sage.modules + | sage: T = transmogrify(V) # optional - bliss sirocco + +is automatically transformed to:: + + | # sage.doctest: optional - sirocco, needs sage.rings.number_field + | r""" + | ... + | + | EXAMPLES:: + | + | sage: # needs sage.modules + | sage: Q5 = QuadraticField(5) + | sage: V = Q5^42 + | sage: T = transmogrify(V) # optional - bliss + +The doctest fixer also aligns the ``# optional/needs FEATURE`` tags on +individual doctests at a fixed set of tab stops. In places where the doctester issues a doctest dataflow warning (``Variable ... referenced here was set only in doctest marked '# optional - FEATURE'``), -the doctest fixer automatically adds the missing ``# optional`` annotations. +the doctest fixer automatically adds the missing ``# optional/needs`` tags. -Sometimes code changes can make existing ``# optional - FEATURE`` annotations unnecessary. -In an installation or virtual environment where FEATURE is not available, +Sometimes code changes can make existing ``# optional/needs FEATURE`` tags unnecessary. +In an installation or virtual environment where ``FEATURE`` is not available, you can invoke the doctest fixer with the option ``--probe FEATURE``. -Then it will run examples marked ``# optional - FEATURE`` silently, and if the example -turns out to work anyway, the annotation is automatically removed. +Then it will run examples marked ``# optional/needs - FEATURE`` silently, and if the example +turns out to work anyway, the tag is automatically removed. + +.. note:: + + Probing works best when the doctests within a docstring do not reuse the same variable + for different values. -To have the doctest fixer take care of the ``# optional`` annotations, +To have the doctest fixer take care of the ``# optional/needs`` tags, but not change the expected results of examples, use the option ``--only-tags``. This mode is suitable for mostly unattended runs on many files. .. warning:: While the doctest fixer guarantees to preserve any comments that - appear before ``# optional``, any comments that may be mixed with - the feature annotations will be lost. + appear before ``# optional/needs``, any comments that may be mixed with + the doctest tags will be lost. If you don't want to update any doctests, you can use the option ``--no-test``. In this mode, the doctest fixer does not run -the doctester and only normalizes the style of the ``# optional`` annotations. +the doctester and only normalizes the style of the ``# optional`` tags. Use in virtual environments diff --git a/src/doc/en/developer/packaging_sage_library.rst b/src/doc/en/developer/packaging_sage_library.rst index 93bb2ca8e6c..e42e79f5750 100644 --- a/src/doc/en/developer/packaging_sage_library.rst +++ b/src/doc/en/developer/packaging_sage_library.rst @@ -552,7 +552,7 @@ Preparing doctests ------------------ Whenever an optional package is needed for a particular test, we use the -doctest annotation ``# optional``. This mechanism can also be used for making a +doctest tag ``# optional``. This mechanism can also be used for making a doctest conditional on the presence of a portion of the Sage library. The available tags take the form of package or module names such as @@ -566,15 +566,21 @@ hints to the user. For example, the package :mod:`sage.tensor` is purely algebraic and has no dependency on symbolics. However, there are a small number of doctests that depend on :class:`sage.symbolic.ring.SymbolicRing` for integration -testing. Hence, these doctests are marked ``# optional - -sage.symbolic``. +testing. Hence, these doctests are marked as depending on the feature +:class:`sage.symbolic <~sage.features.sagemath.sage__symbolic>`. -When defining new features for the purpose of doctest annotations, it may be a good +By convention, because :class:`sage.symbolic <~sage.features.sagemath.sage__symbolic>` +is present in a standard installation of Sage, we use the keyword ``# needs`` +instead of ``# optional``. These two keywords have identical semantics; +the tool :ref:`sage --fixdoctests ` +rewrites the doctest tags according to the convention. + +When defining new features for the purpose of conditionalizing doctests, it may be a good idea to hide implementation details from feature names. For example, all doctests that -use finite fields have to depend on PARI. However, we have defined a feature +use large finite fields have to depend on PARI. However, we have defined a feature :mod:`sage.rings.finite_rings` (which implies the presence of :mod:`sage.libs.pari`). -Annotating the doctests ``# optional - sage.rings.finite_rings`` expresses the -dependency in a clearer way than using ``# optional - sage.libs.pari``, and it +Marking the doctests ``# needs sage.rings.finite_rings`` expresses the +dependency in a clearer way than using ``# needs sage.libs.pari``, and it will be a smaller maintenance burden when implementation details change. diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index b563f41c5a0..7cd67096cb2 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -16,9 +16,9 @@ one of the most fundamental distributions) contains the doctest:: - sage: G = SymmetricGroup(4) # optional - sage.groups - sage: g = G([2, 3, 4, 1]) # optional - sage.groups - sage: g.powers(4) # optional - sage.groups + sage: G = SymmetricGroup(4) # needs sage.groups + sage: g = G([2, 3, 4, 1]) # needs sage.groups + sage: g.powers(4) # needs sage.groups [(), (1,2,3,4), (1,3)(2,4), (1,4,3,2)] This test cannot pass when the distribution :ref:`sagemath-objects ` @@ -56,12 +56,12 @@ class sagemath_doc_html(StaticFile): do not. All doctests that refer to the built documentation need to be marked - ``# optional - sagemath_doc_html``. + ``# needs sagemath_doc_html``. TESTS:: sage: from sage.features.sagemath import sagemath_doc_html - sage: sagemath_doc_html().is_present() # optional - sagemath_doc_html + sage: sagemath_doc_html().is_present() # needs sagemath_doc_html FeatureTestResult('sagemath_doc_html', True) """ def __init__(self): @@ -88,49 +88,49 @@ class sage__combinat(JoinFeature): Python modules that provide elementary combinatorial objects such as :mod:`sage.combinat.subset`, :mod:`sage.combinat.composition`, :mod:`sage.combinat.permutation` are always available; - there is no need for an ``# optional`` annotation:: + there is no need for an ``# optional/needs`` tag:: sage: Permutation([1,2,3]).is_even() True sage: Permutation([6,1,4,5,2,3]).bruhat_inversions() [[0, 1], [0, 2], [0, 3], [2, 4], [2, 5], [3, 4], [3, 5]] - Use ``# optional - sage.combinat`` for doctests that use any other Python modules + Use ``# needs sage.combinat`` for doctests that use any other Python modules from :mod:`sage.combinat`, for example :mod:`sage.combinat.tableau_tuple`:: - sage: TableauTuple([[[7,8,9]],[],[[1,2,3],[4,5],[6]]]).shape() # optional - sage.combinat + sage: TableauTuple([[[7,8,9]],[],[[1,2,3],[4,5],[6]]]).shape() # needs sage.combinat ([3], [], [3, 2, 1]) Doctests that use Python modules from :mod:`sage.combinat` that involve trees, graphs, hypergraphs, posets, quivers, combinatorial designs, - finite state machines etc. should be marked ``# optional - sage.combinat sage.graphs``:: + finite state machines etc. should be marked ``# needs sage.combinat sage.graphs``:: - sage: L = Poset({0: [1], 1: [2], 2:[3], 3:[4]}) # optional - sage.combinat sage.graphs - sage: L.is_chain() # optional - sage.combinat sage.graphs + sage: L = Poset({0: [1], 1: [2], 2:[3], 3:[4]}) # needs sage.combinat sage.graphs + sage: L.is_chain() # needs sage.combinat sage.graphs True - Doctests that use combinatorial modules/algebras, or root systems should use the annotation - ``# optional - sage.combinat sage.modules``:: + Doctests that use combinatorial modules/algebras, or root systems should use the tag + ``# needs sage.combinat sage.modules``:: - sage: A = SchurAlgebra(QQ, 2, 3) # optional - sage.combinat sage.modules - sage: a = A.an_element(); a # optional - sage.combinat sage.modules + sage: A = SchurAlgebra(QQ, 2, 3) # needs sage.combinat sage.modules + sage: a = A.an_element(); a # needs sage.combinat sage.modules 2*S((1, 1, 1), (1, 1, 1)) + 2*S((1, 1, 1), (1, 1, 2)) + 3*S((1, 1, 1), (1, 2, 2)) - sage: L = RootSystem(['A',3,1]).root_lattice() # optional - sage.combinat sage.modules - sage: PIR = L.positive_imaginary_roots(); PIR # optional - sage.combinat sage.modules + sage: L = RootSystem(['A',3,1]).root_lattice() # needs sage.combinat sage.modules + sage: PIR = L.positive_imaginary_roots(); PIR # needs sage.combinat sage.modules Positive imaginary roots of type ['A', 3, 1] - Doctests that use lattices, semilattices, or Dynkin diagrams should use the annotation - ``# optional - sage.combinat sage.graphs sage.modules``:: + Doctests that use lattices, semilattices, or Dynkin diagrams should use the tag + ``# needs sage.combinat sage.graphs sage.modules``:: - sage: L = LatticePoset({0: [1,2], 1: [3], 2: [3,4], 3: [5], 4: [5]}) # optional - sage.combinat sage.graphs sage.modules - sage: L.meet_irreducibles() # optional - sage.combinat sage.graphs sage.modules + sage: L = LatticePoset({0: [1,2], 1: [3], 2: [3,4], 3: [5], 4: [5]}) # needs sage.combinat sage.graphs sage.modules + sage: L.meet_irreducibles() # needs sage.combinat sage.graphs sage.modules [1, 3, 4] TESTS:: sage: from sage.features.sagemath import sage__combinat - sage: sage__combinat().is_present() # optional - sage.combinat + sage: sage__combinat().is_present() # needs sage.combinat FeatureTestResult('sage.combinat', True) """ def __init__(self): @@ -159,22 +159,23 @@ class sage__geometry__polyhedron(JoinFeature): EXAMPLES: Doctests that use polyhedra, cones, geometric complexes, triangulations, etc. should use - the annotations ``# optional - sage.geometry.polyhedron``:: + the tag ``# needs sage.geometry.polyhedron``:: - sage: co = polytopes.truncated_tetrahedron() # optional - sage.geometry.polyhedron - sage: co.volume() # optional - sage.geometry.polyhedron + sage: co = polytopes.truncated_tetrahedron() # needs sage.geometry.polyhedron + sage: co.volume() # needs sage.geometry.polyhedron 184/3 - Some constructions of polyhedra require additional annotations:: + Some constructions of polyhedra require additional tags:: - sage: perm_a3_reg_nf = polytopes.generalized_permutahedron( # optional - sage.combinat sage.geometry.polyhedron sage.rings.number_field + sage: # needs sage.combinat sage.geometry.polyhedron sage.rings.number_field + sage: perm_a3_reg_nf = polytopes.generalized_permutahedron( ....: ['A',3], regular=True, backend='number_field'); perm_a3_reg_nf A 3-dimensional polyhedron in AA^3 defined as the convex hull of 24 vertices TESTS:: sage: from sage.features.sagemath import sage__geometry__polyhedron - sage: sage__geometry__polyhedron().is_present() # optional - sage.geometry.polyhedron + sage: sage__geometry__polyhedron().is_present() # needs sage.geometry.polyhedron FeatureTestResult('sage.geometry.polyhedron', True) """ @@ -202,12 +203,12 @@ class sage__graphs(JoinFeature): EXAMPLES: Doctests that use anything from :mod:`sage.graphs` (:class:`Graph`, :class:`DiGraph`, ...) - should be marked ``# optional - sage.graphs``. The same applies to any doctest that + should be marked ``# needs sage.graphs``. The same applies to any doctest that uses a :class:`~sage.combinat.posets.posets.Poset`, cluster algebra quiver, finite state machines, abelian sandpiles, or Dynkin diagrams:: - sage: g = graphs.PetersenGraph() # optional - sage.graphs - sage: r, s = g.is_weakly_chordal(certificate=True); r # optional - sage.graphs + sage: g = graphs.PetersenGraph() # needs sage.graphs + sage: r, s = g.is_weakly_chordal(certificate=True); r # needs sage.graphs False Also any use of tree classes defined in :mod:`sage.combinat` (:class:`BinaryTree`, @@ -215,38 +216,40 @@ class sage__graphs(JoinFeature): By way of generalization, any use of :class:`SimplicialComplex` or other abstract complexes from :mod:`sage.topology`, hypergraphs, and combinatorial designs, should be marked - ``# optional - sage.graphs`` as well:: + ``# needs sage.graphs`` as well:: - sage: X = SimplicialComplex([[0,1,2], [1,2,3]]) # optional - sage.graphs - sage: X.link(Simplex([0])) # optional - sage.graphs + sage: X = SimplicialComplex([[0,1,2], [1,2,3]]) # needs sage.graphs + sage: X.link(Simplex([0])) # needs sage.graphs Simplicial complex with vertex set (1, 2) and facets {(1, 2)} - sage: IncidenceStructure([[1,2,3],[1,4]]).degrees(2) # optional - sage.graphs + sage: IncidenceStructure([[1,2,3],[1,4]]).degrees(2) # needs sage.graphs {(1, 2): 1, (1, 3): 1, (1, 4): 1, (2, 3): 1, (2, 4): 0, (3, 4): 0} On the other hand, matroids are not implemented as posets in Sage but are instead - closely tied to linear algebra over fields; hence use ``# optional - sage.modules`` instead:: + closely tied to linear algebra over fields; hence use ``# needs sage.modules`` instead:: - sage: M = Matroid(Matrix(QQ, [[1, 0, 0, 0, 1, 1, 1], # optional - sage.modules + sage: # needs sage.modules + sage: M = Matroid(Matrix(QQ, [[1, 0, 0, 0, 1, 1, 1], ....: [0, 1, 0, 1, 0, 1, 1], ....: [0, 0, 1, 1, 1, 0, 1]])) - sage: N = M / [2] \ [3, 4] # optional - sage.modules - sage: sorted(N.groundset()) # optional - sage.modules + sage: N = M / [2] \ [3, 4] + sage: sorted(N.groundset()) [0, 1, 5, 6] However, many constructions (and some methods) of matroids do involve graphs:: - sage: W = matroids.Wheel(3) # despite the name, not created via graphs # optional - sage.modules - sage: W.is_isomorphic(N) # goes through a graph isomorphism test # optional - sage.graphs sage.modules + sage: # needs sage.modules + sage: W = matroids.Wheel(3) # despite the name, not created via graphs + sage: W.is_isomorphic(N) # goes through a graph isomorphism test # needs sage.graphs False - sage: K4 = matroids.CompleteGraphic(4) # this one is created via graphs # optional - sage.graphs sage.modules - sage: K4.is_isomorphic(W) # optional - sage.graphs sage.modules + sage: K4 = matroids.CompleteGraphic(4) # this one is created via graphs # needs sage.graphs + sage: K4.is_isomorphic(W) # needs sage.graphs True TESTS:: sage: from sage.features.sagemath import sage__graphs - sage: sage__graphs().is_present() # optional - sage.graphs + sage: sage__graphs().is_present() # needs sage.graphs FeatureTestResult('sage.graphs', True) """ def __init__(self): @@ -305,17 +308,17 @@ class sage__groups(JoinFeature): EXAMPLES: Permutations and sets of permutations are always available, but permutation groups are - implemented in Sage using the :ref:`GAP ` system and require the annotation - ``# optional - sage.groups``:: + implemented in Sage using the :ref:`GAP ` system and require the tag + ``# needs sage.groups``:: sage: p = Permutation([2,1,4,3]) - sage: p.to_permutation_group_element() # optional - sage.groups + sage: p.to_permutation_group_element() # needs sage.groups (1,2)(3,4) TESTS:: sage: from sage.features.sagemath import sage__groups - sage: sage__groups().is_present() # optional - sage.groups + sage: sage__groups().is_present() # needs sage.groups FeatureTestResult('sage.groups', True) """ def __init__(self): @@ -339,7 +342,7 @@ class sage__libs__flint(JoinFeature): EXAMPLES:: sage: from sage.features.sagemath import sage__libs__flint - sage: sage__libs__flint().is_present() # optional - sage.libs.flint + sage: sage__libs__flint().is_present() # needs sage.libs.flint FeatureTestResult('sage.libs.flint', True) """ def __init__(self): @@ -364,7 +367,7 @@ class sage__libs__ntl(JoinFeature): EXAMPLES:: sage: from sage.features.sagemath import sage__libs__ntl - sage: sage__libs__ntl().is_present() # optional - sage.libs.ntl + sage: sage__libs__ntl().is_present() # needs sage.libs.ntl FeatureTestResult('sage.libs.ntl', True) """ def __init__(self): @@ -385,9 +388,9 @@ class sage__libs__pari(JoinFeature): A :class:`~sage.features.Feature` describing the presence of :mod:`sage.libs.pari`. SageMath uses the :ref:`PARI ` library (via :ref:`cypari2 `) for numerous purposes. - Doctests that involves such features should be marked ``# optional - sage.libs.pari``. + Doctests that involves such features should be marked ``# needs sage.libs.pari``. - In addition to the modularization purposes that this annotation serves, it also provides attribution + In addition to the modularization purposes that this tag serves, it also provides attribution to the upstream project. EXAMPLES:: @@ -395,13 +398,13 @@ class sage__libs__pari(JoinFeature): sage: R. = QQ[] sage: S. = R[] sage: f = x^2 + a; g = x^3 + a - sage: r = f.resultant(g); r # optional - sage.libs.pari + sage: r = f.resultant(g); r # needs sage.libs.pari a^3 + a^2 TESTS:: sage: from sage.features.sagemath import sage__libs__pari - sage: sage__libs__pari().is_present() # optional - sage.libs.pari + sage: sage__libs__pari().is_present() # needs sage.libs.pari FeatureTestResult('sage.libs.pari', True) """ def __init__(self): @@ -417,6 +420,29 @@ def __init__(self): spkg='sagemath_pari', type='standard') +class sage__modular(JoinFeature): + r""" + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.modular`. + + EXAMPLES:: + + sage: from sage.features.sagemath import sage__modular + sage: sage__modular().is_present() # needs sage.modular + FeatureTestResult('sage.modular', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.sagemath import sage__modular + sage: isinstance(sage__modular(), sage__modular) + True + """ + JoinFeature.__init__(self, 'sage.modular', + [PythonModule('sage.modular.modform.eisenstein_submodule')], + spkg='sagemath_schemes', type='standard') + + class sage__modules(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.modules`. @@ -427,19 +453,19 @@ class sage__modules(JoinFeature): :class:`sage.modules.free_module.FreeModule`, :class:`sage.combinat.free_module.CombinatorialFreeModule`, :class:`sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule`, or - additive abelian groups, should be marked ``# optional - sage.modules``. + additive abelian groups, should be marked ``# needs sage.modules``. The same holds for matrices, tensors, algebras, quadratic forms, point lattices, root systems, matrix/affine/Weyl/Coxeter groups, matroids, and ring derivations. Likewise, all uses of :mod:`sage.coding`, :mod:`sage.crypto`, and :mod:`sage.homology` - in doctests should be marked ``# optional - sage.modules``. + in doctests should be marked ``# needs sage.modules``. TESTS:: sage: from sage.features.sagemath import sage__modules - sage: sage__modules().is_present() # optional - sage.modules + sage: sage__modules().is_present() # needs sage.modules FeatureTestResult('sage.modules', True) """ def __init__(self): @@ -472,6 +498,28 @@ def __init__(self): spkg='sagemath_modules', type='standard') +class sage__numerical__mip(PythonModule): + r""" + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.numerical.mip`. + + EXAMPLES:: + + sage: from sage.features.sagemath import sage__numerical__mip + sage: sage__numerical__mip().is_present() # needs sage.numerical.mip + FeatureTestResult('sage.numerical.mip', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.sagemath import sage__numerical__mip + sage: isinstance(sage__numerical__mip(), sage__numerical__mip) + True + """ + PythonModule.__init__(self, 'sage.numerical.mip', + spkg='sagemath_polyhedra') + + class sage__plot(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.plot`. @@ -479,7 +527,7 @@ class sage__plot(JoinFeature): TESTS:: sage: from sage.features.sagemath import sage__plot - sage: sage__plot().is_present() # optional - sage.plot + sage: sage__plot().is_present() # needs sage.plot FeatureTestResult('sage.plot', True) """ def __init__(self): @@ -495,6 +543,27 @@ def __init__(self): spkg='sagemath_symbolics', type='standard') +class sage__rings__complex_double(PythonModule): + r""" + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.rings.complex_double`. + + TESTS:: + + sage: from sage.features.sagemath import sage__rings__complex_double + sage: sage__rings__complex_double().is_present() # needs sage.rings.complex_double + FeatureTestResult('sage.rings.complex_double', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.sagemath import sage__rings__complex_double + sage: isinstance(sage__rings__complex_double(), sage__rings__complex_double) + True + """ + PythonModule.__init__(self, 'sage.rings.complex_double', type='standard') + + class sage__rings__finite_rings(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.rings.finite_rings`; @@ -503,7 +572,7 @@ class sage__rings__finite_rings(JoinFeature): TESTS:: sage: from sage.features.sagemath import sage__rings__finite_rings - sage: sage__rings__finite_rings().is_present() # optional - sage.rings.finite_rings + sage: sage__rings__finite_rings().is_present() # needs sage.rings.finite_rings FeatureTestResult('sage.rings.finite_rings', True) """ def __init__(self): @@ -531,22 +600,22 @@ class sage__rings__function_field(JoinFeature): sage: K.maximal_order() Maximal order of Rational function field in x over Rational Field - Use the annotation ``# optional - sage.rings.function_field`` whenever extensions + Use the tag ``# needs sage.rings.function_field`` whenever extensions of function fields (by adjoining a root of a univariate polynomial) come into play:: sage: R. = K[] - sage: L. = K.extension(y^5 - (x^3 + 2*x*y + 1/x)); L # optional - sage.rings.function_field + sage: L. = K.extension(y^5 - (x^3 + 2*x*y + 1/x)); L # needs sage.rings.function_field Function field in y defined by y^5 - 2*x*y + (-x^4 - 1)/x Such extensions of function fields are implemented using Gröbner bases of polynomial rings; Sage makes essential use of the :ref:`Singular ` system for this. - (It is not necessary to use the annotation ``# optional - sage.libs.singular``; it is - implied by ``# optional - sage.rings.function_field``.) + (It is not necessary to use the tag ``# needs sage.libs.singular``; it is + implied by ``# needs sage.rings.function_field``.) TESTS:: sage: from sage.features.sagemath import sage__rings__function_field - sage: sage__rings__function_field().is_present() # optional - sage.rings.function_field + sage: sage__rings__function_field().is_present() # needs sage.rings.function_field FeatureTestResult('sage.rings.function_field', True) """ def __init__(self): @@ -580,35 +649,39 @@ class sage__rings__number_field(JoinFeature): Doctests that construct algebraic number fields should be marked ``# optional - sage.rings.number_field``:: - sage: K. = NumberField(x^3 - 2) # optional - sage.rings.number_field - sage: L. = K.extension(x^3 - 3) # optional - sage.rings.number_field - sage: S. = L.extension(x^2 - 2); S # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: K. = NumberField(x^3 - 2) + sage: L. = K.extension(x^3 - 3) + sage: S. = L.extension(x^2 - 2); S Number Field in sqrt2 with defining polynomial x^2 - 2 over its base field - sage: K. = CyclotomicField(15) # optional - sage.rings.number_field - sage: CC(zeta) # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: K. = CyclotomicField(15) + sage: CC(zeta) 0.913545457642601 + 0.406736643075800*I Doctests that make use of the Algebraic Field ``QQbar``, the Algebraic Real Field ``AA``, or the Universal Cyclotomic Field should be marked likewise:: - sage: AA(-1)^(1/3) # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: AA(-1)^(1/3) -1 - sage: QQbar(-1)^(1/3) # optional - sage.rings.number_field + sage: QQbar(-1)^(1/3) 0.500000000000000? + 0.866025403784439?*I - sage: UCF = UniversalCyclotomicField(); UCF # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: UCF = UniversalCyclotomicField(); UCF Universal Cyclotomic Field - sage: E = UCF.gen # optional - sage.rings.number_field - sage: f = E(2) + E(3); f # optional - sage.rings.number_field + sage: E = UCF.gen + sage: f = E(2) + E(3); f 2*E(3) + E(3)^2 - sage: f.galois_conjugates() # optional - sage.rings.number_field + sage: f.galois_conjugates() [2*E(3) + E(3)^2, E(3) + 2*E(3)^2] TESTS:: sage: from sage.features.sagemath import sage__rings__number_field - sage: sage__rings__number_field().is_present() # optional - sage.rings.number_field + sage: sage__rings__number_field().is_present() # needs sage.rings.number_field FeatureTestResult('sage.rings.number_field', True) """ def __init__(self): @@ -631,7 +704,7 @@ class sage__rings__padics(JoinFeature): TESTS:: sage: from sage.features.sagemath import sage__rings__padics - sage: sage__rings__padics().is_present() # optional - sage.rings.padics + sage: sage__rings__padics().is_present() # needs sage.rings.padics FeatureTestResult('sage.rings.padics', True) """ def __init__(self): @@ -654,7 +727,7 @@ class sage__rings__polynomial__pbori(JoinFeature): EXAMPLES:: sage: from sage.features.sagemath import sage__rings__polynomial__pbori - sage: sage__rings__polynomial__pbori().is_present() # optional - sage.rings.polynomial.pbori + sage: sage__rings__polynomial__pbori().is_present() # needs sage.rings.polynomial.pbori FeatureTestResult('sage.rings.polynomial.pbori', True) """ def __init__(self): @@ -676,7 +749,7 @@ class sage__rings__real_double(PythonModule): EXAMPLES: - The Real Double Field is basically always available, and no ``# optional`` annotation is needed:: + The Real Double Field is basically always available, and no ``# optional`` tag is needed:: sage: RDF.characteristic() 0 @@ -687,7 +760,7 @@ class sage__rings__real_double(PythonModule): TESTS:: sage: from sage.features.sagemath import sage__rings__real_double - sage: sage__rings__real_double().is_present() # optional - sage.rings.real_double + sage: sage__rings__real_double().is_present() # needs sage.rings.real_double FeatureTestResult('sage.rings.real_double', True) """ def __init__(self): @@ -708,7 +781,7 @@ class sage__rings__real_mpfr(JoinFeature): TESTS:: sage: from sage.features.sagemath import sage__rings__real_mpfr - sage: sage__rings__real_mpfr().is_present() # optional - sage.rings.real_mpfr + sage: sage__rings__real_mpfr().is_present() # needs sage.rings.real_mpfr FeatureTestResult('sage.rings.real_mpfr', True) """ def __init__(self): @@ -723,7 +796,30 @@ def __init__(self): [PythonModule('sage.rings.real_mpfr'), PythonModule('sage.rings.complex_mpfr'), ], - spkg='sagemath_modules') + spkg='sagemath_modules', type='standard') + + +class sage__sat(JoinFeature): + r""" + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.sat`. + + EXAMPLES:: + + sage: from sage.features.sagemath import sage__sat + sage: sage__sat().is_present() # needs sage.sat + FeatureTestResult('sage.sat', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.sagemath import sage__sat + sage: isinstance(sage__sat(), sage__sat) + True + """ + JoinFeature.__init__(self, 'sage.sat', + [PythonModule('sage.sat.expression')], + spkg='sagemath_combinat', type='standard') class sage__schemes(JoinFeature): @@ -733,7 +829,7 @@ class sage__schemes(JoinFeature): EXAMPLES:: sage: from sage.features.sagemath import sage__schemes - sage: sage__schemes().is_present() # optional - sage.schemes + sage: sage__schemes().is_present() # needs sage.schemes FeatureTestResult('sage.schemes', True) """ def __init__(self): @@ -768,7 +864,7 @@ class sage__symbolic(JoinFeature): TESTS:: sage: from sage.features.sagemath import sage__symbolic - sage: sage__symbolic().is_present() # optional - sage.symbolic + sage: sage__symbolic().is_present() # needs sage.symbolic FeatureTestResult('sage.symbolic', True) """ def __init__(self): From 981ba194503710c969b42c646d478e3c9e0eaa04 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 3 Jul 2023 11:48:49 -0700 Subject: [PATCH 091/150] sage.features.all (module_feature, name_feature): Move here from sage-fixdoctests, add examples --- src/bin/sage-fixdoctests | 46 +------------------- src/sage/features/all.py | 91 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 46 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 17f6a875ddf..80286e3f726 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -14,7 +14,6 @@ AUTHORS:: situations when either the expected output or computed output are empty. Added doctest to sage.tests.cmdline """ -import itertools import os import re import shlex @@ -28,8 +27,7 @@ from sage.doctest.control import skipfile from sage.doctest.parsing import parse_optional_tags, unparse_optional_tags, update_optional_tags from sage.env import SAGE_ROOT from sage.features import PythonModule -from sage.features.all import all_features -from sage.misc.dev_tools import find_object_modules +from sage.features.all import all_features, module_feature, name_feature from sage.misc.temporary_file import tmp_filename parser = ArgumentParser(description="Given an input file with doctests, this creates a modified file that passes the doctests (modulo any raised exceptions). By default, the input file is modified. You can also name an output file.") @@ -119,48 +117,6 @@ else: venv_explainer = '' -def module_feature(module_name): - r""" - Find a top-level :class:`Feature` that provides the Python module of the given ``module_name``. - - OUTPUT: a :class:`Feature` or ``None``. - """ - longest_prefix = '' - longest_prefix_feature = None - for feature in all_features(): - for joined in itertools.chain([feature], feature.joined_features()): - if joined.name == module_name: - return feature - if (joined.name + '.').startswith(longest_prefix): - if (module_name + '.').startswith(joined.name + '.'): - longest_prefix = feature.name + '.' - longest_prefix_feature = feature - return longest_prefix_feature - - -def name_feature(name, toplevel=None): - r""" - Find a top-level :class:`Feature` that provides the top-level ``name``. - - OUTPUT: a :class:`Feature` or ``None``. - """ - if toplevel is None: - try: - import sage.all as toplevel - except ImportError: - return None - try: - obj = getattr(toplevel, name) - except AttributeError: - return None - - for module, names in find_object_modules(obj).items(): - if name in names and (feature := module_feature(module)): - return feature - - return None - - def process_block(block, src_in_lines, file_optional_tags): # Extract the line, what was expected, and was got. if not (m := re.match('File "([^"]*)", line ([0-9]+), in ', block)): diff --git a/src/sage/features/all.py b/src/sage/features/all.py index 19eabe60126..599bc575dd7 100644 --- a/src/sage/features/all.py +++ b/src/sage/features/all.py @@ -3,7 +3,7 @@ """ # ***************************************************************************** -# Copyright (C) 2021 Matthias Koeppe +# Copyright (C) 2021-2023 Matthias Koeppe # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -11,6 +11,9 @@ # https://www.gnu.org/licenses/ # ***************************************************************************** +import itertools + + def all_features(): r""" Return an iterable of all features. @@ -34,3 +37,89 @@ def all_features(): else: if af != all_features: yield from af() + + +def module_feature(module_name): + r""" + Find a top-level :class:`Feature` that provides the Python module of the given ``module_name``. + + Only features known to :func:`all_features` are considered. + + INPUT: + + - ``module_name`` -- string + + OUTPUT: a :class:`Feature` or ``None``. + + EXAMPLES:: + + sage: from sage.features.all import module_feature + sage: module_feature('sage.combinat.tableau') # needs sage.combinat + Feature('sage.combinat') + sage: module_feature('sage.combinat.posets.poset') # needs sage.graphs + Feature('sage.graphs') + sage: module_feature('sage.schemes.toric.variety') # needs sage.geometry.polyhedron + Feature('sage.geometry.polyhedron') + sage: module_feature('scipy') # needs scipy + Feature('scipy') + sage: print(module_feature('sage.structure.element')) + None + sage: print(module_feature('sage.does_not_exist')) + None + """ + longest_prefix = '' + longest_prefix_feature = None + for feature in all_features(): + for joined in itertools.chain([feature], feature.joined_features()): + if joined.name == module_name: + return feature + if (joined.name + '.').startswith(longest_prefix): + if (module_name + '.').startswith(joined.name + '.'): + longest_prefix = feature.name + '.' + longest_prefix_feature = feature + return longest_prefix_feature + + +def name_feature(name, toplevel=None): + r""" + Find a top-level :class:`Feature` that provides the top-level ``name``. + + Only features known to :func:`all_features` are considered. + + INPUT: + + - ``name`` -- string + + - ``toplevel`` -- a module or other namespace + + OUTPUT: a :class:`Feature` or ``None``. + + EXAMPLES:: + + sage: from sage.features.all import name_feature + sage: name_feature('QuadraticField') # needs sage.rings.number_field + Feature('sage.rings.number_field') + sage: name_feature('line') # needs sage.plot + Feature('sage.plot') + sage: print(name_feature('ZZ')) + None + sage: print(name_feature('does_not_exist')) + None + """ + if toplevel is None: + try: + import sage.all as toplevel + except ImportError: + return None + try: + obj = getattr(toplevel, name) + except AttributeError: + return None + + from sage.misc.dev_tools import find_object_modules + + for module, names in find_object_modules(obj).items(): + if name in names and (feature := module_feature(module)): + return feature + + return None From 3d38f98a3e1b64e5b2c1605ba9576fc8eaa3bb1c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 3 Jul 2023 12:10:46 -0700 Subject: [PATCH 092/150] sage.features: Add more modules to join features, update # needs --- src/sage/features/gap.py | 2 +- src/sage/features/sagemath.py | 42 ++++++++++++++++++++++++++++++----- src/sage/features/singular.py | 4 ++-- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/sage/features/gap.py b/src/sage/features/gap.py index dad0e975804..80375289006 100644 --- a/src/sage/features/gap.py +++ b/src/sage/features/gap.py @@ -70,7 +70,7 @@ class sage__libs__gap(JoinFeature): EXAMPLES:: sage: from sage.features.gap import sage__libs__gap - sage: sage__libs__gap().is_present() # optional - sage.libs.gap + sage: sage__libs__gap().is_present() # needs sage.libs.gap FeatureTestResult('sage.libs.gap', True) """ def __init__(self): diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 7cd67096cb2..172a458e27d 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -339,6 +339,9 @@ class sage__libs__flint(JoinFeature): A :class:`sage.features.Feature` describing the presence of :mod:`sage.libs.flint` and other modules depending on FLINT and arb. + In addition to the modularization purposes that this tag serves, it also provides attribution + to the upstream project. + EXAMPLES:: sage: from sage.features.sagemath import sage__libs__flint @@ -364,6 +367,9 @@ class sage__libs__ntl(JoinFeature): A :class:`sage.features.Feature` describing the presence of :mod:`sage.libs.ntl` and other modules depending on NTL and arb. + In addition to the modularization purposes that this tag serves, it also provides attribution + to the upstream project. + EXAMPLES:: sage: from sage.features.sagemath import sage__libs__ntl @@ -540,7 +546,7 @@ def __init__(self): """ JoinFeature.__init__(self, 'sage.plot', [PythonModule('sage.plot.plot')], - spkg='sagemath_symbolics', type='standard') + spkg='sagemath_plot', type='standard') class sage__rings__complex_double(PythonModule): @@ -561,7 +567,8 @@ def __init__(self): sage: isinstance(sage__rings__complex_double(), sage__rings__complex_double) True """ - PythonModule.__init__(self, 'sage.rings.complex_double', type='standard') + PythonModule.__init__(self, 'sage.rings.complex_double', , + spkg='sagemath_modules', type='standard') class sage__rings__finite_rings(JoinFeature): @@ -647,7 +654,7 @@ class sage__rings__number_field(JoinFeature): sage: QQ in NumberFields() True - Doctests that construct algebraic number fields should be marked ``# optional - sage.rings.number_field``:: + Doctests that construct algebraic number fields should be marked ``# needs sage.rings.number_field``:: sage: # needs sage.rings.number_field sage: K. = NumberField(x^3 - 2) @@ -749,7 +756,7 @@ class sage__rings__real_double(PythonModule): EXAMPLES: - The Real Double Field is basically always available, and no ``# optional`` tag is needed:: + The Real Double Field is basically always available, and no ``# optional/needs`` tag is needed:: sage: RDF.characteristic() 0 @@ -876,7 +883,32 @@ def __init__(self): True """ JoinFeature.__init__(self, 'sage.symbolic', - [PythonModule('sage.symbolic.expression')]) + [PythonModule('sage.symbolic.expression'), + PythonModule('sage.manifolds'), + PythonModule('sage.calculus.calculus'), + PythonModule('sage.calculus.desolvers'), + PythonModule('sage.calculus.predefined'), + PythonModule('sage.calculus.tests'), + PythonModule('sage.calculus.var'), + PythonModule('sage.geometry.riemannian_manifolds') + PythonModule('sage.geometry.hyperbolic_space'), + PythonModule('sage.dynamics.complex_dynamics'), + PythonModule('sage.libs.pynac'), + PythonModule('sage.libs.ecl'), + PythonModule('sage.interfaces.fricas'), + PythonModule('sage.interfaces.giac'), + PythonModule('sage.interfaces.magma'), + PythonModule('sage.interfaces.magma_free'), + PythonModule('sage.interfaces.maple'), + PythonModule('sage.interfaces.mathematica'), + PythonModule('sage.interfaces.mathics'), + PythonModule('sage.interfaces.maxima'), + PythonModule('sage.interfaces.maxima_abstract'), + PythonModule('sage.interfaces.maxima_lib'), + PythonModule('sage.interfaces.qepcad'), + PythonModule('sage.interfaces.sympy'), + PythonModule('sage.interfaces.sympy_wrapper'), + ], spkg='sagemath_symbolics', type='standard') def all_features(): diff --git a/src/sage/features/singular.py b/src/sage/features/singular.py index 64a24320044..6ce5d3a6b3f 100644 --- a/src/sage/features/singular.py +++ b/src/sage/features/singular.py @@ -23,7 +23,7 @@ class Singular(Executable): EXAMPLES:: sage: from sage.features.singular import Singular - sage: Singular().is_present() + sage: Singular().is_present() # needs singular FeatureTestResult('singular', True) """ def __init__(self): @@ -49,7 +49,7 @@ class sage__libs__singular(JoinFeature): EXAMPLES:: sage: from sage.features.singular import sage__libs__singular - sage: sage__libs__singular().is_present() # optional - sage.libs.singular + sage: sage__libs__singular().is_present() # needs sage.libs.singular FeatureTestResult('sage.libs.singular', True) """ def __init__(self): From 9db9bf77540745db9e49636706ce7b17706d7b71 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 3 Jul 2023 12:31:24 -0700 Subject: [PATCH 093/150] src/sage/features/sagemath.py: Fixup --- src/sage/features/sagemath.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 172a458e27d..27d6b0dd219 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -567,7 +567,7 @@ def __init__(self): sage: isinstance(sage__rings__complex_double(), sage__rings__complex_double) True """ - PythonModule.__init__(self, 'sage.rings.complex_double', , + PythonModule.__init__(self, 'sage.rings.complex_double', spkg='sagemath_modules', type='standard') @@ -890,7 +890,7 @@ def __init__(self): PythonModule('sage.calculus.predefined'), PythonModule('sage.calculus.tests'), PythonModule('sage.calculus.var'), - PythonModule('sage.geometry.riemannian_manifolds') + PythonModule('sage.geometry.riemannian_manifolds'), PythonModule('sage.geometry.hyperbolic_space'), PythonModule('sage.dynamics.complex_dynamics'), PythonModule('sage.libs.pynac'), From 62982904862576d0cb92be307b0e2c943a75afc9 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 3 Jul 2023 12:50:14 -0700 Subject: [PATCH 094/150] src/doc/en/developer/doctesting.rst: Add examples --- src/doc/en/developer/doctesting.rst | 91 +++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 10 deletions(-) diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index 2722a0cf11b..fc1dca22420 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -1360,7 +1360,8 @@ contain installations of built (non-editable) wheels. To test all modules of Sage that are installed in a virtual environment, use the option ``--installed`` (instead of ``--all``):: - [mkoeppe@sage sage]$ pkgs/sagemath-standard/.tox/sagepython-sagewheels-.../sage -tp4 --installed + [mkoeppe@sage sage]$ pkgs/sagemath-standard/.tox/sagepython-sagewheels-.../sage -t \ + -p4 --installed This tests against the doctests as they appear in the installed copies of the files (in ``site-packages/sage/...``). @@ -1371,23 +1372,31 @@ When testing a modularized distribution package other than sagemath-standard, the top-level module :mod:`sage.all` is not available. Use the option ``--environment`` to select an appropriate top-level module:: - [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -tp4 --environment sage.all__sagemath_categories --installed + [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -t \ + -p4 --environment sage.all__sagemath_categories \ + --installed To test the installed modules against the doctests as they appear in the source tree (``src/sage/...``):: - [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -tp4 --environment sage.all__sagemath_categories src/sage/structure + [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -t \ + -p4 --environment sage.all__sagemath_categories \ + src/sage/structure Note that testing all doctests as they appear in the source tree does not make sense because many of the source files may not be installed in the virtual environment. Use the option ``--only-lib`` to skip the source files of all Python/Cython modules that are not installed in the virtual environment:: - [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -tp4 --environment sage.all__sagemath_categories --only-lib src/sage/schemes + [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -t \ + -p4 --environment sage.all__sagemath_categories --only-lib \ + src/sage/schemes This option can also be combined with ``--all``:: - [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -tp4 --environment sage.all__sagemath_categories --only-lib --all + [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -t \ + -p4 --environment sage.all__sagemath_categories --only-lib \ + --all .. _section-fixdoctests: @@ -1405,7 +1414,35 @@ By default, ``./sage --fixdoctests`` runs the doctester and replaces the expecte of all examples by the actual outputs from the current version of Sage:: [mkoeppe@sage sage]$ ./sage --fixdoctests \ - --overwrite src/sage/geometry/cone.py + --overwrite src/sage/arith/weird.py + +For example, when applied to this Python file:: + + | r""" + | ... + | + | EXAMPLES:: + | + | sage: 2 + 2 + | 5 + | sage: factor("91") + | "7" * "13" + | ... + +the doctest fixer edits the file as follows:: + + | r""" + | ... + | + | EXAMPLES:: + | + | sage: 2 + 2 + | 4 + | sage: factor("91") + | Traceback (most recent call last): + | ... + | TypeError: unable to factor '91' + | ... As this command edits the source file, it may be a good practice to first use ``git commit`` to save any changes made in the file. @@ -1420,24 +1457,49 @@ neither of the two copies is run; this makes ``./sage --fixdoctests`` idempotent When exceptions are expected by an example, it is standard practice to abbreviate the tracebacks using ``...``. The doctest fixer uses this abbreviation automatically -when formatting the actual output. To disable it so that the details of the exception +when formatting the actual output, as shown in the above example. +To disable it so that the details of the exception can be inspected, use the option ``--full-tracebacks``. This is particularly useful in combination with ``--keep-both``:: [mkoeppe@sage sage]$ ./sage --fixdoctests --keep-both --full-tracebacks \ - --overwrite src/sage/geometry/cone.py + --overwrite src/sage/arith/weird.py + +This will give the following result on the above example:: + + | r""" + | ... + | + | EXAMPLES:: + | + | sage: 2 + 2 # optional - EXPECTED + | 5 + | sage: 2 + 2 # optional - GOT + | 4 + | sage: factor("91") # optional - EXPECTED + | "7" * "13" + | sage: factor("91") # optional - GOT + | Traceback (most recent call last): + | ... + | File "", line 1, in + | factor("91") + | File ".../src/sage/arith/misc.py", line 2680, in factor + | raise TypeError("unable to factor {!r}".format(n)) + | TypeError: unable to factor '91' + | ... + | """ To make sure that all doctests are updated, you may have to use the option ``--long``:: [mkoeppe@sage sage]$ ./sage --fixdoctests --long \ - --overwrite src/sage/geometry/cone.py + --overwrite src/sage/arith/weird.py If you are not comfortable with allowing this tool to edit your source files, you can use the option ``--no-overwrite``, which will create a new file with the extension ``.fixed`` instead of overwriting the source file:: [mkoeppe@sage sage]$ ./sage --fixdoctests \ - --no-overwrite src/sage/geometry/cone.py + --no-overwrite src/sage/arith/weird.py .. _section-fixdoctests-optional-needs: @@ -1528,3 +1590,12 @@ that uses the more specific options ``--venv`` and ``--environment``:: Either way, the options ``--keep-both``, ``--full-tracebacks``, and ``--only-lib`` are implied. + +In this mode of operation, when the doctester encounters a global name +that is unknown in its virtual environment (:class:`NameError`), +the doctest fixer will look up the name in its own environment (typically +a full installation of the Sage library) and add a ``# needs ...`` tag +to the doctest. + +Likewise, when the doctester runs into a :class:`ModuleNotFoundError`, +the doctest fixer will automatically add a ``# needs`` tag. From ff2b6755cd4f47c0f95c06649aee4876465f0fe6 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 3 Jul 2023 15:12:31 -0700 Subject: [PATCH 095/150] src/doc/en/developer: Update doctest writing / doctesting sections --- src/doc/en/developer/coding_basics.rst | 82 +++++++++++-------- src/doc/en/developer/doctesting.rst | 77 +++++++++-------- .../en/developer/packaging_sage_library.rst | 32 ++++---- 3 files changed, 109 insertions(+), 82 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 158509fa92f..240d22caf59 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -1009,17 +1009,21 @@ written. sage: U._repr_() # this is fine 'Quasi-projective subscheme X - Y of Projective Space of dimension 2 over Integer Ring, where X is defined by:\n (no polynomials)\nand Y is defined by:\n x - y' - - Annotations for modularization purposes such as ``# optional - sagemath-combinat sagemath-modules`` + - Doctest tags for modularization purposes such as ``# needs sage.modules`` (see :ref:`section-further_conventions`) should be aligned at column 88. Clean lines from consistent alignment help reduce visual clutter. - Moreover, at the maximum window width, only the word fragment ``# option`` will be - visible without horizontal scrolling, striking a thoughtfully chosen balance between presenting + Moreover, at the maximum window width, only the word ``# needs`` will be + visible in the HTML output without horizontal scrolling, striking a + thoughtfully chosen balance between presenting the information and reducing visual clutter. (How much can be seen may be browser-dependent, of course.) In visually dense doctests, you can try to sculpt out visual space to separate the test commands from the annotation. - - Annotations for doctests depending on optional packages such as ``# optional - pynormaliz``, on the other hand, + - Doctest tags such as ``# optional - pynormaliz`` that make the doctest + conditional on the presence of optional packages, on the other hand, should be aligned so that they are visible without having to scroll horizontally. + The :ref:`doctest fixer ` uses + tab stops at columns 48, 56, 64, ... for these tags. - **Python3 print:** Python3 syntax for print must be used in Sage code and doctests. If you use an old-style print in doctests, it @@ -1194,8 +1198,9 @@ framework. Here is a comprehensive list: Neither of this applies to files or directories which are explicitly given as command line arguments: those are always tested. -- **optional:** A line flagged with ``optional - keyword`` is not tested unless - the ``--optional=keyword`` flag is passed to ``sage -t`` (see +- **optional/needs:** A line tagged with ``optional - FEATURE`` + or ``needs FEATURE`` is not tested unless the ``--optional=KEYWORD`` flag + is passed to ``sage -t`` (see :ref:`section-optional-doctest-flag`). The main applications are: - **optional packages:** When a line requires an optional package to be @@ -1203,26 +1208,6 @@ framework. Here is a comprehensive list: sage: SloaneEncyclopedia[60843] # optional - sloane_database - .. NOTE:: - - If one of the first 10 lines of a file starts with any of - ``r""" sage.doctest: optional - keyword`` - (or ``""" sage.doctest: optional - keyword`` - or ``# sage.doctest: optional - keyword`` - or ``% sage.doctest: optional - keyword`` - or ``.. sage.doctest: optional - keyword``, - or any of these with different spacing), - then that file will be skipped unless - the ``--optional=keyword`` flag is passed to ``sage -t``. - - When a file is skipped that was explicitly given as a command line argument, - a warning is displayed. - - If you add such a line to a file, you are strongly encouraged - to add a note to the module-level documentation, saying that - the doctests in this file will be skipped unless the - appropriate conditions are met. - - **internet:** For lines that require an internet connection:: sage: oeis(60843) # optional - internet @@ -1230,8 +1215,8 @@ framework. Here is a comprehensive list: n-state Turing machine can make on an initially blank tape before eventually halting. - - **bug:** For lines that describe bugs. Alternatively, use ``# known bug`` - instead: it is an alias for ``optional bug``. + - **known bugs:** For lines that describe known bugs, you can use ``# optional - bug``, + although ``# known bug`` is preferred. .. CODE-BLOCK:: rest @@ -1242,21 +1227,54 @@ framework. Here is a comprehensive list: sage: 2+2 # known bug 5 + - **modularization:** To enable + :ref:`separate testing of the distribution packages ` + of the modularized Sage library, doctests that depend on features provided + by other distribution packages can be tagged ``# needs FEATURE``. + For example: + + .. CODE-BLOCK:: rest + + Consider the following calculation:: + + sage: a = AA(2).sqrt() # needs sage.rings.number_field + sage: b = sqrt(3) # needs sage.symbolic + sage: a + AA(b) # needs sage.rings.number_field sage.symbolic + .. NOTE:: - - Any words after ``# optional`` are interpreted as a list of + - Any words after ``# optional`` and ``# needs`` are interpreted as a list of package (spkg) names or other feature tags, separated by spaces. - Any punctuation other than underscores (``_``) and periods (``.``), that is, commas, hyphens, semicolons, ..., after the first word ends the list of packages. Hyphens or colons between the word ``optional`` and the first package name are allowed. Therefore, - you should not write ``optional: needs package CHomP`` but simply - ``optional: CHomP``. + you should not write ``# optional - depends on package CHomP`` but simply + ``# optional - CHomP``. - - Optional tags are case-insensitive, so you could also write ``optional: + - Optional tags are case-insensitive, so you could also write ``# optional - chOMP``. + If ``# optional`` or ``# needs`` is placed right after the ``sage:`` prompt, + it is a codeblock-scoped tag, which applies to all doctest lines until + a blank line is encountered. + + These tags can also be applied to an entire file. If one of the first 10 lines + of a file starts with any of ``r""" sage.doctest: optional - FEATURE``, + ``# sage.doctest: needs FEATURE``, or ``.. sage.doctest: optional - FEATURE`` + (in ``.rst`` files), etc., then this applies to all doctests in this file. + + When a file is skipped that was explicitly given as a command line argument, + a warning is displayed. + + .. NOTE:: + + If you add such a line to a file, you are strongly encouraged + to add a note to the module-level documentation, saying that + the doctests in this file will be skipped unless the + appropriate conditions are met. + - **indirect doctest:** in the docstring of a function ``A(...)``, a line calling ``A`` and in which the name ``A`` does not appear should have this flag. This prevents ``sage --coverage `` from reporting the docstring as diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index fc1dca22420..a1767b8a928 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -716,7 +716,7 @@ Use the ``--long`` flag to run doctests that have been marked with the comment ``# long time``. These tests are normally skipped in order to reduce the time spent running tests:: - [roed@sage sage-6.0]$ sage -t src/sage/rings/tests.py + [roed@sage sage-6.0]$ ./sage -t src/sage/rings/tests.py Running doctests with ID 2012-06-21-16-00-13-40835825. Doctesting 1 file. sage -t tests.py @@ -730,7 +730,7 @@ reduce the time spent running tests:: In order to run the long tests as well, do the following:: - [roed@sage sage-6.0]$ sage -t --long src/sage/rings/tests.py + [roed@sage sage-6.0]$ ./sage -t --long src/sage/rings/tests.py Running doctests with ID 2012-06-21-16-02-05-d13a9a24. Doctesting 1 file. sage -t tests.py @@ -747,7 +747,7 @@ To find tests that take longer than the allowed time use the print a warning if they take longer than 1.0 second. Note that this is a warning, not an error:: - [roed@sage sage-6.0]$ sage -t --warn-long src/sage/rings/factorint.pyx + [roed@sage sage-6.0]$ ./sage -t --warn-long src/sage/rings/factorint.pyx Running doctests with ID 2012-07-14-03-27-03-2c952ac1. Doctesting 1 file. sage -t --warn-long src/sage/rings/factorint.pyx @@ -781,7 +781,7 @@ a warning, not an error:: You can also pass in an explicit amount of time:: - [roed@sage sage-6.0]$ sage -t --long --warn-long 2.0 src/sage/rings/tests.py + [roed@sage sage-6.0]$ ./sage -t --long --warn-long 2.0 src/sage/rings/tests.py Running doctests with ID 2012-07-14-03-30-13-c9164c9d. Doctesting 1 file. sage -t --long --warn-long 2.0 tests.py @@ -808,7 +808,7 @@ Finally, you can disable any warnings about long tests with Doctests start from a random seed:: - [kliem@sage sage-9.2]$ sage -t src/sage/doctest/tests/random_seed.rst + [kliem@sage sage-9.2]$ ./sage -t src/sage/doctest/tests/random_seed.rst Running doctests with ID 2020-06-23-23-22-59-49f37a55. ... Doctesting 1 file. @@ -834,7 +834,9 @@ Doctests start from a random seed:: This seed can be set explicitly to reproduce possible failures:: - [kliem@sage sage-9.2]$ sage -t --warn-long 89.5 --random-seed=112986622569797306072457879734474628454 src/sage/doctest/tests/random_seed.rst + [kliem@sage sage-9.2]$ ./sage -t --warn-long 89.5 \ + --random-seed=112986622569797306072457879734474628454 \ + src/sage/doctest/tests/random_seed.rst Running doctests with ID 2020-06-23-23-24-28-14a52269. ... Doctesting 1 file. @@ -873,7 +875,8 @@ necessary optional packages in order for these tests to succeed. By default, Sage only runs doctests that are not marked with the ``optional`` tag. This is equivalent to running :: - [roed@sage sage-6.0]$ sage -t --optional=sagemath_doc_html,sage src/sage/rings/real_mpfr.pyx + [roed@sage sage-6.0]$ ./sage -t --optional=sagemath_doc_html,sage \ + src/sage/rings/real_mpfr.pyx Running doctests with ID 2012-06-21-16-18-30-a368a200. Doctesting 1 file. sage -t src/sage/rings/real_mpfr.pyx @@ -887,7 +890,8 @@ By default, Sage only runs doctests that are not marked with the ``optional`` ta If you want to also run tests that require magma, you can do the following:: - [roed@sage sage-6.0]$ sage -t --optional=sagemath_doc_html,sage,magma src/sage/rings/real_mpfr.pyx + [roed@sage sage-6.0]$ ./sage -t --optional=sagemath_doc_html,sage,magma \ + src/sage/rings/real_mpfr.pyx Running doctests with ID 2012-06-21-16-18-30-a00a7319 Doctesting 1 file. sage -t src/sage/rings/real_mpfr.pyx @@ -901,7 +905,7 @@ If you want to also run tests that require magma, you can do the following:: In order to just run the tests that are marked as requiring magma, omit ``sage`` and ``sagemath_doc_html``:: - [roed@sage sage-6.0]$ sage -t --optional=magma src/sage/rings/real_mpfr.pyx + [roed@sage sage-6.0]$ ./sage -t --optional=magma src/sage/rings/real_mpfr.pyx Running doctests with ID 2012-06-21-16-18-33-a2bc1fdf Doctesting 1 file. sage -t src/sage/rings/real_mpfr.pyx @@ -917,7 +921,7 @@ If you want Sage to detect external software or other capabilities (such as magma, latex, internet) automatically and run all of the relevant tests, then add ``external``:: - $ sage -t --optional=external src/sage/rings/real_mpfr.pyx + [roed@sage sage-6.0]$ $ ./sage -t --optional=external src/sage/rings/real_mpfr.pyx Running doctests with ID 2016-03-16-14-10-21-af2ebb67. Using --optional=external External software to be detected: cplex,gurobi,internet,latex,macaulay2,magma,maple,mathematica,matlab,octave,scilab @@ -934,7 +938,7 @@ relevant tests, then add ``external``:: To run all tests, regardless of whether they are marked optional, pass ``all`` as the ``optional`` tag:: - [roed@sage sage-6.0]$ sage -t --optional=all src/sage/rings/real_mpfr.pyx + [roed@sage sage-6.0]$ ./sage -t --optional=all src/sage/rings/real_mpfr.pyx Running doctests with ID 2012-06-21-16-31-18-8c097f55 Doctesting 1 file. sage -t src/sage/rings/real_mpfr.pyx @@ -955,7 +959,7 @@ than one thread. To run doctests in parallel use the ``--nthreads`` flag (``-p`` is a shortened version). Pass in the number of threads you would like to use (by default Sage just uses 1):: - [roed@sage sage-6.0]$ sage -tp 2 src/sage/doctest/ + [roed@sage sage-6.0]$ ./sage -tp 2 src/sage/doctest/ Running doctests with ID 2012-06-22-19-09-25-a3afdb8c. Sorting sources by runtime so that slower doctests are run first.... Doctesting 8 files using 2 threads. @@ -991,7 +995,7 @@ short). In addition to testing the code in Sage's Python and Cython files, this command will run the tests defined in Sage's documentation as well as testing the Sage notebook:: - [roed@sage sage-6.0]$ sage -t -a + [roed@sage sage-6.0]$ ./sage -t -a Running doctests with ID 2012-06-22-19-10-27-e26fce6d. Doctesting entire Sage library. Sorting sources by runtime so that slower doctests are run first.... @@ -1012,7 +1016,8 @@ short) then you will drop into an interactive Python debugger whenever a Python exception occurs. As an example, I modified :mod:`sage.schemes.elliptic_curves.constructor` to produce an error:: - [roed@sage sage-6.0]$ sage -t --debug src/sage/schemes/elliptic_curves/constructor.py + [roed@sage sage-6.0]$ ./sage -t --debug \ + src/sage/schemes/elliptic_curves/constructor.py Running doctests with ID 2012-06-23-12-09-04-b6352629. Doctesting 1 file. ********************************************************************** @@ -1021,22 +1026,22 @@ a Python exception occurs. As an example, I modified EllipticCurve([0,0]) Exception raised: Traceback (most recent call last): - File "/Users/roed/sage/sage-5.3/local/lib/python2.7/site-packages/sage/doctest/forker.py", line 573, in _run + File ".../site-packages/sage/doctest/forker.py", line 573, in _run self.execute(example, compiled, test.globs) - File "/Users/roed/sage/sage-5.3/local/lib/python2.7/site-packages/sage/doctest/forker.py", line 835, in execute + File ".../site-packages/sage/doctest/forker.py", line 835, in execute exec compiled in globs File "", line 1, in EllipticCurve([Integer(0),Integer(0)]) - File "/Users/roed/sage/sage-5.3/local/lib/python2.7/site-packages/sage/schemes/elliptic_curves/constructor.py", line 346, in EllipticCurve + File ".../site-packages/sage/schemes/elliptic_curves/constructor.py", line 346, in EllipticCurve return ell_rational_field.EllipticCurve_rational_field(x, y) - File "/Users/roed/sage/sage-5.3/local/lib/python2.7/site-packages/sage/schemes/elliptic_curves/ell_rational_field.py", line 216, in __init__ + File ".../site-packages/sage/schemes/elliptic_curves/ell_rational_field.py", line 216, in __init__ EllipticCurve_number_field.__init__(self, Q, ainvs) - File "/Users/roed/sage/sage-5.3/local/lib/python2.7/site-packages/sage/schemes/elliptic_curves/ell_number_field.py", line 159, in __init__ + File ".../site-packages/sage/schemes/elliptic_curves/ell_number_field.py", line 159, in __init__ EllipticCurve_field.__init__(self, [field(x) for x in ainvs]) - File "/Users/roed/sage/sage-5.3/local/lib/python2.7/site-packages/sage/schemes/elliptic_curves/ell_generic.py", line 156, in __init__ + File ".../site-packages/sage/schemes/elliptic_curves/ell_generic.py", line 156, in __init__ "Invariants %s define a singular curve."%ainvs ArithmeticError: Invariants [0, 0, 0, 0, 0] define a singular curve. - > /Users/roed/sage/sage-5.3/local/lib/python2.7/site-packages/sage/schemes/elliptic_curves/ell_generic.py(156)__init__() + > .../site-packages/sage/schemes/elliptic_curves/ell_generic.py(156)__init__() -> "Invariants %s define a singular curve."%ainvs (Pdb) l 151 if len(ainvs) == 2: @@ -1073,7 +1078,8 @@ you know what test caused the problem (if you want this output to appear in real time use the ``--verbose`` flag). To have doctests run under the control of gdb, use the ``--gdb`` flag:: - [roed@sage sage-6.0]$ sage -t --gdb src/sage/schemes/elliptic_curves/constructor.py + [roed@sage sage-6.0]$ ./sage -t --gdb \ + src/sage/schemes/elliptic_curves/constructor.py exec gdb --eval-commands="run" --args /home/roed/sage-9.7/local/var/lib/sage/venv-python3.9/bin/python3 sage-runtests --serial --timeout=0 --stats_path=/home/roed/.sage/timings2.json --optional=pip,sage,sage_spkg src/sage/schemes/elliptic_curves/constructor.py GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. @@ -1108,7 +1114,7 @@ Once you're done fixing whatever problems where revealed by the doctests, you can rerun just those files that failed their most recent test by using the ``--failed`` flag (``-f`` for short):: - [roed@sage sage-6.0]$ sage -t -fa + [roed@sage sage-6.0]$ ./sage -t -fa Running doctests with ID 2012-07-07-00-45-35-d8b5a408. Doctesting entire Sage library. Only doctesting files that failed last test. @@ -1136,7 +1142,8 @@ Show skipped optional tests To print a summary at the end of each file with the number of optional tests skipped, use the ``--show-skipped`` flag:: - [roed@sage sage-6.0]$ sage -t --show-skipped src/sage/rings/finite_rings/integer_mod.pyx + [roed@sage sage-6.0]$ ./sage -t --show-skipped \ + src/sage/rings/finite_rings/integer_mod.pyx Running doctests with ID 2013-03-14-15-32-05-8136f5e3. Doctesting 1 file. sage -t sage/rings/finite_rings/integer_mod.pyx @@ -1161,7 +1168,7 @@ you to run tests repeatedly in an attempt to search for Heisenbugs. The flag ``--global-iterations`` takes an integer and runs the whole set of tests that many times serially:: - [roed@sage sage-6.0]$ sage -t --global-iterations 2 src/sage/sandpiles + [roed@sage sage-6.0]$ ./sage -t --global-iterations 2 src/sage/sandpiles Running doctests with ID 2012-07-07-00-59-28-e7048ad9. Doctesting 3 files (2 global iterations). sage -t src/sage/sandpiles/__init__.py @@ -1192,7 +1199,7 @@ set of tests that many times serially:: You can also iterate in a different order: the ``--file-iterations`` flag runs the tests in each file ``N`` times before proceeding:: - [roed@sage sage-6.0]$ sage -t --file-iterations 2 src/sage/sandpiles + [roed@sage sage-6.0]$ ./sage -t --file-iterations 2 src/sage/sandpiles Running doctests with ID 2012-07-07-01-01-43-8f954206. Doctesting 3 files (2 file iterations). sage -t src/sage/sandpiles/__init__.py @@ -1220,7 +1227,7 @@ On a slow machine the default timeout of 5 minutes may not be enough for the slowest files. Use the ``--timeout`` flag (``-T`` for short) to set it to something else:: - [roed@sage sage-6.0]$ sage -tp 2 --all --timeout 1 + [roed@sage sage-6.0]$ ./sage -tp 2 --all --timeout 1 Running doctests with ID 2012-07-07-01-09-37-deb1ab83. Doctesting entire Sage library. Sorting sources by runtime so that slower doctests are run first.... @@ -1235,7 +1242,7 @@ Using absolute paths By default filenames are printed using relative paths. To use absolute paths instead pass in the ``--abspath`` flag:: - [roed@sage sage-6.0]$ sage -t --abspath src/sage/doctest/control.py + [roed@sage sage-6.0]$ ./sage -t --abspath src/sage/doctest/control.py Running doctests with ID 2012-07-07-01-13-03-a023e212. Doctesting 1 file. sage -t /home/roed/sage-6.0/src/sage/doctest/control.py @@ -1256,7 +1263,7 @@ convenient to test only the files that have changed. To do so use the ``--new`` flag, which tests files that have been modified or added since the last commit:: - [roed@sage sage-6.0]$ sage -t --new + [roed@sage sage-6.0]$ ./sage -t --new Running doctests with ID 2012-07-07-01-15-52-645620ee. Doctesting files changed since last git commit. Doctesting 1 file. @@ -1277,7 +1284,7 @@ By default, tests are run in the order in which they appear in the file. To run tests in a random order (which can reveal subtle bugs), use the ``--randorder`` flag and pass in a random seed:: - [roed@sage sage-6.0]$ sage -t --new --randorder 127 + [roed@sage sage-6.0]$ ./sage -t --new --randorder 127 Running doctests with ID 2012-07-07-01-19-06-97c8484e. Doctesting files changed since last git commit. Doctesting 1 file. @@ -1307,7 +1314,7 @@ Auxiliary files To specify a logfile (rather than use the default which is created for ``sage -t --all``), use the ``--logfile`` flag:: - [roed@sage sage-6.0]$ sage -t --logfile test1.log src/sage/doctest/control.py + [roed@sage sage-6.0]$ ../sage -t --logfile test1.log src/sage/doctest/control.py Running doctests with ID 2012-07-07-01-25-49-e7c0e52d. Doctesting 1 file. sage -t src/sage/doctest/control.py @@ -1336,7 +1343,7 @@ To give a json file storing the timings for each file, use the that slower tests are run first (and thus multiple processes are utilized most efficiently):: - [roed@sage sage-6.0]$ sage -tp 2 --stats-path ~/.sage/timings2.json --all + [roed@sage sage-6.0]$ ../sage -tp 2 --stats-path ~/.sage/timings2.json --all Running doctests with ID 2012-07-07-01-28-34-2df4251d. Doctesting entire Sage library. Sorting sources by runtime so that slower doctests are run first.... @@ -1576,14 +1583,14 @@ Use in virtual environments The doctest fixer can also run tests using the Sage doctester installed in a virtual environment:: - [mkoeppe@sage sage]$ ./sage --fixdoctests \ + [mkoeppe@sage sage]$ ./sage --fixdoctests --overwrite \ --distribution sagemath-categories \ src/sage/geometry/schemes/generic/*.py This command, using ``--distribution``, is equivalent to a command that uses the more specific options ``--venv`` and ``--environment``:: - [mkoeppe@sage sage]$ ./sage --fixdoctests \ + [mkoeppe@sage sage]$ ./sage --fixdoctests --overwrite \ --venv pkgs/sagemath-categories/.tox/sagepython-... \ --environment sage.all__sagemath_categories src/sage/geometry/schemes/generic/*.py @@ -1598,4 +1605,4 @@ a full installation of the Sage library) and add a ``# needs ...`` tag to the doctest. Likewise, when the doctester runs into a :class:`ModuleNotFoundError`, -the doctest fixer will automatically add a ``# needs`` tag. +the doctest fixer will automatically add a ``# needs ...`` tag. diff --git a/src/doc/en/developer/packaging_sage_library.rst b/src/doc/en/developer/packaging_sage_library.rst index e42e79f5750..abda71bbed8 100644 --- a/src/doc/en/developer/packaging_sage_library.rst +++ b/src/doc/en/developer/packaging_sage_library.rst @@ -544,14 +544,19 @@ Testing distribution packages Of course, we need tools for testing modularized distributions of portions of the Sage library. -- Modularized distributions must be testable separately! +- Distribution packages of the modularized Sage library must be testable separately! - But we want to keep integration testing with other portions of Sage too! -Preparing doctests ------------------- +Preparing doctests for modularized testing +------------------------------------------ -Whenever an optional package is needed for a particular test, we use the +Section :ref:`section-doctest-writing` explains how to write doctests +for Sage. Here we show how to prepare existing or new doctests so that +they are suitable for modularized testing. + +Per section :ref:`section-further_conventions`, +whenever an optional package is needed for a particular test, we use the doctest tag ``# optional``. This mechanism can also be used for making a doctest conditional on the presence of a portion of the Sage library. @@ -587,14 +592,11 @@ will be a smaller maintenance burden when implementation details change. Testing the distribution in virtual environments with tox --------------------------------------------------------- -So how to test that this works? - -Sure, we could go into the installation directory -``SAGE_VENV/lib/python3.9/site-packages/`` and do ``rm -rf -sage/symbolic`` and test that things still work. But that's not a good -way of testing. +Chapter :ref:`chapter-doctesting` explains in detail how to run the +Sage doctester with various options. -Instead, we use a virtual environment in which we only install the +To test a distribution package of the modularized Sage library, +we use a virtual environment in which we only install the distribution to be tested (and its Python dependencies). Let's try it out first with the entire Sage library, represented by @@ -649,7 +651,7 @@ command:: This command does not make any changes to the normal installation of Sage. The virtual environment is created in a subdirectory of -``SAGE_ROOT/pkgs/sagemath-standard-no-symbolics/.tox/``. After the command +``SAGE_ROOT/pkgs/sagemath-standard/.tox/``. After the command finishes, we can start the separate installation of the Sage library in its virtual environment:: @@ -663,10 +665,10 @@ The whole ``.tox`` directory can be safely deleted at any time. We can do the same with other distributions, for example the large distribution **sagemath-standard-no-symbolics** -(from :trac:`32601`), which is intended to provide +(from :trac:`35095`), which is intended to provide everything that is currently in the standard Sage library, i.e., without depending on optional packages, but without the packages -:mod:`sage.symbolic`, :mod:`sage.functions`, :mod:`sage.calculus`, etc. +:mod:`sage.symbolic`, :mod:`sage.calculus`, etc. Again we can run the test with ``tox`` in a separate virtual environment:: @@ -690,4 +692,4 @@ Building these small distributions serves as a valuable regression testsuite. However, a current issue with both of these distributions is that they are not separately testable: The doctests for these modules depend on a lot of other functionality from higher-level parts -of the Sage library. +of the Sage library. This is being addressed in :issue:`35095`. From 627c5e8588c58da6909c8e1ed18b23589b4c4153 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 3 Jul 2023 19:02:47 -0700 Subject: [PATCH 096/150] src/doc/en/developer/coding_basics.rst: Explain why variables should not be reused --- src/doc/en/developer/coding_basics.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 240d22caf59..e0e2a4db203 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -935,6 +935,15 @@ written. Sage does not know about the function ``AA()`` by default, so it needs to be imported before it is tested. Hence the first line in the example. + All blocks within the same docstring are linked: Variables set + in a doctest keep their values for the remaining doctests within the + same docstring. It is good practice to use different variable names for different + values, as it makes the data flow in the examples easier to understand + for human readers. (It also makes the data flow analysis in the + Sage doctester more precise.) In particular, when unrelated examples + appear in the same docstring, do not use the same variable name + for both examples. + - **Preparsing:** As in Sage's console, `4/3` returns `4/3` and not `1.3333333333333333` as in Python 3.8. Testing occurs with full Sage preparsing of input within the standard Sage shell environment, as From 2ef982cb32e4a3206602fc811064049fc5d08656 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 3 Jul 2023 20:23:26 -0700 Subject: [PATCH 097/150] src/sage/doctest/parsing.py: Merge file optional tags into block tags --- src/sage/doctest/parsing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 048e26241fe..6de4e8518e1 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -1024,6 +1024,7 @@ def parse(self, string, *args): optional_tags = set(optional_tags) if is_persistent: persistent_optional_tags = optional_tags + persistent_optional_tags.update(self.file_optional_tags) continue optional_tags.update(persistent_optional_tags) item.optional_tags = frozenset(optional_tags) From ffd6177010d24e29370badf87e89754662bab640 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 10:06:52 -0700 Subject: [PATCH 098/150] src/doc/en/developer/coding_basics.rst: Fix doctest --- src/doc/en/developer/coding_basics.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index e0e2a4db203..5b4d56a549a 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -1249,6 +1249,7 @@ framework. Here is a comprehensive list: sage: a = AA(2).sqrt() # needs sage.rings.number_field sage: b = sqrt(3) # needs sage.symbolic sage: a + AA(b) # needs sage.rings.number_field sage.symbolic + 3.146264369941973? .. NOTE:: From 8e41453a48d6530cba2300e6c2b18dc28cf54940 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 10:15:08 -0700 Subject: [PATCH 099/150] src/sage/coding/guruswami_sudan/gs_decoder.py: Fix expected doctest output --- src/sage/coding/guruswami_sudan/gs_decoder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/coding/guruswami_sudan/gs_decoder.py b/src/sage/coding/guruswami_sudan/gs_decoder.py index ce92530637d..3021cbbfab6 100644 --- a/src/sage/coding/guruswami_sudan/gs_decoder.py +++ b/src/sage/coding/guruswami_sudan/gs_decoder.py @@ -1,4 +1,4 @@ -# sage.doctest: optional - sage.modules sage.rings.finite_rings +# sage.doctest: needs sage.modules sage.rings.finite_rings r""" Guruswami-Sudan decoder for (Generalized) Reed-Solomon codes @@ -754,7 +754,7 @@ def list_size(self): def decode_to_message(self, r): r""" - Decodes ``r`` to the list of polynomials whose encoding by + Decode ``r`` to the list of polynomials whose encoding by :meth:`self.code()` is within Hamming distance :meth:`self.decoding_radius` of ``r``. @@ -795,7 +795,7 @@ def decode_to_message(self, r): Traceback (most recent call last): ... ValueError: The provided root-finding algorithm has a wrong signature. - See the documentation of `GSD.rootfinding_algorithm()` for details + See the documentation of `...rootfinding_algorithm()` for details """ return [self.connected_encoder().unencode(c) for c in self.decode_to_code(r)] From ef0b5cfe8e5b20e30fb7dd42273c46dbc55b6823 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 10:25:06 -0700 Subject: [PATCH 100/150] src/sage/coding/linear_rank_metric.py: Fix broken doctest that was not being run --- src/sage/coding/linear_rank_metric.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/coding/linear_rank_metric.py b/src/sage/coding/linear_rank_metric.py index 01e1b9bb4f8..2b687775102 100644 --- a/src/sage/coding/linear_rank_metric.py +++ b/src/sage/coding/linear_rank_metric.py @@ -391,7 +391,7 @@ def __init__(self, base_field, sub_field, length, default_encoder_name, sage: from sage.coding.linear_rank_metric import AbstractLinearRankMetricCode sage: class RankRepetitionCode(AbstractLinearRankMetricCode): ....: def __init__(self, base_field, sub_field, length): - ....: super().__init__(self, base_field, sub_field, length, + ....: super().__init__(base_field, sub_field, length, ....: "GeneratorMatrix", "NearestNeighbor") ....: beta = base_field.gen() ....: self._generator_matrix = matrix(base_field, From 16771d6f12ecf0f09294bffe57e5f2da94faa932 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 10:38:02 -0700 Subject: [PATCH 101/150] src/sage/combinat/diagram.py: Update # needs --- src/sage/combinat/diagram.py | 50 ++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index fc47efbd49c..5d0e3c743df 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -498,9 +498,9 @@ def specht_module(self, base_ring=None): sage: from sage.combinat.diagram import Diagram sage: D = Diagram([(0,0), (1,1), (2,2), (2,3)]) - sage: SM = D.specht_module(QQ) - sage: s = SymmetricFunctions(QQ).s() - sage: s(SM.frobenius_image()) + sage: SM = D.specht_module(QQ) # needs sage.modules + sage: s = SymmetricFunctions(QQ).s() # needs sage.modules + sage: s(SM.frobenius_image()) # needs sage.modules s[2, 1, 1] + s[2, 2] + 2*s[3, 1] + s[4] """ from sage.combinat.specht_module import SpechtModule @@ -523,9 +523,9 @@ def specht_module_dimension(self, base_ring=None): sage: from sage.combinat.diagram import Diagram sage: D = Diagram([(0,0), (1,1), (2,2), (2,3)]) - sage: D.specht_module_dimension() + sage: D.specht_module_dimension() # needs sage.modules 12 - sage: D.specht_module(QQ).dimension() + sage: D.specht_module(QQ).dimension() # needs sage.modules 12 """ from sage.combinat.specht_module import specht_module_rank @@ -656,9 +656,9 @@ def _element_constructor_(self, cells, n_rows=None, n_cols=None, check=True): . . O - sage: from sage.combinat.tiling import Polyomino - sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)]) - sage: Dgms(p).pp() + sage: from sage.combinat.tiling import Polyomino # needs sage.modules + sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)]) # needs sage.modules + sage: Dgms(p).pp() # needs sage.modules O . . O O O @@ -671,8 +671,8 @@ def _element_constructor_(self, cells, n_rows=None, n_cols=None, check=True): O O . . O O O O - sage: M = Matrix([[1,1,1,1],[1,1,0,0],[0,0,0,0],[1,1,0,0],[1,1,1,1]]) - sage: Dgms(M).pp() + sage: M = Matrix([[1,1,1,1],[1,1,0,0],[0,0,0,0],[1,1,0,0],[1,1,1,1]]) # needs sage.modules + sage: Dgms(M).pp() # needs sage.modules O O O O O O . . . . . . @@ -716,23 +716,23 @@ def from_polyomino(self, p): EXAMPLES:: - sage: from sage.combinat.tiling import Polyomino - sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)]) + sage: from sage.combinat.tiling import Polyomino # needs sage.modules + sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)]) # needs sage.modules sage: from sage.combinat.diagram import Diagrams - sage: Diagrams()(p).pp() + sage: Diagrams()(p).pp() # needs sage.modules O . . O O O We can also call this method directly:: - sage: Diagrams().from_polyomino(p).pp() + sage: Diagrams().from_polyomino(p).pp() # needs sage.modules O . . O O O This only works for a 2d :class:`~sage.combinat.tiling.Polyomino`:: - sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue') - sage: Diagrams().from_polyomino(p) + sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue') # needs sage.modules + sage: Diagrams().from_polyomino(p) # needs sage.modules Traceback (most recent call last): ... ValueError: the polyomino must be 2 dimensional @@ -777,17 +777,17 @@ def from_zero_one_matrix(self, M, check=True): EXAMPLES:: - sage: M = matrix([[1,0,1,1],[0,1,1,0]]) # optional - sage.modules + sage: M = matrix([[1,0,1,1],[0,1,1,0]]) # needs sage.modules sage: from sage.combinat.diagram import Diagrams - sage: Diagrams()(M).pp() # optional - sage.modules + sage: Diagrams()(M).pp() # needs sage.modules O . O O . O O . - sage: Diagrams().from_zero_one_matrix(M).pp() # optional - sage.modules + sage: Diagrams().from_zero_one_matrix(M).pp() # needs sage.modules O . O O . O O . - sage: M = matrix([[1, 0, 0], [1, 0, 0], [0, 0, 0]]) # optional - sage.modules - sage: Diagrams()(M).pp() # optional - sage.modules + sage: M = matrix([[1, 0, 0], [1, 0, 0], [0, 0, 0]]) # needs sage.modules + sage: Diagrams()(M).pp() # needs sage.modules O . . O . . . . . @@ -1435,10 +1435,10 @@ def from_parallelogram_polyomino(self, p): EXAMPLES:: - sage: p = ParallelogramPolyomino([[0, 0, 1, 0, 0, 0, 1, 1], # optional - sage.modules + sage: p = ParallelogramPolyomino([[0, 0, 1, 0, 0, 0, 1, 1], # needs sage.modules ....: [1, 1, 0, 1, 0, 0, 0, 0]]) sage: from sage.combinat.diagram import NorthwestDiagrams - sage: NorthwestDiagrams().from_parallelogram_polyomino(p).pp() + sage: NorthwestDiagrams().from_parallelogram_polyomino(p).pp() # needs sage.modules O O . O O O . O O @@ -1494,8 +1494,8 @@ def RotheDiagram(w): :class:`sage.combinat.permutations.Permutations` are supported. In particular, elements of permutation groups are not supported:: - sage: w = SymmetricGroup(9).an_element() - sage: RotheDiagram(w) + sage: w = SymmetricGroup(9).an_element() # needs sage.groups + sage: RotheDiagram(w) # needs sage.groups Traceback (most recent call last): ... ValueError: w must be a permutation From 87b4e1d04d697adda0a7ba48d4c42e8db5d29f7c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 10:49:15 -0700 Subject: [PATCH 102/150] src/sage/geometry/polyhedron/base4.py: Remove deprecation warnings from doctests that were not being run; update # needs --- src/sage/geometry/polyhedron/base4.py | 68 +++++++++++++++------------ 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/src/sage/geometry/polyhedron/base4.py b/src/sage/geometry/polyhedron/base4.py index 6b309553123..ae092615ac5 100644 --- a/src/sage/geometry/polyhedron/base4.py +++ b/src/sage/geometry/polyhedron/base4.py @@ -1,4 +1,4 @@ -# sage.doctest: optional - sage.graphs +# sage.doctest: needs sage.graphs r""" Base class for polyhedra: Graph-theoretic methods @@ -353,7 +353,7 @@ def face_lattice(self): sage: c5_20_fl = c5_20.face_lattice() # long time sage: [len(x) for x in c5_20_fl.level_sets()] # long time [1, 20, 190, 580, 680, 272, 1] - sage: polytopes.hypercube(2).face_lattice().plot() # optional - sage.plot + sage: polytopes.hypercube(2).face_lattice().plot() # needs sage.plot Graphics object consisting of 27 graphics primitives sage: level_sets = polytopes.cross_polytope(2).face_lattice().level_sets() sage: level_sets[0][0].ambient_V_indices(), level_sets[-1][0].ambient_V_indices() @@ -399,31 +399,33 @@ def hasse_diagram(self): EXAMPLES:: - sage: P = polytopes.regular_polygon(4).pyramid() # optional - sage.rings.number_field - sage: D = P.hasse_diagram(); D # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: P = polytopes.regular_polygon(4).pyramid() + sage: D = P.hasse_diagram(); D Digraph on 20 vertices - sage: D.degree_polynomial() # optional - sage.rings.number_field + sage: D.degree_polynomial() x^5 + x^4*y + x*y^4 + y^5 + 4*x^3*y + 8*x^2*y^2 + 4*x*y^3 Faces of an mutable polyhedron are not hashable. Hence those are not suitable as vertices of the hasse diagram. Use the combinatorial polyhedron instead:: - sage: P = polytopes.regular_polygon(4).pyramid() # optional - sage.rings.number_field - sage: parent = P.parent() # optional - sage.rings.number_field - sage: parent = parent.change_ring(QQ, backend='ppl') # optional - sage.rings.number_field - sage: Q = parent._element_constructor_(P, mutable=True) # optional - sage.rings.number_field - sage: Q.hasse_diagram() # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: P = polytopes.regular_polygon(4).pyramid() + sage: parent = P.parent() + sage: parent = parent.change_ring(QQ, backend='ppl') + sage: Q = parent._element_constructor_(P, mutable=True) + sage: Q.hasse_diagram() Traceback (most recent call last): ... TypeError: mutable polyhedra are unhashable - sage: C = Q.combinatorial_polyhedron() # optional - sage.rings.number_field - sage: D = C.hasse_diagram() # optional - sage.rings.number_field - sage: set(D.vertices()) == set(range(20)) # optional - sage.rings.number_field + sage: C = Q.combinatorial_polyhedron() + sage: D = C.hasse_diagram() + sage: set(D.vertices(sort=False)) == set(range(20)) True sage: def index_to_combinatorial_face(n): ....: return C.face_by_face_lattice_index(n) - sage: D.relabel(index_to_combinatorial_face, inplace=True) # optional - sage.rings.number_field - sage: D.vertices() # optional - sage.rings.number_field + sage: D.relabel(index_to_combinatorial_face, inplace=True) + sage: D.vertices(sort=True) [A -1-dimensional face of a 3-dimensional combinatorial polyhedron, A 0-dimensional face of a 3-dimensional combinatorial polyhedron, A 0-dimensional face of a 3-dimensional combinatorial polyhedron, @@ -444,7 +446,7 @@ def hasse_diagram(self): A 2-dimensional face of a 3-dimensional combinatorial polyhedron, A 2-dimensional face of a 3-dimensional combinatorial polyhedron, A 3-dimensional face of a 3-dimensional combinatorial polyhedron] - sage: D.degree_polynomial() # optional - sage.rings.number_field + sage: D.degree_polynomial() x^5 + x^4*y + x*y^4 + y^5 + 4*x^3*y + 8*x^2*y^2 + 4*x*y^3 """ @@ -643,7 +645,8 @@ def combinatorial_automorphism_group(self, vertex_graph_only=False): EXAMPLES:: sage: quadrangle = Polyhedron(vertices=[(0,0),(1,0),(0,1),(2,3)]) - sage: quadrangle.combinatorial_automorphism_group().is_isomorphic(groups.permutation.Dihedral(4)) + sage: quadrangle.combinatorial_automorphism_group().is_isomorphic( + ....: groups.permutation.Dihedral(4)) True sage: quadrangle.restricted_automorphism_group() Permutation Group with generators [()] @@ -655,19 +658,24 @@ def combinatorial_automorphism_group(self, vertex_graph_only=False): Permutation Group with generators [(A vertex at (1,0),A vertex at (1,1))] This shows an example of two polytopes whose vertex-edge graphs are isomorphic, - but their face_lattices are not isomorphic:: - - sage: Q=Polyhedron([[-123984206864/2768850730773, -101701330976/922950243591, -64154618668/2768850730773, -2748446474675/2768850730773], - ....: [-11083969050/98314591817, -4717557075/98314591817, -32618537490/98314591817, -91960210208/98314591817], - ....: [-9690950/554883199, -73651220/554883199, 1823050/554883199, -549885101/554883199], [-5174928/72012097, 5436288/72012097, -37977984/72012097, 60721345/72012097], - ....: [-19184/902877, 26136/300959, -21472/902877, 899005/902877], [53511524/1167061933, 88410344/1167061933, 621795064/1167061933, 982203941/1167061933], - ....: [4674489456/83665171433, -4026061312/83665171433, 28596876672/83665171433, -78383796375/83665171433], [857794884940/98972360190089, -10910202223200/98972360190089, 2974263671400/98972360190089, -98320463346111/98972360190089]]) + but their face lattices are not isomorphic:: + + sage: Q = Polyhedron([[-123984206864/2768850730773, -101701330976/922950243591, -64154618668/2768850730773, -2748446474675/2768850730773], + ....: [-11083969050/98314591817, -4717557075/98314591817, -32618537490/98314591817, -91960210208/98314591817], + ....: [-9690950/554883199, -73651220/554883199, 1823050/554883199, -549885101/554883199], + ....: [-5174928/72012097, 5436288/72012097, -37977984/72012097, 60721345/72012097], + ....: [-19184/902877, 26136/300959, -21472/902877, 899005/902877], + ....: [53511524/1167061933, 88410344/1167061933, 621795064/1167061933, 982203941/1167061933], + ....: [4674489456/83665171433, -4026061312/83665171433, 28596876672/83665171433, -78383796375/83665171433], + ....: [857794884940/98972360190089, -10910202223200/98972360190089, 2974263671400/98972360190089, -98320463346111/98972360190089]]) sage: C = polytopes.cyclic_polytope(4,8) sage: C.is_combinatorially_isomorphic(Q) False - sage: C.combinatorial_automorphism_group(vertex_graph_only=True).is_isomorphic(Q.combinatorial_automorphism_group(vertex_graph_only=True)) + sage: C.combinatorial_automorphism_group(vertex_graph_only=True).is_isomorphic( + ....: Q.combinatorial_automorphism_group(vertex_graph_only=True)) True - sage: C.combinatorial_automorphism_group(vertex_graph_only=False).is_isomorphic(Q.combinatorial_automorphism_group(vertex_graph_only=False)) + sage: C.combinatorial_automorphism_group(vertex_graph_only=False).is_isomorphic( + ....: Q.combinatorial_automorphism_group(vertex_graph_only=False)) False The automorphism group of the face lattice is isomorphic to the combinatorial automorphism group:: @@ -1080,10 +1088,10 @@ def is_combinatorially_isomorphic(self, other, algorithm='bipartite_graph'): All the faces of the 3-dimensional permutahedron are either combinatorially isomorphic to a square or a hexagon:: - sage: H = polytopes.regular_polygon(6) # optional - sage.rings.number_field + sage: H = polytopes.regular_polygon(6) # needs sage.rings.number_field sage: S = polytopes.hypercube(2) sage: P = polytopes.permutahedron(4) - sage: all(F.as_polyhedron().is_combinatorially_isomorphic(S) # optional - sage.rings.number_field + sage: all(F.as_polyhedron().is_combinatorially_isomorphic(S) # needs sage.rings.number_field ....: or F.as_polyhedron().is_combinatorially_isomorphic(H) ....: for F in P.faces(2)) True @@ -1102,7 +1110,7 @@ def is_combinatorially_isomorphic(self, other, algorithm='bipartite_graph'): ....: return C.intersection(H) sage: [simplex_intersection(k).is_combinatorially_isomorphic(cube_intersection(k)) for k in range(2,5)] [True, True, True] - sage: simplex_intersection(2).is_combinatorially_isomorphic(polytopes.regular_polygon(6)) # optional - sage.rings.number_field + sage: simplex_intersection(2).is_combinatorially_isomorphic(polytopes.regular_polygon(6)) # needs sage.rings.number_field True sage: simplex_intersection(3).is_combinatorially_isomorphic(polytopes.octahedron()) True @@ -1225,7 +1233,7 @@ def is_self_dual(self): True sage: polytopes.cube().is_self_dual() False - sage: polytopes.hypersimplex(5,2).is_self_dual() # optional - sage.combinat + sage: polytopes.hypersimplex(5,2).is_self_dual() # needs sage.combinat False sage: P = Polyhedron(vertices=[[1/2, 1/3]], rays=[[1, 1]]).is_self_dual() Traceback (most recent call last): From 893889774d2d495d41a183832cca7ad01ce5cfa2 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 14:57:06 -0700 Subject: [PATCH 103/150] src/sage/doctest/parsing.py: Issue style warnings for repeated # optional/needs tags --- src/sage/doctest/parsing.py | 88 +++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 6de4e8518e1..01a008f19db 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -983,6 +983,29 @@ def parse(self, string, *args): 'Zp(5,4,print_mode="digits")(5)\n' sage: dte.want '...00010\n' + + Style warnings:: + + sage: example4 = 'sage: 1 # optional guava mango\nsage: 2 # optional guava\nsage: 3 # optional guava\nsage: 4 # optional guava\n sage: 5 # optional guava\n' + sage: parsed4 = DTP.parse(example4) + sage: parsed4[1].warnings, parsed4[1].sage_source, parsed4[1].source + (["Consider using a block-scoped tag by inserting the line 'sage: # optional - guava' just before this line to avoid repeating the tag 5 times"], + '1 # optional guava mango\n', + 'None # virtual doctest') + + sage: example5 = 'sage: 1 # optional guava\nsage: 2 # optional guava mango\nsage: 3 # optional guava\nsage: 4 # optional guava\n sage: 5 # optional guava\n' + sage: parsed5 = DTP.parse(example5) + sage: parsed5[1].warnings, parsed5[1].sage_source, parsed5[1].source + (["Consider using a block-scoped tag by inserting the line 'sage: # optional - guava' just before this line to avoid repeating the tag 5 times"], + '1 # optional guava\n', + 'Integer(1) # optional guava\n') + + sage: example6 = 'sage: # optional mango\nsage: 1 # optional guava\nsage: 2 # optional guava mango\nsage: 3 # optional guava\nsage: 4 # optional guava\n sage: 5 # optional guava\n' + sage: parsed6 = DTP.parse(example6) + sage: parsed6[1].warnings, parsed6[1].sage_source, parsed6[1].source + (["Consider updating this block-scoped tag to 'sage: # optional - guava mango' to avoid repeating the tag 5 times"], + '# optional mango\n', + 'None # virtual doctest') """ # Regular expressions find_sage_prompt = re.compile(r"^(\s*)sage: ", re.M) @@ -1018,14 +1041,73 @@ def parse(self, string, *args): res = doctest.DocTestParser.parse(self, string, *args) filtered = [] persistent_optional_tags = self.file_optional_tags + persistent_optional_tag_setter = None + persistent_optional_tag_setter_index = None + first_example_in_block = None + first_example_in_block_index = None + tag_count_within_block = defaultdict(lambda: 0) + + def update_tag_counts(optional_tags): + for tag in optional_tags: + if tag not in persistent_optional_tags: + tag_count_within_block[tag] += 1 + tag_count_within_block[''] += 1 + + def check_and_clear_tag_counts(): + if (num_examples := tag_count_within_block['']) >= 4: + if overused_tags := set(tag for tag, count in tag_count_within_block.items() + if tag and count >= num_examples): + overused_tags.update(persistent_optional_tags) + overused_tags.difference_update(self.file_optional_tags) + suggested = unparse_optional_tags(overused_tags, prefix='sage: # ') + + if persistent_optional_tag_setter: + warning_example = persistent_optional_tag_setter + index = persistent_optional_tag_setter_index + warning = (f"Consider updating this block-scoped tag to '{suggested}' " + f"to avoid repeating the tag {num_examples} times") + else: + warning_example = first_example_in_block + index = first_example_in_block_index + warning = (f"Consider using a block-scoped tag by " + f"inserting the line '{suggested}' just before this line " + f"to avoid repeating the tag {num_examples} times") + + if not (index < len(filtered) and filtered[index] == warning_example): + # The example to which we want to attach our warning is + # not in ``filtered``. It is either the persistent tag line, + # or the first example of the block and not run because of unmet tags, + # or just a comment. Either way, we transform this example + # to a virtual example and attach the warning to it. + warning_example.sage_source = warning_example.source + if warning_example.sage_source.startswith("sage: "): + warning_example.sage_source = warning_example.source[6:] + warning_example.source = 'None # virtual doctest' + warning_example.want = '' + filtered.insert(index, warning_example) + + if not hasattr(warning_example, 'warnings'): + warning_example.warnings = [] + warning_example.warnings.append(warning) + tag_count_within_block.clear() + for item in res: if isinstance(item, doctest.Example): optional_tags, source_sans_tags, is_persistent = parse_optional_tags(item.source, return_string_sans_tags=True) optional_tags = set(optional_tags) if is_persistent: + check_and_clear_tag_counts() persistent_optional_tags = optional_tags persistent_optional_tags.update(self.file_optional_tags) + persistent_optional_tag_setter = first_example_in_block = item + persistent_optional_tag_setter_index = len(filtered) + first_example_in_block_index = None continue + + if not first_example_in_block: + first_example_in_block = item + first_example_in_block_index = len(filtered) + update_tag_counts(optional_tags) optional_tags.update(persistent_optional_tags) item.optional_tags = frozenset(optional_tags) item.probed_tags = set() @@ -1077,8 +1159,14 @@ def parse(self, string, *args): item.source = preparse(item.sage_source) else: if item == '\n': + check_and_clear_tag_counts() persistent_optional_tags = self.file_optional_tags + persistent_optional_tag_setter = first_example_in_block = None + persistent_optional_tag_setter_index = first_example_in_block_index = None filtered.append(item) + + check_and_clear_tag_counts() + return filtered From edaeab23d4c1531bddafafe3c2e2e64d9ee4d73b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 15:22:06 -0700 Subject: [PATCH 104/150] src/sage/doctest/forker.py: Do not initialize warnings when already set --- src/sage/doctest/forker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 179663ea065..89bb97a1ab1 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -1121,7 +1121,8 @@ def compile_and_execute(self, example, compiler, globs): if isinstance(globs, RecordingDict): globs.start() example.sequence_number = len(self.history) - example.warnings = [] + if not hasattr(example, 'warnings'): + example.warnings = [] self.history.append(example) timer = Timer().start() try: From 1ecb25c696419d62a48efb480562d621f02dab6f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 15:22:40 -0700 Subject: [PATCH 105/150] src/bin/sage-fixdoctests: Take care of style warnings issued by the doctester --- src/bin/sage-fixdoctests | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 80286e3f726..95386543e74 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -124,6 +124,14 @@ def process_block(block, src_in_lines, file_optional_tags): filename = m.group(1) first_line_num = line_num = int(m.group(2)) # 1-based line number of the first line of the example + if m := re.search(r"using.*block-scoped tag.*'(sage: .*)'.*to avoid repeating the tag", block): + indent = (len(src_in_lines[first_line_num - 1]) - len(src_in_lines[first_line_num - 1].lstrip())) + src_in_lines[line_num - 1] = ' ' * indent + m.group(1) + '\n' + src_in_lines[line_num - 1] + + if m := re.search(r"updating.*block-scoped tag.*'sage: (.*)'.*to avoid repeating the tag", block): + src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], + tags=parse_optional_tags('# ' + m.group(1))) + if m := re.search(r"referenced here was set only in doctest marked '# (optional|needs)[-: ]*([^;']*)", block): optional = m.group(2).split() if src_in_lines[first_line_num - 1].strip() in ['"""', "'''"]: @@ -330,7 +338,7 @@ for input, output in zip(inputs, outputs): src_in_lines = src_in.splitlines() shallow_copy_of_src_in_lines = list(src_in_lines) - # First remove duplicate optional tags and rewrite all '# optional' that should be '# needs' + # Emove duplicate optional tags and rewrite all '# optional' that should be '# needs' file_optional_tags = {} persistent_optional_tags = {} for i, line in enumerate(src_in_lines): From d63808a0c6f190f1a8768379dcf0315728b7db3d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 15:37:53 -0700 Subject: [PATCH 106/150] src/bin/sage-fixdoctests: Clean tags after processing blocks --- src/bin/sage-fixdoctests | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 95386543e74..1308465a7ee 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -14,6 +14,7 @@ AUTHORS:: situations when either the expected output or computed output are empty. Added doctest to sage.tests.cmdline """ +import itertools import os import re import shlex @@ -24,7 +25,7 @@ from argparse import ArgumentParser, FileType from pathlib import Path from sage.doctest.control import skipfile -from sage.doctest.parsing import parse_optional_tags, unparse_optional_tags, update_optional_tags +from sage.doctest.parsing import parse_file_optional_tags, parse_optional_tags, unparse_optional_tags, update_optional_tags from sage.env import SAGE_ROOT from sage.features import PythonModule from sage.features.all import all_features, module_feature, name_feature @@ -338,8 +339,17 @@ for input, output in zip(inputs, outputs): src_in_lines = src_in.splitlines() shallow_copy_of_src_in_lines = list(src_in_lines) - # Emove duplicate optional tags and rewrite all '# optional' that should be '# needs' - file_optional_tags = {} + file_optional_tags = set(parse_file_optional_tags(enumerate(src_in_lines))) + + for block in doctests: + process_block(block, src_in_lines, file_optional_tags) + + # Now source line numbers do not matter any more, and lines can be real lines again + src_in_lines = list(itertools.chain.from_iterable( + [] if line is None else [''] if not line else line.splitlines() + for line in src_in_lines)) + + # Remove duplicate optional tags and rewrite all '# optional' that should be '# needs' persistent_optional_tags = {} for i, line in enumerate(src_in_lines): if m := re.match(' *sage: *(.*)#', line): @@ -359,22 +369,9 @@ for input, output in zip(inputs, outputs): and tag not in persistent_optional_tags)} line = update_optional_tags(line, tags=tags, force_rewrite='standard') src_in_lines[i] = line - elif m := re.match('([#.]* *sage[.]doctest:[ #]*)([^(]*)(.*)', line): - # file-scoped annotation - prefix = m.group(1) - tags = parse_optional_tags('#' + m.group(2)) - suffix = m.group(3) - file_optional_tags.update(tags) - line = unparse_optional_tags(tags, prefix='') - if suffix: - suffix = ' ' + suffix - src_in_lines[i] = prefix + line + suffix elif line.strip() in ['', '"""', "'''"]: # Blank line or end of docstring persistent_optional_tags = {} - for block in doctests: - process_block(block, src_in_lines, file_optional_tags) - if src_in_lines != shallow_copy_of_src_in_lines: with open(output, 'w') as test_output: for line in src_in_lines: From 6204e0cc235ad780cc041f83e6f7841b78b3c759 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 18:00:16 -0700 Subject: [PATCH 107/150] src/sage/doctest/parsing.py: The end of a Sphinx codeblock also ends the # optional / # needs block --- src/sage/doctest/parsing.py | 83 +++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 01a008f19db..94547bef4f1 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -986,26 +986,67 @@ def parse(self, string, *args): Style warnings:: - sage: example4 = 'sage: 1 # optional guava mango\nsage: 2 # optional guava\nsage: 3 # optional guava\nsage: 4 # optional guava\n sage: 5 # optional guava\n' - sage: parsed4 = DTP.parse(example4) - sage: parsed4[1].warnings, parsed4[1].sage_source, parsed4[1].source - (["Consider using a block-scoped tag by inserting the line 'sage: # optional - guava' just before this line to avoid repeating the tag 5 times"], - '1 # optional guava mango\n', - 'None # virtual doctest') - - sage: example5 = 'sage: 1 # optional guava\nsage: 2 # optional guava mango\nsage: 3 # optional guava\nsage: 4 # optional guava\n sage: 5 # optional guava\n' - sage: parsed5 = DTP.parse(example5) - sage: parsed5[1].warnings, parsed5[1].sage_source, parsed5[1].source - (["Consider using a block-scoped tag by inserting the line 'sage: # optional - guava' just before this line to avoid repeating the tag 5 times"], - '1 # optional guava\n', - 'Integer(1) # optional guava\n') - - sage: example6 = 'sage: # optional mango\nsage: 1 # optional guava\nsage: 2 # optional guava mango\nsage: 3 # optional guava\nsage: 4 # optional guava\n sage: 5 # optional guava\n' - sage: parsed6 = DTP.parse(example6) - sage: parsed6[1].warnings, parsed6[1].sage_source, parsed6[1].source - (["Consider updating this block-scoped tag to 'sage: # optional - guava mango' to avoid repeating the tag 5 times"], - '# optional mango\n', - 'None # virtual doctest') + sage: def parse(test_string): + ....: return [x if isinstance(x, str) + ....: else (getattr(x, 'warnings', None), x.sage_source, x.source) + ....: for x in DTP.parse(test_string)] + + sage: # optional - guava + sage: parse('sage: 1 # optional guava mango\nsage: 2 # optional guava\nsage: 3 # optional guava\nsage: 4 # optional guava\nsage: 5 # optional guava\n\nsage: 11 # optional guava') + ['', + (["Consider using a block-scoped tag by inserting the line 'sage: # optional - guava' just before this line to avoid repeating the tag 5 times"], + '1 # optional guava mango\n', + 'None # virtual doctest'), + '', + (None, '2 # optional guava\n', 'Integer(2) # optional guava\n'), + '', + (None, '3 # optional guava\n', 'Integer(3) # optional guava\n'), + '', + (None, '4 # optional guava\n', 'Integer(4) # optional guava\n'), + '', + (None, '5 # optional guava\n', 'Integer(5) # optional guava\n'), + '\n', + (None, '11 # optional guava\n', 'Integer(11) # optional guava\n'), + ''] + + sage: # optional - guava + sage: parse('sage: 1 # optional guava\nsage: 2 # optional guava mango\nsage: 3 # optional guava\nsage: 4 # optional guava\nsage: 5 # optional guava\n') + ['', + (["Consider using a block-scoped tag by inserting the line 'sage: # optional - guava' just before this line to avoid repeating the tag 5 times"], + '1 # optional guava\n', + 'Integer(1) # optional guava\n'), + '', + '', + (None, '3 # optional guava\n', 'Integer(3) # optional guava\n'), + '', + (None, '4 # optional guava\n', 'Integer(4) # optional guava\n'), + '', + (None, '5 # optional guava\n', 'Integer(5) # optional guava\n'), + ''] + + sage: parse('sage: # optional mango\nsage: 1 # optional guava\nsage: 2 # optional guava mango\nsage: 3 # optional guava\nsage: 4 # optional guava\n sage: 5 # optional guava\n') # optional - guava mango + ['', + (["Consider updating this block-scoped tag to 'sage: # optional - guava mango' to avoid repeating the tag 5 times"], + '# optional mango\n', + 'None # virtual doctest'), + '', + '', + '', + '', + '', + ''] + + sage: parse('::\n\n sage: 1 # optional guava\n sage: 2 # optional guava mango\n sage: 3 # optional guava\n\n::\n\n sage: 4 # optional guava\n sage: 5 # optional guava\n') + ['::\n\n', + (None, '1 # optional guava\n', 'Integer(1) # optional guava\n'), + '', + '', + (None, '3 # optional guava\n', 'Integer(3) # optional guava\n'), + '\n::\n\n', + (None, '4 # optional guava\n', 'Integer(4) # optional guava\n'), + '', + (None, '5 # optional guava\n', 'Integer(5) # optional guava\n'), + ''] """ # Regular expressions find_sage_prompt = re.compile(r"^(\s*)sage: ", re.M) @@ -1158,7 +1199,7 @@ def check_and_clear_tag_counts(): continue item.source = preparse(item.sage_source) else: - if item == '\n': + if '\n' in item: check_and_clear_tag_counts() persistent_optional_tags = self.file_optional_tags persistent_optional_tag_setter = first_example_in_block = None From 0ca90168a16e4e7c7dcb12a25368a9008423a0a6 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 18:51:54 -0700 Subject: [PATCH 108/150] src/doc/en/developer/doctesting.rst (sage --fixdoctests): Update on what comments are preserved --- src/doc/en/developer/doctesting.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index a1767b8a928..d1fb2da53ae 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -1569,8 +1569,9 @@ This mode is suitable for mostly unattended runs on many files. .. warning:: While the doctest fixer guarantees to preserve any comments that - appear before ``# optional/needs``, any comments that may be mixed with - the doctest tags will be lost. + appear before ``# optional/needs`` and all parenthesized comments + of the form ``# optional - FEATURE (EXPLANATION)``, any free-form comments + that may be mixed with the doctest tags will be lost. If you don't want to update any doctests, you can use the option ``--no-test``. In this mode, the doctest fixer does not run From d09ac6fee6ae19229f8a05b1221e6b2c668337c4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 17:13:03 -0700 Subject: [PATCH 109/150] src/sage/doctest/parsing.py: Parse the format # optional - foo (explanation) --- src/sage/doctest/parsing.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 94547bef4f1..2cc7cbb4897 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -84,8 +84,12 @@ def fake_RIFtol(*args): ansi_escape_sequence = re.compile(r'(\x1b[@-Z\\-~]|\x1b\[.*?[@-~]|\x9b.*?[@-~])') special_optional_regex = 'arb216|arb218|py2|long time|not implemented|not tested|known bug' -optional_regex = re.compile(fr'({special_optional_regex})|[^ a-z]\s*(optional|needs)\s*[:-]*((?:\s|\w|[.])*)', re.IGNORECASE) +tag_with_explanation_regex = fr'((?:\w|[.])+)\s*(?:\((.*?)\))?' +optional_regex = re.compile(fr'(?P{special_optional_regex})\s*(?:\((?P.*?)\))?|' + fr'[^ a-z]\s*(optional|needs)(?:\s|[:-])*(?P(?:(?:{tag_with_explanation_regex})\s*)*)', + re.IGNORECASE) special_optional_regex = re.compile(special_optional_regex, re.IGNORECASE) +tag_with_explanation_regex = re.compile(tag_with_explanation_regex, re.IGNORECASE) nodoctest_regex = re.compile(r'\s*(#+|%+|r"+|"+|\.\.)\s*nodoctest') optionaltag_regex = re.compile(r'^(\w|[.])+$') @@ -153,6 +157,11 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): sage: parse_optional_tags("'ěščřžýáíéďĎ'") {} + Tags with parenthesized explanations:: + + sage: parse_optional_tags(" sage: 1 + 1 # long time (1 year, 2 months??), optional - bliss (because)") + {'bliss': 'because', 'long time': '1 year, 2 months??'} + With ``return_string_sans_tags=True``:: sage: parse_optional_tags("sage: print(1) # very important 1 # optional - foo", @@ -198,15 +207,15 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): tags = {} for m in optional_regex.finditer(comment): - cmd = m.group(1) + cmd = m.group('cmd') if cmd and cmd.lower() == 'known bug': tags['bug'] = None # so that such tests will be run by sage -t ... -only-optional=bug elif cmd: - tags[cmd.lower()] = None + tags[cmd.lower()] = m.group('cmd_explanation') or None else: - words = m.group(3).split() - if words: - tags.update({s.lower(): None for s in words}) + # optional/needs + tags.update({m.group(1).lower(): m.group(2) or None + for m in tag_with_explanation_regex.finditer(m.group('tags'))}) if return_string_sans_tags: is_persistent = tags and first_line_sans_comments.strip() == 'sage:' and not rest # persistent (block-scoped) annotation From e7a0cc0ef9ca27633722f3e0d50f2bf1f9dbccff Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 18:04:53 -0700 Subject: [PATCH 110/150] src/sage/doctest/forker.py: Follow the style warning --- src/sage/doctest/forker.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 89bb97a1ab1..515cbf96795 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -132,10 +132,11 @@ def init_sage(controller=None): Check that SymPy equation pretty printer is limited in doctest mode to default width (80 chars):: - sage: from sympy import sympify # needs sympy - sage: from sympy.printing.pretty.pretty import PrettyPrinter # needs sympy - sage: s = sympify('+x^'.join(str(i) for i in range(30))) # needs sympy - sage: print(PrettyPrinter(settings={'wrap_line': True}).doprint(s)) # needs sympy + sage: # needs sympy + sage: from sympy import sympify + sage: from sympy.printing.pretty.pretty import PrettyPrinter + sage: s = sympify('+x^'.join(str(i) for i in range(30))) + sage: print(PrettyPrinter(settings={'wrap_line': True}).doprint(s)) 29 28 27 26 25 24 23 22 21 20 19 18 17 x + x + x + x + x + x + x + x + x + x + x + x + x + From b0bbedc6c24465e4c7daf90bf822d0878e6b75c7 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 19:51:25 -0700 Subject: [PATCH 111/150] src/sage/features/sagemath.py: Add to a join feature --- src/sage/features/sagemath.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 27d6b0dd219..1f1ea139223 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -591,7 +591,8 @@ def __init__(self): True """ JoinFeature.__init__(self, 'sage.rings.finite_rings', - [PythonModule('sage.rings.finite_rings.element_pari_ffelt')], + [PythonModule('sage.rings.finite_rings.element_pari_ffelt'), + PythonModule('sage.rings.algebraic_closure_finite_field')], type='standard') From 557098e6b64c02044e0386648e381406e138d0c5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 22:04:55 -0700 Subject: [PATCH 112/150] sage.features.sagemath: Move features sage.libs.gap, sage.libs.singular here --- src/sage/features/gap.py | 35 +++----------- src/sage/features/sagemath.py | 88 ++++++++++++++++++++++++++++++----- src/sage/features/singular.py | 35 +++----------- 3 files changed, 88 insertions(+), 70 deletions(-) diff --git a/src/sage/features/gap.py b/src/sage/features/gap.py index 80375289006..c157227267f 100644 --- a/src/sage/features/gap.py +++ b/src/sage/features/gap.py @@ -13,12 +13,16 @@ from . import Feature, FeatureTestResult, PythonModule from .join_feature import JoinFeature - +from .sagemath import sage__libs__gap class GapPackage(Feature): r""" A :class:`~sage.features.Feature` describing the presence of a GAP package. + .. SEEALSO:: + + :class:`Feature sage.libs.gap <~sage.features.sagemath.sage__libs__gap>` + EXAMPLES:: sage: from sage.features.gap import GapPackage @@ -59,32 +63,5 @@ def _is_present(self): reason="`{command}` evaluated to `{presence}` in GAP.".format(command=command, presence=presence)) -class sage__libs__gap(JoinFeature): - r""" - A :class:`sage.features.Feature` describing the presence of :mod:`sage.libs.gap` - (the library interface to :ref:`GAP `) and :mod:`sage.interfaces.gap` (the pexpect - interface to GAP). By design, we do not distinguish between these two, in order - to facilitate the conversion of code from the pexpect interface to the library - interface. - - EXAMPLES:: - - sage: from sage.features.gap import sage__libs__gap - sage: sage__libs__gap().is_present() # needs sage.libs.gap - FeatureTestResult('sage.libs.gap', True) - """ - def __init__(self): - r""" - TESTS:: - - sage: from sage.features.gap import sage__libs__gap - sage: isinstance(sage__libs__gap(), sage__libs__gap) - True - """ - JoinFeature.__init__(self, 'sage.libs.gap', - [PythonModule('sage.libs.gap.libgap'), - PythonModule('sage.interfaces.gap')]) - - def all_features(): - return [sage__libs__gap()] + return [] diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 1f1ea139223..2da6190b21e 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -41,7 +41,6 @@ from . import PythonModule, StaticFile from .join_feature import JoinFeature -from .singular import sage__libs__singular class sagemath_doc_html(StaticFile): @@ -112,12 +111,13 @@ class sage__combinat(JoinFeature): Doctests that use combinatorial modules/algebras, or root systems should use the tag ``# needs sage.combinat sage.modules``:: - sage: A = SchurAlgebra(QQ, 2, 3) # needs sage.combinat sage.modules - sage: a = A.an_element(); a # needs sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: A = SchurAlgebra(QQ, 2, 3) + sage: a = A.an_element(); a 2*S((1, 1, 1), (1, 1, 1)) + 2*S((1, 1, 1), (1, 1, 2)) + 3*S((1, 1, 1), (1, 2, 2)) - sage: L = RootSystem(['A',3,1]).root_lattice() # needs sage.combinat sage.modules - sage: PIR = L.positive_imaginary_roots(); PIR # needs sage.combinat sage.modules + sage: L = RootSystem(['A',3,1]).root_lattice() + sage: PIR = L.positive_imaginary_roots(); PIR Positive imaginary roots of type ['A', 3, 1] Doctests that use lattices, semilattices, or Dynkin diagrams should use the tag @@ -342,7 +342,7 @@ class sage__libs__flint(JoinFeature): In addition to the modularization purposes that this tag serves, it also provides attribution to the upstream project. - EXAMPLES:: + TESTS:: sage: from sage.features.sagemath import sage__libs__flint sage: sage__libs__flint().is_present() # needs sage.libs.flint @@ -362,6 +362,37 @@ def __init__(self): spkg='sagemath_flint', type='standard') +class sage__libs__gap(JoinFeature): + r""" + A :class:`sage.features.Feature` describing the presence of :mod:`sage.libs.gap` + (the library interface to :ref:`GAP `) and :mod:`sage.interfaces.gap` (the pexpect + interface to GAP). By design, we do not distinguish between these two, in order + to facilitate the conversion of code from the pexpect interface to the library + interface. + + .. SEEALSO:: + + :class:`Features for GAP packages <~sage.features.gap.GapPackage>` + + TESTS:: + + sage: from sage.features.gap import sage__libs__gap + sage: sage__libs__gap().is_present() # needs sage.libs.gap + FeatureTestResult('sage.libs.gap', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.gap import sage__libs__gap + sage: isinstance(sage__libs__gap(), sage__libs__gap) + True + """ + JoinFeature.__init__(self, 'sage.libs.gap', + [PythonModule('sage.libs.gap.libgap'), + PythonModule('sage.interfaces.gap')]) + + class sage__libs__ntl(JoinFeature): r""" A :class:`sage.features.Feature` describing the presence of :mod:`sage.libs.ntl` @@ -370,7 +401,7 @@ class sage__libs__ntl(JoinFeature): In addition to the modularization purposes that this tag serves, it also provides attribution to the upstream project. - EXAMPLES:: + TESTS:: sage: from sage.features.sagemath import sage__libs__ntl sage: sage__libs__ntl().is_present() # needs sage.libs.ntl @@ -426,11 +457,42 @@ def __init__(self): spkg='sagemath_pari', type='standard') +class sage__libs__singular(JoinFeature): + r""" + A :class:`sage.features.Feature` describing the presence of :mod:`sage.libs.singular` + (the library interface to Singular) and :mod:`sage.interfaces.singular` (the pexpect + interface to Singular). By design, we do not distinguish between these two, in order + to facilitate the conversion of code from the pexpect interface to the library + interface. + + .. SEEALSO:: + + :class:`Feature singular <~sage.features.singular.Singular>` + + TESTS:: + + sage: from sage.features.singular import sage__libs__singular + sage: sage__libs__singular().is_present() # needs sage.libs.singular + FeatureTestResult('sage.libs.singular', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.singular import sage__libs__singular + sage: isinstance(sage__libs__singular(), sage__libs__singular) + True + """ + JoinFeature.__init__(self, 'sage.libs.singular', + [PythonModule('sage.libs.singular.singular'), + PythonModule('sage.interfaces.singular')]) + + class sage__modular(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.modular`. - EXAMPLES:: + TESTS:: sage: from sage.features.sagemath import sage__modular sage: sage__modular().is_present() # needs sage.modular @@ -508,7 +570,7 @@ class sage__numerical__mip(PythonModule): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.numerical.mip`. - EXAMPLES:: + TESTS:: sage: from sage.features.sagemath import sage__numerical__mip sage: sage__numerical__mip().is_present() # needs sage.numerical.mip @@ -732,7 +794,7 @@ class sage__rings__polynomial__pbori(JoinFeature): r""" A :class:`sage.features.Feature` describing the presence of :mod:`sage.rings.polynomial.pbori`. - EXAMPLES:: + TESTS:: sage: from sage.features.sagemath import sage__rings__polynomial__pbori sage: sage__rings__polynomial__pbori().is_present() # needs sage.rings.polynomial.pbori @@ -811,7 +873,7 @@ class sage__sat(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.sat`. - EXAMPLES:: + TESTS:: sage: from sage.features.sagemath import sage__sat sage: sage__sat().is_present() # needs sage.sat @@ -834,7 +896,7 @@ class sage__schemes(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.schemes`. - EXAMPLES:: + TESTS:: sage: from sage.features.sagemath import sage__schemes sage: sage__schemes().is_present() # needs sage.schemes @@ -940,8 +1002,10 @@ def all_features(): sage__graphs(), sage__groups(), sage__libs__flint(), + sage__libs__gap(), sage__libs__ntl(), sage__libs__pari(), + sage__libs__singular(), sage__modular(), sage__modules(), sage__plot(), diff --git a/src/sage/features/singular.py b/src/sage/features/singular.py index 6ce5d3a6b3f..fce89a8e91c 100644 --- a/src/sage/features/singular.py +++ b/src/sage/features/singular.py @@ -13,6 +13,7 @@ from . import Executable, PythonModule from .join_feature import JoinFeature +from .sagemath import sage__libs__singular from sage.env import SINGULAR_BIN @@ -20,6 +21,10 @@ class Singular(Executable): r""" A :class:`~sage.features.Feature` describing the presence of the :ref:`singular ` executable. + .. SEEALSO:: + + :class:`Feature sage.libs.singular <~sage.features.sagemath.sage__libs__singular>` + EXAMPLES:: sage: from sage.features.singular import Singular @@ -38,33 +43,5 @@ def __init__(self): spkg='singular', type='standard') -class sage__libs__singular(JoinFeature): - r""" - A :class:`sage.features.Feature` describing the presence of :mod:`sage.libs.singular` - (the library interface to Singular) and :mod:`sage.interfaces.singular` (the pexpect - interface to Singular). By design, we do not distinguish between these two, in order - to facilitate the conversion of code from the pexpect interface to the library - interface. - - EXAMPLES:: - - sage: from sage.features.singular import sage__libs__singular - sage: sage__libs__singular().is_present() # needs sage.libs.singular - FeatureTestResult('sage.libs.singular', True) - """ - def __init__(self): - r""" - TESTS:: - - sage: from sage.features.singular import sage__libs__singular - sage: isinstance(sage__libs__singular(), sage__libs__singular) - True - """ - JoinFeature.__init__(self, 'sage.libs.singular', - [PythonModule('sage.libs.singular.singular'), - PythonModule('sage.interfaces.singular')]) - - def all_features(): - return [Singular(), - sage__libs__singular()] + return [Singular()] From b50ca9419096e93c2c8ebdbb5bcffd7647f7ee9a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 18:08:16 -0700 Subject: [PATCH 113/150] src/sage/rings/big_oh.py: Follow the style warning --- src/sage/rings/big_oh.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/sage/rings/big_oh.py b/src/sage/rings/big_oh.py index ad43b8fbc84..21938a2256b 100644 --- a/src/sage/rings/big_oh.py +++ b/src/sage/rings/big_oh.py @@ -57,44 +57,45 @@ def O(*x, **kwds): This is also useful to create `p`-adic numbers:: - sage: O(7^6) # optional - sage.rings.padics + sage: O(7^6) # needs sage.rings.padics O(7^6) - sage: 1/3 + O(7^6) # optional - sage.rings.padics + sage: 1/3 + O(7^6) # needs sage.rings.padics 5 + 4*7 + 4*7^2 + 4*7^3 + 4*7^4 + 4*7^5 + O(7^6) It behaves well with respect to adding negative powers of `p`:: - sage: a = O(11^-32); a # optional - sage.rings.padics + sage: a = O(11^-32); a # needs sage.rings.padics O(11^-32) - sage: a.parent() # optional - sage.rings.padics + sage: a.parent() # needs sage.rings.padics 11-adic Field with capped relative precision 20 There are problems if you add a rational with very negative valuation to an `O`-Term:: - sage: 11^-12 + O(11^15) # optional - sage.rings.padics + sage: 11^-12 + O(11^15) # needs sage.rings.padics 11^-12 + O(11^8) The reason that this fails is that the constructor doesn't know the right precision cap to use. If you cast explicitly or use other means of element creation, you can get around this issue:: - sage: K = Qp(11, 30) # optional - sage.rings.padics - sage: K(11^-12) + O(11^15) # optional - sage.rings.padics + sage: # needs sage.rings.padics + sage: K = Qp(11, 30) + sage: K(11^-12) + O(11^15) 11^-12 + O(11^15) - sage: 11^-12 + K(O(11^15)) # optional - sage.rings.padics + sage: 11^-12 + K(O(11^15)) 11^-12 + O(11^15) - sage: K(11^-12, absprec=15) # optional - sage.rings.padics + sage: K(11^-12, absprec=15) 11^-12 + O(11^15) - sage: K(11^-12, 15) # optional - sage.rings.padics + sage: K(11^-12, 15) 11^-12 + O(11^15) We can also work with `asymptotic expansions`_:: - sage: A. = AsymptoticRing(growth_group='QQ^n * n^QQ * log(n)^QQ', # optional - sage.symbolic + sage: A. = AsymptoticRing(growth_group='QQ^n * n^QQ * log(n)^QQ', # needs sage.symbolic ....: coefficient_ring=QQ); A Asymptotic Ring over Rational Field - sage: O(n) # optional - sage.symbolic + sage: O(n) # needs sage.symbolic O(n) Application with Puiseux series:: @@ -108,17 +109,17 @@ def O(*x, **kwds): TESTS:: - sage: var('x, y') # optional - sage.symbolic + sage: var('x, y') # needs sage.symbolic (x, y) - sage: O(x) # optional - sage.symbolic + sage: O(x) # needs sage.symbolic Traceback (most recent call last): ... ArithmeticError: O(x) not defined - sage: O(y) # optional - sage.symbolic + sage: O(y) # needs sage.symbolic Traceback (most recent call last): ... ArithmeticError: O(y) not defined - sage: O(x, y) # optional - sage.symbolic + sage: O(x, y) # needs sage.symbolic Traceback (most recent call last): ... ArithmeticError: O(x, y) not defined From af3128c2624c2078135564c3f570503bdc9ffed4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Jul 2023 18:14:55 -0700 Subject: [PATCH 114/150] src/sage/rings/homset.py: Follow the style advice --- src/sage/rings/homset.py | 57 ++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/sage/rings/homset.py b/src/sage/rings/homset.py index b89ba5a1ed7..6013a6ca28b 100644 --- a/src/sage/rings/homset.py +++ b/src/sage/rings/homset.py @@ -28,9 +28,9 @@ def is_RingHomset(H): True sage: is_RH(ZZ) False - sage: is_RH(Hom(RR, CC)) + sage: is_RH(Hom(RR, CC)) # needs sage.rings.real_mpfr True - sage: is_RH(Hom(FreeModule(ZZ,1), FreeModule(QQ,1))) + sage: is_RH(Hom(FreeModule(ZZ,1), FreeModule(QQ,1))) # needs sage.modules False """ return isinstance(H, RingHomset_generic) @@ -133,26 +133,27 @@ def _element_constructor_(self, x, check=True, base_map=None): You can provide a morphism on the base:: - sage: k = GF(9) # optional - sage.rings.finite_rings - sage: z2 = k.gen() # optional - sage.rings.finite_rings - sage: cc = k.frobenius_endomorphism() # optional - sage.rings.finite_rings - sage: R. = k[] # optional - sage.rings.finite_rings - sage: H = Hom(R, R) # optional - sage.rings.finite_rings - sage: phi = H([x^2], base_map=cc); phi # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: k = GF(9) + sage: z2 = k.gen() + sage: cc = k.frobenius_endomorphism() + sage: R. = k[] + sage: H = Hom(R, R) + sage: phi = H([x^2], base_map=cc); phi Ring endomorphism of Univariate Polynomial Ring in x over Finite Field in z2 of size 3^2 Defn: x |--> x^2 with map of base ring - sage: phi(z2 * x) == z2^3 * x^2 # optional - sage.rings.finite_rings + sage: phi(z2 * x) == z2^3 * x^2 True sage: R. = ZZ[] - sage: K. = GF(7^2) # optional - sage.rings.finite_rings - sage: L. = K.extension(x^3 - 3) # optional - sage.rings.finite_rings - sage: phi = L.hom([u^7], base_map=K.frobenius_endomorphism()) # optional - sage.rings.finite_rings - sage: phi(u) == u^7 # optional - sage.rings.finite_rings + sage: K. = GF(7^2) # needs sage.rings.finite_rings + sage: L. = K.extension(x^3 - 3) # needs sage.rings.finite_rings + sage: phi = L.hom([u^7], base_map=K.frobenius_endomorphism()) # needs sage.rings.finite_rings + sage: phi(u) == u^7 # needs sage.rings.finite_rings True - sage: phi(a) == a^7 # optional - sage.rings.finite_rings + sage: phi(a) == a^7 # needs sage.rings.finite_rings True TESTS:: @@ -248,15 +249,15 @@ class RingHomset_quo_ring(RingHomset_generic): EXAMPLES:: sage: R. = PolynomialRing(QQ, 2) - sage: S. = R.quotient(x^2 + y^2) # optional - sage.libs.singular - sage: phi = S.hom([b,a]); phi # optional - sage.libs.singular + sage: S. = R.quotient(x^2 + y^2) # needs sage.libs.singular + sage: phi = S.hom([b,a]); phi # needs sage.libs.singular Ring endomorphism of Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x^2 + y^2) Defn: a |--> b b |--> a - sage: phi(a) # optional - sage.libs.singular + sage: phi(a) # needs sage.libs.singular b - sage: phi(b) # optional - sage.libs.singular + sage: phi(b) # needs sage.libs.singular a TESTS: @@ -266,15 +267,15 @@ class RingHomset_quo_ring(RingHomset_generic): :: sage: R. = PolynomialRing(QQ, 2) - sage: S. = R.quotient(x^2 + y^2) # optional - sage.libs.singular - sage: H = S.Hom(R) # optional - sage.libs.singular - sage: H == loads(dumps(H)) # optional - sage.libs.singular + sage: S. = R.quotient(x^2 + y^2) # needs sage.libs.singular + sage: H = S.Hom(R) # needs sage.libs.singular + sage: H == loads(dumps(H)) # needs sage.libs.singular True We test pickling of actual homomorphisms in a quotient:: - sage: phi = S.hom([b,a]) # optional - sage.libs.singular - sage: phi == loads(dumps(phi)) # optional - sage.libs.singular + sage: phi = S.hom([b,a]) # needs sage.libs.singular + sage: phi == loads(dumps(phi)) # needs sage.libs.singular True """ @@ -287,17 +288,17 @@ def _element_constructor_(self, x, base_map=None, check=True): EXAMPLES:: sage: R. = PolynomialRing(QQ, 2) - sage: S. = R.quotient(x^2 + y^2) # optional - sage.libs.singular - sage: H = S.Hom(R) # optional - sage.libs.singular - sage: phi = H([b, a]); phi # optional - sage.libs.singular + sage: S. = R.quotient(x^2 + y^2) # needs sage.libs.singular + sage: H = S.Hom(R) # needs sage.libs.singular + sage: phi = H([b, a]); phi # needs sage.libs.singular Ring morphism: From: Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x^2 + y^2) To: Multivariate Polynomial Ring in x, y over Rational Field Defn: a |--> b b |--> a sage: R2. = PolynomialRing(ZZ, 2) - sage: H2 = Hom(R2, S) # optional - sage.libs.singular - sage: H2(phi) # optional - sage.libs.singular + sage: H2 = Hom(R2, S) # needs sage.libs.singular + sage: H2(phi) # needs sage.libs.singular Composite map: From: Multivariate Polynomial Ring in x, y over Integer Ring To: Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x^2 + y^2) From 91bc915b2d8a98e9ebd95c785b66b37344620929 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 5 Jul 2023 09:42:14 -0700 Subject: [PATCH 115/150] src/sage/features/sagemath.py: Update from #35509 --- src/sage/features/sagemath.py | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 2da6190b21e..6dfb00cd472 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -278,29 +278,6 @@ def __init__(self): spkg='sagemath_graphs', type="standard") -class sage__modular(JoinFeature): - r""" - A :class:`~sage.features.Feature` describing the presence of :mod:`sage.modular`. - - EXAMPLES:: - - sage: from sage.features.sagemath import sage__modular - sage: sage__modular().is_present() # optional - sage.modular - FeatureTestResult('sage.modular', True) - """ - def __init__(self): - r""" - TESTS:: - - sage: from sage.features.sagemath import sage__modular - sage: isinstance(sage__modular(), sage__modular) - True - """ - JoinFeature.__init__(self, 'sage.modular', - [PythonModule('sage.modular.modform.eisenstein_submodule')], - spkg='sagemath_schemes', type='standard') - - class sage__groups(JoinFeature): r""" A :class:`~sage.features.Feature` describing the presence of ``sage.groups``. @@ -763,7 +740,9 @@ def __init__(self): True """ JoinFeature.__init__(self, 'sage.rings.number_field', - [PythonModule('sage.rings.number_field.number_field_element')], + [PythonModule('sage.rings.number_field.number_field_element'), + PythonModule('sage.rings.universal_cyclotomic_field'), + PythonModule('sage.rings.qqbar')], type='standard') @@ -841,7 +820,7 @@ def __init__(self): sage: isinstance(sage__rings__real_double(), sage__rings__real_double) True """ - PythonModule.__init__(self, 'sage.rings.real_double') + PythonModule.__init__(self, 'sage.rings.real_double', type='standard') class sage__rings__real_mpfr(JoinFeature): @@ -912,7 +891,7 @@ def __init__(self): """ JoinFeature.__init__(self, 'sage.schemes', [PythonModule('sage.schemes.elliptic_curves.ell_generic')], - spkg="sagemath_schemes") + spkg="sagemath_schemes", type='standard') class sage__symbolic(JoinFeature): @@ -1008,7 +987,9 @@ def all_features(): sage__libs__singular(), sage__modular(), sage__modules(), + sage__numerical__mip(), sage__plot(), + sage__rings__complex_double(), sage__rings__finite_rings(), sage__rings__function_field(), sage__rings__number_field(), @@ -1016,5 +997,6 @@ def all_features(): sage__rings__polynomial__pbori(), sage__rings__real_double(), sage__rings__real_mpfr(), + sage__sat(), sage__schemes(), sage__symbolic()] From f9add88dc3fcf9b8ac4660cf83378d12399819ed Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 12 Mar 2023 00:10:07 -0800 Subject: [PATCH 116/150] sage.features: Fix doctests when sage.libs.ecl, sage.libs.gap are not available --- src/sage/features/__init__.py | 16 ++++++++++------ src/sage/features/gap.py | 6 +++++- src/sage/features/interfaces.py | 2 +- src/sage/features/kenzo.py | 5 ++++- src/sage/features/sagemath.py | 23 +++++++++++++++++++++++ 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index 5deb0085a63..0c421a2540e 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -232,7 +232,7 @@ def require(self): EXAMPLES:: sage: from sage.features.gap import GapPackage - sage: GapPackage("ve1EeThu").require() + sage: GapPackage("ve1EeThu").require() # optional - sage.libs.gap Traceback (most recent call last): ... FeatureNotPresentError: gap_package_ve1EeThu is not available. @@ -452,7 +452,7 @@ def __str__(self): EXAMPLES:: sage: from sage.features.gap import GapPackage - sage: GapPackage("gapZuHoh8Uu").require() # indirect doctest + sage: GapPackage("gapZuHoh8Uu").require() # indirect doctest # optional - sage.libs.gap Traceback (most recent call last): ... FeatureNotPresentError: gap_package_gapZuHoh8Uu is not available. @@ -485,7 +485,7 @@ class FeatureTestResult(): Explanatory messages might be available as ``reason`` and ``resolution``:: - sage: presence.reason + sage: presence.reason # optional - sage.libs.gap '`TestPackageAvailability("NOT_A_PACKAGE")` evaluated to `fail` in GAP.' sage: bool(presence.resolution) False @@ -870,8 +870,9 @@ class CythonFeature(Feature): ....: ....: assert fabs(-1) == 1 ....: ''' - sage: fabs = CythonFeature("fabs", test_code=fabs_test_code, spkg="gcc", url="https://gnu.org", type="standard") - sage: fabs.is_present() + sage: fabs = CythonFeature("fabs", test_code=fabs_test_code, # optional - sage.misc.cython + ....: spkg="gcc", url="https://gnu.org", type="standard") + sage: fabs.is_present() # optional - sage.misc.cython FeatureTestResult('fabs', True) Test various failures:: @@ -922,7 +923,7 @@ def _is_present(self): sage: from sage.features import CythonFeature sage: empty = CythonFeature("empty", test_code="") - sage: empty.is_present() + sage: empty.is_present() # optional - sage.misc.cython FeatureTestResult('empty', True) """ from sage.misc.temporary_file import tmp_filename @@ -935,6 +936,9 @@ def _is_present(self): pyx.write(self.test_code) try: from sage.misc.cython import cython_import + except ImportError: + return FeatureTestResult(self, False, reason="sage.misc.cython is not available") + try: cython_import(pyx.name, verbose=-1) except CCompilerError: return FeatureTestResult(self, False, reason="Failed to compile test code.") diff --git a/src/sage/features/gap.py b/src/sage/features/gap.py index c157227267f..17aabcdca54 100644 --- a/src/sage/features/gap.py +++ b/src/sage/features/gap.py @@ -52,7 +52,11 @@ def _is_present(self): sage: GapPackage("grape", spkg="gap_packages")._is_present() # optional - gap_packages FeatureTestResult('gap_package_grape', True) """ - from sage.libs.gap.libgap import libgap + try: + from sage.libs.gap.libgap import libgap + except ImportError: + return FeatureTestResult(self, False, + reason="sage.libs.gap is not available") command = 'TestPackageAvailability("{package}")'.format(package=self.package) presence = libgap.eval(command) if presence: diff --git a/src/sage/features/interfaces.py b/src/sage/features/interfaces.py index ddfc3b9b7ee..21d7c1bedfc 100644 --- a/src/sage/features/interfaces.py +++ b/src/sage/features/interfaces.py @@ -32,7 +32,7 @@ class InterfaceFeature(Feature): sage: also_broken = InterfaceFeature("also_broken_interface", "sage.interfaces.interface") sage: also_broken.is_present() - FeatureTestResult('also_broken_interface', False) + FeatureTestResult('sage.interfaces.interface', False) sage: _.reason "Interface also_broken_interface cannot be imported: module 'sage.interfaces.interface' has no attribute 'also_broken_interface'" """ diff --git a/src/sage/features/kenzo.py b/src/sage/features/kenzo.py index 0060e6deb03..72f703da15f 100644 --- a/src/sage/features/kenzo.py +++ b/src/sage/features/kenzo.py @@ -48,7 +48,10 @@ def _is_present(self): sage: Kenzo()._is_present() # optional - kenzo FeatureTestResult('kenzo', True) """ - from sage.libs.ecl import ecl_eval + try: + from sage.libs.ecl import ecl_eval + except ImportError: + return FeatureTestResult(self, False, reason="sage.libs.ecl is not available") # Redirection of ECL and Maxima stdout to /dev/null # This is also done in the Maxima library, but we # also do it here for redundancy. diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 6dfb00cd472..d3c9d33132d 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -311,6 +311,28 @@ def __init__(self): spkg='sagemath_groups', type='standard') +class sage__libs__ecl(PythonModule): + r""" + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.libs.ecl`. + + EXAMPLES:: + + sage: from sage.features.sagemath import sage__libs__ecl + sage: sage__libs__ecl().is_present() # optional - sage.libs.ecl + FeatureTestResult('sage.libs.ecl', True) + """ + + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.sagemath import sage__libs__ecl + sage: isinstance(sage__libs__ecl(), sage__libs__ecl) + True + """ + PythonModule.__init__(self, 'sage.libs.ecl') + + class sage__libs__flint(JoinFeature): r""" A :class:`sage.features.Feature` describing the presence of :mod:`sage.libs.flint` @@ -980,6 +1002,7 @@ def all_features(): sage__geometry__polyhedron(), sage__graphs(), sage__groups(), + sage__libs__ecl(), sage__libs__flint(), sage__libs__gap(), sage__libs__ntl(), From 95f538321b5adc51df37e37959ca065b0a394b0d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Jun 2023 22:20:07 -0700 Subject: [PATCH 117/150] src/sage/features/__init__.py: Add # optional --- src/sage/features/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index 0c421a2540e..2695d2596ad 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -405,7 +405,7 @@ def unhide(self): sage: from sage.features.gap import GapPackage sage: Polycyclic = GapPackage("polycyclic", spkg="gap_packages") sage: Polycyclic.hide() - sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) + sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # optional - sage.libs.gap Traceback (most recent call last): ... FeatureNotPresentError: gap_package_polycyclic is not available. @@ -413,7 +413,7 @@ def unhide(self): Use method `unhide` to make it available again. sage: Polycyclic.unhide() - sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) + sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # optional - sage.libs.gap Pcp-group with orders [ 0, 3, 4 ] """ self._hidden = False From fb689a24239a51dbcdff7beec3fc43a108d6a7bf Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Jun 2023 16:59:17 -0700 Subject: [PATCH 118/150] ./sage -fixdoctests src/sage/features/__init__.py --- src/sage/features/__init__.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index 2695d2596ad..89a7b184a4c 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -232,7 +232,7 @@ def require(self): EXAMPLES:: sage: from sage.features.gap import GapPackage - sage: GapPackage("ve1EeThu").require() # optional - sage.libs.gap + sage: GapPackage("ve1EeThu").require() # needs sage.libs.gap Traceback (most recent call last): ... FeatureNotPresentError: gap_package_ve1EeThu is not available. @@ -379,7 +379,7 @@ def hide(self): sage: from sage.features.graph_generators import Benzene sage: Benzene().hide() - sage: len(list(graphs.fusenes(2))) + sage: len(list(graphs.fusenes(2))) # needs sage.graphs Traceback (most recent call last): ... FeatureNotPresentError: benzene is not available. @@ -387,7 +387,7 @@ def hide(self): Use method `unhide` to make it available again. sage: Benzene().unhide() - sage: len(list(graphs.fusenes(2))) # optional benzene + sage: len(list(graphs.fusenes(2))) # optional - benzene, needs sage.graphs 1 """ self._hidden = True @@ -405,7 +405,7 @@ def unhide(self): sage: from sage.features.gap import GapPackage sage: Polycyclic = GapPackage("polycyclic", spkg="gap_packages") sage: Polycyclic.hide() - sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # optional - sage.libs.gap + sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # needs sage.libs.gap Traceback (most recent call last): ... FeatureNotPresentError: gap_package_polycyclic is not available. @@ -413,7 +413,7 @@ def unhide(self): Use method `unhide` to make it available again. sage: Polycyclic.unhide() - sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # optional - sage.libs.gap + sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # needs sage.libs.gap Pcp-group with orders [ 0, 3, 4 ] """ self._hidden = False @@ -452,7 +452,7 @@ def __str__(self): EXAMPLES:: sage: from sage.features.gap import GapPackage - sage: GapPackage("gapZuHoh8Uu").require() # indirect doctest # optional - sage.libs.gap + sage: GapPackage("gapZuHoh8Uu").require() # indirect doctest # needs sage.libs.gap Traceback (most recent call last): ... FeatureNotPresentError: gap_package_gapZuHoh8Uu is not available. @@ -485,7 +485,7 @@ class FeatureTestResult(): Explanatory messages might be available as ``reason`` and ``resolution``:: - sage: presence.reason # optional - sage.libs.gap + sage: presence.reason # needs sage.libs.gap '`TestPackageAvailability("NOT_A_PACKAGE")` evaluated to `fail` in GAP.' sage: bool(presence.resolution) False @@ -870,9 +870,10 @@ class CythonFeature(Feature): ....: ....: assert fabs(-1) == 1 ....: ''' - sage: fabs = CythonFeature("fabs", test_code=fabs_test_code, # optional - sage.misc.cython - ....: spkg="gcc", url="https://gnu.org", type="standard") - sage: fabs.is_present() # optional - sage.misc.cython + sage: fabs = CythonFeature("fabs", test_code=fabs_test_code, # needs sage.misc.cython + ....: spkg="gcc", url="https://gnu.org", + ....: type="standard") + sage: fabs.is_present() # needs sage.misc.cython FeatureTestResult('fabs', True) Test various failures:: @@ -923,7 +924,7 @@ def _is_present(self): sage: from sage.features import CythonFeature sage: empty = CythonFeature("empty", test_code="") - sage: empty.is_present() # optional - sage.misc.cython + sage: empty.is_present() # needs sage.misc.cython FeatureTestResult('empty', True) """ from sage.misc.temporary_file import tmp_filename From 7a002a4205499ffc3d8bf58eb06bb20e5943c4b6 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 7 Jul 2023 06:49:20 -0700 Subject: [PATCH 119/150] src/sage/features/interfaces.py: Revert unintended change --- src/sage/features/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/features/interfaces.py b/src/sage/features/interfaces.py index 21d7c1bedfc..ddfc3b9b7ee 100644 --- a/src/sage/features/interfaces.py +++ b/src/sage/features/interfaces.py @@ -32,7 +32,7 @@ class InterfaceFeature(Feature): sage: also_broken = InterfaceFeature("also_broken_interface", "sage.interfaces.interface") sage: also_broken.is_present() - FeatureTestResult('sage.interfaces.interface', False) + FeatureTestResult('also_broken_interface', False) sage: _.reason "Interface also_broken_interface cannot be imported: module 'sage.interfaces.interface' has no attribute 'also_broken_interface'" """ From db3569b56c14795a17525e46dca575367e2e56fa Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 7 Jul 2023 07:23:59 -0700 Subject: [PATCH 120/150] src/sage/doctest: Update copyright --- src/sage/doctest/control.py | 18 ++++++++++++++---- src/sage/doctest/external.py | 7 ++++++- src/sage/doctest/fixtures.py | 2 +- src/sage/doctest/forker.py | 20 ++++++++++++++++---- src/sage/doctest/parsing.py | 16 +++++++++++++--- src/sage/doctest/reporting.py | 14 ++++++++++---- src/sage/doctest/sources.py | 14 +++++++++++--- src/sage/doctest/util.py | 7 +++++-- 8 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index f2e581037b6..b1497fee500 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -8,10 +8,20 @@ - David Roe (2012-03-27) -- initial version, based on Robert Bradshaw's code. """ # **************************************************************************** -# Copyright (C) 2012 David Roe -# Robert Bradshaw -# William Stein -# Copyright (C) 2016 Jeroen Demeyer +# Copyright (C) 2012-2013 David Roe +# 2012-2013 Robert Bradshaw +# 2012 William Stein +# 2013 R. Andrew Ohana +# 2013-2014 Volker Braun +# 2013-2018 Jeroen Demeyer +# 2013-2021 John H. Palmieri +# 2017 Erik M. Bray +# 2017-2021 Frédéric Chapoton +# 2018 Sébastien Labbé +# 2019 François Bissey +# 2020-2023 Matthias Koeppe +# 2022 Michael Orlitzky +# 2022 Sebastian Oehms # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/sage/doctest/external.py b/src/sage/doctest/external.py index 60299862755..8744bd93e72 100644 --- a/src/sage/doctest/external.py +++ b/src/sage/doctest/external.py @@ -16,7 +16,12 @@ """ #***************************************************************************** -# Copyright (C) 2016 KWANKYU LEE +# Copyright (C) 2016 Kwankyu Lee +# 2018 Thierry Monteil +# 2018-2021 Sébastien Labbé +# 2019 Markus Wageringel +# 2020 John H. Palmieri +# 2021 Matthias Koeppe # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/sage/doctest/fixtures.py b/src/sage/doctest/fixtures.py index e6f8fc45f8e..a3b5e9edda7 100644 --- a/src/sage/doctest/fixtures.py +++ b/src/sage/doctest/fixtures.py @@ -33,7 +33,7 @@ """ #***************************************************************************** -# Copyright (C) 2014 Martin von Gagern +# Copyright (C) 2014-2015 Martin von Gagern # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 515cbf96795..538a11442a5 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -22,10 +22,22 @@ """ # **************************************************************************** -# Copyright (C) 2012 David Roe -# Robert Bradshaw -# William Stein -# Copyright (C) 2013-2015 Jeroen Demeyer +# Copyright (C) 2012-2013 David Roe +# 2012 Robert Bradshaw +# 2012 William Stein +# 2013 R. Andrew Ohana +# 2013-2018 Jeroen Demeyer +# 2013-2020 John H. Palmieri +# 2013-2017 Volker Braun +# 2014 André Apitzsch +# 2014 Darij Grinberg +# 2016-2021 Frédéric Chapoton +# 2017-2019 Erik M. Bray +# 2018 Julian Rüth +# 2020 Jonathan Kliem +# 2020-2023 Matthias Koeppe +# 2022 Markus Wageringel +# 2022 Michael Orlitzky # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 2cc7cbb4897..b876961ab1a 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -13,9 +13,19 @@ """ # **************************************************************************** -# Copyright (C) 2012 David Roe -# Robert Bradshaw -# William Stein +# Copyright (C) 2012-2018 David Roe +# 2012 Robert Bradshaw +# 2012 William Stein +# 2013 R. Andrew Ohana +# 2013 Volker Braun +# 2013-2018 Jeroen Demeyer +# 2016-2021 Frédéric Chapoton +# 2017-2018 Erik M. Bray +# 2020 Marc Mezzarobba +# 2020-2023 Matthias Koeppe +# 2022 John H. Palmieri +# 2022 Sébastien Labbé +# 2023 Kwankyu Lee # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of diff --git a/src/sage/doctest/reporting.py b/src/sage/doctest/reporting.py index 84ef0cf45cc..c2b4537d596 100644 --- a/src/sage/doctest/reporting.py +++ b/src/sage/doctest/reporting.py @@ -23,10 +23,16 @@ """ # **************************************************************************** -# Copyright (C) 2012 David Roe -# Robert Bradshaw -# William Stein -# Copyright (C) 2013 Jeroen Demeyer +# Copyright (C) 2012-2013 David Roe +# 2012 Robert Bradshaw +# 2012 William Stein +# 2013 R. Andrew Ohana +# 2013 Jeroen Demeyer +# 2013-2017 Volker Braun +# 2018 Julian Rüth +# 2018-2021 Sébastien Labbé +# 2020 Samuel Lelièvre +# 2022 Matthias Koeppe # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index c7317f3568b..b49bd1b674b 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -10,9 +10,17 @@ """ # **************************************************************************** -# Copyright (C) 2012 David Roe -# Robert Bradshaw -# William Stein +# Copyright (C) 2012-2013 David Roe +# 2012 Robert Bradshaw +# 2012 William Stein +# 2013 R. Andrew Ohana +# 2013-2017 Jeroen Demeyer +# 2013-2019 John H. Palmieri +# 2014 Volker Braun +# 2014-2022 Frédéric Chapoton +# 2017 Erik M. Bray +# 2021 Sébastien Labbé +# 2021-2023 Matthias Koeppe # # Distributed under the terms of the GNU General Public License (GPL) # diff --git a/src/sage/doctest/util.py b/src/sage/doctest/util.py index 029611497a3..7446373eae0 100644 --- a/src/sage/doctest/util.py +++ b/src/sage/doctest/util.py @@ -10,8 +10,11 @@ # **************************************************************************** # Copyright (C) 2012 David Roe -# Robert Bradshaw -# William Stein +# 2012 Robert Bradshaw +# 2012 William Stein +# 2013 Jeroen Demeyer +# 2014 Volker Braun +# 2017 Frédéric Chapoton # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of From 9109d0ed4352bd97e63493620b1f1e918a893050 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 7 Jul 2023 07:32:56 -0700 Subject: [PATCH 121/150] src/bin/sage-fixdoctests: Add copyright --- src/bin/sage-fixdoctests | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 1308465a7ee..f26eefd769e 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -14,6 +14,22 @@ AUTHORS:: situations when either the expected output or computed output are empty. Added doctest to sage.tests.cmdline """ + +# **************************************************************************** +# Copyright (C) 2006 William Stein +# 2009 Nicolas M. Thiery +# 2013 Andrew Mathas +# 2014 Volker Braun +# 2020 Jonathan Kliem +# 2021 Frédéric Chapoton +# 2023 Matthias Koeppe +# +# 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. +# https://www.gnu.org/licenses/ +# **************************************************************************** + import itertools import os import re From ba8dd562a9eb9b18fe4d6ba734c9d41289e1ee37 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 7 Jul 2023 07:47:13 -0700 Subject: [PATCH 122/150] src/sage/rings/polynomial/multi_polynomial_ideal.py: Fix doctest that was not being run --- .../polynomial/multi_polynomial_ideal.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal.py b/src/sage/rings/polynomial/multi_polynomial_ideal.py index 7bf29415157..9520251a300 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ideal.py +++ b/src/sage/rings/polynomial/multi_polynomial_ideal.py @@ -1046,38 +1046,38 @@ def triangular_decomposition(self, algorithm=None, singular=singular_default): EXAMPLES:: - sage: P. = PolynomialRing(QQ, 5, order='lex'); P.rename("P") + sage: P. = PolynomialRing(QQ, 5, order='lex') sage: I = sage.rings.ideal.Cyclic(P) sage: GB = Ideal(I.groebner_basis('libsingular:stdfglm')) sage: GB.triangular_decomposition('singular:triangLfak') - [Ideal (a - 1, b - 1, c - 1, d^2 + 3*d + 1, e + d + 3) of P, - Ideal (a - 1, b - 1, c^2 + 3*c + 1, d + c + 3, e - 1) of P, - Ideal (a - 1, b^2 + 3*b + 1, c + b + 3, d - 1, e - 1) of P, + [Ideal (a - 1, b - 1, c - 1, d^2 + 3*d + 1, e + d + 3) of Multivariate Polynomial Ring in e, d, c, b, a over Rational Field, + Ideal (a - 1, b - 1, c^2 + 3*c + 1, d + c + 3, e - 1) of Multivariate Polynomial Ring in e, d, c, b, a over Rational Field, + Ideal (a - 1, b^2 + 3*b + 1, c + b + 3, d - 1, e - 1) of Multivariate Polynomial Ring in e, d, c, b, a over Rational Field, Ideal (a - 1, b^4 + b^3 + b^2 + b + 1, -c + b^2, -d + b^3, - e + b^3 + b^2 + b + 1) of P, - Ideal (a^2 + 3*a + 1, b - 1, c - 1, d - 1, e + a + 3) of P, - Ideal (a^2 + 3*a + 1, b + a + 3, c - 1, d - 1, e - 1) of P, + e + b^3 + b^2 + b + 1) of Multivariate Polynomial Ring in e, d, c, b, a over Rational Field, + Ideal (a^2 + 3*a + 1, b - 1, c - 1, d - 1, e + a + 3) of Multivariate Polynomial Ring in e, d, c, b, a over Rational Field, + Ideal (a^2 + 3*a + 1, b + a + 3, c - 1, d - 1, e - 1) of Multivariate Polynomial Ring in e, d, c, b, a over Rational Field, Ideal (a^4 - 4*a^3 + 6*a^2 + a + 1, -11*b^2 + 6*b*a^3 - 26*b*a^2 + 41*b*a - 4*b - 8*a^3 + 31*a^2 - 40*a - 24, 11*c + 3*a^3 - 13*a^2 + 26*a - 2, 11*d + 3*a^3 - 13*a^2 + 26*a - 2, - -11*e - 11*b + 6*a^3 - 26*a^2 + 41*a - 4) of P, + -11*e - 11*b + 6*a^3 - 26*a^2 + 41*a - 4) of Multivariate Polynomial Ring in e, d, c, b, a over Rational Field, Ideal (a^4 + a^3 + a^2 + a + 1, - b - 1, c + a^3 + a^2 + a + 1, -d + a^3, -e + a^2) of P, + b - 1, c + a^3 + a^2 + a + 1, -d + a^3, -e + a^2) of Multivariate Polynomial Ring in e, d, c, b, a over Rational Field, Ideal (a^4 + a^3 + a^2 + a + 1, - b - a, c - a, d^2 + 3*d*a + a^2, e + d + 3*a) of P, + b - a, c - a, d^2 + 3*d*a + a^2, e + d + 3*a) of Multivariate Polynomial Ring in e, d, c, b, a over Rational Field, Ideal (a^4 + a^3 + a^2 + a + 1, - b - a, c^2 + 3*c*a + a^2, d + c + 3*a, e - a) of P, + b - a, c^2 + 3*c*a + a^2, d + c + 3*a, e - a) of Multivariate Polynomial Ring in e, d, c, b, a over Rational Field, Ideal (a^4 + a^3 + a^2 + a + 1, - b^2 + 3*b*a + a^2, c + b + 3*a, d - a, e - a) of P, + b^2 + 3*b*a + a^2, c + b + 3*a, d - a, e - a) of Multivariate Polynomial Ring in e, d, c, b, a over Rational Field, Ideal (a^4 + a^3 + a^2 + a + 1, b^3 + b^2*a + b^2 + b*a^2 + b*a + b + a^3 + a^2 + a + 1, c + b^2*a^3 + b^2*a^2 + b^2*a + b^2, -d + b^2*a^2 + b^2*a + b^2 + b*a^2 + b*a + a^2, - -e + b^2*a^3 - b*a^2 - b*a - b - a^2 - a) of P, + -e + b^2*a^3 - b*a^2 - b*a - b - a^2 - a) of Multivariate Polynomial Ring in e, d, c, b, a over Rational Field, Ideal (a^4 + a^3 + 6*a^2 - 4*a + 1, -11*b^2 + 6*b*a^3 + 10*b*a^2 + 39*b*a + 2*b + 16*a^3 + 23*a^2 + 104*a - 24, 11*c + 3*a^3 + 5*a^2 + 25*a + 1, 11*d + 3*a^3 + 5*a^2 + 25*a + 1, - -11*e - 11*b + 6*a^3 + 10*a^2 + 39*a + 2) of P] + -11*e - 11*b + 6*a^3 + 10*a^2 + 39*a + 2) of Multivariate Polynomial Ring in e, d, c, b, a over Rational Field] sage: R. = PolynomialRing(QQ, 2, order='lex') sage: f1 = 1/2*((x1^2 + 2*x1 - 4)*x2^2 + 2*(x1^2 + x1)*x2 + x1^2) From c5d2adfab6e506cd0febf4ad9f1600a9af1e600b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 7 Jul 2023 07:54:05 -0700 Subject: [PATCH 123/150] ./sage -fixdoctests src/sage/rings/function_field/ideal_polymod.py --- .../rings/function_field/ideal_polymod.py | 997 ++++++++++-------- 1 file changed, 537 insertions(+), 460 deletions(-) diff --git a/src/sage/rings/function_field/ideal_polymod.py b/src/sage/rings/function_field/ideal_polymod.py index 936b63b48fe..6735dd6a8fa 100644 --- a/src/sage/rings/function_field/ideal_polymod.py +++ b/src/sage/rings/function_field/ideal_polymod.py @@ -44,10 +44,11 @@ class FunctionFieldIdeal_polymod(FunctionFieldIdeal): EXAMPLES:: - sage: K. = FunctionField(GF(2)); R. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(y^2 - x^3*y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: O.ideal(y) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); R. = K[] + sage: L. = K.extension(y^2 - x^3*y - x) + sage: O = L.maximal_order() + sage: O.ideal(y) Ideal (y) of Maximal order of Function field in y defined by y^2 + x^3*y + x """ def __init__(self, ring, hnf, denominator=1): @@ -56,11 +57,12 @@ def __init__(self, ring, hnf, denominator=1): TESTS:: - sage: K. = FunctionField(GF(2)); R. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(y^2 - x^3*y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: TestSuite(I).run() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); R. = K[] + sage: L. = K.extension(y^2 - x^3*y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: TestSuite(I).run() """ FunctionFieldIdeal.__init__(self, ring) @@ -96,29 +98,31 @@ def __bool__(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); R. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(y^2 - x^3*y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y); I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); R. = K[] + sage: L. = K.extension(y^2 - x^3*y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(y); I Ideal (y) of Maximal order of Function field in y defined by y^2 + x^3*y + x - sage: I.is_zero() # optional - sage.rings.finite_rings + sage: I.is_zero() False - sage: J = 0*I; J # optional - sage.rings.finite_rings + sage: J = 0*I; J Zero ideal of Maximal order of Function field in y defined by y^2 + x^3*y + x - sage: J.is_zero() # optional - sage.rings.finite_rings + sage: J.is_zero() True - sage: K. = FunctionField(GF(2)); _.=K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y); I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _.=K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y); I Ideal (y) of Maximal order of Function field in y defined by y^2 + y + (x^2 + 1)/x - sage: I.is_zero() # optional - sage.rings.finite_rings + sage: I.is_zero() False - sage: J = 0*I; J # optional - sage.rings.finite_rings + sage: J = 0*I; J Zero ideal of Maximal order of Function field in y defined by y^2 + y + (x^2 + 1)/x - sage: J.is_zero() # optional - sage.rings.finite_rings + sage: J.is_zero() True """ return self._hnf.nrows() != 0 @@ -129,17 +133,19 @@ def __hash__(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 - x^3*Y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(1/y) # optional - sage.rings.finite_rings - sage: { I: 2 }[I] == 2 # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(1/y) + sage: { I: 2 }[I] == 2 True - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(1/y) # optional - sage.rings.finite_rings - sage: { I: 2 }[I] == 2 # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(1/y) + sage: { I: 2 }[I] == 2 True """ return hash((self._ring, self._hnf, self._denominator)) @@ -150,30 +156,32 @@ def __contains__(self, x): EXAMPLES:: - sage: K. = FunctionField(GF(7)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 - x^3 - 1) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal([y]); I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(7)); _. = K[] + sage: L. = K.extension(Y^2 - x^3 - 1) + sage: O = L.maximal_order() + sage: I = O.ideal([y]); I Ideal (y) of Maximal order of Function field in y defined by y^2 + 6*x^3 + 6 - sage: x * y in I # optional - sage.rings.finite_rings + sage: x * y in I True - sage: y / x in I # optional - sage.rings.finite_rings + sage: y / x in I False - sage: y^2 - 2 in I # optional - sage.rings.finite_rings + sage: y^2 - 2 in I False - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal([y]); I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal([y]); I Ideal (y) of Maximal order of Function field in y defined by y^2 + y + (x^2 + 1)/x - sage: x * y in I # optional - sage.rings.finite_rings + sage: x * y in I True - sage: y / x in I # optional - sage.rings.finite_rings + sage: y / x in I False - sage: y^2 - 2 in I # optional - sage.rings.finite_rings + sage: y^2 - 2 in I False sage: K. = FunctionField(QQ); _. = K[] @@ -219,30 +227,32 @@ def __invert__(self): EXAMPLES:: - sage: K. = FunctionField(GF(7)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 - x^3 - 1) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: ~I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(7)); _. = K[] + sage: L. = K.extension(Y^2 - x^3 - 1) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: ~I Ideal ((1/(x^3 + 1))*y) of Maximal order of Function field in y defined by y^2 + 6*x^3 + 6 - sage: I^(-1) # optional - sage.rings.finite_rings + sage: I^(-1) Ideal ((1/(x^3 + 1))*y) of Maximal order of Function field in y defined by y^2 + 6*x^3 + 6 - sage: ~I * I # optional - sage.rings.finite_rings + sage: ~I * I Ideal (1) of Maximal order of Function field in y defined by y^2 + 6*x^3 + 6 :: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: ~I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: ~I Ideal ((x/(x^2 + 1))*y + x/(x^2 + 1)) of Maximal order of Function field in y defined by y^2 + y + (x^2 + 1)/x - sage: I^(-1) # optional - sage.rings.finite_rings + sage: I^(-1) Ideal ((x/(x^2 + 1))*y + x/(x^2 + 1)) of Maximal order of Function field in y defined by y^2 + y + (x^2 + 1)/x - sage: ~I * I # optional - sage.rings.finite_rings + sage: ~I * I Ideal (1) of Maximal order of Function field in y defined by y^2 + y + (x^2 + 1)/x :: @@ -291,28 +301,30 @@ def _richcmp_(self, other, op): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 - x^3*Y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(1/y) # optional - sage.rings.finite_rings - sage: I == I + I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(1/y) + sage: I == I + I True - sage: I == I * I # optional - sage.rings.finite_rings + sage: I == I * I False :: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(1/y) # optional - sage.rings.finite_rings - sage: I == I + I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(1/y) + sage: I == I + I True - sage: I == I * I # optional - sage.rings.finite_rings + sage: I == I * I False - sage: I < I * I # optional - sage.rings.finite_rings + sage: I < I * I True - sage: I > I * I # optional - sage.rings.finite_rings + sage: I > I * I False """ return richcmp((self._denominator, self._hnf), (other._denominator, other._hnf), op) @@ -323,19 +335,21 @@ def _add_(self, other): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 - x^3*Y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: J = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: I + J # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: J = O.ideal(x + y) + sage: I + J Ideal (y) of Maximal order of Function field in y defined by y^2 + x^3*y + x - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: J = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: I + J # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: J = O.ideal(x + y) + sage: I + J Ideal (1, y) of Maximal order of Function field in y defined by y^2 + y + (x^2 + 1)/x """ ds = self._denominator @@ -350,20 +364,22 @@ def _mul_(self, other): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 - x^3*Y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: J = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: I * J # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: J = O.ideal(x + y) + sage: I * J Ideal (x^4 + x^2 + x, x*y + x^2) of Maximal order of Function field in y defined by y^2 + x^3*y + x - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: J = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: I * J # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: J = O.ideal(x + y) + sage: I * J Ideal ((x + 1)*y + (x^2 + 1)/x) of Maximal order of Function field in y defined by y^2 + y + (x^2 + 1)/x """ @@ -401,19 +417,21 @@ def _acted_upon_(self, other, on_left): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 - x^3*Y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: J = O.ideal(x) # optional - sage.rings.finite_rings - sage: x * I == I * J # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(x + y) + sage: J = O.ideal(x) + sage: x * I == I * J True - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: J = O.ideal(x) # optional - sage.rings.finite_rings - sage: x * I == I * J # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(x + y) + sage: J = O.ideal(x) + sage: x * I == I * J True """ from sage.modules.free_module_element import vector @@ -441,12 +459,13 @@ def intersect(self, other): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 - x^3*Y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: J = O.ideal(x) # optional - sage.rings.finite_rings - sage: I.intersect(J) == I * J * (I + J)^-1 # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(x + y) + sage: J = O.ideal(x) + sage: I.intersect(J) == I * J * (I + J)^-1 True """ @@ -486,10 +505,11 @@ def hnf(self): EXAMPLES:: - sage: K. = FunctionField(GF(7)); R. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(y^2 - x^3 - 1) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y*(y+1)); I.hnf() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.maximal_order() + sage: I = O.ideal(y*(y+1)); I.hnf() [x^6 + x^3 0] [ x^3 + 1 1] @@ -510,13 +530,14 @@ def denominator(self): EXAMPLES:: - sage: K. = FunctionField(GF(7)); R. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(y^2 - x^3 - 1) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y/(y+1)) # optional - sage.rings.finite_rings - sage: d = I.denominator(); d # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.maximal_order() + sage: I = O.ideal(y/(y+1)) + sage: d = I.denominator(); d x^3 - sage: d in O # optional - sage.rings.finite_rings + sage: d in O True :: @@ -540,11 +561,12 @@ def module(self): EXAMPLES:: - sage: K. = FunctionField(GF(7)); R. = K[] # optional - sage.rings.finite_rings - sage: F. = K.extension(y^2 - x^3 - 1) # optional - sage.rings.finite_rings - sage: O = F.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x, 1/y) # optional - sage.rings.finite_rings - sage: I.module() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(7)); R. = K[] + sage: F. = K.extension(y^2 - x^3 - 1) + sage: O = F.maximal_order() + sage: I = O.ideal(x, 1/y) + sage: I.module() Free module of degree 2 and rank 2 over Maximal order of Rational function field in x over Finite Field of size 7 Echelon basis matrix: @@ -564,17 +586,19 @@ def gens_over_base(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 - x^3*Y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: I.gens_over_base() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(x + y) + sage: I.gens_over_base() (x^4 + x^2 + x, y + x) - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: I.gens_over_base() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(x + y) + sage: I.gens_over_base() (x^3 + 1, y + x) """ gens, d = self._gens_over_base @@ -588,11 +612,12 @@ def _gens_over_base(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); R. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(y^2 - x^3*y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(1/y) # optional - sage.rings.finite_rings - sage: I._gens_over_base # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); R. = K[] + sage: L. = K.extension(y^2 - x^3*y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(1/y) + sage: I._gens_over_base ([x, y], x) """ gens = [] @@ -609,17 +634,19 @@ def gens(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 - x^3*Y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: I.gens() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(x + y) + sage: I.gens() (x^4 + x^2 + x, y + x) - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: I.gens() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(x + y) + sage: I.gens() (x^3 + 1, y + x) """ return self.gens_over_base() @@ -634,11 +661,12 @@ def basis_matrix(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); R. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) # optional - sage.rings.finite_rings - sage: O = F.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x, 1/y) # optional - sage.rings.finite_rings - sage: I.denominator() * I.basis_matrix() == I.hnf() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(x, 1/y) + sage: I.denominator() * I.basis_matrix() == I.hnf() True """ d = self.denominator() @@ -654,24 +682,26 @@ def is_integral(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) # optional - sage.rings.finite_rings - sage: O = F.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x, 1/y) # optional - sage.rings.finite_rings - sage: I.is_integral() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(x, 1/y) + sage: I.is_integral() False - sage: J = I.denominator() * I # optional - sage.rings.finite_rings - sage: J.is_integral() # optional - sage.rings.finite_rings + sage: J = I.denominator() * I + sage: J.is_integral() True - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x, 1/y) # optional - sage.rings.finite_rings - sage: I.is_integral() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(x, 1/y) + sage: I.is_integral() False - sage: J = I.denominator() * I # optional - sage.rings.finite_rings - sage: J.is_integral() # optional - sage.rings.finite_rings + sage: J = I.denominator() * I + sage: J.is_integral() True sage: K. = FunctionField(QQ); _. = PolynomialRing(K) @@ -694,29 +724,31 @@ def ideal_below(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) # optional - sage.rings.finite_rings - sage: O = F.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x, 1/y) # optional - sage.rings.finite_rings - sage: I.ideal_below() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(x, 1/y) + sage: I.ideal_below() Traceback (most recent call last): ... TypeError: not an integral ideal - sage: J = I.denominator() * I # optional - sage.rings.finite_rings - sage: J.ideal_below() # optional - sage.rings.finite_rings + sage: J = I.denominator() * I + sage: J.ideal_below() Ideal (x^3 + x^2 + x) of Maximal order of Rational function field in x over Finite Field of size 2 - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x, 1/y) # optional - sage.rings.finite_rings - sage: I.ideal_below() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(x, 1/y) + sage: I.ideal_below() Traceback (most recent call last): ... TypeError: not an integral ideal - sage: J = I.denominator() * I # optional - sage.rings.finite_rings - sage: J.ideal_below() # optional - sage.rings.finite_rings + sage: J = I.denominator() * I + sage: J.ideal_below() Ideal (x^3 + x) of Maximal order of Rational function field in x over Finite Field of size 2 @@ -760,38 +792,40 @@ def norm(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) # optional - sage.rings.finite_rings - sage: O = F.maximal_order() # optional - sage.rings.finite_rings - sage: i1 = O.ideal(x) # optional - sage.rings.finite_rings - sage: i2 = O.ideal(y) # optional - sage.rings.finite_rings - sage: i3 = i1 * i2 # optional - sage.rings.finite_rings - sage: i3.norm() == i1.norm() * i2.norm() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: i1 = O.ideal(x) + sage: i2 = O.ideal(y) + sage: i3 = i1 * i2 + sage: i3.norm() == i1.norm() * i2.norm() True - sage: i1.norm() # optional - sage.rings.finite_rings + sage: i1.norm() x^3 - sage: i1.norm() == x ** F.degree() # optional - sage.rings.finite_rings + sage: i1.norm() == x ** F.degree() True - sage: i2.norm() # optional - sage.rings.finite_rings + sage: i2.norm() x^6 + x^4 + x^2 - sage: i2.norm() == y.norm() # optional - sage.rings.finite_rings + sage: i2.norm() == y.norm() True - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: i1 = O.ideal(x) # optional - sage.rings.finite_rings - sage: i2 = O.ideal(y) # optional - sage.rings.finite_rings - sage: i3 = i1 * i2 # optional - sage.rings.finite_rings - sage: i3.norm() == i1.norm() * i2.norm() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: i1 = O.ideal(x) + sage: i2 = O.ideal(y) + sage: i3 = i1 * i2 + sage: i3.norm() == i1.norm() * i2.norm() True - sage: i1.norm() # optional - sage.rings.finite_rings + sage: i1.norm() x^2 - sage: i1.norm() == x ** L.degree() # optional - sage.rings.finite_rings + sage: i1.norm() == x ** L.degree() True - sage: i2.norm() # optional - sage.rings.finite_rings + sage: i2.norm() (x^2 + 1)/x - sage: i2.norm() == y.norm() # optional - sage.rings.finite_rings + sage: i2.norm() == y.norm() True """ n = 1 @@ -806,18 +840,20 @@ def is_prime(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) # optional - sage.rings.finite_rings - sage: O = F.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: [f.is_prime() for f,_ in I.factor()] # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(y) + sage: [f.is_prime() for f,_ in I.factor()] [True, True] - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: [f.is_prime() for f,_ in I.factor()] # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: [f.is_prime() for f,_ in I.factor()] [True, True] sage: K. = FunctionField(QQ); _. = PolynomialRing(K) @@ -854,21 +890,23 @@ def valuation(self, ideal): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) # optional - sage.rings.finite_rings - sage: O = F.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x, (1/(x^3 + x^2 + x))*y^2) # optional - sage.rings.finite_rings - sage: I.is_prime() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(x, (1/(x^3 + x^2 + x))*y^2) + sage: I.is_prime() True - sage: J = O.ideal(y) # optional - sage.rings.finite_rings - sage: I.valuation(J) # optional - sage.rings.finite_rings + sage: J = O.ideal(y) + sage: I.valuation(J) 2 - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: [f.valuation(I) for f,_ in I.factor()] # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: [f.valuation(I) for f,_ in I.factor()] [-1, 2] The method closely follows Algorithm 4.8.17 of [Coh1993]_. @@ -923,20 +961,22 @@ def prime_below(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: F. = K.extension(Y^3 - x^2*(x^2 + x + 1)^2) # optional - sage.rings.finite_rings - sage: O = F.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: [f.prime_below() for f,_ in I.factor()] # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^3 - x^2*(x^2 + x + 1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(y) + sage: [f.prime_below() for f,_ in I.factor()] [Ideal (x) of Maximal order of Rational function field in x over Finite Field of size 2, Ideal (x^2 + x + 1) of Maximal order of Rational function field in x over Finite Field of size 2] - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: [f.prime_below() for f,_ in I.factor()] # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: [f.prime_below() for f,_ in I.factor()] [Ideal (x) of Maximal order of Rational function field in x over Finite Field of size 2, Ideal (x + 1) of Maximal order of Rational function field in x over Finite Field of size 2] @@ -956,11 +996,12 @@ def _factor(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) # optional - sage.rings.finite_rings - sage: O = F.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: I == I.factor().prod() # indirect doctest # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(y) + sage: I == I.factor().prod() # indirect doctest True """ O = self.ring() @@ -999,10 +1040,11 @@ class FunctionFieldIdeal_global(FunctionFieldIdeal_polymod): EXAMPLES:: - sage: K. = FunctionField(GF(2)); R. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(y^2 - x^3*y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: O.ideal(y) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); R. = K[] + sage: L. = K.extension(y^2 - x^3*y - x) + sage: O = L.maximal_order() + sage: O.ideal(y) Ideal (y) of Maximal order of Function field in y defined by y^2 + x^3*y + x """ def __init__(self, ring, hnf, denominator=1): @@ -1011,11 +1053,12 @@ def __init__(self, ring, hnf, denominator=1): TESTS:: - sage: K. = FunctionField(GF(5)); R. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(y^2 - x^3*y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: TestSuite(I).run() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(5)); R. = K[] + sage: L. = K.extension(y^2 - x^3*y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: TestSuite(I).run() """ FunctionFieldIdeal_polymod.__init__(self, ring, hnf, denominator) @@ -1028,18 +1071,19 @@ def __pow__(self, mod): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^7 - x^3*Y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: J = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: S = I / J # optional - sage.rings.finite_rings - sage: a = S^100 # optional - sage.rings.finite_rings - sage: _ = S.gens_two() # optional - sage.rings.finite_rings - sage: b = S^100 # faster # optional - sage.rings.finite_rings - sage: b == I^100 / J^100 # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^7 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: J = O.ideal(x + y) + sage: S = I / J + sage: a = S^100 + sage: _ = S.gens_two() + sage: b = S^100 # faster + sage: b == I^100 / J^100 True - sage: b == a # optional - sage.rings.finite_rings + sage: b == a True """ if mod > 2 and self._gens_two_vecs is not None: @@ -1074,17 +1118,19 @@ def gens(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 - x^3*Y - x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: I.gens() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(x + y) + sage: I.gens() (x^4 + x^2 + x, y + x) - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x + y) # optional - sage.rings.finite_rings - sage: I.gens() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(x + y) + sage: I.gens() (x^3 + 1, y + x) """ if self._gens_two.is_in_cache(): @@ -1100,25 +1146,27 @@ def gens_two(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) # optional - sage.rings.finite_rings - sage: O = F.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: I # indirect doctest # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(y) + sage: I # indirect doctest Ideal (y) of Maximal order of Function field in y defined by y^3 + x^6 + x^4 + x^2 - sage: ~I # indirect doctest # optional - sage.rings.finite_rings + sage: ~I # indirect doctest Ideal ((1/(x^6 + x^4 + x^2))*y^2) of Maximal order of Function field in y defined by y^3 + x^6 + x^4 + x^2 - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: O = L.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(y) # optional - sage.rings.finite_rings - sage: I # indirect doctest # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: I # indirect doctest Ideal (y) of Maximal order of Function field in y defined by y^2 + y + (x^2 + 1)/x - sage: ~I # indirect doctest # optional - sage.rings.finite_rings + sage: ~I # indirect doctest Ideal ((x/(x^2 + 1))*y + x/(x^2 + 1)) of Maximal order of Function field in y defined by y^2 + y + (x^2 + 1)/x """ @@ -1153,17 +1201,19 @@ def _gens_two(self): EXAMPLES:: - sage: K. = FunctionField(GF(4)); _. = K[] # optional - sage.rings.finite_rings - sage: F. = K.extension(Y^3 + x^3*Y + x) # optional - sage.rings.finite_rings - sage: O = F.maximal_order() # optional - sage.rings.finite_rings - sage: I = O.ideal(x^2, x*y, x + y) # optional - sage.rings.finite_rings - sage: I._gens_two() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(4)); _. = K[] + sage: F. = K.extension(Y^3 + x^3*Y + x) + sage: O = F.maximal_order() + sage: I = O.ideal(x^2, x*y, x + y) + sage: I._gens_two() (x, y) - sage: K. = FunctionField(GF(3)) # optional - sage.rings.finite_rings - sage: _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y - x) # optional - sage.rings.finite_rings - sage: y.zeros()[0].prime_ideal()._gens_two() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3)) + sage: _. = K[] + sage: L. = K.extension(Y - x) + sage: y.zeros()[0].prime_ideal()._gens_two() (x,) """ O = self.ring() @@ -1264,10 +1314,11 @@ class FunctionFieldIdealInfinite_polymod(FunctionFieldIdealInfinite): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: Oinf.ideal(1/y) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: Oinf.ideal(1/y) Ideal (1/x^4*y^2) of Maximal infinite order of Function field in y defined by y^3 + y^2 + 2*x^4 """ @@ -1277,11 +1328,12 @@ def __init__(self, ring, ideal): TESTS:: - sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/y) # optional - sage.rings.finite_rings - sage: TestSuite(I).run() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/y) + sage: TestSuite(I).run() """ FunctionFieldIdealInfinite.__init__(self, ring) self._ideal = ideal @@ -1292,17 +1344,19 @@ def __hash__(self): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/y) # optional - sage.rings.finite_rings - sage: d = { I: 1 } # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/y) + sage: d = { I: 1 } - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: Oinf = L.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/y) # optional - sage.rings.finite_rings - sage: d = { I: 1 } # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/y) + sage: d = { I: 1 } """ return hash((self.ring(), self._ideal)) @@ -1316,15 +1370,16 @@ def __contains__(self, x): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/y) # optional - sage.rings.finite_rings - sage: 1/y in I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/y) + sage: 1/y in I True - sage: 1/x in I # optional - sage.rings.finite_rings + sage: 1/x in I False - sage: 1/x^2 in I # optional - sage.rings.finite_rings + sage: 1/x^2 in I True """ F = self.ring().fraction_field() @@ -1341,21 +1396,23 @@ def _add_(self, other): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/x^2*1/y) # optional - sage.rings.finite_rings - sage: J = Oinf.ideal(1/x) # optional - sage.rings.finite_rings - sage: I + J # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/x^2*1/y) + sage: J = Oinf.ideal(1/x) + sage: I + J Ideal (1/x) of Maximal infinite order of Function field in y defined by y^3 + y^2 + 2*x^4 - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: Oinf = L.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/x^2*1/y) # optional - sage.rings.finite_rings - sage: J = Oinf.ideal(1/x) # optional - sage.rings.finite_rings - sage: I + J # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/x^2*1/y) + sage: J = Oinf.ideal(1/x) + sage: I + J Ideal (1/x) of Maximal infinite order of Function field in y defined by y^2 + y + (x^2 + 1)/x """ @@ -1371,21 +1428,23 @@ def _mul_(self, other): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/x^2*1/y) # optional - sage.rings.finite_rings - sage: J = Oinf.ideal(1/x) # optional - sage.rings.finite_rings - sage: I * J # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/x^2*1/y) + sage: J = Oinf.ideal(1/x) + sage: I * J Ideal (1/x^7*y^2) of Maximal infinite order of Function field in y defined by y^3 + y^2 + 2*x^4 - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: Oinf = L.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/x^2*1/y) # optional - sage.rings.finite_rings - sage: J = Oinf.ideal(1/x) # optional - sage.rings.finite_rings - sage: I * J # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/x^2*1/y) + sage: J = Oinf.ideal(1/x) + sage: I * J Ideal (1/x^4*y) of Maximal infinite order of Function field in y defined by y^2 + y + (x^2 + 1)/x """ @@ -1397,11 +1456,12 @@ def __pow__(self, n): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: J = Oinf.ideal(1/x) # optional - sage.rings.finite_rings - sage: J^3 # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: J = Oinf.ideal(1/x) + sage: J^3 Ideal (1/x^3) of Maximal infinite order of Function field in y defined by y^3 + y^2 + 2*x^4 """ @@ -1413,25 +1473,27 @@ def __invert__(self): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: J = Oinf.ideal(y) # optional - sage.rings.finite_rings - sage: ~J # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: J = Oinf.ideal(y) + sage: ~J Ideal (1/x^4*y^2) of Maximal infinite order of Function field in y defined by y^3 + y^2 + 2*x^4 - sage: J * ~J # optional - sage.rings.finite_rings + sage: J * ~J Ideal (1) of Maximal infinite order of Function field in y defined by y^3 + y^2 + 2*x^4 - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: Oinf = L.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: J = Oinf.ideal(y) # optional - sage.rings.finite_rings - sage: ~J # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: J = Oinf.ideal(y) + sage: ~J Ideal (1/x*y) of Maximal infinite order of Function field in y defined by y^2 + y + (x^2 + 1)/x - sage: J * ~J # optional - sage.rings.finite_rings + sage: J * ~J Ideal (1) of Maximal infinite order of Function field in y defined by y^2 + y + (x^2 + 1)/x """ @@ -1443,32 +1505,34 @@ def _richcmp_(self, other, op): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/x^2*1/y) # optional - sage.rings.finite_rings - sage: J = Oinf.ideal(1/x) # optional - sage.rings.finite_rings - sage: I * J == J * I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/x^2*1/y) + sage: J = Oinf.ideal(1/x) + sage: I * J == J * I True - sage: I + J == J # optional - sage.rings.finite_rings + sage: I + J == J True - sage: I + J == I # optional - sage.rings.finite_rings + sage: I + J == I False - sage: (I < J) == (not J < I) # optional - sage.rings.finite_rings + sage: (I < J) == (not J < I) True - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: Oinf = L.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/x^2*1/y) # optional - sage.rings.finite_rings - sage: J = Oinf.ideal(1/x) # optional - sage.rings.finite_rings - sage: I * J == J * I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/x^2*1/y) + sage: J = Oinf.ideal(1/x) + sage: I * J == J * I True - sage: I + J == J # optional - sage.rings.finite_rings + sage: I + J == J True - sage: I + J == I # optional - sage.rings.finite_rings + sage: I + J == I False - sage: (I < J) == (not J < I) # optional - sage.rings.finite_rings + sage: (I < J) == (not J < I) True """ return richcmp(self._ideal, other._ideal, op) @@ -1480,11 +1544,12 @@ def _relative_degree(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: Oinf = L.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/x) # optional - sage.rings.finite_rings - sage: [J._relative_degree for J,_ in I.factor()] # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/x) + sage: [J._relative_degree for J,_ in I.factor()] [1] """ if not self.is_prime(): @@ -1498,18 +1563,20 @@ def gens(self): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(x + y) # optional - sage.rings.finite_rings - sage: I.gens() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(x + y) + sage: I.gens() (x, y, 1/x^2*y^2) - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: Oinf = L.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(x + y) # optional - sage.rings.finite_rings - sage: I.gens() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(x + y) + sage: I.gens() (x, y) """ F = self.ring().fraction_field() @@ -1522,18 +1589,20 @@ def gens_two(self): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(x + y) # optional - sage.rings.finite_rings - sage: I.gens_two() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(x + y) + sage: I.gens_two() (x, y) - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: Oinf = L.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(x + y) # optional - sage.rings.finite_rings - sage: I.gens_two() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(x + y) + sage: I.gens_two() (x,) """ F = self.ring().fraction_field() @@ -1546,11 +1615,12 @@ def gens_over_base(self): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); _. = K[] # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(x + y) # optional - sage.rings.finite_rings - sage: I.gens_over_base() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); _. = K[] + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(x + y) + sage: I.gens_over_base() (x, y, 1/x^2*y^2) """ F = self.ring().fraction_field() @@ -1563,11 +1633,12 @@ def ideal_below(self): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); _. = K[] # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/y^2) # optional - sage.rings.finite_rings - sage: I.ideal_below() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); _. = K[] + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/y^2) + sage: I.ideal_below() Ideal (x^3) of Maximal order of Rational function field in x over Finite Field in z2 of size 3^2 """ @@ -1579,30 +1650,32 @@ def is_prime(self): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/x) # optional - sage.rings.finite_rings - sage: I.factor() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/x) + sage: I.factor() (Ideal (1/x^3*y^2) of Maximal infinite order of Function field in y defined by y^3 + y^2 + 2*x^4)^3 - sage: I.is_prime() # optional - sage.rings.finite_rings + sage: I.is_prime() False - sage: J = I.factor()[0][0] # optional - sage.rings.finite_rings - sage: J.is_prime() # optional - sage.rings.finite_rings + sage: J = I.factor()[0][0] + sage: J.is_prime() True - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: Oinf = L.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/x) # optional - sage.rings.finite_rings - sage: I.factor() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/x) + sage: I.factor() (Ideal (1/x*y) of Maximal infinite order of Function field in y defined by y^2 + y + (x^2 + 1)/x)^2 - sage: I.is_prime() # optional - sage.rings.finite_rings + sage: I.is_prime() False - sage: J = I.factor()[0][0] # optional - sage.rings.finite_rings - sage: J.is_prime() # optional - sage.rings.finite_rings + sage: J = I.factor()[0][0] + sage: J.is_prime() True """ return self._ideal.is_prime() @@ -1614,31 +1687,33 @@ def prime_below(self): EXAMPLES:: - sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 + t^2 - x^4) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/x) # optional - sage.rings.finite_rings - sage: I.factor() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/x) + sage: I.factor() (Ideal (1/x^3*y^2) of Maximal infinite order of Function field in y defined by y^3 + y^2 + 2*x^4)^3 - sage: J = I.factor()[0][0] # optional - sage.rings.finite_rings - sage: J.is_prime() # optional - sage.rings.finite_rings + sage: J = I.factor()[0][0] + sage: J.is_prime() True - sage: J.prime_below() # optional - sage.rings.finite_rings + sage: J.prime_below() Ideal (1/x) of Maximal infinite order of Rational function field in x over Finite Field in z2 of size 3^2 - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: Oinf = L.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(1/x) # optional - sage.rings.finite_rings - sage: I.factor() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/x) + sage: I.factor() (Ideal (1/x*y) of Maximal infinite order of Function field in y defined by y^2 + y + (x^2 + 1)/x)^2 - sage: J = I.factor()[0][0] # optional - sage.rings.finite_rings - sage: J.is_prime() # optional - sage.rings.finite_rings + sage: J = I.factor()[0][0] + sage: J.is_prime() True - sage: J.prime_below() # optional - sage.rings.finite_rings + sage: J.prime_below() Ideal (1/x) of Maximal infinite order of Rational function field in x over Finite Field of size 2 """ @@ -1659,11 +1734,12 @@ def valuation(self, ideal): EXAMPLES:: - sage: K. = FunctionField(GF(2)); _. = K[] # optional - sage.rings.finite_rings - sage: L. = K.extension(Y^2 + Y + x + 1/x) # optional - sage.rings.finite_rings - sage: Oinf = L.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(y) # optional - sage.rings.finite_rings - sage: [f.valuation(I) for f,_ in I.factor()] # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(y) + sage: [f.valuation(I) for f,_ in I.factor()] [-1] """ if not self.is_prime(): @@ -1677,16 +1753,17 @@ def _factor(self): EXAMPLES:: - sage: K. = FunctionField(GF(2)); R. = PolynomialRing(K) # optional - sage.rings.finite_rings - sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) # optional - sage.rings.finite_rings - sage: Oinf = F.maximal_order_infinite() # optional - sage.rings.finite_rings - sage: f = 1/x # optional - sage.rings.finite_rings - sage: I = Oinf.ideal(f) # optional - sage.rings.finite_rings - sage: I._factor() # optional - sage.rings.finite_rings - [(Ideal (1/x, 1/x^4*y^2 + 1/x^2*y + 1) of Maximal infinite order of Function field in y - defined by y^3 + x^6 + x^4 + x^2, 1), - (Ideal (1/x, 1/x^2*y + 1) of Maximal infinite order of Function field in y - defined by y^3 + x^6 + x^4 + x^2, 1)] + sage: # needs sage.rings.finite_rings + sage: K. = FunctionField(GF(2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) + sage: Oinf = F.maximal_order_infinite() + sage: f = 1/x + sage: I = Oinf.ideal(f) + sage: I._factor() + [(Ideal ((1/(x^4 + x^3 + x^2))*y^2 + 1/x^2*y + 1) of Maximal infinite order of Function field in y defined by y^3 + x^6 + x^4 + x^2, + 1), + (Ideal ((1/(x^4 + x^3 + x^2))*y^2 + 1) of Maximal infinite order of Function field in y defined by y^3 + x^6 + x^4 + x^2, + 1)] """ if self._ideal.is_prime.is_in_cache() and self._ideal.is_prime(): return [(self, 1)] From 31a30aabe6e8c723c801876884061bec787abf4c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 7 Jul 2023 08:00:08 -0700 Subject: [PATCH 124/150] src/sage/schemes/cyclic_covers/cycliccover_finite_field.py: Fix typo in doctest that was not being run --- src/sage/schemes/cyclic_covers/cycliccover_finite_field.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/cyclic_covers/cycliccover_finite_field.py b/src/sage/schemes/cyclic_covers/cycliccover_finite_field.py index d2bdefa5e3a..b1121543de6 100644 --- a/src/sage/schemes/cyclic_covers/cycliccover_finite_field.py +++ b/src/sage/schemes/cyclic_covers/cycliccover_finite_field.py @@ -1147,7 +1147,7 @@ def frobenius_polynomial(self): + 24687045654725446027864774006541463602997309796*x^10 + 11320844849639649951608809973589776933203136765026963553258401 - sage; h = PolynomialRing(GF(1009^2), 'x')([-1] + [0]*(5-1) + [1]) + sage: h = PolynomialRing(GF(1009^2), 'x')([-1] + [0]*(5-1) + [1]) sage: CyclicCover(3, h).frobenius_polynomial() # long time x^8 + 532*x^7 - 2877542*x^6 - 242628176*x^5 + 4390163797795*x^4 - 247015136050256*x^3 - 2982540407204025062*x^2 + 561382189105547134612*x + 1074309286591662654798721 From 3106a067c3fe449d97c354b99fed1171dc8dcd11 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 7 Jul 2023 08:54:04 -0700 Subject: [PATCH 125/150] src/sage/interfaces/gnuplot.py: Fix tag that is now parsed as TAG (COMMENT) --- src/sage/interfaces/gnuplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/interfaces/gnuplot.py b/src/sage/interfaces/gnuplot.py index 8ab14c75e1c..b423866fd15 100644 --- a/src/sage/interfaces/gnuplot.py +++ b/src/sage/interfaces/gnuplot.py @@ -159,7 +159,7 @@ def plot3d_parametric(self, f='cos(u)*(3 + v*cos(u/2)), sin(u)*(3 + v*cos(u/2)), EXAMPLES:: - sage: gnuplot.plot3d_parametric('v^2*sin(u), v*cos(u), v*(1-v)') # optional - gnuplot (not tested, since something pops up). + sage: gnuplot.plot3d_parametric('v^2*sin(u), v*cos(u), v*(1-v)') # optional - gnuplot, not tested (since something pops up) """ if title is None: title = str(f) From 48ea62c3656a0b8bce2e5c1515a41beaf248120c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 7 Jul 2023 09:04:58 -0700 Subject: [PATCH 126/150] './sage -fixdoctests --long' for all files that sage.doctest.sources.FileDocTestSource._test_enough_doctests complains about --- src/doc/de/tutorial/latex.rst | 17 +- src/doc/fr/tutorial/latex.rst | 17 +- src/doc/ja/tutorial/latex.rst | 17 +- src/doc/pt/tutorial/latex.rst | 17 +- src/sage/calculus/ode.pyx | 15 +- src/sage/categories/category_singleton.pyx | 9 +- src/sage/categories/lie_algebras.py | 318 ++--- src/sage/categories/posets.py | 9 +- src/sage/combinat/tutorial.py | 131 +- src/sage/graphs/generic_graph.py | 1079 +++++++++-------- src/sage/interfaces/r.py | 274 +++-- src/sage/knots/knotinfo.py | 9 +- src/sage/misc/latex_standalone.py | 118 +- src/sage/misc/profiler.py | 11 +- src/sage/quivers/algebra_elements.pyx | 11 +- src/sage/rings/cfinite_sequence.py | 9 +- .../polynomial/multi_polynomial_ideal.py | 891 +++++++------- src/sage/rings/polynomial/polynomial_ring.py | 574 ++++----- 18 files changed, 1823 insertions(+), 1703 deletions(-) diff --git a/src/doc/de/tutorial/latex.rst b/src/doc/de/tutorial/latex.rst index c664bb970cd..77c0834ab77 100644 --- a/src/doc/de/tutorial/latex.rst +++ b/src/doc/de/tutorial/latex.rst @@ -319,17 +319,18 @@ lässt. Diese Liste wird verwaltet durch die Befehle ``latex.add_to_mathjax_avoid_list`` und ``latex.mathjax_avoid_list``. :: - sage: latex.mathjax_avoid_list([]) # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: # not tested + sage: latex.mathjax_avoid_list([]) + sage: latex.mathjax_avoid_list() [] - sage: latex.mathjax_avoid_list(['foo', 'bar']) # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: latex.mathjax_avoid_list(['foo', 'bar']) + sage: latex.mathjax_avoid_list() ['foo', 'bar'] - sage: latex.add_to_mathjax_avoid_list('tikzpicture') # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: latex.add_to_mathjax_avoid_list('tikzpicture') + sage: latex.mathjax_avoid_list() ['foo', 'bar', 'tikzpicture'] - sage: latex.mathjax_avoid_list([]) # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: latex.mathjax_avoid_list([]) + sage: latex.mathjax_avoid_list() [] Nehmen wir an ein LaTeX-Ausdruck wurde im Notebook durch ``view()`` diff --git a/src/doc/fr/tutorial/latex.rst b/src/doc/fr/tutorial/latex.rst index ae326102be1..bed8a4fad93 100644 --- a/src/doc/fr/tutorial/latex.rst +++ b/src/doc/fr/tutorial/latex.rst @@ -306,17 +306,18 @@ d'appeler latex (ou plus généralement le moteur choisi via ``latex.engine()``) au lieu MathJax. Les méthodes ``latex.add_to_mathjax_avoid_list`` et ``latex.mathjax_avoid_list`` permettent de gérer le contenu de cette liste. :: - sage: latex.mathjax_avoid_list([]) # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: # not tested + sage: latex.mathjax_avoid_list([]) + sage: latex.mathjax_avoid_list() [] - sage: latex.mathjax_avoid_list(['foo', 'bar']) # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: latex.mathjax_avoid_list(['foo', 'bar']) + sage: latex.mathjax_avoid_list() ['foo', 'bar'] - sage: latex.add_to_mathjax_avoid_list('tikzpicture') # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: latex.add_to_mathjax_avoid_list('tikzpicture') + sage: latex.mathjax_avoid_list() ['foo', 'bar', 'tikzpicture'] - sage: latex.mathjax_avoid_list([]) # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: latex.mathjax_avoid_list([]) + sage: latex.mathjax_avoid_list() [] Supposons maintenant que, dans le bloc-notes, un appel à ``view()`` ou diff --git a/src/doc/ja/tutorial/latex.rst b/src/doc/ja/tutorial/latex.rst index e02d8507eb6..9f4dc85090b 100644 --- a/src/doc/ja/tutorial/latex.rst +++ b/src/doc/ja/tutorial/latex.rst @@ -280,17 +280,18 @@ LaTeX処理のカスタマイズ :: - sage: latex.mathjax_avoid_list([]) # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: # not tested + sage: latex.mathjax_avoid_list([]) + sage: latex.mathjax_avoid_list() [] - sage: latex.mathjax_avoid_list(['foo', 'bar']) # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: latex.mathjax_avoid_list(['foo', 'bar']) + sage: latex.mathjax_avoid_list() ['foo', 'bar'] - sage: latex.add_to_mathjax_avoid_list('tikzpicture') # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: latex.add_to_mathjax_avoid_list('tikzpicture') + sage: latex.mathjax_avoid_list() ['foo', 'bar', 'tikzpicture'] - sage: latex.mathjax_avoid_list([]) # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: latex.mathjax_avoid_list([]) + sage: latex.mathjax_avoid_list() [] diff --git a/src/doc/pt/tutorial/latex.rst b/src/doc/pt/tutorial/latex.rst index 3ed1a642ba8..56cb8e778c7 100644 --- a/src/doc/pt/tutorial/latex.rst +++ b/src/doc/pt/tutorial/latex.rst @@ -328,17 +328,18 @@ que for definido pelo comando ``latex.engine()``). Essa lista é gerenciada pelos comandos ``latex.add_to_mathjax_avoid_list`` e ``latex.mathjax_avoid_list``. :: - sage: latex.mathjax_avoid_list([]) # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: # not tested + sage: latex.mathjax_avoid_list([]) + sage: latex.mathjax_avoid_list() [] - sage: latex.mathjax_avoid_list(['foo', 'bar']) # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: latex.mathjax_avoid_list(['foo', 'bar']) + sage: latex.mathjax_avoid_list() ['foo', 'bar'] - sage: latex.add_to_mathjax_avoid_list('tikzpicture') # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: latex.add_to_mathjax_avoid_list('tikzpicture') + sage: latex.mathjax_avoid_list() ['foo', 'bar', 'tikzpicture'] - sage: latex.mathjax_avoid_list([]) # not tested - sage: latex.mathjax_avoid_list() # not tested + sage: latex.mathjax_avoid_list([]) + sage: latex.mathjax_avoid_list() [] Suponha que uma expressão em LaTeX é produzida no Notebook com o diff --git a/src/sage/calculus/ode.pyx b/src/sage/calculus/ode.pyx index 544556ddf72..03cca394d6d 100644 --- a/src/sage/calculus/ode.pyx +++ b/src/sage/calculus/ode.pyx @@ -324,14 +324,15 @@ class ode_solver(): following (WARNING: the following is *not* automatically doctested):: - sage: T = ode_solver() # not tested - sage: T.algorithm = "bsimp" # not tested - sage: vander = van_der_pol() # not tested - sage: T.function = vander # not tested - sage: T.ode_solve(y_0=[1, 0], t_span=[0, 2000], # not tested + sage: # not tested + sage: T = ode_solver() + sage: T.algorithm = "bsimp" + sage: vander = van_der_pol() + sage: T.function = vander + sage: T.ode_solve(y_0=[1, 0], t_span=[0, 2000], ....: num_points=1000) - sage: from tempfile import NamedTemporaryFile # not tested - sage: with NamedTemporaryFile(suffix=".png") as f: # not tested + sage: from tempfile import NamedTemporaryFile + sage: with NamedTemporaryFile(suffix=".png") as f: ....: T.plot_solution(i=0, filename=f.name) """ diff --git a/src/sage/categories/category_singleton.pyx b/src/sage/categories/category_singleton.pyx index ea8f9265033..543ce0375f7 100644 --- a/src/sage/categories/category_singleton.pyx +++ b/src/sage/categories/category_singleton.pyx @@ -135,13 +135,14 @@ class Category_singleton(Category): One sees that containment tests for the singleton class is a lot faster than for a usual class:: - sage: timeit("R in MyRings()", number=10000) # not tested + sage: # not tested + sage: timeit("R in MyRings()", number=10000) 10000 loops, best of 3: 7.12 µs per loop - sage: timeit("R1 in MyRings()", number=10000) # not tested + sage: timeit("R1 in MyRings()", number=10000) 10000 loops, best of 3: 6.98 µs per loop - sage: timeit("R in MyRingsSingleton()", number=10000) # not tested + sage: timeit("R in MyRingsSingleton()", number=10000) 10000 loops, best of 3: 3.08 µs per loop - sage: timeit("R2 in MyRingsSingleton()", number=10000) # not tested + sage: timeit("R2 in MyRingsSingleton()", number=10000) 10000 loops, best of 3: 2.99 µs per loop So this is an improvement, but not yet competitive with a pure diff --git a/src/sage/categories/lie_algebras.py b/src/sage/categories/lie_algebras.py index 04597de0308..0bd0c3185e7 100644 --- a/src/sage/categories/lie_algebras.py +++ b/src/sage/categories/lie_algebras.py @@ -45,24 +45,24 @@ class LieAlgebras(Category_over_base_ring): We construct a typical parent in this category, and do some computations with it:: - sage: A = C.example(); A # optional - sage.groups sage.modules + sage: A = C.example(); A # needs sage.groups sage.modules An example of a Lie algebra: the Lie algebra from the associative algebra Symmetric group algebra of order 3 over Rational Field generated by ([2, 1, 3], [2, 3, 1]) - sage: A.category() # optional - sage.groups sage.modules + sage: A.category() # needs sage.groups sage.modules Category of Lie algebras over Rational Field - sage: A.base_ring() # optional - sage.groups sage.modules + sage: A.base_ring() # needs sage.groups sage.modules Rational Field - sage: a, b = A.lie_algebra_generators() # optional - sage.groups sage.modules - sage: a.bracket(b) # optional - sage.groups sage.modules + sage: a, b = A.lie_algebra_generators() # needs sage.groups sage.modules + sage: a.bracket(b) # needs sage.groups sage.modules -[1, 3, 2] + [3, 2, 1] - sage: b.bracket(2*a + b) # optional - sage.groups sage.modules + sage: b.bracket(2*a + b) # needs sage.groups sage.modules 2*[1, 3, 2] - 2*[3, 2, 1] - sage: A.bracket(a, b) # optional - sage.groups sage.modules + sage: A.bracket(a, b) # needs sage.groups sage.modules -[1, 3, 2] + [3, 2, 1] Please see the source code of `A` (with ``A??``) for how to @@ -70,9 +70,9 @@ class LieAlgebras(Category_over_base_ring): TESTS:: - sage: C = LieAlgebras(QQ) # optional - sage.combinat sage.modules - sage: TestSuite(C).run() # optional - sage.combinat sage.modules - sage: TestSuite(C.example()).run() # optional - sage.combinat sage.modules + sage: C = LieAlgebras(QQ) # needs sage.combinat sage.modules + sage: TestSuite(C).run() # needs sage.combinat sage.modules + sage: TestSuite(C.example()).run() # needs sage.combinat sage.modules .. TODO:: @@ -151,15 +151,15 @@ def example(self, gens=None): EXAMPLES:: - sage: LieAlgebras(QQ).example() # optional - sage.groups sage.modules + sage: LieAlgebras(QQ).example() # needs sage.groups sage.modules An example of a Lie algebra: the Lie algebra from the associative algebra Symmetric group algebra of order 3 over Rational Field generated by ([2, 1, 3], [2, 3, 1]) Another set of generators can be specified as an optional argument:: - sage: F. = FreeAlgebra(QQ) # optional - sage.combinat sage.modules - sage: LieAlgebras(QQ).example(F.gens()) # optional - sage.combinat sage.modules + sage: F. = FreeAlgebra(QQ) # needs sage.combinat sage.modules + sage: LieAlgebras(QQ).example(F.gens()) # needs sage.combinat sage.modules An example of a Lie algebra: the Lie algebra from the associative algebra Free Algebra on 3 generators (x, y, z) over Rational Field generated by (x, y, z) @@ -189,14 +189,14 @@ def extra_super_categories(self): [Category of finite sets] sage: LieAlgebras(ZZ).FiniteDimensional().extra_super_categories() [] - sage: C = LieAlgebras(GF(5)).FiniteDimensional() # optional - sage.rings.finite_rings - sage: C.is_subcategory(Sets().Finite()) # optional - sage.rings.finite_rings + sage: C = LieAlgebras(GF(5)).FiniteDimensional() # needs sage.rings.finite_rings + sage: C.is_subcategory(Sets().Finite()) # needs sage.rings.finite_rings True sage: C = LieAlgebras(ZZ).FiniteDimensional() sage: C.is_subcategory(Sets().Finite()) False - sage: C = LieAlgebras(GF(5)).WithBasis().FiniteDimensional() # optional - sage.rings.finite_rings - sage: C.is_subcategory(Sets().Finite()) # optional - sage.rings.finite_rings + sage: C = LieAlgebras(GF(5)).WithBasis().FiniteDimensional() # needs sage.rings.finite_rings + sage: C.is_subcategory(Sets().Finite()) # needs sage.rings.finite_rings True """ if self.base_ring() in Sets().Finite(): @@ -209,8 +209,8 @@ class Nilpotent(CategoryWithAxiom_over_base_ring): TESTS:: - sage: C = LieAlgebras(QQ).Nilpotent() # optional - sage.combinat sage.modules - sage: TestSuite(C).run() # optional - sage.combinat sage.modules + sage: C = LieAlgebras(QQ).Nilpotent() # needs sage.combinat sage.modules + sage: TestSuite(C).run() # needs sage.combinat sage.modules """ class ParentMethods: @abstract_method @@ -220,8 +220,8 @@ def step(self): EXAMPLES:: - sage: h = lie_algebras.Heisenberg(ZZ, oo) # optional - sage.combinat sage.modules - sage: h.step() # optional - sage.combinat sage.modules + sage: h = lie_algebras.Heisenberg(ZZ, oo) # needs sage.combinat sage.modules + sage: h.step() # needs sage.combinat sage.modules 2 """ @@ -231,8 +231,8 @@ def is_nilpotent(self): EXAMPLES:: - sage: h = lie_algebras.Heisenberg(ZZ, oo) # optional - sage.combinat sage.modules - sage: h.is_nilpotent() # optional - sage.combinat sage.modules + sage: h = lie_algebras.Heisenberg(ZZ, oo) # needs sage.combinat sage.modules + sage: h.is_nilpotent() # needs sage.combinat sage.modules True """ return True @@ -257,30 +257,31 @@ def bracket(self, lhs, rhs): EXAMPLES:: - sage: L = LieAlgebras(QQ).example() # optional - sage.combinat sage.modules - sage: x, y = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: L.bracket(x, x + y) # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: L = LieAlgebras(QQ).example() + sage: x, y = L.lie_algebra_generators() + sage: L.bracket(x, x + y) -[1, 3, 2] + [3, 2, 1] - sage: L.bracket(x, 0) # optional - sage.combinat sage.modules + sage: L.bracket(x, 0) 0 - sage: L.bracket(0, x) # optional - sage.combinat sage.modules + sage: L.bracket(0, x) 0 Constructing the product space:: - sage: L = lie_algebras.Heisenberg(QQ, 1) # optional - sage.combinat sage.modules - sage: Z = L.bracket(L, L); Z # optional - sage.combinat sage.modules + sage: L = lie_algebras.Heisenberg(QQ, 1) # needs sage.combinat sage.modules + sage: Z = L.bracket(L, L); Z # needs sage.combinat sage.modules Ideal (z) of Heisenberg algebra of rank 1 over Rational Field - sage: L.bracket(L, Z) # optional - sage.combinat sage.modules + sage: L.bracket(L, Z) # needs sage.combinat sage.modules Ideal () of Heisenberg algebra of rank 1 over Rational Field Constructing ideals:: - sage: p, q, z = L.basis(); p, q, z # optional - sage.combinat sage.modules + sage: p, q, z = L.basis(); p, q, z # needs sage.combinat sage.modules (p1, q1, z) - sage: L.bracket(3*p, L) # optional - sage.combinat sage.modules + sage: L.bracket(3*p, L) # needs sage.combinat sage.modules Ideal (3*p1) of Heisenberg algebra of rank 1 over Rational Field - sage: L.bracket(L, q + p) # optional - sage.combinat sage.modules + sage: L.bracket(L, q + p) # needs sage.combinat sage.modules Ideal (p1 + q1) of Heisenberg algebra of rank 1 over Rational Field """ if lhs in LieAlgebras: @@ -300,15 +301,15 @@ def universal_enveloping_algebra(self): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: L.universal_enveloping_algebra() # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # needs sage.combinat sage.modules + sage: L.universal_enveloping_algebra() # needs sage.combinat sage.modules Noncommutative Multivariate Polynomial Ring in b0, b1, b2 over Rational Field, nc-relations: {} :: - sage: L = LieAlgebra(QQ, 3, 'x', abelian=True) # optional - sage.combinat sage.modules - sage: L.universal_enveloping_algebra() # optional - sage.combinat sage.modules + sage: L = LieAlgebra(QQ, 3, 'x', abelian=True) # needs sage.combinat sage.modules + sage: L.universal_enveloping_algebra() # needs sage.combinat sage.modules Multivariate Polynomial Ring in x0, x1, x2 over Rational Field .. SEEALSO:: @@ -333,15 +334,15 @@ def _construct_UEA(self): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: L._construct_UEA() # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # needs sage.combinat sage.modules + sage: L._construct_UEA() # needs sage.combinat sage.modules Noncommutative Multivariate Polynomial Ring in b0, b1, b2 over Rational Field, nc-relations: {} :: - sage: L = LieAlgebra(QQ, 3, 'x', abelian=True) # optional - sage.combinat sage.modules - sage: L.universal_enveloping_algebra() # indirect doctest # optional - sage.combinat sage.modules + sage: L = LieAlgebra(QQ, 3, 'x', abelian=True) # needs sage.combinat sage.modules + sage: L.universal_enveloping_algebra() # indirect doctest # needs sage.combinat sage.modules Multivariate Polynomial Ring in x0, x1, x2 over Rational Field """ @@ -397,8 +398,8 @@ def module(self): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: L.module() # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # needs sage.combinat sage.modules + sage: L.module() # needs sage.combinat sage.modules Vector space of dimension 3 over Rational Field """ @@ -413,10 +414,10 @@ def from_vector(self, v, order=None, coerce=False): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: u = L.from_vector(vector(QQ, (1, 0, 0))); u # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # needs sage.combinat sage.modules + sage: u = L.from_vector(vector(QQ, (1, 0, 0))); u # needs sage.combinat sage.modules (1, 0, 0) - sage: parent(u) is L # optional - sage.combinat sage.modules + sage: parent(u) is L # needs sage.combinat sage.modules True """ @@ -433,11 +434,12 @@ def lift(self): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: a, b, c = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: lifted = L.lift(2*a + b - c); lifted # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() + sage: a, b, c = L.lie_algebra_generators() + sage: lifted = L.lift(2*a + b - c); lifted 2*b0 + b1 - b2 - sage: lifted.parent() is L.universal_enveloping_algebra() # optional - sage.combinat sage.modules + sage: lifted.parent() is L.universal_enveloping_algebra() True """ M = LiftMorphism(self, self._construct_UEA()) @@ -450,9 +452,9 @@ def subalgebra(self, gens, names=None, index_set=None, category=None): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: a, b, c = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: L.subalgebra([2*a - c, b + c]) # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # needs sage.combinat sage.modules + sage: a, b, c = L.lie_algebra_generators() # needs sage.combinat sage.modules + sage: L.subalgebra([2*a - c, b + c]) # needs sage.combinat sage.modules An example of a finite dimensional Lie algebra with basis: the 2-dimensional abelian Lie algebra over Rational Field with basis matrix: @@ -461,9 +463,9 @@ def subalgebra(self, gens, names=None, index_set=None, category=None): :: - sage: L = LieAlgebras(QQ).example() # optional - sage.combinat sage.modules - sage: x,y = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: L.subalgebra([x + y]) # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).example() # needs sage.combinat sage.modules + sage: x,y = L.lie_algebra_generators() # needs sage.combinat sage.modules + sage: L.subalgebra([x + y]) # needs sage.combinat sage.modules Traceback (most recent call last): ... NotImplementedError: subalgebras not yet implemented: see #17416 @@ -478,9 +480,9 @@ def ideal(self, *gens, **kwds): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: a, b, c = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: L.ideal([2*a - c, b + c]) # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # needs sage.combinat sage.modules + sage: a, b, c = L.lie_algebra_generators() # needs sage.combinat sage.modules + sage: L.ideal([2*a - c, b + c]) # needs sage.combinat sage.modules An example of a finite dimensional Lie algebra with basis: the 2-dimensional abelian Lie algebra over Rational Field with basis matrix: @@ -489,9 +491,9 @@ def ideal(self, *gens, **kwds): :: - sage: L = LieAlgebras(QQ).example() # optional - sage.combinat sage.modules - sage: x, y = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: L.ideal([x + y]) # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).example() # needs sage.combinat sage.modules + sage: x, y = L.lie_algebra_generators() # needs sage.combinat sage.modules + sage: L.ideal([x + y]) # needs sage.combinat sage.modules Traceback (most recent call last): ... NotImplementedError: ideals not yet implemented: see #16824 @@ -511,8 +513,8 @@ def is_ideal(self, A): EXAMPLES:: - sage: L = LieAlgebras(QQ).example() # optional - sage.combinat sage.modules - sage: L.is_ideal(L) # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).example() # needs sage.combinat sage.modules + sage: L.is_ideal(L) # needs sage.combinat sage.modules True """ if A == self: @@ -528,9 +530,9 @@ def killing_form(self, x, y): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: a, b, c = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: L.killing_form(a, b + c) # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # needs sage.combinat sage.modules + sage: a, b, c = L.lie_algebra_generators() # needs sage.combinat sage.modules + sage: L.killing_form(a, b + c) # needs sage.combinat sage.modules 0 """ @@ -543,21 +545,23 @@ def is_abelian(self): EXAMPLES:: - sage: L = LieAlgebras(QQ).example() # optional - sage.combinat sage.modules - sage: L.is_abelian() # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: L = LieAlgebras(QQ).example() + sage: L.is_abelian() False - sage: R = QQ['x,y'] # optional - sage.combinat sage.modules - sage: L = LieAlgebras(QQ).example(R.gens()) # optional - sage.combinat sage.modules - sage: L.is_abelian() # optional - sage.combinat sage.modules + sage: R = QQ['x,y'] + sage: L = LieAlgebras(QQ).example(R.gens()) + sage: L.is_abelian() True :: - sage: L. = LieAlgebra(QQ, 1) # todo: not implemented - #16823 # optional - sage.combinat sage.modules - sage: L.is_abelian() # todo: not implemented - #16823 # optional - sage.combinat sage.modules + sage: # not implemented, needs sage.combinat sage.modules + sage: L. = LieAlgebra(QQ, 1) + sage: L.is_abelian() True - sage: L. = LieAlgebra(QQ, 2) # todo: not implemented - #16823 # optional - sage.combinat sage.modules - sage: L.is_abelian() # todo: not implemented - #16823 # optional - sage.combinat sage.modules + sage: L. = LieAlgebra(QQ, 2) + sage: L.is_abelian() False """ G = self.lie_algebra_generators() @@ -573,14 +577,14 @@ def is_commutative(self): EXAMPLES:: - sage: L = LieAlgebras(QQ).example() # optional - sage.combinat sage.modules - sage: L.is_commutative() # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).example() # needs sage.combinat sage.modules + sage: L.is_commutative() # needs sage.combinat sage.modules False :: - sage: L. = LieAlgebra(QQ, 1) # todo: not implemented - #16823 # optional - sage.combinat sage.modules - sage: L.is_commutative() # todo: not implemented - #16823 # optional - sage.combinat sage.modules + sage: L. = LieAlgebra(QQ, 1) # not implemented # needs sage.combinat sage.modules + sage: L.is_commutative() # not implemented # needs sage.combinat sage.modules True """ return self.is_abelian() @@ -592,8 +596,8 @@ def is_solvable(self): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: L.is_solvable() # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # needs sage.combinat sage.modules + sage: L.is_solvable() # needs sage.combinat sage.modules True """ @@ -604,8 +608,8 @@ def is_nilpotent(self): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: L.is_nilpotent() # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # needs sage.combinat sage.modules + sage: L.is_nilpotent() # needs sage.combinat sage.modules True """ @@ -630,36 +634,36 @@ def bch(self, X, Y, prec=None): The BCH formula for the generators of a free nilpotent Lie algebra of step 4:: - sage: L = LieAlgebra(QQ, 2, step=4) # optional - sage.combinat sage.modules - sage: L.inject_variables() # optional - sage.combinat sage.modules + sage: L = LieAlgebra(QQ, 2, step=4) # needs sage.combinat sage.modules + sage: L.inject_variables() # needs sage.combinat sage.modules Defining X_1, X_2, X_12, X_112, X_122, X_1112, X_1122, X_1222 - sage: L.bch(X_1, X_2) # optional - sage.combinat sage.modules + sage: L.bch(X_1, X_2) # needs sage.combinat sage.modules X_1 + X_2 + 1/2*X_12 + 1/12*X_112 + 1/12*X_122 + 1/24*X_1122 An example of the BCH formula in a quotient:: - sage: Q = L.quotient(X_112 + X_122) # optional - sage.combinat sage.modules - sage: x, y = Q.basis().list()[:2] # optional - sage.combinat sage.modules - sage: Q.bch(x, y) # optional - sage.combinat sage.modules + sage: Q = L.quotient(X_112 + X_122) # needs sage.combinat sage.modules + sage: x, y = Q.basis().list()[:2] # needs sage.combinat sage.modules + sage: Q.bch(x, y) # needs sage.combinat sage.modules X_1 + X_2 + 1/2*X_12 - 1/24*X_1112 The BCH formula for a non-nilpotent Lie algebra requires the precision to be explicitly stated:: - sage: L. = LieAlgebra(QQ) # optional - sage.combinat sage.modules - sage: L.bch(X, Y) # optional - sage.combinat sage.modules + sage: L. = LieAlgebra(QQ) # needs sage.combinat sage.modules + sage: L.bch(X, Y) # needs sage.combinat sage.modules Traceback (most recent call last): ... ValueError: the Lie algebra is not known to be nilpotent, so you must specify the precision - sage: L.bch(X, Y, 4) # optional - sage.combinat sage.modules + sage: L.bch(X, Y, 4) # needs sage.combinat sage.modules X + 1/12*[X, [X, Y]] + 1/24*[X, [[X, Y], Y]] + 1/2*[X, Y] + 1/12*[[X, Y], Y] + Y The BCH formula requires a coercion from the rationals:: - sage: L. = LieAlgebra(ZZ, 2, step=2) # optional - sage.combinat sage.modules - sage: L.bch(X, Y) # optional - sage.combinat sage.modules + sage: L. = LieAlgebra(ZZ, 2, step=2) # needs sage.combinat sage.modules + sage: L.bch(X, Y) # needs sage.combinat sage.modules Traceback (most recent call last): ... TypeError: the BCH formula is not well defined @@ -688,8 +692,8 @@ def lie_group(self, name='G', **kwds): EXAMPLES:: - sage: L = lie_algebras.Heisenberg(QQ, 1) # optional - sage.combinat sage.modules - sage: G = L.lie_group('G'); G # optional - sage.combinat sage.modules sage.symbolic + sage: L = lie_algebras.Heisenberg(QQ, 1) # needs sage.combinat sage.modules + sage: G = L.lie_group('G'); G # needs sage.combinat sage.modules sage.symbolic Lie group G of Heisenberg algebra of rank 1 over Rational Field """ @@ -707,15 +711,15 @@ def _test_jacobi_identity(self, **options): By default, this method runs the tests only on the elements returned by ``self.some_elements()``:: - sage: L = LieAlgebras(QQ).example() # optional - sage.combinat sage.modules - sage: L._test_jacobi_identity() # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).example() # needs sage.combinat sage.modules + sage: L._test_jacobi_identity() # needs sage.combinat sage.modules However, the elements tested can be customized with the ``elements`` keyword argument:: - sage: L = LieAlgebras(QQ).example() # optional - sage.combinat sage.modules - sage: x,y = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: L._test_jacobi_identity(elements=[x + y, x, 2*y, x.bracket(y)]) # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).example() # needs sage.combinat sage.modules + sage: x,y = L.lie_algebra_generators() # needs sage.combinat sage.modules + sage: L._test_jacobi_identity(elements=[x + y, x, 2*y, x.bracket(y)]) # needs sage.combinat sage.modules See the documentation for :class:`TestSuite` for more information. """ @@ -746,15 +750,15 @@ def _test_antisymmetry(self, **options): By default, this method runs the tests only on the elements returned by ``self.some_elements()``:: - sage: L = LieAlgebras(QQ).example() # optional - sage.combinat sage.modules - sage: L._test_antisymmetry() # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).example() # needs sage.combinat sage.modules + sage: L._test_antisymmetry() # needs sage.combinat sage.modules However, the elements tested can be customized with the ``elements`` keyword argument:: - sage: L = LieAlgebras(QQ).example() # optional - sage.combinat sage.modules - sage: x,y = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: L._test_antisymmetry(elements=[x + y, x, 2*y, x.bracket(y)]) # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).example() # needs sage.combinat sage.modules + sage: x,y = L.lie_algebra_generators() # needs sage.combinat sage.modules + sage: L._test_antisymmetry(elements=[x + y, x, 2*y, x.bracket(y)]) # needs sage.combinat sage.modules See the documentation for :class:`TestSuite` for more information. """ @@ -775,27 +779,28 @@ def _test_distributivity(self, **options): TESTS:: - sage: L = LieAlgebras(QQ).example() # optional - sage.combinat sage.modules - sage: L._test_distributivity() # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).example() # needs sage.combinat sage.modules + sage: L._test_distributivity() # needs sage.combinat sage.modules EXAMPLES: By default, this method runs the tests only on the elements returned by ``self.some_elements()``:: - sage: L = LieAlgebra(QQ, 3, 'x,y,z', representation="polynomial") # optional - sage.combinat sage.modules - sage: L.some_elements() # optional - sage.combinat sage.modules + sage: L = LieAlgebra(QQ, 3, 'x,y,z', representation="polynomial") # needs sage.combinat sage.modules + sage: L.some_elements() # needs sage.combinat sage.modules [x + y + z] - sage: L._test_distributivity() # optional - sage.combinat sage.modules + sage: L._test_distributivity() # needs sage.combinat sage.modules However, the elements tested can be customized with the ``elements`` keyword argument:: - sage: L = LieAlgebra(QQ, cartan_type=['A', 2]) # todo: not implemented - #16821 # optional - sage.combinat sage.modules - sage: h1 = L.gen(0) # todo: not implemented - #16821 # optional - sage.combinat sage.modules - sage: h2 = L.gen(1) # todo: not implemented - #16821 # optional - sage.combinat sage.modules - sage: e2 = L.gen(3) # todo: not implemented - #16821 # optional - sage.combinat sage.modules - sage: L._test_distributivity(elements=[h1, h2, e2]) # todo: not implemented - #16821 # optional - sage.combinat sage.modules + sage: # not implemented, needs sage.combinat sage.modules + sage: L = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: h1 = L.gen(0) + sage: h2 = L.gen(1) + sage: e2 = L.gen(3) + sage: L._test_distributivity(elements=[h1, h2, e2]) See the documentation for :class:`TestSuite` for more information. """ @@ -818,11 +823,12 @@ def bracket(self, rhs): EXAMPLES:: - sage: L = LieAlgebras(QQ).example() # optional - sage.combinat sage.modules - sage: x,y = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: x.bracket(y) # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: L = LieAlgebras(QQ).example() + sage: x,y = L.lie_algebra_generators() + sage: x.bracket(y) -[1, 3, 2] + [3, 2, 1] - sage: x.bracket(0) # optional - sage.combinat sage.modules + sage: x.bracket(0) 0 """ return self._bracket_(rhs) @@ -837,13 +843,14 @@ def _bracket_(self, y): EXAMPLES:: - sage: L = LieAlgebras(QQ).example() # optional - sage.combinat sage.modules - sage: x,y = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: x._bracket_(y) # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: L = LieAlgebras(QQ).example() + sage: x,y = L.lie_algebra_generators() + sage: x._bracket_(y) -[1, 3, 2] + [3, 2, 1] - sage: y._bracket_(x) # optional - sage.combinat sage.modules + sage: y._bracket_(x) [1, 3, 2] - [3, 2, 1] - sage: x._bracket_(x) # optional - sage.combinat sage.modules + sage: x._bracket_(x) 0 """ @@ -859,10 +866,10 @@ def to_vector(self, order=None): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: u = L((1, 0, 0)).to_vector(); u # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # needs sage.combinat sage.modules + sage: u = L((1, 0, 0)).to_vector(); u # needs sage.combinat sage.modules (1, 0, 0) - sage: parent(u) # optional - sage.combinat sage.modules + sage: parent(u) # needs sage.combinat sage.modules Vector space of dimension 3 over Rational Field """ @@ -874,16 +881,17 @@ def lift(self): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: a, b, c = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: elt = 3*a + b - c # optional - sage.combinat sage.modules - sage: elt.lift() # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() + sage: a, b, c = L.lie_algebra_generators() + sage: elt = 3*a + b - c + sage: elt.lift() 3*b0 + b1 - b2 :: - sage: L. = LieAlgebra(QQ, abelian=True) # optional - sage.combinat sage.modules - sage: x.lift() # optional - sage.combinat sage.modules + sage: L. = LieAlgebra(QQ, abelian=True) # needs sage.combinat sage.modules + sage: x.lift() # needs sage.combinat sage.modules x """ @@ -893,9 +901,9 @@ def killing_form(self, x): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: a, b, c = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: a.killing_form(b) # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # needs sage.combinat sage.modules + sage: a, b, c = L.lie_algebra_generators() # needs sage.combinat sage.modules + sage: a.killing_form(b) # needs sage.combinat sage.modules 0 """ return self.parent().killing_form(self, x) @@ -912,26 +920,28 @@ def exp(self, lie_group=None): EXAMPLES:: - sage: L. = LieAlgebra(QQ, 2, step=2) # optional - sage.combinat sage.modules - sage: g = (X + Y + Z).exp(); g # optional - sage.combinat sage.modules sage.symbolic + sage: # needs sage.combinat sage.modules + sage: L. = LieAlgebra(QQ, 2, step=2) + sage: g = (X + Y + Z).exp(); g # needs sage.symbolic exp(X + Y + Z) - sage: h = X.exp(); h # optional - sage.combinat sage.modules sage.symbolic + sage: h = X.exp(); h # needs sage.symbolic exp(X) - sage: g.parent() # optional - sage.combinat sage.modules sage.symbolic + sage: g.parent() # needs sage.symbolic Lie group G of Free Nilpotent Lie algebra on 3 generators (X, Y, Z) over Rational Field - sage: g.parent() is h.parent() # optional - sage.combinat sage.modules sage.symbolic + sage: g.parent() is h.parent() # needs sage.symbolic True The Lie group can be specified explicitly:: - sage: H = L.lie_group('H') # optional - sage.combinat sage.modules sage.symbolic - sage: k = Z.exp(lie_group=H); k # optional - sage.combinat sage.modules sage.symbolic + sage: # needs sage.combinat sage.modules sage.symbolic + sage: H = L.lie_group('H') + sage: k = Z.exp(lie_group=H); k exp(Z) - sage: k.parent() # optional - sage.combinat sage.modules sage.symbolic + sage: k.parent() Lie group H of Free Nilpotent Lie algebra on 3 generators (X, Y, Z) over Rational Field - sage: g.parent() == k.parent() # optional - sage.combinat sage.modules sage.symbolic + sage: g.parent() == k.parent() False """ if lie_group is None: @@ -949,13 +959,13 @@ def __init__(self, domain, codomain): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: f = L.lift # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # needs sage.combinat sage.modules + sage: f = L.lift # needs sage.combinat sage.modules We skip the category test since this is currently not an element of a homspace:: - sage: TestSuite(f).run(skip="_test_category") # optional - sage.combinat sage.modules + sage: TestSuite(f).run(skip="_test_category") # needs sage.combinat sage.modules """ Morphism.__init__(self, Hom(domain, codomain)) @@ -965,9 +975,9 @@ def _call_(self, x): EXAMPLES:: - sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # optional - sage.combinat sage.modules - sage: a, b, c = L.lie_algebra_generators() # optional - sage.combinat sage.modules - sage: L.lift(3*a + b - c) # optional - sage.combinat sage.modules + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() # needs sage.combinat sage.modules + sage: a, b, c = L.lie_algebra_generators() # needs sage.combinat sage.modules + sage: L.lift(3*a + b - c) # needs sage.combinat sage.modules 3*b0 + b1 - b2 """ return x.lift() diff --git a/src/sage/categories/posets.py b/src/sage/categories/posets.py index 24a83e02dbd..47408110da2 100644 --- a/src/sage/categories/posets.py +++ b/src/sage/categories/posets.py @@ -71,13 +71,14 @@ class Posets(Category): :trac:`10130` will be resolved, this will be automatically provided by this category:: - sage: x < y # todo: not implemented + sage: # not implemented + sage: x < y True - sage: x < x # todo: not implemented + sage: x < x False - sage: x <= x # todo: not implemented + sage: x <= x True - sage: y >= x # todo: not implemented + sage: y >= x True .. SEEALSO:: :func:`Poset`, :class:`FinitePosets`, :class:`LatticePosets` diff --git a/src/sage/combinat/tutorial.py b/src/sage/combinat/tutorial.py index 51ade61b8d1..a5edc7146ea 100644 --- a/src/sage/combinat/tutorial.py +++ b/src/sage/combinat/tutorial.py @@ -275,37 +275,37 @@ introduce two variables, `C` and `z`, and we define the equation:: - sage: C, z = var('C,z') # optional - sage.symbolic - sage: sys = [ C == z + C*C ] # optional - sage.symbolic + sage: C, z = var('C,z') # needs sage.symbolic + sage: sys = [ C == z + C*C ] # needs sage.symbolic There are two solutions, which happen to have closed forms:: - sage: sol = solve(sys, C, solution_dict=True); sol # optional - sage.symbolic + sage: sol = solve(sys, C, solution_dict=True); sol # needs sage.symbolic [{C: -1/2*sqrt(-4*z + 1) + 1/2}, {C: 1/2*sqrt(-4*z + 1) + 1/2}] - sage: s0 = sol[0][C]; s1 = sol[1][C] # optional - sage.symbolic + sage: s0 = sol[0][C]; s1 = sol[1][C] # needs sage.symbolic and whose Taylor series begin as follows:: - sage: s0.series(z, 6) # optional - sage.symbolic + sage: s0.series(z, 6) # needs sage.symbolic 1*z + 1*z^2 + 2*z^3 + 5*z^4 + 14*z^5 + Order(z^6) - sage: s1.series(z, 6) # optional - sage.symbolic + sage: s1.series(z, 6) # needs sage.symbolic 1 + (-1)*z + (-1)*z^2 + (-2)*z^3 + (-5)*z^4 + (-14)*z^5 + Order(z^6) The second solution is clearly aberrant, while the first one gives the expected coefficients. Therefore, we set:: - sage: C = s0 # optional - sage.symbolic + sage: C = s0 # needs sage.symbolic We can now calculate the next terms:: - sage: C.series(z, 11) # optional - sage.symbolic + sage: C.series(z, 11) # needs sage.symbolic 1*z + 1*z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + 429*z^8 + 1430*z^9 + 4862*z^10 + Order(z^11) or calculate, more or less instantaneously, the 100-th coefficient:: - sage: C.series(z, 101).coefficient(z,100) # optional - sage.symbolic + sage: C.series(z, 101).coefficient(z,100) # needs sage.symbolic 227508830794229349661819540395688853956041682601541047340 It is unfortunate to have to recalculate everything if at some point we @@ -338,19 +338,19 @@ We now return to the closed form of `C(z)`:: - sage: z = var('z') # optional - sage.symbolic - sage: C = s0; C # optional - sage.symbolic + sage: z = var('z') # needs sage.symbolic + sage: C = s0; C # needs sage.symbolic -1/2*sqrt(-4*z + 1) + 1/2 The `n`-th coefficient in the Taylor series for `C(z)` being given by `\frac{1}{n!} C(z)^{(n)}(0)`, we look at the successive derivatives `C(z)^{(n)}(z)`:: - sage: derivative(C, z, 1) # optional - sage.symbolic + sage: derivative(C, z, 1) # needs sage.symbolic 1/sqrt(-4*z + 1) - sage: derivative(C, z, 2) # optional - sage.symbolic + sage: derivative(C, z, 2) # needs sage.symbolic 2/(-4*z + 1)^(3/2) - sage: derivative(C, z, 3) # optional - sage.symbolic + sage: derivative(C, z, 3) # needs sage.symbolic 12/(-4*z + 1)^(5/2) This suggests the existence of a simple explicit formula, which we will @@ -360,7 +360,7 @@ Taking successive quotients:: - sage: [ (d(n+1) / d(n)) for n in range(1,17) ] # optional - sage.symbolic + sage: [ (d(n+1) / d(n)) for n in range(1,17) ] # needs sage.symbolic [2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58, 62] we observe that `d_n` satisfies the recurrence relation @@ -373,9 +373,9 @@ We check this:: - sage: n = var('n') # optional - sage.symbolic - sage: c = 1/n*binomial(2*(n-1),n-1) # optional - sage.symbolic - sage: [c.subs(n=k) for k in range(1, 11)] # optional - sage.symbolic + sage: n = var('n') # needs sage.symbolic + sage: c = 1/n*binomial(2*(n-1),n-1) # needs sage.symbolic + sage: [c.subs(n=k) for k in range(1, 11)] # needs sage.symbolic [1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862] sage: [catalan_number(k-1) for k in range(1, 11)] [1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862] @@ -383,14 +383,14 @@ We can now calculate coefficients much further; here we calculate `c_{100000}` which has more than `60000` digits:: - sage: cc = c(n=100000) # optional - sage.symbolic + sage: cc = c(n=100000) # needs sage.symbolic This takes a couple of seconds:: - sage: %time cc = c(100000) # not tested # optional - sage.symbolic + sage: %time cc = c(100000) # not tested # needs sage.symbolic CPU times: user 2.34 s, sys: 0.00 s, total: 2.34 s Wall time: 2.34 s - sage: ZZ(cc).ndigits() # optional - sage.symbolic + sage: ZZ(cc).ndigits() # needs sage.symbolic 60198 The methods which we have used generalize to all recursively defined @@ -417,11 +417,12 @@ In the present case, `P=y^2-y+x`. We formally differentiate this equation with respect to `z`:: - sage: x, y, z = var('x, y, z') # optional - sage.symbolic - sage: P = function('P')(x, y) # optional - sage.symbolic - sage: C = function('C')(z) # optional - sage.symbolic - sage: equation = P(x=z, y=C) == 0 # optional - sage.symbolic - sage: diff(equation, z) # optional - sage.symbolic + sage: # needs sage.symbolic + sage: x, y, z = var('x, y, z') + sage: P = function('P')(x, y) + sage: C = function('C')(z) + sage: equation = P(x=z, y=C) == 0 + sage: diff(equation, z) diff(C(z), z)*D[1](P)(z, C(z)) + D[0](P)(z, C(z)) == 0 or, in a more readable format, @@ -434,9 +435,9 @@ In the case of complete binary trees, this gives:: - sage: P = y^2 - y + x # optional - sage.symbolic - sage: Px = diff(P, x); Py = diff(P, y) # optional - sage.symbolic - sage: - Px / Py # optional - sage.symbolic + sage: P = y^2 - y + x # needs sage.symbolic + sage: Px = diff(P, x); Py = diff(P, y) # needs sage.symbolic + sage: - Px / Py # needs sage.symbolic -1/(2*y - 1) Recall that `P(z, C(z))=0`. Thus, we can calculate this fraction @@ -447,7 +448,7 @@ sage: Qx = QQ['x'].fraction_field() sage: Qxy = Qx['y'] - sage: R = Qxy.quo(P); R # optional - sage.symbolic + sage: R = Qxy.quo(P); R # needs sage.symbolic Univariate Quotient Polynomial Ring in ybar over Fraction Field of Univariate Polynomial Ring in x over Rational Field with modulus y^2 - y + x @@ -458,7 +459,7 @@ We continue the calculation of this fraction in `R`:: - sage: fraction = - R(Px) / R(Py); fraction # optional - sage.symbolic + sage: fraction = - R(Px) / R(Py); fraction # needs sage.symbolic (1/2/(x - 1/4))*ybar - 1/4/(x - 1/4) .. note:: @@ -474,9 +475,9 @@ `z` and `C(z)` to obtain an expression for `\frac{d}{dz}C(z)`:: - sage: fraction = fraction.lift(); fraction # optional - sage.symbolic + sage: fraction = fraction.lift(); fraction # needs sage.symbolic (1/2/(x - 1/4))*y - 1/4/(x - 1/4) - sage: fraction(x=z, y=C) # optional - sage.symbolic + sage: fraction(x=z, y=C) # needs sage.symbolic 2*C(z)/(4*z - 1) - 1/(4*z - 1) or, more legibly, @@ -486,13 +487,14 @@ In this simple case, we can directly deduce from this expression a linear differential equation with coefficients in `\QQ[z]`:: - sage: equadiff = diff(C,z) == fraction(x=z, y=C) # optional - sage.symbolic - sage: equadiff # optional - sage.symbolic + sage: # needs sage.symbolic + sage: equadiff = diff(C,z) == fraction(x=z, y=C) + sage: equadiff diff(C(z), z) == 2*C(z)/(4*z - 1) - 1/(4*z - 1) - sage: equadiff = equadiff.simplify_rational() # optional - sage.symbolic - sage: equadiff = equadiff * equadiff.rhs().denominator() # optional - sage.symbolic - sage: equadiff = equadiff - equadiff.rhs() # optional - sage.symbolic - sage: equadiff # optional - sage.symbolic + sage: equadiff = equadiff.simplify_rational() + sage: equadiff = equadiff * equadiff.rhs().denominator() + sage: equadiff = equadiff - equadiff.rhs() + sage: equadiff (4*z - 1)*diff(C(z), z) - 2*C(z) + 1 == 0 or, more legibly, @@ -501,10 +503,10 @@ It is trivial to verify this equation on the closed form:: - sage: Cf = sage.symbolic.function_factory.function('C') # optional - sage.symbolic - sage: equadiff.substitute_function(Cf, s0.function(z)) # optional - sage.symbolic + sage: Cf = sage.symbolic.function_factory.function('C') # needs sage.symbolic + sage: equadiff.substitute_function(Cf, s0.function(z)) # needs sage.symbolic (4*z - 1)/sqrt(-4*z + 1) + sqrt(-4*z + 1) == 0 - sage: bool(equadiff.substitute_function(Cf, s0.function(z))) # optional - sage.symbolic + sage: bool(equadiff.substitute_function(Cf, s0.function(z))) # needs sage.symbolic True .. On veut non seulement remplacer les occurrences de C(z), mais @@ -780,8 +782,8 @@ Similarly, if we consider the number of compositions of `5` by length, we find a line of Pascal’s triangle:: - sage: x = var('x') # optional - sage.symbolic - sage: sum(x^len(c) for c in C5) # optional - sage.symbolic + sage: x = var('x') # needs sage.symbolic + sage: sum(x^len(c) for c in C5) # needs sage.symbolic x^5 + 4*x^4 + 6*x^3 + 4*x^2 + x The above example uses a functionality which we have not seen yet: @@ -872,12 +874,13 @@ ``Sage``; as a result, the following commands are not yet implemented:: - sage: C = Graphs(5) # todo: not implemented - sage: C.cardinality() # todo: not implemented + sage: # not implemented + sage: C = Graphs(5) + sage: C.cardinality() 34 - sage: Graphs(19).cardinality() # todo: not implemented + sage: Graphs(19).cardinality() 24637809253125004524383007491432768 - sage: Graphs(19).random_element() # todo: not implemented + sage: Graphs(19).random_element() Graph on 19 vertices What we have seen so far also applies, in principle, to finite algebraic @@ -893,8 +896,8 @@ or the algebra of `2\times 2` matrices over the finite field `\ZZ/2\ZZ`:: - sage: C = MatrixSpace(GF(2), 2) # optional - sage.modules sage.rings.finite_rings - sage: C.list() # optional - sage.modules sage.rings.finite_rings + sage: C = MatrixSpace(GF(2), 2) # needs sage.modules sage.rings.finite_rings + sage: C.list() # needs sage.modules sage.rings.finite_rings [ [0 0] [1 0] [0 1] [0 0] [0 0] [1 1] [1 0] [1 0] [0 1] [0 1] [0 0], [0 0], [0 0], [1 0], [0 1], [0 0], [1 0], [0 1], [1 0], [0 1], @@ -902,7 +905,7 @@ [0 0] [1 1] [1 1] [1 0] [0 1] [1 1] [1 1], [1 0], [0 1], [1 1], [1 1], [1 1] ] - sage: C.cardinality() # optional - sage.modules sage.rings.finite_rings + sage: C.cardinality() # needs sage.modules sage.rings.finite_rings 16 .. topic:: Exercise @@ -1146,18 +1149,18 @@ :: - sage: x = var('x') # optional - sage.symbolic - sage: sum(x^len(s) for s in Subsets(8)) # optional - sage.symbolic + sage: x = var('x') # needs sage.symbolic + sage: sum(x^len(s) for s in Subsets(8)) # needs sage.symbolic x^8 + 8*x^7 + 28*x^6 + 56*x^5 + 70*x^4 + 56*x^3 + 28*x^2 + 8*x + 1 :: - sage: sum(x^p.length() for p in Permutations(3)) # optional - sage.symbolic + sage: sum(x^p.length() for p in Permutations(3)) # needs sage.symbolic x^3 + 2*x^2 + 2*x + 1 :: - sage: factor(sum(x^p.length() for p in Permutations(3))) # optional - sage.symbolic + sage: factor(sum(x^p.length() for p in Permutations(3))) # needs sage.symbolic (x^2 + x + 1)*(x + 1) :: @@ -1375,15 +1378,15 @@ to define a formal variable ``Leaf`` for the leaves and a formal 2-ary function ``Node``:: - sage: var('Leaf') # optional - sage.symbolic + sage: var('Leaf') # needs sage.symbolic Leaf - sage: function('Node', nargs=2) # optional - sage.symbolic + sage: function('Node', nargs=2) # needs sage.symbolic Node The second tree in :ref:`figure-examples-catalan-trees` can be represented by the expression:: - sage: tr = Node(Node(Leaf, Node(Leaf, Leaf)), Leaf) # optional - sage.symbolic + sage: tr = Node(Node(Leaf, Node(Leaf, Leaf)), Leaf) # needs sage.symbolic .. _section-constructions: @@ -1666,7 +1669,7 @@ combinatorial species:: sage: from sage.combinat.species.library import * - sage: o = var('o') # optional - sage.symbolic + sage: o = var('o') # needs sage.symbolic We begin by redefining the complete binary trees; to do so, we stipulate the recurrence relation directly on the sets:: @@ -1678,10 +1681,10 @@ Now we can construct the set of trees with five nodes, list them, count them...:: - sage: BT5 = BT.isotypes([o]*5) # optional - sage.symbolic - sage: BT5.cardinality() # optional - sage.symbolic + sage: BT5 = BT.isotypes([o]*5) # needs sage.symbolic + sage: BT5.cardinality() # needs sage.symbolic 14 - sage: BT5.list() # optional - sage.symbolic + sage: BT5.list() # needs sage.symbolic [o*(o*(o*(o*o))), o*(o*((o*o)*o)), o*((o*o)*(o*o)), o*((o*(o*o))*o), o*(((o*o)*o)*o), (o*o)*(o*(o*o)), (o*o)*((o*o)*o), (o*(o*o))*(o*o), ((o*o)*o)*(o*o), @@ -1730,8 +1733,8 @@ :: - sage: FW3 = FW.isotypes([o]*3) # optional - sage.symbolic - sage: FW3.list() # optional - sage.symbolic + sage: FW3 = FW.isotypes([o]*3) # needs sage.symbolic + sage: FW3.list() # needs sage.symbolic [o*(o*(o*{})), o*(o*(({}*o)*{})), o*((({}*o)*o)*{}), (({}*o)*o)*(o*{}), (({}*o)*o)*(({}*o)*{})] diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 1f8db82faed..851b341643c 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -909,7 +909,7 @@ def _latex_(self): sage: from sage.graphs.graph_latex import check_tkz_graph sage: check_tkz_graph() # random - depends on TeX installation sage: g = graphs.CompleteGraph(2) - sage: print(g._latex_()) # optional - sage.plot + sage: print(g._latex_()) # needs sage.plot \begin{tikzpicture} \definecolor{cv0}{rgb}{0.0,0.0,0.0} \definecolor{cfv0}{rgb}{1.0,1.0,1.0} @@ -946,21 +946,21 @@ def _matrix_(self, R=None, vertices=None): EXAMPLES:: sage: G = graphs.CompleteBipartiteGraph(2, 3) - sage: m = matrix(G); m.parent() # optional - sage.modules + sage: m = matrix(G); m.parent() # needs sage.modules Full MatrixSpace of 5 by 5 dense matrices over Integer Ring - sage: m # optional - sage.modules + sage: m # needs sage.modules [0 0 1 1 1] [0 0 1 1 1] [1 1 0 0 0] [1 1 0 0 0] [1 1 0 0 0] - sage: G._matrix_() # optional - sage.modules + sage: G._matrix_() # needs sage.modules [0 0 1 1 1] [0 0 1 1 1] [1 1 0 0 0] [1 1 0 0 0] [1 1 0 0 0] - sage: factor(m.charpoly()) # optional - sage.modules + sage: factor(m.charpoly()) # needs sage.modules x^3 * (x^2 - 6) """ return self.am(vertices=vertices, base_ring=R) @@ -1369,24 +1369,24 @@ def export_to_file(self, filename, format=None, **kwds): sage: g = graphs.PetersenGraph() sage: filename = tmp_filename(ext=".pajek") - sage: g.export_to_file(filename) # optional - networkx - sage: import networkx # optional - networkx - sage: G_networkx = networkx.read_pajek(filename) # optional - networkx - sage: Graph(G_networkx).is_isomorphic(g) # optional - networkx + sage: g.export_to_file(filename) # needs networkx + sage: import networkx # needs networkx + sage: G_networkx = networkx.read_pajek(filename) # needs networkx + sage: Graph(G_networkx).is_isomorphic(g) # needs networkx True sage: filename = tmp_filename(ext=".edgelist") - sage: g.export_to_file(filename, data=False) # optional - networkx - sage: h = Graph(networkx.read_edgelist(filename)) # optional - networkx - sage: g.is_isomorphic(h) # optional - networkx + sage: g.export_to_file(filename, data=False) # needs networkx + sage: h = Graph(networkx.read_edgelist(filename)) # needs networkx + sage: g.is_isomorphic(h) # needs networkx True TESTS:: - sage: g.export_to_file("hey", format="When I feel heavy metaaaaaallll...") # optional - networkx + sage: g.export_to_file("hey", format="When I feel heavy metaaaaaallll...") # needs networkx Traceback (most recent call last): ... ValueError: format 'When I feel heavy metaaaaaallll...' unknown - sage: g.export_to_file("my_file.Yeeeeppeeeeee") # optional - networkx + sage: g.export_to_file("my_file.Yeeeeppeeeeee") # needs networkx Traceback (most recent call last): ... RuntimeError: the file format could not be guessed from 'my_file.Yeeeeppeeeeee' @@ -1508,21 +1508,21 @@ def networkx_graph(self, weight_function=None): EXAMPLES:: sage: G = graphs.TetrahedralGraph() - sage: N = G.networkx_graph() # optional - networkx - sage: type(N) # optional - networkx + sage: N = G.networkx_graph() # needs networkx + sage: type(N) # needs networkx sage: def weight_fn(e): ....: return e[2] sage: G1 = Graph([(1,2,1), (1,3,4), (2,3,3), (3,4,4)]) - sage: H = G1.networkx_graph(weight_function=weight_fn) # optional - networkx - sage: H.edges(data=True) # optional - networkx + sage: H = G1.networkx_graph(weight_function=weight_fn) # needs networkx + sage: H.edges(data=True) # needs networkx EdgeDataView([(1, 2, {'weight': 1}), (1, 3, {'weight': 4}), (2, 3, {'weight': 3}), (3, 4, {'weight': 4})]) sage: G2 = DiGraph([(1,2,1), (1,3,4), (2,3,3), (3,4,4), (3,4,5)], ....: multiedges=True) - sage: H = G2.networkx_graph(weight_function=weight_fn) # optional - networkx - sage: H.edges(data=True) # optional - networkx + sage: H = G2.networkx_graph(weight_function=weight_fn) # needs networkx + sage: H.edges(data=True) # needs networkx OutMultiEdgeDataView([(1, 2, {'weight': 1}), (1, 3, {'weight': 4}), (2, 3, {'weight': 3}), (3, 4, {'weight': 5}), (3, 4, {'weight': 4})]) @@ -1912,7 +1912,7 @@ def adjacency_matrix(self, sparse=None, vertices=None, *, base_ring=None, **kwds EXAMPLES:: sage: G = graphs.CubeGraph(4) - sage: G.adjacency_matrix() # optional - sage.modules + sage: G.adjacency_matrix() # needs sage.modules [0 1 1 0 1 0 0 0 1 0 0 0 0 0 0 0] [1 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0] [1 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0] @@ -1932,7 +1932,7 @@ def adjacency_matrix(self, sparse=None, vertices=None, *, base_ring=None, **kwds :: - sage: matrix(GF(2), G) # matrix over GF(2) # optional - sage.modules sage.rings.finite_rings + sage: matrix(GF(2), G) # matrix over GF(2) # needs sage.modules sage.rings.finite_rings [0 1 1 0 1 0 0 0 1 0 0 0 0 0 0 0] [1 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0] [1 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0] @@ -1954,7 +1954,7 @@ def adjacency_matrix(self, sparse=None, vertices=None, *, base_ring=None, **kwds sage: D = DiGraph({0: [1, 2, 3], 1: [0, 2], 2: [3], ....: 3: [4], 4: [0, 5], 5: [1]}) - sage: D.adjacency_matrix() # optional - sage.modules + sage: D.adjacency_matrix() # needs sage.modules [0 1 1 1 0 0] [1 0 1 0 0 0] [0 0 0 1 0 0] @@ -1964,7 +1964,7 @@ def adjacency_matrix(self, sparse=None, vertices=None, *, base_ring=None, **kwds A different ordering of the vertices:: - sage: graphs.PathGraph(5).adjacency_matrix(vertices=[2, 4, 1, 3, 0]) # optional - sage.modules + sage: graphs.PathGraph(5).adjacency_matrix(vertices=[2, 4, 1, 3, 0]) # needs sage.modules [0 0 1 1 0] [0 0 0 1 0] [1 0 0 0 1] @@ -1973,18 +1973,18 @@ def adjacency_matrix(self, sparse=None, vertices=None, *, base_ring=None, **kwds A different base ring:: - sage: graphs.PathGraph(5).adjacency_matrix(base_ring=RDF) # optional - sage.modules + sage: graphs.PathGraph(5).adjacency_matrix(base_ring=RDF) # needs sage.modules [0.0 1.0 0.0 0.0 0.0] [1.0 0.0 1.0 0.0 0.0] [0.0 1.0 0.0 1.0 0.0] [0.0 0.0 1.0 0.0 1.0] [0.0 0.0 0.0 1.0 0.0] - sage: type(_) # optional - sage.modules + sage: type(_) # needs sage.modules A different matrix implementation:: - sage: graphs.PathGraph(5).adjacency_matrix(sparse=False, # optional - sage.modules + sage: graphs.PathGraph(5).adjacency_matrix(sparse=False, # needs sage.modules ....: implementation='numpy') [0 1 0 0 0] [1 0 1 0 0] @@ -1996,14 +1996,14 @@ def adjacency_matrix(self, sparse=None, vertices=None, *, base_ring=None, **kwds As an immutable matrix:: - sage: M = graphs.PathGraph(5).adjacency_matrix(sparse=False, # optional - sage.modules + sage: M = graphs.PathGraph(5).adjacency_matrix(sparse=False, # needs sage.modules ....: immutable=True); M [0 1 0 0 0] [1 0 1 0 0] [0 1 0 1 0] [0 0 1 0 1] [0 0 0 1 0] - sage: M[2, 2] = 1 + sage: M[2, 2] = 1 # needs sage.modules Traceback (most recent call last): ... ValueError: matrix is immutable; please change a copy instead @@ -2011,18 +2011,18 @@ def adjacency_matrix(self, sparse=None, vertices=None, *, base_ring=None, **kwds TESTS:: - sage: graphs.CubeGraph(8).adjacency_matrix().parent() # optional - sage.modules + sage: graphs.CubeGraph(8).adjacency_matrix().parent() # needs sage.modules Full MatrixSpace of 256 by 256 dense matrices over Integer Ring - sage: graphs.CubeGraph(9).adjacency_matrix().parent() # optional - sage.modules + sage: graphs.CubeGraph(9).adjacency_matrix().parent() # needs sage.modules Full MatrixSpace of 512 by 512 sparse matrices over Integer Ring - sage: Graph([(i, i+1) for i in range(500)] + [(0,1),], # optional - sage.modules + sage: Graph([(i, i+1) for i in range(500)] + [(0,1),], # needs sage.modules ....: multiedges=True).adjacency_matrix().parent() Full MatrixSpace of 501 by 501 dense matrices over Integer Ring - sage: graphs.PathGraph(5).adjacency_matrix(vertices=[0,0,0,0,0]) # optional - sage.modules + sage: graphs.PathGraph(5).adjacency_matrix(vertices=[0,0,0,0,0]) # needs sage.modules Traceback (most recent call last): ... ValueError: ``vertices`` must be a permutation of the vertices - sage: graphs.PathGraph(5).adjacency_matrix(vertices=[1,2,3]) # optional - sage.modules + sage: graphs.PathGraph(5).adjacency_matrix(vertices=[1,2,3]) # needs sage.modules Traceback (most recent call last): ... ValueError: ``vertices`` must be a permutation of the vertices @@ -2129,7 +2129,7 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None EXAMPLES:: sage: G = graphs.PetersenGraph() - sage: G.incidence_matrix() # optional - sage.modules + sage: G.incidence_matrix() # needs sage.modules [1 1 1 0 0 0 0 0 0 0 0 0 0 0 0] [1 0 0 1 1 0 0 0 0 0 0 0 0 0 0] [0 0 0 1 0 1 1 0 0 0 0 0 0 0 0] @@ -2140,7 +2140,7 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None [0 0 0 0 0 0 1 0 0 0 1 0 0 0 1] [0 0 0 0 0 0 0 0 1 0 0 1 1 0 0] [0 0 0 0 0 0 0 0 0 1 0 0 0 1 1] - sage: G.incidence_matrix(oriented=True) # optional - sage.modules + sage: G.incidence_matrix(oriented=True) # needs sage.modules [-1 -1 -1 0 0 0 0 0 0 0 0 0 0 0 0] [ 1 0 0 -1 -1 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 1 0 -1 -1 0 0 0 0 0 0 0 0] @@ -2153,18 +2153,18 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None [ 0 0 0 0 0 0 0 0 0 1 0 0 0 1 1] sage: G = digraphs.Circulant(4, [1, 3]) - sage: G.incidence_matrix() # optional - sage.modules + sage: G.incidence_matrix() # needs sage.modules [-1 -1 1 0 0 0 1 0] [ 1 0 -1 -1 1 0 0 0] [ 0 0 0 1 -1 -1 0 1] [ 0 1 0 0 0 1 -1 -1] - sage: graphs.CompleteGraph(3).incidence_matrix() # optional - sage.modules + sage: graphs.CompleteGraph(3).incidence_matrix() # needs sage.modules [1 1 0] [1 0 1] [0 1 1] sage: G = Graph([(0, 0), (0, 1), (0, 1)], loops=True, multiedges=True) - sage: G.incidence_matrix(oriented=False) # optional - sage.modules + sage: G.incidence_matrix(oriented=False) # needs sage.modules [2 1 1] [0 1 1] @@ -2173,30 +2173,30 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None Kirchhoff matrix:: sage: G = graphs.PetersenGraph() - sage: m = G.incidence_matrix(oriented=True) # optional - sage.modules - sage: m * m.transpose() == G.kirchhoff_matrix() # optional - sage.modules + sage: m = G.incidence_matrix(oriented=True) # needs sage.modules + sage: m * m.transpose() == G.kirchhoff_matrix() # needs sage.modules True sage: K = graphs.CompleteGraph(3) - sage: m = K.incidence_matrix(oriented=True) # optional - sage.modules - sage: m * m.transpose() == K.kirchhoff_matrix() # optional - sage.modules + sage: m = K.incidence_matrix(oriented=True) # needs sage.modules + sage: m * m.transpose() == K.kirchhoff_matrix() # needs sage.modules True sage: H = Graph([(0, 0), (0, 1), (0, 1)], loops=True, multiedges=True) - sage: m = H.incidence_matrix(oriented=True) # optional - sage.modules - sage: m * m.transpose() == H.kirchhoff_matrix() # optional - sage.modules + sage: m = H.incidence_matrix(oriented=True) # needs sage.modules + sage: m * m.transpose() == H.kirchhoff_matrix() # needs sage.modules True A different ordering of the vertices:: sage: P5 = graphs.PathGraph(5) - sage: P5.incidence_matrix() # optional - sage.modules + sage: P5.incidence_matrix() # needs sage.modules [1 0 0 0] [1 1 0 0] [0 1 1 0] [0 0 1 1] [0 0 0 1] - sage: P5.incidence_matrix(vertices=[2, 4, 1, 3, 0]) # optional - sage.modules + sage: P5.incidence_matrix(vertices=[2, 4, 1, 3, 0]) # needs sage.modules [0 1 1 0] [0 0 0 1] [1 1 0 0] @@ -2206,13 +2206,13 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None A different ordering of the edges:: sage: E = list(P5.edge_iterator(labels=False)) - sage: P5.incidence_matrix(edges=E[::-1]) # optional - sage.modules + sage: P5.incidence_matrix(edges=E[::-1]) # needs sage.modules [0 0 0 1] [0 0 1 1] [0 1 1 0] [1 1 0 0] [1 0 0 0] - sage: P5.incidence_matrix(vertices=[2, 4, 1, 3, 0], edges=E[::-1]) # optional - sage.modules + sage: P5.incidence_matrix(vertices=[2, 4, 1, 3, 0], edges=E[::-1]) # needs sage.modules [0 1 1 0] [1 0 0 0] [0 0 1 1] @@ -2221,7 +2221,7 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None A different base ring:: - sage: P5.incidence_matrix(base_ring=RDF) # optional - sage.modules + sage: P5.incidence_matrix(base_ring=RDF) # needs sage.modules [1.0 0.0 0.0 0.0] [1.0 1.0 0.0 0.0] [0.0 1.0 1.0 0.0] @@ -2230,13 +2230,13 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None Creating an immutable matrix:: - sage: m = P5.incidence_matrix(immutable=True); m # optional - sage.modules + sage: m = P5.incidence_matrix(immutable=True); m # needs sage.modules [1 0 0 0] [1 1 0 0] [0 1 1 0] [0 0 1 1] [0 0 0 1] - sage: m[1,2] = 1 # optional - sage.modules + sage: m[1,2] = 1 # needs sage.modules Traceback (most recent call last): ... ValueError: matrix is immutable; please change a copy instead @@ -2245,15 +2245,15 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None TESTS:: sage: P5 = graphs.PathGraph(5) - sage: P5.incidence_matrix(vertices=[1] * P5.order()) # optional - sage.modules + sage: P5.incidence_matrix(vertices=[1] * P5.order()) # needs sage.modules Traceback (most recent call last): ... ValueError: ``vertices`` must be a permutation of the vertices - sage: P5.incidence_matrix(edges=[(0, 1)] * P5.size()) # optional - sage.modules + sage: P5.incidence_matrix(edges=[(0, 1)] * P5.size()) # needs sage.modules Traceback (most recent call last): ... ValueError: ``edges`` must be a permutation of the edges - sage: P5.incidence_matrix(edges=P5.edges(sort=False, labels=True)) # optional - sage.modules + sage: P5.incidence_matrix(edges=P5.edges(sort=False, labels=True)) # needs sage.modules [1 0 0 0] [1 1 0 0] [0 1 1 0] @@ -2342,19 +2342,19 @@ def distance_matrix(self, vertices=None, *, base_ring=None, **kwds): EXAMPLES:: sage: d = DiGraph({1: [2, 3], 2: [3], 3: [4], 4: [1]}) - sage: d.distance_matrix() # optional - sage.modules + sage: d.distance_matrix() # needs sage.modules [0 1 1 2] [3 0 1 2] [2 3 0 1] [1 2 2 0] - sage: d.distance_matrix(vertices=[4, 3, 2, 1]) # optional - sage.modules + sage: d.distance_matrix(vertices=[4, 3, 2, 1]) # needs sage.modules [0 2 2 1] [1 0 3 2] [2 1 0 3] [2 1 1 0] sage: G = graphs.CubeGraph(3) - sage: G.distance_matrix() # optional - sage.modules + sage: G.distance_matrix() # needs sage.modules [0 1 1 2 1 2 2 3] [1 0 2 1 2 1 3 2] [1 2 0 1 2 3 1 2] @@ -2368,7 +2368,7 @@ def distance_matrix(self, vertices=None, *, base_ring=None, **kwds): of the distance matrix of any tree of order `n` is `(-1)^{n-1}(n-1)2^{n-2}`:: - sage: all(T.distance_matrix().det() == (-1)^9*(9)*2^8 # optional - sage.modules + sage: all(T.distance_matrix().det() == (-1)^9*(9)*2^8 # needs sage.modules ....: for T in graphs.trees(10)) True @@ -2382,18 +2382,18 @@ def distance_matrix(self, vertices=None, *, base_ring=None, **kwds): Asking for an immutable matrix:: sage: G = Graph([(0, 1)]) - sage: G.distance_matrix().is_immutable() # optional - sage.modules + sage: G.distance_matrix().is_immutable() # needs sage.modules False - sage: G.distance_matrix(immutable=True).is_immutable() # optional - sage.modules + sage: G.distance_matrix(immutable=True).is_immutable() # needs sage.modules True Specifying a base ring:: sage: G = Graph([(0, 1)]) - sage: G.distance_matrix(vertices=[0, 1], base_ring=ZZ) # optional - sage.modules + sage: G.distance_matrix(vertices=[0, 1], base_ring=ZZ) # needs sage.modules [0 1] [1 0] - sage: G.distance_matrix(vertices=[0, 1], base_ring=RDF) # optional - sage.modules + sage: G.distance_matrix(vertices=[0, 1], base_ring=RDF) # needs sage.modules [0.0 1.0] [1.0 0.0] @@ -2401,7 +2401,7 @@ def distance_matrix(self, vertices=None, *, base_ring=None, **kwds): constructor:: sage: G = Graph([(0, 1)]) - sage: G.distance_matrix(vertices=[0, 1], weight_function=lambda e:2) # optional - sage.modules + sage: G.distance_matrix(vertices=[0, 1], weight_function=lambda e:2) # needs sage.modules [0 2] [2 0] """ @@ -2479,15 +2479,15 @@ def weighted_adjacency_matrix(self, sparse=True, vertices=None, sage: G = Graph(sparse=True, weighted=True) sage: G.add_edges([(0, 1, 1), (1, 2, 2), (0, 2, 3), (0, 3, 4)]) - sage: M = G.weighted_adjacency_matrix(); M # optional - sage.modules + sage: M = G.weighted_adjacency_matrix(); M # needs sage.modules [0 1 3 4] [1 0 2 0] [3 2 0 0] [4 0 0 0] - sage: H = Graph(data=M, format='weighted_adjacency_matrix', sparse=True) # optional - sage.modules - sage: H == G # optional - sage.modules + sage: H = Graph(data=M, format='weighted_adjacency_matrix', sparse=True) # needs sage.modules + sage: H == G # needs sage.modules True - sage: G.weighted_adjacency_matrix(vertices=[3, 2, 1, 0]) # optional - sage.modules + sage: G.weighted_adjacency_matrix(vertices=[3, 2, 1, 0]) # needs sage.modules [0 0 0 4] [0 0 2 3] [0 2 0 1] @@ -2495,7 +2495,7 @@ def weighted_adjacency_matrix(self, sparse=True, vertices=None, Using a different matrix implementation:: - sage: M = G.weighted_adjacency_matrix(sparse=False, base_ring=ZZ, # optional - numpy sage.modules + sage: M = G.weighted_adjacency_matrix(sparse=False, base_ring=ZZ, # needs numpy sage.modules ....: implementation='numpy'); M [0 1 3 4] [1 0 2 0] @@ -2504,12 +2504,12 @@ def weighted_adjacency_matrix(self, sparse=True, vertices=None, As an immutable matrix:: - sage: M = G.weighted_adjacency_matrix(immutable=True); M # optional - sage.modules + sage: M = G.weighted_adjacency_matrix(immutable=True); M # needs sage.modules [0 1 3 4] [1 0 2 0] [3 2 0 0] [4 0 0 0] - sage: M[2, 2] = 1 # optional - sage.modules + sage: M[2, 2] = 1 # needs sage.modules Traceback (most recent call last): ... ValueError: matrix is immutable; please change a copy instead @@ -2520,7 +2520,7 @@ def weighted_adjacency_matrix(self, sparse=True, vertices=None, The following doctest verifies that :trac:`4888` is fixed:: sage: G = DiGraph({0:{}, 1:{0:1}, 2:{0:1}}, weighted=True, sparse=True) - sage: G.weighted_adjacency_matrix() # optional - sage.modules + sage: G.weighted_adjacency_matrix() # needs sage.modules [0 0 0] [1 0 0] [1 0 0] @@ -2528,16 +2528,16 @@ def weighted_adjacency_matrix(self, sparse=True, vertices=None, Check error message for non numerical edge weights (:trac:`33562`):: sage: G = Graph([(0, 1)]) - sage: G.weighted_adjacency_matrix() # optional - sage.modules + sage: G.weighted_adjacency_matrix() # needs sage.modules Traceback (most recent call last): ... ValueError: cannot find the weight of (0, 1, None). Consider setting parameter 'default_weight' - sage: G.weighted_adjacency_matrix(default_weight=3) # optional - sage.modules + sage: G.weighted_adjacency_matrix(default_weight=3) # needs sage.modules [0 3] [3 0] sage: G = Graph([(0, 1, 'a')]) - sage: G.weighted_adjacency_matrix() # optional - sage.modules + sage: G.weighted_adjacency_matrix() # needs sage.modules Traceback (most recent call last): ... TypeError: Cannot convert NoneType to sage.structure.parent.Parent @@ -2656,33 +2656,33 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, signl sage: G = Graph(sparse=True) sage: G.add_edges([(0, 1, 1), (1, 2, 2), (0, 2, 3), (0, 3, 4)]) - sage: M = G.kirchhoff_matrix(weighted=True); M # optional - sage.modules + sage: M = G.kirchhoff_matrix(weighted=True); M # needs sage.modules [ 8 -1 -3 -4] [-1 3 -2 0] [-3 -2 5 0] [-4 0 0 4] - sage: M = G.kirchhoff_matrix(); M # optional - sage.modules + sage: M = G.kirchhoff_matrix(); M # needs sage.modules [ 3 -1 -1 -1] [-1 2 -1 0] [-1 -1 2 0] [-1 0 0 1] - sage: M = G.laplacian_matrix(normalized=True); M # optional - sage.modules sage.symbolic + sage: M = G.laplacian_matrix(normalized=True); M # needs sage.modules sage.symbolic [ 1 -1/6*sqrt(3)*sqrt(2) -1/6*sqrt(3)*sqrt(2) -1/3*sqrt(3)] [-1/6*sqrt(3)*sqrt(2) 1 -1/2 0] [-1/6*sqrt(3)*sqrt(2) -1/2 1 0] [ -1/3*sqrt(3) 0 0 1] - sage: M = G.kirchhoff_matrix(weighted=True, signless=True); M # optional - sage.modules + sage: M = G.kirchhoff_matrix(weighted=True, signless=True); M # needs sage.modules [8 1 3 4] [1 3 2 0] [3 2 5 0] [4 0 0 4] sage: G = Graph({0: [], 1: [2]}) - sage: G.laplacian_matrix(normalized=True) # optional - sage.modules + sage: G.laplacian_matrix(normalized=True) # needs sage.modules [ 0 0 0] [ 0 1 -1] [ 0 -1 1] - sage: G.laplacian_matrix(normalized=True, signless=True) # optional - sage.modules + sage: G.laplacian_matrix(normalized=True, signless=True) # needs sage.modules [0 0 0] [0 1 1] [0 1 1] @@ -2690,14 +2690,14 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, signl A weighted directed graph with loops, changing the variable ``indegree`` :: sage: G = DiGraph({1: {1: 2, 2: 3}, 2: {1: 4}}, weighted=True, sparse=True) - sage: G.laplacian_matrix() # optional - sage.modules + sage: G.laplacian_matrix() # needs sage.modules [ 4 -3] [-4 3] :: sage: G = DiGraph({1: {1: 2, 2: 3}, 2: {1: 4}}, weighted=True, sparse=True) - sage: G.laplacian_matrix(indegree=False) # optional - sage.modules + sage: G.laplacian_matrix(indegree=False) # needs sage.modules [ 3 -3] [-4 4] @@ -2706,12 +2706,12 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, signl sage: G = Graph(sparse=True) sage: G.add_edges([(0, 1, 1), (1, 2, 2), (0, 2, 3), (0, 3, 4)]) - sage: M = G.kirchhoff_matrix(vertices=[3, 2, 1, 0]); M # optional - sage.modules + sage: M = G.kirchhoff_matrix(vertices=[3, 2, 1, 0]); M # needs sage.modules [ 1 0 0 -1] [ 0 2 -1 -1] [ 0 -1 2 -1] [-1 -1 -1 3] - sage: M = G.kirchhoff_matrix(weighted=True, vertices=[3, 2, 1, 0]); M # optional - sage.modules + sage: M = G.kirchhoff_matrix(weighted=True, vertices=[3, 2, 1, 0]); M # needs sage.modules [ 4 0 0 -4] [ 0 5 -2 -3] [ 0 -2 3 -1] @@ -2721,8 +2721,8 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, signl immutable:: sage: G = Graph([(0, 1)]) - sage: M = G.kirchhoff_matrix(vertices=[0, 1], immutable=True) # optional - sage.modules - sage: M.is_immutable() # optional - sage.modules + sage: M = G.kirchhoff_matrix(vertices=[0, 1], immutable=True) # needs sage.modules + sage: M.is_immutable() # needs sage.modules True """ from sage.matrix.constructor import diagonal_matrix @@ -3717,8 +3717,8 @@ def get_pos(self, dim=2): sage: G.get_pos() sage: G.get_pos() is None True - sage: P = G.plot(save_pos=True) # optional - sage.plot - sage: G.get_pos() # optional - sage.plot + sage: P = G.plot(save_pos=True) # needs sage.plot + sage: G.get_pos() # needs sage.plot {} Some of the named graphs come with a pre-specified positioning:: @@ -3843,8 +3843,8 @@ def set_pos(self, pos, dim=2): invalid positioning are ignored:: sage: G.set_pos(dict(enumerate('abcdefghi'))) - sage: P = G.plot() # positions are ignored # optional - sage.plot - sage: G.get_pos() is None # optional - sage.plot + sage: P = G.plot() # positions are ignored # needs sage.plot + sage: G.get_pos() is None # needs sage.plot True """ if pos is None: @@ -3974,21 +3974,21 @@ def antisymmetric(self): A directed acyclic graph is antisymmetric:: - sage: G = digraphs.RandomDirectedGNR(20, 0.5) # optional - networkx - sage: G.antisymmetric() # optional - networkx + sage: G = digraphs.RandomDirectedGNR(20, 0.5) # needs networkx + sage: G.antisymmetric() # needs networkx True Loops are allowed:: - sage: G.allow_loops(True) # optional - networkx - sage: G.add_edge(0, 0) # optional - networkx - sage: G.antisymmetric() # optional - networkx + sage: G.allow_loops(True) # needs networkx + sage: G.add_edge(0, 0) # needs networkx + sage: G.antisymmetric() # needs networkx True An undirected graph is never antisymmetric unless it is just a union of isolated vertices (with possible loops):: - sage: graphs.RandomGNP(20, 0.5).antisymmetric() # optional - networkx + sage: graphs.RandomGNP(20, 0.5).antisymmetric() # needs networkx False sage: Graph(3).antisymmetric() True @@ -4070,7 +4070,7 @@ def is_bipartite(self, certificate=False): True sage: graphs.CycleGraph(5).is_bipartite() False - sage: graphs.RandomBipartite(10, 10, 0.7).is_bipartite() # optional - numpy + sage: graphs.RandomBipartite(10, 10, 0.7).is_bipartite() # needs numpy True A random graph is very rarely bipartite:: @@ -4705,7 +4705,7 @@ def min_spanning_tree(self, NetworkX algorithm:: - sage: sorted(g.min_spanning_tree(algorithm='NetworkX')) # optional - networkx + sage: sorted(g.min_spanning_tree(algorithm='NetworkX')) # needs networkx [(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None)] More complicated weights:: @@ -4756,7 +4756,7 @@ def min_spanning_tree(self, [(0, 1, 1), (1, 2, 1)] sage: sorted(g.min_spanning_tree(algorithm='Prim_Boost')) [(0, 1, 1), (1, 2, 1)] - sage: sorted(g.min_spanning_tree(algorithm='NetworkX')) # optional - networkx + sage: sorted(g.min_spanning_tree(algorithm='NetworkX')) # needs networkx [(0, 1, 1), (1, 2, 1)] sage: sorted(g.min_spanning_tree(algorithm='Boruvka')) [(0, 1, 1), (1, 2, 1)] @@ -4778,7 +4778,7 @@ def min_spanning_tree(self, [(0, 2, 10), (1, 2, 1)] sage: sorted(g.min_spanning_tree(algorithm='Prim_Boost', weight_function=weight)) [(0, 2, 10), (1, 2, 1)] - sage: sorted(g.min_spanning_tree(algorithm='NetworkX', weight_function=weight)) # optional - networkx + sage: sorted(g.min_spanning_tree(algorithm='NetworkX', weight_function=weight)) # needs networkx [(0, 2, 10), (1, 2, 1)] sage: sorted(g.min_spanning_tree(algorithm='Boruvka', weight_function=weight)) [(0, 2, 10), (1, 2, 1)] @@ -4977,13 +4977,14 @@ def spanning_trees_count(self, root_vertex=None): :: - sage: M = matrix(3, 3, [0, 1, 0, 0, 0, 1, 1, 1, 0]) # optional - sage.modules - sage: D = DiGraph(M) # optional - sage.modules - sage: D.spanning_trees_count() # optional - sage.modules + sage: # needs sage.modules + sage: M = matrix(3, 3, [0, 1, 0, 0, 0, 1, 1, 1, 0]) + sage: D = DiGraph(M) + sage: D.spanning_trees_count() 1 - sage: D.spanning_trees_count(0) # optional - sage.modules + sage: D.spanning_trees_count(0) 1 - sage: D.spanning_trees_count(2) # optional - sage.modules + sage: D.spanning_trees_count(2) 2 """ if not self.order(): @@ -5041,13 +5042,13 @@ def cycle_basis(self, output='vertex'): A cycle basis in Petersen's Graph :: sage: g = graphs.PetersenGraph() - sage: g.cycle_basis() # optional - networkx + sage: g.cycle_basis() # needs networkx [[1, 6, 8, 5, 0], [4, 9, 6, 8, 5, 0], [7, 9, 6, 8, 5], [4, 3, 8, 5, 0], [1, 2, 3, 8, 5, 0], [7, 2, 3, 8, 5]] One can also get the result as a list of lists of edges:: - sage: g.cycle_basis(output='edge') # optional - networkx + sage: g.cycle_basis(output='edge') # needs networkx [[(1, 6, None), (6, 8, None), (8, 5, None), (5, 0, None), (0, 1, None)], [(4, 9, None), (9, 6, None), (6, 8, None), (8, 5, None), (5, 0, None), (0, 4, None)], [(7, 9, None), @@ -5059,17 +5060,17 @@ def cycle_basis(self, output='vertex'): Checking the given cycles are algebraically free:: - sage: g = graphs.RandomGNP(30, .4) # optional - networkx - sage: basis = g.cycle_basis() # optional - networkx + sage: g = graphs.RandomGNP(30, .4) # needs networkx + sage: basis = g.cycle_basis() # needs networkx Building the space of (directed) edges over `Z/2Z`. On the way, building a dictionary associating a unique vector to each undirected edge:: sage: m = g.size() - sage: edge_space = VectorSpace(FiniteField(2), m) # optional - sage.modules sage.rings.finite_rings - sage: edge_vector = dict(zip(g.edges(labels=False, sort=False), # optional - sage.modules sage.rings.finite_rings + sage: edge_space = VectorSpace(FiniteField(2), m) # needs sage.modules sage.rings.finite_rings + sage: edge_vector = dict(zip(g.edges(labels=False, sort=False), # needs sage.modules sage.rings.finite_rings ....: edge_space.basis())) - sage: for (u, v), vec in list(edge_vector.items()): # optional - sage.modules sage.rings.finite_rings + sage: for (u, v), vec in list(edge_vector.items()): # needs sage.modules sage.rings.finite_rings ....: edge_vector[(v, u)] = vec Defining a lambda function associating a vector to the vertices of a @@ -5080,29 +5081,29 @@ def cycle_basis(self, output='vertex'): Finally checking the cycles are a free set:: - sage: basis_as_vectors = [cycle_to_vector(_) for _ in basis] # optional - sage.modules sage.rings.finite_rings - sage: edge_space.span(basis_as_vectors).rank() == len(basis) # optional - sage.modules sage.rings.finite_rings + sage: basis_as_vectors = [cycle_to_vector(_) for _ in basis] # needs networkx sage.modules sage.rings.finite_rings + sage: edge_space.span(basis_as_vectors).rank() == len(basis) # needs networkx sage.modules sage.rings.finite_rings True For undirected graphs with multiple edges:: sage: G = Graph([(0, 2, 'a'), (0, 2, 'b'), (0, 1, 'c'), (1, 2, 'd')], ....: multiedges=True) - sage: G.cycle_basis() # optional - networkx + sage: G.cycle_basis() # needs networkx [[0, 2], [2, 1, 0]] - sage: G.cycle_basis(output='edge') # optional - networkx + sage: G.cycle_basis(output='edge') # needs networkx [[(0, 2, 'a'), (2, 0, 'b')], [(2, 1, 'd'), (1, 0, 'c'), (0, 2, 'a')]] sage: H = Graph([(1, 2), (2, 3), (2, 3), (3, 4), (1, 4), ....: (1, 4), (4, 5), (5, 6), (4, 6), (6, 7)], multiedges=True) - sage: H.cycle_basis() # optional - networkx + sage: H.cycle_basis() # needs networkx [[1, 4], [2, 3], [4, 3, 2, 1], [6, 5, 4]] Disconnected graph:: sage: G.add_cycle(["Hey", "Wuuhuu", "Really ?"]) - sage: [sorted(c) for c in G.cycle_basis()] # optional - networkx + sage: [sorted(c) for c in G.cycle_basis()] # needs networkx [['Hey', 'Really ?', 'Wuuhuu'], [0, 2], [0, 1, 2]] - sage: [sorted(c) for c in G.cycle_basis(output='edge')] # optional - networkx + sage: [sorted(c) for c in G.cycle_basis(output='edge')] # needs networkx [[('Hey', 'Wuuhuu', None), ('Really ?', 'Hey', None), ('Wuuhuu', 'Really ?', None)], @@ -5113,13 +5114,13 @@ def cycle_basis(self, output='vertex'): sage: G = graphs.CycleGraph(3) sage: G.allow_multiple_edges(True) - sage: G.cycle_basis() # optional - networkx + sage: G.cycle_basis() # needs networkx [[2, 1, 0]] Not yet implemented for directed graphs:: sage: G = DiGraph([(0, 2, 'a'), (0, 1, 'c'), (1, 2, 'd')]) - sage: G.cycle_basis() # optional - networkx + sage: G.cycle_basis() # needs networkx Traceback (most recent call last): ... NotImplementedError: not implemented for directed graphs @@ -5130,9 +5131,9 @@ def cycle_basis(self, output='vertex'): sage: G = Graph([(1, 2, 'a'), (2, 3, 'b'), (2, 3, 'c'), ....: (3, 4, 'd'), (3, 4, 'e'), (4, 1, 'f')], multiedges=True) - sage: G.cycle_basis() # optional - networkx + sage: G.cycle_basis() # needs networkx [[2, 3], [4, 3, 2, 1], [4, 3, 2, 1]] - sage: G.cycle_basis(output='edge') # optional - networkx + sage: G.cycle_basis(output='edge') # needs networkx [[(2, 3, 'b'), (3, 2, 'c')], [(4, 3, 'd'), (3, 2, 'b'), (2, 1, 'a'), (1, 4, 'f')], [(4, 3, 'e'), (3, 2, 'b'), (2, 1, 'a'), (1, 4, 'f')]] @@ -5213,9 +5214,9 @@ def minimum_cycle_basis(self, algorithm=None, weight_function=None, by_weight=Fa [[1, 2, 3], [1, 2, 3, 4], [5, 6, 7]] sage: sorted(g.minimum_cycle_basis(by_weight=False)) [[1, 2, 3], [1, 3, 4], [5, 6, 7]] - sage: sorted(g.minimum_cycle_basis(by_weight=True, algorithm='NetworkX')) # optional - networkx + sage: sorted(g.minimum_cycle_basis(by_weight=True, algorithm='NetworkX')) # needs networkx [[1, 2, 3], [1, 2, 3, 4], [5, 6, 7]] - sage: g.minimum_cycle_basis(by_weight=False, algorithm='NetworkX') # optional - networkx + sage: g.minimum_cycle_basis(by_weight=False, algorithm='NetworkX') # needs networkx [[1, 2, 3], [1, 3, 4], [5, 6, 7]] :: @@ -5223,7 +5224,7 @@ def minimum_cycle_basis(self, algorithm=None, weight_function=None, by_weight=Fa sage: g = Graph([(1, 2), (2, 3), (3, 4), (4, 5), (5, 1), (5, 3)]) sage: sorted(g.minimum_cycle_basis(by_weight=False)) [[1, 2, 3, 5], [3, 4, 5]] - sage: sorted(g.minimum_cycle_basis(by_weight=False, algorithm='NetworkX')) # optional - networkx + sage: sorted(g.minimum_cycle_basis(by_weight=False, algorithm='NetworkX')) # needs networkx [[1, 2, 3, 5], [3, 4, 5]] TESTS:: @@ -5352,7 +5353,7 @@ def is_planar(self, on_embedding=None, kuratowski=False, set_embedding=False, se :: sage: g = graphs.PetersenGraph() - sage: (g.is_planar(kuratowski=True))[1].adjacency_matrix() # optional - sage.modules + sage: (g.is_planar(kuratowski=True))[1].adjacency_matrix() # needs sage.modules [0 1 0 0 0 1 0 0 0] [1 0 1 0 0 0 1 0 0] [0 1 0 1 0 0 0 1 0] @@ -5555,7 +5556,7 @@ def is_circular_planar(self, on_embedding=None, kuratowski=False, EXAMPLES:: sage: g439 = Graph({1: [5, 7], 2: [5, 6], 3: [6, 7], 4: [5, 6, 7]}) - sage: g439.show() # optional - sage.plot + sage: g439.show() # needs sage.plot sage: g439.is_circular_planar(boundary=[1, 2, 3, 4]) False sage: g439.is_circular_planar(kuratowski=True, boundary=[1, 2, 3, 4]) @@ -5731,11 +5732,11 @@ def layout_planar(self, set_embedding=False, on_embedding=None, 7: [2, 4], 8: [1, 6], 9: [2, 5]} - sage: g = graphs.BalancedTree(3, 4) # optional - networkx - sage: pos = g.layout(layout='planar', save_pos=True, test=True) # optional - networkx - sage: pos[0] # optional - networkx + sage: g = graphs.BalancedTree(3, 4) # needs networkx + sage: pos = g.layout(layout='planar', save_pos=True, test=True) # needs networkx + sage: pos[0] # needs networkx [0, 119] - sage: pos[120] # optional - networkx + sage: pos[120] # needs networkx [21, 37] sage: g = graphs.CycleGraph(7) sage: g.layout(layout='planar', save_pos=True, test=True) @@ -10161,7 +10162,7 @@ def _build_flow_graph(self, flow, integer): The method removes zero-cost flow cycles and updates the values accordingly:: - sage: g = digraphs.DeBruijn(2,3) # optional - sage.combinat + sage: g = digraphs.DeBruijn(2,3) # needs sage.combinat sage: flow = {('001', '010'): 1, ('010', '100'): 1, ....: ('010', '101'): 1, ('101', '010'): 1} sage: flow_graph = g._build_flow_graph(flow, True) @@ -10480,34 +10481,34 @@ def pagerank(self, alpha=0.85, personalization=None, by_weight=False, EXAMPLES:: sage: G = graphs.CycleGraph(4) - sage: G.pagerank(algorithm="Networkx") # optional - networkx + sage: G.pagerank(algorithm="Networkx") # needs networkx {0: 0.25, 1: 0.25, 2: 0.25, 3: 0.25} sage: G.pagerank(alpha=0.50, algorithm="igraph") # abs tol 1e-9, optional - python_igraph {0: 0.25, 1: 0.25, 2: 0.25, 3: 0.25} sage: G = Graph([(1, 2, 40), (2, 3, 50), (3, 4, 60), ....: (1, 4, 70), (4, 5, 80), (5, 6, 20)]) - sage: G.pagerank(algorithm="NetworkX") # abs tol 1e-9 # optional - networkx + sage: G.pagerank(algorithm="NetworkX") # abs tol 1e-9 # needs networkx {1: 0.16112205885619563, 2: 0.1619531043247219, 3: 0.16112205885619563, 4: 0.2374999999999999, 5: 0.17775588228760858, 6: 0.100546895675278} - sage: G.pagerank(algorithm="NetworkX", by_weight=True) # abs tol 1e-9 # optional - networkx + sage: G.pagerank(algorithm="NetworkX", by_weight=True) # abs tol 1e-9 # needs networkx {1: 0.16459583718588994, 2: 0.13977928595154515, 3: 0.16539840184339605, 4: 0.3063198690713853, 5: 0.1700057609707141, 6: 0.05390084497706962} - sage: G.pagerank(algorithm="Scipy") # abs tol 1e-9 # optional - scipy + sage: G.pagerank(algorithm="Scipy") # abs tol 1e-9 # needs scipy {1: 0.16112205885619563, 2: 0.1619531043247219, 3: 0.16112205885619563, 4: 0.2374999999999999, 5: 0.17775588228760858, 6: 0.100546895675278} - sage: G.pagerank(algorithm="Scipy", by_weight=True) # abs tol 1e-9 # optional - scipy + sage: G.pagerank(algorithm="Scipy", by_weight=True) # abs tol 1e-9 # needs scipy {1: 0.16459583718588994, 2: 0.13977928595154515, 3: 0.16539840184339605, @@ -10539,7 +10540,7 @@ def pagerank(self, alpha=0.85, personalization=None, by_weight=False, TESTS:: sage: G = Graph([(1, 2), (2, 3), (3, 4), (1, 3)]) - sage: G.pagerank(algorithm="NetworkX", # optional - networkx + sage: G.pagerank(algorithm="NetworkX", # needs networkx ....: personalization={1:0, 2:3, 3:-2, 4:-1}) Traceback (most recent call last): ... @@ -10678,7 +10679,7 @@ def delete_vertex(self, vertex, in_order=False): sage: G = Graph(graphs.WheelGraph(9)) sage: G.delete_vertex(0) - sage: G.show() # optional - sage.plot + sage: G.show() # needs sage.plot :: @@ -11354,13 +11355,14 @@ def vertices(self, sort=None, key=None, degree=None, vertex_property=None): If you do not care about sorted output and you are concerned about the time taken to sort, consider the following alternative:: - sage: timeit V = P.vertices(sort=True) # not tested + sage: # not tested + sage: timeit V = P.vertices(sort=True) 625 loops, best of 3: 3.86 [micro]s per loop - sage: timeit V = P.vertices(sort=False) # not tested + sage: timeit V = P.vertices(sort=False) 625 loops, best of 3: 2.06 [micro]s per loop - sage: timeit V = list(P.vertex_iterator()) # not tested + sage: timeit V = list(P.vertex_iterator()) 625 loops, best of 3: 2.05 [micro]s per loop - sage: timeit('V = list(P)') # not tested + sage: timeit('V = list(P)') 625 loops, best of 3: 1.98 [micro]s per loop We illustrate various ways to use a ``key`` to sort the list:: @@ -12979,13 +12981,14 @@ def degree(self, vertices=None, labels=False): returned list is the degree of the `i`-th vertex in the list ``list(self)``:: - sage: D = digraphs.DeBruijn(4, 2) # optional - sage.combinat - sage: D.delete_vertex('20') # optional - sage.combinat - sage: print(D.degree()) # optional - sage.combinat + sage: # needs sage.combinat + sage: D = digraphs.DeBruijn(4, 2) + sage: D.delete_vertex('20') + sage: print(D.degree()) [7, 7, 6, 7, 8, 8, 7, 8, 8, 7, 8, 8, 8, 7, 8] - sage: print(D.degree(vertices=list(D))) # optional - sage.combinat + sage: print(D.degree(vertices=list(D))) [7, 7, 6, 7, 8, 8, 7, 8, 8, 7, 8, 8, 8, 7, 8] - sage: print(D.degree(vertices=D.vertices(sort=False))) # optional - sage.combinat + sage: print(D.degree(vertices=D.vertices(sort=False))) [7, 7, 6, 7, 8, 8, 7, 8, 8, 7, 8, 8, 8, 7, 8] """ if labels: @@ -13123,11 +13126,11 @@ def degree_iterator(self, vertices=None, labels=False): When ``vertices=None`` yields values in the order of ``list(D)``:: sage: V = list(D) - sage: D = digraphs.DeBruijn(4, 2) # optional - sage.combinat - sage: D.delete_vertex('20') # optional - sage.combinat - sage: print(list(D.degree_iterator())) # optional - sage.combinat + sage: D = digraphs.DeBruijn(4, 2) # needs sage.combinat + sage: D.delete_vertex('20') # needs sage.combinat + sage: print(list(D.degree_iterator())) # needs sage.combinat [7, 7, 6, 7, 8, 8, 7, 8, 8, 7, 8, 8, 8, 7, 8] - sage: print([D.degree(v) for v in D]) # optional - sage.combinat + sage: print([D.degree(v) for v in D]) # needs sage.combinat [7, 7, 6, 7, 8, 8, 7, 8, 8, 7, 8, 8, 8, 7, 8] """ if vertices is None: @@ -13758,99 +13761,100 @@ def subgraph_search(self, G, induced=False): The Petersen graph contains the path graph `P_5`:: sage: g = graphs.PetersenGraph() - sage: h1 = g.subgraph_search(graphs.PathGraph(5)); h1 # optional - sage.modules + sage: h1 = g.subgraph_search(graphs.PathGraph(5)); h1 # needs sage.modules Subgraph of (Petersen graph): Graph on 5 vertices - sage: h1.vertices(sort=True); h1.edges(sort=True, labels=False) # optional - sage.modules + sage: h1.vertices(sort=True); h1.edges(sort=True, labels=False) # needs sage.modules [0, 1, 2, 3, 4] [(0, 1), (1, 2), (2, 3), (3, 4)] - sage: I1 = g.subgraph_search(graphs.PathGraph(5), induced=True); I1 # optional - sage.modules + sage: I1 = g.subgraph_search(graphs.PathGraph(5), induced=True); I1 # needs sage.modules Subgraph of (Petersen graph): Graph on 5 vertices - sage: I1.vertices(sort=True); I1.edges(sort=True, labels=False) # optional - sage.modules + sage: I1.vertices(sort=True); I1.edges(sort=True, labels=False) # needs sage.modules [0, 1, 2, 3, 8] [(0, 1), (1, 2), (2, 3), (3, 8)] It also contains the claw `K_{1,3}`:: - sage: h2 = g.subgraph_search(graphs.ClawGraph()); h2 # optional - sage.modules + sage: # needs sage.modules + sage: h2 = g.subgraph_search(graphs.ClawGraph()); h2 Subgraph of (Petersen graph): Graph on 4 vertices - sage: h2.vertices(sort=True); h2.edges(sort=True, labels=False) # optional - sage.modules + sage: h2.vertices(sort=True); h2.edges(sort=True, labels=False) [0, 1, 4, 5] [(0, 1), (0, 4), (0, 5)] - sage: I2 = g.subgraph_search(graphs.ClawGraph(), induced=True); I2 # optional - sage.modules + sage: I2 = g.subgraph_search(graphs.ClawGraph(), induced=True); I2 Subgraph of (Petersen graph): Graph on 4 vertices - sage: I2.vertices(sort=True); I2.edges(sort=True, labels=False) # optional - sage.modules + sage: I2.vertices(sort=True); I2.edges(sort=True, labels=False) [0, 1, 4, 5] [(0, 1), (0, 4), (0, 5)] Of course the induced copies are isomorphic to the graphs we were looking for:: - sage: I1.is_isomorphic(graphs.PathGraph(5)) # optional - sage.modules + sage: I1.is_isomorphic(graphs.PathGraph(5)) # needs sage.modules True - sage: I2.is_isomorphic(graphs.ClawGraph()) # optional - sage.modules + sage: I2.is_isomorphic(graphs.ClawGraph()) # needs sage.modules True However, the Petersen graph does not contain a subgraph isomorphic to `K_3`:: - sage: g.subgraph_search(graphs.CompleteGraph(3)) is None # optional - sage.modules + sage: g.subgraph_search(graphs.CompleteGraph(3)) is None # needs sage.modules True Nor does it contain a nonempty induced subgraph isomorphic to `P_6`:: - sage: g.subgraph_search(graphs.PathGraph(6), induced=True) is None # optional - sage.modules + sage: g.subgraph_search(graphs.PathGraph(6), induced=True) is None # needs sage.modules True The empty graph is a subgraph of every graph:: - sage: g.subgraph_search(graphs.EmptyGraph()) # optional - sage.modules + sage: g.subgraph_search(graphs.EmptyGraph()) # needs sage.modules Graph on 0 vertices - sage: g.subgraph_search(graphs.EmptyGraph(), induced=True) # optional - sage.modules + sage: g.subgraph_search(graphs.EmptyGraph(), induced=True) # needs sage.modules Graph on 0 vertices The subgraph may just have edges missing:: sage: k3 = graphs.CompleteGraph(3); p3 = graphs.PathGraph(3) sage: k3.relabel(list('abc')) - sage: s = k3.subgraph_search(p3) # optional - sage.modules - sage: s.edges(sort=True, labels=False) # optional - sage.modules + sage: s = k3.subgraph_search(p3) # needs sage.modules + sage: s.edges(sort=True, labels=False) # needs sage.modules [('a', 'b'), ('b', 'c')] Of course, `P_3` is not an induced subgraph of `K_3`, though:: sage: k3 = graphs.CompleteGraph(3); p3 = graphs.PathGraph(3) sage: k3.relabel(list('abc')) - sage: k3.subgraph_search(p3, induced=True) is None # optional - sage.modules + sage: k3.subgraph_search(p3, induced=True) is None # needs sage.modules True If the graph has labels, the labels are just ignored:: sage: g.set_vertex(0, 'foo') - sage: c = g.subgraph_search(graphs.PathGraph(5)) # optional - sage.modules - sage: c.get_vertices() # optional - sage.modules + sage: c = g.subgraph_search(graphs.PathGraph(5)) # needs sage.modules + sage: c.get_vertices() # needs sage.modules {0: 'foo', 1: None, 2: None, 3: None, 4: None} TESTS: Inside of a small graph (:trac:`13906`):: - sage: Graph(5).subgraph_search(Graph(1)) # optional - sage.modules + sage: Graph(5).subgraph_search(Graph(1)) # needs sage.modules Graph on 1 vertex For labelled edges (:trac:`14999`):: sage: G = graphs.CompleteGraph(10) - sage: C = G.subgraph_search(graphs.CycleGraph(4)) # optional - sage.modules - sage: C.size() # optional - sage.modules + sage: C = G.subgraph_search(graphs.CycleGraph(4)) # needs sage.modules + sage: C.size() # needs sage.modules 4 - sage: C.edges(sort=True) # optional - sage.modules + sage: C.edges(sort=True) # needs sage.modules [(0, 1, None), (0, 3, None), (1, 2, None), (2, 3, None)] sage: for (u,v) in G.edges(sort=True, labels=False): ....: G.set_edge_label(u, v, u) - sage: C = G.subgraph_search(graphs.CycleGraph(4)) # optional - sage.modules - sage: C.edges(sort=True) # optional - sage.modules + sage: C = G.subgraph_search(graphs.CycleGraph(4)) # needs sage.modules + sage: C.edges(sort=True) # needs sage.modules [(0, 1, 0), (0, 3, 0), (1, 2, 1), (2, 3, 2)] """ @@ -13891,12 +13895,12 @@ def subgraph_search_count(self, G, induced=False): Counting the number of paths `P_5` in a PetersenGraph:: sage: g = graphs.PetersenGraph() - sage: g.subgraph_search_count(graphs.PathGraph(5)) # optional - sage.modules + sage: g.subgraph_search_count(graphs.PathGraph(5)) # needs sage.modules 240 Requiring these subgraphs be induced:: - sage: g.subgraph_search_count(graphs.PathGraph(5), induced=True) # optional - sage.modules + sage: g.subgraph_search_count(graphs.PathGraph(5), induced=True) # needs sage.modules 120 If we define the graph `T_k` (the transitive tournament on `k` vertices) @@ -13905,36 +13909,36 @@ def subgraph_search_count(self, G, induced=False): `0`:: sage: T5 = digraphs.TransitiveTournament(5) - sage: T5.subgraph_search_count(digraphs.Circuit(3)) # optional - sage.modules + sage: T5.subgraph_search_count(digraphs.Circuit(3)) # needs sage.modules 0 If we count instead the number of `T_3` in `T_5`, we expect the answer to be `\binom{5}{3}`:: sage: T3 = digraphs.TransitiveTournament(3) - sage: T5.subgraph_search_count(T3) # optional - sage.modules + sage: T5.subgraph_search_count(T3) # needs sage.modules 10 sage: binomial(5,3) 10 - sage: T3.is_isomorphic(T5.subgraph(vertices=[0, 1, 2])) # optional - sage.modules + sage: T3.is_isomorphic(T5.subgraph(vertices=[0, 1, 2])) # needs sage.modules True The empty graph is a subgraph of every graph:: - sage: g.subgraph_search_count(graphs.EmptyGraph()) # optional - sage.modules + sage: g.subgraph_search_count(graphs.EmptyGraph()) # needs sage.modules 1 If the graph has vertex labels or edge labels, the label is just ignored:: sage: g.set_vertex(0, 'foo') - sage: g.subgraph_search_count(graphs.PathGraph(5)) # optional - sage.modules + sage: g.subgraph_search_count(graphs.PathGraph(5)) # needs sage.modules 240 TESTS: Inside of a small graph (:trac:`13906`):: - sage: Graph(5).subgraph_search_count(Graph(1)) # optional - sage.modules + sage: Graph(5).subgraph_search_count(Graph(1)) # needs sage.modules 5 """ from sage.graphs.generic_graph_pyx import SubgraphSearch @@ -14001,7 +14005,7 @@ def subgraph_search_iterator(self, G, induced=False, return_graphs=True): sage: g = graphs.PathGraph(5) sage: P3 = graphs.PathGraph(3) - sage: for p in g.subgraph_search_iterator(P3, return_graphs=False): # optional - sage.modules + sage: for p in g.subgraph_search_iterator(P3, return_graphs=False): # needs sage.modules ....: print(p) [0, 1, 2] [1, 2, 3] @@ -14009,7 +14013,7 @@ def subgraph_search_iterator(self, G, induced=False, return_graphs=True): [2, 3, 4] [3, 2, 1] [4, 3, 2] - sage: for p in g.subgraph_search_iterator(P3, return_graphs=True): # optional - sage.modules + sage: for p in g.subgraph_search_iterator(P3, return_graphs=True): # needs sage.modules ....: print(p) Subgraph of (Path graph) Subgraph of (Path graph) @@ -14017,13 +14021,13 @@ def subgraph_search_iterator(self, G, induced=False, return_graphs=True): Subgraph of (Path graph) Subgraph of (Path graph) Subgraph of (Path graph) - sage: all(h.is_isomorphic(P3) for h in g.subgraph_search_iterator(P3)) # optional - sage.modules + sage: all(h.is_isomorphic(P3) for h in g.subgraph_search_iterator(P3)) # needs sage.modules True If the graph has vertex labels or edge labels, the label is just ignored:: sage: g.set_vertex(0, 'foo') - sage: for p in g.subgraph_search_iterator(P3, return_graphs=False): # optional - sage.modules + sage: for p in g.subgraph_search_iterator(P3, return_graphs=False): # needs sage.modules ....: print(p) [0, 1, 2] [1, 2, 3] @@ -14036,47 +14040,47 @@ def subgraph_search_iterator(self, G, induced=False, return_graphs=True): sage: H = graphs.HouseGraph() sage: P4 = graphs.PathGraph(4) - sage: all(h.is_isomorphic(P4) # optional - sage.modules + sage: all(h.is_isomorphic(P4) # needs sage.modules ....: for h in H.subgraph_search_iterator(P4, induced=True)) True - sage: sum(1 for h in H.subgraph_search_iterator(P4, induced=True)) # optional - sage.modules + sage: sum(1 for h in H.subgraph_search_iterator(P4, induced=True)) # needs sage.modules 4 - sage: sum(1 for h in H.subgraph_search_iterator(P4, induced=False)) # optional - sage.modules + sage: sum(1 for h in H.subgraph_search_iterator(P4, induced=False)) # needs sage.modules 20 Search for subdigraphs:: sage: H = digraphs.Complete(5) sage: P4 = digraphs.Path(4) - sage: sum(1 for _ in H.subgraph_search_iterator(P4, induced=True)) # optional - sage.modules + sage: sum(1 for _ in H.subgraph_search_iterator(P4, induced=True)) # needs sage.modules 0 - sage: sum(1 for _ in H.subgraph_search_iterator(P4, induced=False)) # optional - sage.modules + sage: sum(1 for _ in H.subgraph_search_iterator(P4, induced=False)) # needs sage.modules 120 This method also works for bipartite graphs:: sage: K33 = BipartiteGraph(graphs.CompleteBipartiteGraph(3, 3)) sage: K22 = BipartiteGraph(graphs.CompleteBipartiteGraph(2, 2)) - sage: sum(1 for _ in K33.subgraph_search_iterator(K22)) # optional - sage.modules + sage: sum(1 for _ in K33.subgraph_search_iterator(K22)) # needs sage.modules 72 TESTS: Inside of a small graph (:trac:`13906`):: - sage: list(Graph(5).subgraph_search_iterator(Graph(1))) # optional - sage.modules + sage: list(Graph(5).subgraph_search_iterator(Graph(1))) # needs sage.modules [Graph on 1 vertex, Graph on 1 vertex, Graph on 1 vertex, Graph on 1 vertex, Graph on 1 vertex] Check that the behavior of the method is consistent (:trac:`34004`):: sage: g = graphs.CycleGraph(3) - sage: for i in range(3): # optional - sage.modules + sage: for i in range(3): # needs sage.modules ....: g.subgraph_search_iterator(graphs.PathGraph(i)) sage: K4 = digraphs.Complete(4) sage: K3 = digraphs.Complete(3) - sage: for g in K4.subgraph_search_iterator(K3, return_graphs=True): # optional - sage.modules + sage: for g in K4.subgraph_search_iterator(K3, return_graphs=True): # needs sage.modules ....: print(type(g)) ....: break sage: K33 = BipartiteGraph(graphs.CompleteBipartiteGraph(3, 3)) sage: K22 = BipartiteGraph(graphs.CompleteBipartiteGraph(2, 2)) - sage: for b in K33.subgraph_search_iterator(K22, return_graphs=True): # optional - sage.modules + sage: for b in K33.subgraph_search_iterator(K22, return_graphs=True): # needs sage.modules ....: print(type(b)) ....: break sage: P5 = graphs.PathGraph(5) - sage: for b in K33.subgraph_search_iterator(P5, return_graphs=True): # optional - sage.modules + sage: for b in K33.subgraph_search_iterator(P5, return_graphs=True): # needs sage.modules ....: print(type(b)) ....: break - sage: for b in Graph(K33).subgraph_search_iterator(K22, return_graphs=True): # optional - sage.modules + sage: for b in Graph(K33).subgraph_search_iterator(K22, return_graphs=True): # needs sage.modules ....: print(type(b)) ....: break @@ -14244,21 +14248,21 @@ def is_chordal(self, certificate=False, algorithm="B"): The same goes with the product of a random lobster (which is a tree) and a Complete Graph :: - sage: grl = graphs.RandomLobster(10, .5, .5) # optional - networkx - sage: g = grl.lexicographic_product(graphs.CompleteGraph(3)) # optional - networkx - sage: g.is_chordal() # optional - networkx + sage: grl = graphs.RandomLobster(10, .5, .5) # needs networkx + sage: g = grl.lexicographic_product(graphs.CompleteGraph(3)) # needs networkx + sage: g.is_chordal() # needs networkx True The disjoint union of chordal graphs is still chordal:: - sage: (2 * g).is_chordal() # optional - networkx + sage: (2 * g).is_chordal() # needs networkx True Let us check the certificate given by Sage is indeed a perfect elimination order:: - sage: _, peo = g.is_chordal(certificate=True) # optional - networkx - sage: for v in peo: # optional - networkx + sage: _, peo = g.is_chordal(certificate=True) # needs networkx + sage: for v in peo: # needs networkx ....: if not g.subgraph(g.neighbors(v)).is_clique(): ....: raise ValueError("this should never happen") ....: g.delete_vertex(v) @@ -14478,27 +14482,27 @@ def is_circulant(self, certificate=False): The Petersen graph is not a circulant graph:: sage: g = graphs.PetersenGraph() - sage: g.is_circulant() # optional - sage.groups + sage: g.is_circulant() # needs sage.groups False A cycle is obviously a circulant graph, but several sets of parameters can be used to define it:: sage: g = graphs.CycleGraph(5) - sage: g.is_circulant(certificate=True) # optional - sage.groups + sage: g.is_circulant(certificate=True) # needs sage.groups (True, [(5, [1, 4]), (5, [2, 3])]) The same goes for directed graphs:: sage: g = digraphs.Circuit(5) - sage: g.is_circulant(certificate=True) # optional - sage.groups + sage: g.is_circulant(certificate=True) # needs sage.groups (True, [(5, [1]), (5, [3]), (5, [2]), (5, [4])]) With this information, it is very easy to create (and plot) all possible drawings of a circulant graph:: sage: g = graphs.CirculantGraph(13, [2, 3, 10, 11]) - sage: for param in g.is_circulant(certificate=True)[1]: # optional - sage.groups + sage: for param in g.is_circulant(certificate=True)[1]: # needs sage.groups ....: graphs.CirculantGraph(*param) Circulant graph ([2, 3, 10, 11]): Graph on 13 vertices Circulant graph ([1, 5, 8, 12]): Graph on 13 vertices @@ -14506,13 +14510,14 @@ def is_circulant(self, certificate=False): TESTS:: - sage: digraphs.DeBruijn(3,1).is_circulant(certificate=True) # optional - sage.combinat sage.groups + sage: # needs sage.groups + sage: digraphs.DeBruijn(3,1).is_circulant(certificate=True) # needs sage.combinat (True, [(3, [0, 1, 2])]) - sage: Graph(1).is_circulant(certificate=True) # optional - sage.groups + sage: Graph(1).is_circulant(certificate=True) (True, (1, [])) - sage: Graph(0).is_circulant(certificate=True) # optional - sage.groups + sage: Graph(0).is_circulant(certificate=True) (True, (0, [])) - sage: Graph({0: [0]}).is_circulant(certificate=True) # optional - sage.groups + sage: Graph({0: [0]}).is_circulant(certificate=True) (True, (1, [0])) """ self._scream_if_not_simple(allow_loops=True) @@ -15102,11 +15107,11 @@ def is_subgraph(self, other, induced=True, up_to_isomorphism=False): sage: p11 = graphs.PathGraph(11) sage: p15 = graphs.PathGraph(15) sage: g = graphs.Grid2dGraph(4, 4) - sage: p15.is_subgraph(g, induced=False, up_to_isomorphism=True) # optional - sage.modules + sage: p15.is_subgraph(g, induced=False, up_to_isomorphism=True) # needs sage.modules True - sage: p15.is_subgraph(g, induced=True, up_to_isomorphism=True) # optional - sage.modules + sage: p15.is_subgraph(g, induced=True, up_to_isomorphism=True) # needs sage.modules False - sage: p11.is_subgraph(g, induced=True, up_to_isomorphism=True) # optional - sage.modules + sage: p11.is_subgraph(g, induced=True, up_to_isomorphism=True) # needs sage.modules True TESTS: @@ -15184,15 +15189,15 @@ def cluster_triangles(self, nbunch=None, implementation=None): :: sage: G = graphs.RandomGNP(20, .3) - sage: d1 = G.cluster_triangles(implementation="networkx") # optional - networkx + sage: d1 = G.cluster_triangles(implementation="networkx") # needs networkx sage: d2 = G.cluster_triangles(implementation="dense_copy") sage: d3 = G.cluster_triangles(implementation="sparse_copy") - sage: d1 == d2 and d1 == d3 + sage: d1 == d2 and d1 == d3 # needs networkx True TESTS:: - sage: DiGraph().cluster_triangles(implementation="networkx") # optional - networkx + sage: DiGraph().cluster_triangles(implementation="networkx") # needs networkx Traceback (most recent call last): ... ValueError: the 'networkx' implementation does not support directed graphs @@ -15252,7 +15257,7 @@ def clustering_average(self, implementation=None): sage: (graphs.FruchtGraph()).clustering_average() 1/4 - sage: (graphs.FruchtGraph()).clustering_average(implementation='networkx') # optional - networkx + sage: (graphs.FruchtGraph()).clustering_average(implementation='networkx') # needs networkx 0.25 TESTS: @@ -15268,7 +15273,7 @@ def clustering_average(self, implementation=None): sage: G = graphs.RandomGNM(10,20) sage: impls = ['boost','sparse_copy','dense_copy'] - sage: impls += ['networkx'] # optional - networkx + sage: impls += ['networkx'] # needs networkx sage: coeffs = [G.clustering_average(implementation=impl) ....: for impl in impls] sage: max(coeffs) - min(coeffs) # tol abs 1e-12 @@ -15454,7 +15459,7 @@ def cluster_transitivity(self): EXAMPLES:: - sage: graphs.FruchtGraph().cluster_transitivity() # optional - networkx + sage: graphs.FruchtGraph().cluster_transitivity() # needs networkx 0.25 """ import networkx @@ -15689,13 +15694,13 @@ def girth(self, certificate=False): sage: g = digraphs.Circuit(6) sage: g.girth() 6 - sage: g = digraphs.RandomDirectedGNC(10) # optional - networkx - sage: g.girth() # optional - networkx + sage: g = digraphs.RandomDirectedGNC(10) # needs networkx + sage: g.girth() # needs networkx +Infinity - sage: g = DiGraph([(0, 1), (1, 2), (1, 3), (2, 3), (3, 4), (4, 0)]) # optional - networkx - sage: g.girth() # optional - networkx + sage: g = DiGraph([(0, 1), (1, 2), (1, 3), (2, 3), (3, 4), (4, 0)]) # needs networkx + sage: g.girth() # needs networkx 4 - sage: Graph(g).girth() # optional - networkx + sage: Graph(g).girth() # needs networkx 3 """ # Cases where girth <= 2 @@ -15746,10 +15751,10 @@ def odd_girth(self, algorithm="bfs", certificate=False): The McGee graph has girth 7 and therefore its odd girth is 7 as well:: - sage: G = graphs.McGeeGraph() # optional - networkx - sage: G.girth() # optional - networkx + sage: G = graphs.McGeeGraph() # needs networkx + sage: G.girth() # needs networkx 7 - sage: G.odd_girth() # optional - networkx + sage: G.odd_girth() # needs networkx 7 Any complete (directed) graph on more than 2 vertices contains @@ -15774,15 +15779,16 @@ def odd_girth(self, algorithm="bfs", certificate=False): The odd girth of a (directed) graph with loops is 1:: - sage: G = graphs.RandomGNP(10, .5) # optional - networkx - sage: G.allow_loops(True) # optional - networkx - sage: G.add_edge(0, 0) # optional - networkx - sage: G.odd_girth() # optional - networkx + sage: # needs networkx + sage: G = graphs.RandomGNP(10, .5) + sage: G.allow_loops(True) + sage: G.add_edge(0, 0) + sage: G.odd_girth() 1 - sage: G = digraphs.RandomDirectedGNP(10, .5) # optional - networkx - sage: G.allow_loops(True) # optional - networkx - sage: G.add_edge(0, 0) # optional - networkx - sage: G.odd_girth() # optional - networkx + sage: G = digraphs.RandomDirectedGNP(10, .5) + sage: G.allow_loops(True) + sage: G.add_edge(0, 0) + sage: G.odd_girth() 1 .. SEEALSO:: @@ -15987,17 +15993,17 @@ def centrality_betweenness(self, k=None, normalized=True, weight=None, 9: 3.333333333333333, 10: 3.333333333333333, 11: 3.333333333333333} sage: D = DiGraph({0:[1,2,3], 1:[2], 3:[0,1]}) - sage: D.show(figsize=[2,2]) # optional - sage.plot + sage: D.show(figsize=[2,2]) # needs sage.plot sage: D = D.to_undirected() - sage: D.show(figsize=[2,2]) # optional - sage.plot + sage: D.show(figsize=[2,2]) # needs sage.plot sage: D.centrality_betweenness() # abs tol abs 1e-10 {0: 0.16666666666666666, 1: 0.16666666666666666, 2: 0.0, 3: 0.0} TESTS:: - sage: tests = ([graphs.RandomGNP(30,.1) for i in range(10)]+ # optional - networkx + sage: tests = ([graphs.RandomGNP(30,.1) for i in range(10)]+ # needs networkx ....: [digraphs.RandomDirectedGNP(30,.1) for i in range(10)]) - sage: for g in tests: # optional - networkx + sage: for g in tests: # needs networkx ....: r1 = g.centrality_betweenness(algorithm="Sage",exact=0) ....: r2 = g.centrality_betweenness(algorithm="Sage",exact=1) ....: r3 = g.centrality_betweenness(algorithm="NetworkX") @@ -16127,11 +16133,11 @@ def centrality_closeness(self, vert=None, by_weight=False, algorithm=None, 8: 0.61111111111111..., 9: 0.61111111111111..., 10: 0.61111111111111..., 11: 0.61111111111111...} sage: D = DiGraph({0:[1,2,3], 1:[2], 3:[0,1]}) - sage: D.show(figsize=[2,2]) # optional - sage.plot + sage: D.show(figsize=[2,2]) # needs sage.plot sage: D.centrality_closeness(vert=[0,1]) {0: 1.0, 1: 0.3333333333333333} sage: D = D.to_undirected() - sage: D.show(figsize=[2,2]) # optional - sage.plot + sage: D.show(figsize=[2,2]) # needs sage.plot sage: D.centrality_closeness() {0: 1.0, 1: 1.0, 2: 0.75, 3: 0.75} @@ -16503,9 +16509,9 @@ def shortest_path(self, u, v, by_weight=False, algorithm=None, [4, 17, 16, 12, 13, 9] sage: D.shortest_path(4, 9, algorithm='BFS') [4, 3, 2, 1, 8, 9] - sage: D.shortest_path(4, 8, algorithm='Dijkstra_NetworkX') # optional - networkx + sage: D.shortest_path(4, 8, algorithm='Dijkstra_NetworkX') # needs networkx [4, 3, 2, 1, 8] - sage: D.shortest_path(4, 8, algorithm='Dijkstra_Bid_NetworkX') # optional - networkx + sage: D.shortest_path(4, 8, algorithm='Dijkstra_Bid_NetworkX') # needs networkx [4, 3, 2, 1, 8] sage: D.shortest_path(4, 9, algorithm='Dijkstra_Bid') [4, 3, 19, 0, 10, 9] @@ -16521,10 +16527,10 @@ def shortest_path(self, u, v, by_weight=False, algorithm=None, [0, 4, 3] sage: G.shortest_path(0, 3, by_weight=True) [0, 1, 2, 3] - sage: G.shortest_path(0, 3, by_weight=True, # optional - networkx + sage: G.shortest_path(0, 3, by_weight=True, # needs networkx ....: algorithm='Dijkstra_NetworkX') [0, 1, 2, 3] - sage: G.shortest_path(0, 3, by_weight=True, # optional - networkx + sage: G.shortest_path(0, 3, by_weight=True, # needs networkx ....: algorithm='Dijkstra_Bid_NetworkX') [0, 1, 2, 3] @@ -16564,7 +16570,7 @@ def shortest_path(self, u, v, by_weight=False, algorithm=None, sage: G = Graph() sage: G.add_vertices([1, 2]) sage: algs = ['BFS', 'BFS_Bid', 'Dijkstra_Bid', 'Bellman-Ford_Boost'] - sage: algs += ['Dijkstra_NetworkX', 'Dijkstra_Bid_NetworkX'] # optional - networkx + sage: algs += ['Dijkstra_NetworkX', 'Dijkstra_Bid_NetworkX'] # needs networkx sage: all(G.shortest_path(1, 2, algorithm=alg) == [] ....: for alg in algs) True @@ -16688,9 +16694,9 @@ def shortest_path_length(self, u, v, by_weight=False, algorithm=None, 5 sage: D.shortest_path_length(4, 9, algorithm='BFS') 5 - sage: D.shortest_path_length(4, 9, algorithm='Dijkstra_NetworkX') # optional - networkx + sage: D.shortest_path_length(4, 9, algorithm='Dijkstra_NetworkX') # needs networkx 5 - sage: D.shortest_path_length(4, 9, algorithm='Dijkstra_Bid_NetworkX') # optional - networkx + sage: D.shortest_path_length(4, 9, algorithm='Dijkstra_Bid_NetworkX') # needs networkx 5 sage: D.shortest_path_length(4, 9, algorithm='Dijkstra_Bid') 5 @@ -16708,10 +16714,10 @@ def shortest_path_length(self, u, v, by_weight=False, algorithm=None, 2 sage: G.shortest_path_length(0, 3, by_weight=True) 3 - sage: G.shortest_path_length(0, 3, by_weight=True, # optional - networkx + sage: G.shortest_path_length(0, 3, by_weight=True, # needs networkx ....: algorithm='Dijkstra_NetworkX') 3 - sage: G.shortest_path_length(0, 3, by_weight=True, # optional - networkx + sage: G.shortest_path_length(0, 3, by_weight=True, # needs networkx ....: algorithm='Dijkstra_Bid_NetworkX') 3 @@ -16754,7 +16760,7 @@ def shortest_path_length(self, u, v, by_weight=False, algorithm=None, sage: G = Graph() sage: G.add_vertices([1, 2]) sage: algs = ['BFS', 'BFS_Bid', 'Dijkstra_Bid', 'Bellman-Ford_Boost'] - sage: algs += ['Dijkstra_NetworkX', 'Dijkstra_Bid_NetworkX'] # optional - networkx + sage: algs += ['Dijkstra_NetworkX', 'Dijkstra_Bid_NetworkX'] # needs networkx sage: all(G.shortest_path_length(1, 2, algorithm=alg) == Infinity ....: for alg in algs) True @@ -17265,7 +17271,7 @@ def shortest_path_lengths(self, u, by_weight=False, algorithm=None, sage: G = Graph({0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2}}, ....: sparse=True) - sage: G.plot(edge_labels=True).show() # long time # optional - sage.plot + sage: G.plot(edge_labels=True).show() # long time # needs sage.plot sage: G.shortest_path_lengths(0, by_weight=True) {0: 0, 1: 1, 2: 2, 3: 3, 4: 2} @@ -17273,7 +17279,7 @@ def shortest_path_lengths(self, u, by_weight=False, algorithm=None, sage: D = DiGraph([(0,1,{'weight':1}), (1,2,{'weight':3}), (0,2,{'weight':5})]) sage: weight_function = lambda e: e[2]['weight'] - sage: D.shortest_path_lengths(1, algorithm='Dijkstra_NetworkX', # optional - networkx + sage: D.shortest_path_lengths(1, algorithm='Dijkstra_NetworkX', # needs networkx ....: by_weight=False) {1: 0, 2: 1} sage: D.shortest_path_lengths(0, weight_function=weight_function) @@ -17299,10 +17305,10 @@ def shortest_path_lengths(self, u, by_weight=False, algorithm=None, sage: g = graphs.Grid2dGraph(5,5) sage: d1 = g.shortest_path_lengths((0,0), algorithm="BFS") - sage: d2 = g.shortest_path_lengths((0,0), algorithm="Dijkstra_NetworkX") # optional - networkx + sage: d2 = g.shortest_path_lengths((0,0), algorithm="Dijkstra_NetworkX") # needs networkx sage: d3 = g.shortest_path_lengths((0,0), algorithm="Dijkstra_Boost") sage: d4 = g.shortest_path_lengths((0,0), algorithm="Bellman-Ford_Boost") - sage: d1 == d2 == d3 == d4 + sage: d1 == d2 == d3 == d4 # needs networkx True """ by_weight, weight_function = self._get_weight_function(by_weight=by_weight, @@ -18131,7 +18137,7 @@ def breadth_first_search(self, start, ignore_direction=False, [0, 1, 4, 5, 2, 6, 3, 9, 7, 8] sage: D = DiGraph({0: [1, 3], 1: [0, 2], 2: [0, 3], 3: [4]}) - sage: D.show() # optional - sage.plot + sage: D.show() # needs sage.plot sage: list(D.breadth_first_search(4, neighbors=D.neighbor_in_iterator, ....: report_distance=True)) [(4, 0), (3, 1), (0, 2), (2, 2), (1, 3)] @@ -18476,11 +18482,11 @@ def add_cycle(self, vertices): sage: G = Graph() sage: G.add_vertices(range(10)); G Graph on 10 vertices - sage: show(G) # optional - sage.plot + sage: show(G) # needs sage.plot sage: G.add_cycle(list(range(10, 20))) - sage: show(G) # optional - sage.plot + sage: show(G) # needs sage.plot sage: G.add_cycle(list(range(10))) - sage: show(G) # optional - sage.plot + sage: show(G) # needs sage.plot :: @@ -18532,11 +18538,11 @@ def add_path(self, vertices): sage: G = Graph() sage: G.add_vertices(range(10)); G Graph on 10 vertices - sage: show(G) # optional - sage.plot + sage: show(G) # needs sage.plot sage: G.add_path(list(range(10, 20))) - sage: show(G) # optional - sage.plot + sage: show(G) # needs sage.plot sage: G.add_path(list(range(10))) - sage: show(G) # optional - sage.plot + sage: show(G) # needs sage.plot :: @@ -18561,10 +18567,10 @@ def complement(self): EXAMPLES:: sage: P = graphs.PetersenGraph() - sage: P.plot() # long time # optional - sage.plot + sage: P.plot() # long time # needs sage.plot Graphics object consisting of 26 graphics primitives sage: PC = P.complement() - sage: PC.plot() # long time # optional - sage.plot + sage: PC.plot() # long time # needs sage.plot Graphics object consisting of 41 graphics primitives :: @@ -18893,9 +18899,9 @@ def cartesian_product(self, other): Cartesian product of digraphs:: sage: P = DiGraph([(0, 1)]) - sage: B = digraphs.DeBruijn(['a', 'b'], 2) # optional - sage.combinat - sage: Q = P.cartesian_product(B) # optional - sage.combinat - sage: Q.edges(sort=True, labels=None) # optional - sage.combinat + sage: B = digraphs.DeBruijn(['a', 'b'], 2) # needs sage.combinat + sage: Q = P.cartesian_product(B) # needs sage.combinat + sage: Q.edges(sort=True, labels=None) # needs sage.combinat [((0, 'aa'), (0, 'aa')), ((0, 'aa'), (0, 'ab')), ((0, 'aa'), (1, 'aa')), ((0, 'ab'), (0, 'ba')), ((0, 'ab'), (0, 'bb')), ((0, 'ab'), (1, 'ab')), @@ -18906,10 +18912,10 @@ def cartesian_product(self, other): ((1, 'ab'), (1, 'ba')), ((1, 'ab'), (1, 'bb')), ((1, 'ba'), (1, 'aa')), ((1, 'ba'), (1, 'ab')), ((1, 'bb'), (1, 'ba')), ((1, 'bb'), (1, 'bb'))] - sage: Q.strongly_connected_components_digraph().num_verts() # optional - sage.combinat + sage: Q.strongly_connected_components_digraph().num_verts() # needs sage.combinat 2 - sage: V = Q.strongly_connected_component_containing_vertex((0, 'aa')) # optional - sage.combinat - sage: B.is_isomorphic(Q.subgraph(V)) # optional - sage.combinat + sage: V = Q.strongly_connected_component_containing_vertex((0, 'aa')) # needs sage.combinat + sage: B.is_isomorphic(Q.subgraph(V)) # needs sage.combinat True """ self._scream_if_not_simple(allow_loops=True) @@ -19144,13 +19150,14 @@ def strong_product(self, other): Counting the edges (see :trac:`13699`):: - sage: g = graphs.RandomGNP(5, .5) # optional - networkx - sage: gn,gm = g.order(), g.size() # optional - networkx - sage: h = graphs.RandomGNP(5, .5) # optional - networkx - sage: hn,hm = h.order(), h.size() # optional - networkx - sage: product_size = g.strong_product(h).size() # optional - networkx - sage: expected = gm * hn + hm * gn + 2 * gm * hm # optional - networkx - sage: product_size == expected # optional - networkx + sage: # needs networkx + sage: g = graphs.RandomGNP(5, .5) + sage: gn,gm = g.order(), g.size() + sage: h = graphs.RandomGNP(5, .5) + sage: hn,hm = h.order(), h.size() + sage: product_size = g.strong_product(h).size() + sage: expected = gm * hn + hm * gn + 2 * gm * hm + sage: product_size == expected True """ self._scream_if_not_simple(allow_loops=True) @@ -19444,35 +19451,36 @@ def _color_by_label(self, format='hex', as_function=False, default_color="black" We consider the Cayley graph of the symmetric group, whose edges are labelled by the numbers 1,2, and 3:: - sage: G = SymmetricGroup(4).cayley_graph() # optional - sage.groups - sage: set(G.edge_labels()) # optional - sage.groups + sage: G = SymmetricGroup(4).cayley_graph() # needs sage.groups + sage: set(G.edge_labels()) # needs sage.groups {1, 2, 3} We first request the coloring as a function:: - sage: f = G._color_by_label(as_function=True) # optional - sage.groups - sage: [f(1), f(2), f(3)] # optional - sage.groups + sage: # needs sage.groups + sage: f = G._color_by_label(as_function=True) + sage: [f(1), f(2), f(3)] ['#0000ff', '#ff0000', '#00ff00'] - sage: f = G._color_by_label({1: "blue", 2: "red", 3: "green"}, # optional - sage.groups + sage: f = G._color_by_label({1: "blue", 2: "red", 3: "green"}, ....: as_function=True) - sage: [f(1), f(2), f(3)] # optional - sage.groups + sage: [f(1), f(2), f(3)] ['blue', 'red', 'green'] - sage: f = G._color_by_label({1: "red"}, as_function=True) # optional - sage.groups - sage: [f(1), f(2), f(3)] # optional - sage.groups + sage: f = G._color_by_label({1: "red"}, as_function=True) + sage: [f(1), f(2), f(3)] ['red', 'black', 'black'] - sage: f = G._color_by_label({1: "red"}, as_function=True, # optional - sage.groups + sage: f = G._color_by_label({1: "red"}, as_function=True, ....: default_color='blue') - sage: [f(1), f(2), f(3)] # optional - sage.groups + sage: [f(1), f(2), f(3)] ['red', 'blue', 'blue'] The default output is a dictionary assigning edges to colors:: - sage: G._color_by_label() # optional - sage.groups + sage: G._color_by_label() # needs sage.groups {'#0000ff': [((), (1,2), 1), ...], '#00ff00': [((), (3,4), 3), ...], '#ff0000': [((), (2,3), 2), ...]} - sage: G._color_by_label({1: "blue", 2: "red", 3: "green"}) # optional - sage.groups + sage: G._color_by_label({1: "blue", 2: "red", 3: "green"}) # needs sage.groups {'blue': [((), (1,2), 1), ...], 'green': [((), (3,4), 3), ...], 'red': [((), (2,3), 2), ...]} @@ -19481,12 +19489,13 @@ def _color_by_label(self, format='hex', as_function=False, default_color="black" We check what happens when several labels have the same color:: - sage: result = G._color_by_label({1: "blue", 2: "blue", 3: "green"}) # optional - sage.groups - sage: sorted(result) # optional - sage.groups + sage: # needs sage.groups + sage: result = G._color_by_label({1: "blue", 2: "blue", 3: "green"}) + sage: sorted(result) ['blue', 'green'] - sage: len(result['blue']) # optional - sage.groups + sage: len(result['blue']) 48 - sage: len(result['green']) # optional - sage.groups + sage: len(result['green']) 24 """ if format is True: @@ -19540,8 +19549,8 @@ def latex_options(self): sage: opts = g.latex_options() sage: opts LaTeX options for Petersen graph: {} - sage: opts.set_option('tkz_style', 'Classic') # optional - sage.plot - sage: opts # optional - sage.plot + sage: opts.set_option('tkz_style', 'Classic') # needs sage.plot + sage: opts # needs sage.plot LaTeX options for Petersen graph: {'tkz_style': 'Classic'} """ if self._latex_opts is None: @@ -19569,9 +19578,9 @@ def set_latex_options(self, **kwds): EXAMPLES:: sage: g = graphs.PetersenGraph() - sage: g.set_latex_options(tkz_style='Welsh') # optional - sage.plot - sage: opts = g.latex_options() # optional - sage.plot - sage: opts.get_option('tkz_style') # optional - sage.plot + sage: g.set_latex_options(tkz_style='Welsh') # needs sage.plot + sage: opts = g.latex_options() # needs sage.plot + sage: opts.get_option('tkz_style') # needs sage.plot 'Welsh' """ opts = self.latex_options() @@ -19735,7 +19744,7 @@ def layout_spring(self, by_component=True, **options): 4: [2.14..., -0.30...], 5: [2.80..., 0.22...]} sage: g = graphs.LadderGraph(7) - sage: g.plot(layout="spring") # optional - sage.plot + sage: g.plot(layout="spring") # needs sage.plot Graphics object consisting of 34 graphics primitives """ return spring_layout_fast(self, by_component=by_component, **options) @@ -19775,7 +19784,7 @@ def layout_ranked(self, heights=None, dim=2, spring=False, **options): 4: [1.33..., 1], 5: [1.33..., 2]} sage: g = graphs.LadderGraph(7) - sage: g.plot(layout="ranked", heights={i: (i, i+7) for i in range(7)}) # optional - sage.plot + sage: g.plot(layout="ranked", heights={i: (i, i+7) for i in range(7)}) # needs sage.plot Graphics object consisting of 34 graphics primitives """ assert heights is not None @@ -19903,7 +19912,7 @@ def layout_circular(self, dim=2, center=(0, 0), radius=1, shift=0, angle=0, **op 4: (0.43..., -0.90...), 5: (0.97..., -0.22...), 6: (0.78..., 0.62...)} - sage: G.plot(layout="circular") # optional - sage.plot + sage: G.plot(layout="circular") # needs sage.plot Graphics object consisting of 22 graphics primitives """ assert dim == 2, "3D circular layout not implemented" @@ -19937,19 +19946,19 @@ def layout_forest(self, tree_orientation="down", forest_roots=None, sage: G = graphs.RandomTree(4) + graphs.RandomTree(5) + graphs.RandomTree(6) sage: p = G.layout_forest() - sage: G.plot(pos=p) # random # optional - sage.plot + sage: G.plot(pos=p) # random # needs sage.plot Graphics object consisting of 28 graphics primitives sage: P5 = graphs.PathGraph(5) - sage: H = P5 + P5 + graphs.BalancedTree(2,2) # optional - networkx - sage: p = H.layout_forest(forest_roots=[14,3]) # optional - networkx - sage: H.plot(pos=p) # optional - networkx sage.plot + sage: H = P5 + P5 + graphs.BalancedTree(2,2) # needs networkx + sage: p = H.layout_forest(forest_roots=[14,3]) # needs networkx + sage: H.plot(pos=p) # needs networkx sage.plot Graphics object consisting of 32 graphics primitives TESTS:: sage: G = Graph(0) - sage: G.plot(layout='forest') # optional - sage.plot + sage: G.plot(layout='forest') # needs sage.plot Graphics object consisting of 0 graphics primitives Works for forests that are trees:: @@ -20010,19 +20019,19 @@ def layout_tree(self, tree_orientation="down", tree_root=None, EXAMPLES:: sage: G = graphs.RandomTree(80) - sage: G.plot(layout="tree", tree_orientation="right") # optional - sage.plot + sage: G.plot(layout="tree", tree_orientation="right") # needs sage.plot Graphics object consisting of 160 graphics primitives - sage: T = graphs.RandomLobster(25, 0.3, 0.3) # optional - networkx - sage: T.show(layout='tree', tree_orientation='up') # optional - networkx sage.plot + sage: T = graphs.RandomLobster(25, 0.3, 0.3) # needs networkx + sage: T.show(layout='tree', tree_orientation='up') # needs networkx sage.plot sage: G = graphs.HoffmanSingletonGraph() sage: T = Graph() sage: T.add_edges(G.min_spanning_tree(starting_vertex=0)) - sage: T.show(layout='tree', tree_root=0) # optional - sage.plot + sage: T.show(layout='tree', tree_root=0) # needs sage.plot - sage: G = graphs.BalancedTree(2, 2) # optional - networkx - sage: G.layout_tree(tree_root=0) # optional - networkx + sage: G = graphs.BalancedTree(2, 2) # needs networkx + sage: G.layout_tree(tree_root=0) # needs networkx {0: [1.5, 0], 1: [2.5, -1], 2: [0.5, -1], @@ -20031,8 +20040,8 @@ def layout_tree(self, tree_orientation="down", tree_root=None, 5: [1.0, -2], 6: [0.0, -2]} - sage: G = graphs.BalancedTree(2, 4) # optional - networkx - sage: G.plot(layout="tree", tree_root=0, tree_orientation="up") # optional - networkx sage.plot + sage: G = graphs.BalancedTree(2, 4) # needs networkx + sage: G.plot(layout="tree", tree_root=0, tree_orientation="up") # needs networkx sage.plot Graphics object consisting of 62 graphics primitives Using the embedding when it exists:: @@ -20050,13 +20059,13 @@ def layout_tree(self, tree_orientation="down", tree_root=None, 6: [2.0, -1], 7: [1.0, -2], 8: [0.0, -2]} - sage: T.plot(layout="tree", tree_root=3) # optional - sage.plot + sage: T.plot(layout="tree", tree_root=3) # needs sage.plot Graphics object consisting of 18 graphics primitives TESTS:: - sage: G = graphs.BalancedTree(2, 2) # optional - networkx - sage: G.layout_tree(tree_root=0, tree_orientation='left') # optional - networkx + sage: G = graphs.BalancedTree(2, 2) # needs networkx + sage: G.layout_tree(tree_root=0, tree_orientation='left') # needs networkx {0: [0, 1.5], 1: [-1, 2.5], 2: [-1, 0.5], @@ -20066,12 +20075,12 @@ def layout_tree(self, tree_orientation="down", tree_root=None, 6: [-2, 0.0]} sage: G = graphs.CycleGraph(3) - sage: G.plot(layout='tree') # optional - sage.plot + sage: G.plot(layout='tree') # needs sage.plot Traceback (most recent call last): ... RuntimeError: cannot use tree layout on this graph: self.is_tree() returns False sage: G = Graph(0) - sage: G.plot(layout='tree') # optional - sage.plot + sage: G.plot(layout='tree') # needs sage.plot Graphics object consisting of 0 graphics primitives """ if dim != 2: @@ -20238,8 +20247,8 @@ def layout_graphviz(self, dim=2, prog='dot', **options): Graphics object consisting of 29 graphics primitives sage: g.plot(layout="graphviz", prog="fdp") # optional - dot2tex graphviz Graphics object consisting of 29 graphics primitives - sage: g = graphs.BalancedTree(5,2) # optional - networkx - sage: g.plot(layout="graphviz", prog="circo") # optional - dot2tex graphviz networkx + sage: g = graphs.BalancedTree(5,2) # needs networkx + sage: g.plot(layout="graphviz", prog="circo") # optional - dot2tex graphviz, needs networkx Graphics object consisting of 62 graphics primitives .. TODO:: @@ -20364,7 +20373,7 @@ def _circle_embedding(self, vertices, center=(0, 0), radius=1, shift=0, angle=0, sage: g = graphs.CycleGraph(5) sage: g._circle_embedding([0, 2, 4, 1, 3], radius=2, shift=.5) - sage: g.show() # optional - sage.plot + sage: g.show() # needs sage.plot sage: g._circle_embedding(g.vertices(sort=True), angle=0) sage: g._pos[0] @@ -20444,7 +20453,7 @@ def _line_embedding(self, vertices, first=(0, 0), last=(0, 1), return_dict=False sage: g = graphs.PathGraph(5) sage: g._line_embedding([0, 2, 4, 1, 3], first=(-1, -1), last=(1, 1)) - sage: g.show() # optional - sage.plot + sage: g.show() # needs sage.plot sage: pos = g._line_embedding([4, 2, 0, 1, 3], first=(-1, -1), last=(1, 1), ....: return_dict=True) @@ -20514,19 +20523,20 @@ def graphplot(self, **options): sage: g = Graph({}, loops=True, multiedges=True, sparse=True) sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), ....: (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) - sage: GP = g.graphplot(edge_labels=True, color_by_label=True, # optional - sage.plot + sage: GP = g.graphplot(edge_labels=True, color_by_label=True, # needs sage.plot ....: edge_style='dashed') - sage: GP.plot() # optional - sage.plot + sage: GP.plot() # needs sage.plot Graphics object consisting of 22 graphics primitives We can modify the :class:`~sage.graphs.graph_plot.GraphPlot` object. Notice that the changes are cumulative:: - sage: GP.set_edges(edge_style='solid') # optional - sage.plot - sage: GP.plot() # optional - sage.plot + sage: # needs sage.plot + sage: GP.set_edges(edge_style='solid') + sage: GP.plot() Graphics object consisting of 22 graphics primitives - sage: GP.set_vertices(talk=True) # optional - sage.plot - sage: GP.plot() # optional - sage.plot + sage: GP.set_vertices(talk=True) + sage: GP.plot() Graphics object consisting of 22 graphics primitives """ from sage.graphs.graph_plot import GraphPlot @@ -20551,7 +20561,7 @@ def _rich_repr_(self, display_manager, **kwds): sage: dm.preferences.supplemental_plot 'never' sage: del dm.preferences.supplemental_plot - sage: graphs.RandomGNP(20,0.0) # optional - networkx + sage: graphs.RandomGNP(20,0.0) # needs networkx RandomGNP(20,0.000000000000000): Graph on 20 vertices (use the .plot() method to plot) sage: dm.preferences.supplemental_plot = 'never' """ @@ -20707,21 +20717,21 @@ def plot(self, **options): ....: x = float(0.5*cos(pi/2 + ((2*pi)/5)*i)) ....: y = float(0.5*sin(pi/2 + ((2*pi)/5)*i)) ....: pos_dict[i] = [x,y] - sage: pl = P.plot(pos=pos_dict, vertex_colors=d) # optional - sage.plot - sage: pl.show() # optional - sage.plot + sage: pl = P.plot(pos=pos_dict, vertex_colors=d) # needs sage.plot + sage: pl.show() # needs sage.plot :: sage: C = graphs.CubeGraph(8) - sage: P = C.plot(vertex_labels=False, vertex_size=0, graph_border=True) # optional - sage.plot - sage: P.show() # optional - sage.plot + sage: P = C.plot(vertex_labels=False, vertex_size=0, graph_border=True) # needs sage.plot + sage: P.show() # needs sage.plot :: sage: G = graphs.HeawoodGraph() sage: for u, v, l in G.edges(sort=False): ....: G.set_edge_label(u, v, '(' + str(u) + ',' + str(v) + ')') - sage: G.plot(edge_labels=True).show() # optional - sage.plot + sage: G.plot(edge_labels=True).show() # needs sage.plot :: @@ -20732,7 +20742,7 @@ def plot(self, **options): ....: 16: [17], 17: [18], 18: [19]}, sparse=True) sage: for u,v,l in D.edges(sort=False): ....: D.set_edge_label(u, v, '(' + str(u) + ',' + str(v) + ')') - sage: D.plot(edge_labels=True, layout='circular').show() # optional - sage.plot + sage: D.plot(edge_labels=True, layout='circular').show() # needs sage.plot :: @@ -20744,45 +20754,45 @@ def plot(self, **options): ....: for i in range(5): ....: if u[i] != v[i]: ....: edge_colors[R[i]].append((u, v, l)) - sage: C.plot(vertex_labels=False, vertex_size=0, # optional - sage.plot + sage: C.plot(vertex_labels=False, vertex_size=0, # needs sage.plot ....: edge_colors=edge_colors).show() :: sage: D = graphs.DodecahedralGraph() sage: Pi = [[6,5,15,14,7], [16,13,8,2,4], [12,17,9,3,1], [0,19,18,10,11]] - sage: D.show(partition=Pi) # optional - sage.plot + sage: D.show(partition=Pi) # needs sage.plot :: sage: G = graphs.PetersenGraph() sage: G.allow_loops(True) sage: G.add_edge(0, 0) - sage: G.show() # optional - sage.plot + sage: G.show() # needs sage.plot :: sage: D = DiGraph({0: [0, 1], 1: [2], 2: [3]}, loops=True) - sage: D.show() # optional - sage.plot - sage: D.show(edge_colors={(0, 1, 0): [(0, 1, None), (1, 2, None)], # optional - sage.plot + sage: D.show() # needs sage.plot + sage: D.show(edge_colors={(0, 1, 0): [(0, 1, None), (1, 2, None)], # needs sage.plot ....: (0, 0, 0): [(2, 3, None)]}) :: sage: pos = {0: [0.0, 1.5], 1: [-0.8, 0.3], 2: [-0.6, -0.8], 3: [0.6, -0.8], 4: [0.8, 0.3]} sage: g = Graph({0: [1], 1: [2], 2: [3], 3: [4], 4: [0]}) - sage: g.plot(pos=pos, layout='spring', iterations=0) # optional - sage.plot + sage: g.plot(pos=pos, layout='spring', iterations=0) # needs sage.plot Graphics object consisting of 11 graphics primitives :: sage: G = Graph() - sage: P = G.plot() # optional - sage.plot - sage: P.axes() # optional - sage.plot + sage: P = G.plot() # needs sage.plot + sage: P.axes() # needs sage.plot False sage: G = DiGraph() - sage: P = G.plot() # optional - sage.plot - sage: P.axes() # optional - sage.plot + sage: P = G.plot() # needs sage.plot + sage: P.axes() # needs sage.plot False :: @@ -20799,11 +20809,11 @@ def plot(self, **options): 7: (-0.29..., -0.40...), 8: (0.29..., -0.40...), 9: (0.47..., 0.15...)} - sage: P = G.plot(save_pos=True, layout='spring') # optional - sage.plot + sage: P = G.plot(save_pos=True, layout='spring') # needs sage.plot The following illustrates the format of a position dictionary:: - sage: G.get_pos() # currently random across platforms, see #9593 # optional - sage.plot + sage: G.get_pos() # currently random across platforms, see #9593 # needs sage.plot {0: [1.17..., -0.855...], 1: [1.81..., -0.0990...], 2: [1.35..., 0.184...], @@ -20819,14 +20829,14 @@ def plot(self, **options): sage: T = list(graphs.trees(7)) sage: t = T[3] - sage: t.plot(heights={0: [0], 1: [4, 5, 1], 2: [2], 3: [3, 6]}) # optional - sage.plot + sage: t.plot(heights={0: [0], 1: [4, 5, 1], 2: [2], 3: [3, 6]}) # needs sage.plot Graphics object consisting of 14 graphics primitives :: sage: T = list(graphs.trees(7)) sage: t = T[3] - sage: t.plot(heights={0: [0], 1: [4, 5, 1], 2: [2], 3: [3, 6]}) # optional - sage.plot + sage: t.plot(heights={0: [0], 1: [4, 5, 1], 2: [2], 3: [3, 6]}) # needs sage.plot Graphics object consisting of 14 graphics primitives sage: t.set_edge_label(0, 1, -7) sage: t.set_edge_label(0, 5, 3) @@ -20835,7 +20845,7 @@ def plot(self, **options): sage: t.set_edge_label(3, 2, 'spam') sage: t.set_edge_label(2, 6, 3/2) sage: t.set_edge_label(0, 4, 66) - sage: t.plot(heights={0: [0], 1: [4, 5, 1], 2: [2], 3: [3, 6]}, # optional - sage.plot + sage: t.plot(heights={0: [0], 1: [4, 5, 1], 2: [2], 3: [3, 6]}, # needs sage.plot ....: edge_labels=True) Graphics object consisting of 20 graphics primitives @@ -20843,32 +20853,32 @@ def plot(self, **options): sage: T = list(graphs.trees(7)) sage: t = T[3] - sage: t.plot(layout='tree') # optional - sage.plot + sage: t.plot(layout='tree') # needs sage.plot Graphics object consisting of 14 graphics primitives :: sage: t = DiGraph('JCC???@A??GO??CO??GO??') - sage: t.plot(layout='tree', tree_root=0, tree_orientation="up") # optional - sage.plot + sage: t.plot(layout='tree', tree_root=0, tree_orientation="up") # needs sage.plot Graphics object consisting of 22 graphics primitives sage: D = DiGraph({0: [1, 2, 3], 2: [1, 4], 3: [0]}) - sage: D.plot() # optional - sage.plot + sage: D.plot() # needs sage.plot Graphics object consisting of 16 graphics primitives sage: D = DiGraph(multiedges=True,sparse=True) sage: for i in range(5): ....: D.add_edge((i, i + 1, 'a')) ....: D.add_edge((i, i - 1, 'b')) - sage: D.plot(edge_labels=True, edge_colors=D._color_by_label()) # optional - sage.plot + sage: D.plot(edge_labels=True, edge_colors=D._color_by_label()) # needs sage.plot Graphics object consisting of 34 graphics primitives - sage: D.plot(edge_labels=True, color_by_label={'a': 'blue', 'b': 'red'}, # optional - sage.plot + sage: D.plot(edge_labels=True, color_by_label={'a': 'blue', 'b': 'red'}, # needs sage.plot ....: edge_style='dashed') Graphics object consisting of 34 graphics primitives sage: g = Graph({}, loops=True, multiedges=True, sparse=True) sage: g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), (0, 1, 'd'), ....: (0, 1, 'e'), (0, 1, 'f'), (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) - sage: g.plot(edge_labels=True, color_by_label=True, edge_style='dashed') # optional - sage.plot + sage: g.plot(edge_labels=True, color_by_label=True, edge_style='dashed') # needs sage.plot Graphics object consisting of 22 graphics primitives :: @@ -20876,21 +20886,21 @@ def plot(self, **options): sage: S = SupersingularModule(389) sage: H = S.hecke_matrix(2) sage: D = DiGraph(H, sparse=True) - sage: P = D.plot() # optional - sage.plot + sage: P = D.plot() # needs sage.plot :: sage: G = Graph({'a': ['a','b','b','b','e'], 'b': ['c','d','e'], ....: 'c':['c','d','d','d'],'d':['e']}, sparse=True) - sage: G.show(pos={'a':[0,1],'b':[1,1],'c':[2,0],'d':[1,0],'e':[0,0]}) # optional - sage.plot + sage: G.show(pos={'a':[0,1],'b':[1,1],'c':[2,0],'d':[1,0],'e':[0,0]}) # needs sage.plot TESTS:: sage: G = DiGraph({0: {1: 'a', 2: 'a'}, 1: {0: 'b'}, 2: {0: 'c'}}) - sage: p = G.plot(edge_labels=True, # optional - sage.plot + sage: p = G.plot(edge_labels=True, # needs sage.plot ....: color_by_label={'a': 'yellow', 'b': 'purple'}); p Graphics object consisting of 14 graphics primitives - sage: sorted(x.options()['rgbcolor'] for x in p # optional - sage.plot + sage: sorted(x.options()['rgbcolor'] for x in p # needs sage.plot ....: if isinstance(x, sage.plot.arrow.CurveArrow)) ['black', 'purple', 'yellow', 'yellow'] """ @@ -20917,8 +20927,8 @@ def show(self, method="matplotlib", **kwds): EXAMPLES:: sage: C = graphs.CubeGraph(8) - sage: P = C.plot(vertex_labels=False, vertex_size=0, graph_border=True) # optional - sage.plot - sage: P.show() # long time (3s on sage.math, 2011) # optional - sage.plot + sage: P = C.plot(vertex_labels=False, vertex_size=0, graph_border=True) # needs sage.plot + sage: P.show() # long time (3s on sage.math, 2011), needs sage.plot """ if method == "js": @@ -21006,15 +21016,15 @@ def plot3d(self, bgcolor=(1, 1, 1), EXAMPLES:: sage: G = graphs.CubeGraph(5) - sage: G.plot3d(iterations=500, edge_size=None, vertex_size=0.04) # long time, optional - sage.plot + sage: G.plot3d(iterations=500, edge_size=None, vertex_size=0.04) # long time, needs sage.plot Graphics3d Object We plot a fairly complicated Cayley graph:: - sage: A5 = AlternatingGroup(5); A5 # optional - sage.groups + sage: A5 = AlternatingGroup(5); A5 # needs sage.groups Alternating group of order 5!/2 as a permutation group - sage: G = A5.cayley_graph() # optional - sage.groups - sage: G.plot3d(vertex_size=0.03, edge_size=0.01, # long time, optional - sage.groups sage.plot + sage: G = A5.cayley_graph() # needs sage.groups + sage: G.plot3d(vertex_size=0.03, edge_size=0.01, # long time # needs sage.groups sage.plot ....: vertex_colors={(1,1,1): list(G)}, bgcolor=(0,0,0), ....: color_by_label=True, iterations=200) Graphics3d Object @@ -21022,26 +21032,26 @@ def plot3d(self, bgcolor=(1, 1, 1), Some :class:`~sage.plot.plot3d.tachyon.Tachyon` examples:: sage: D = graphs.DodecahedralGraph() - sage: P3D = D.plot3d(engine='tachyon') # optional - sage.plot - sage: P3D.show() # long time, optional - sage.plot + sage: P3D = D.plot3d(engine='tachyon') # needs sage.plot + sage: P3D.show() # long time # needs sage.plot :: sage: G = graphs.PetersenGraph() - sage: G.plot3d(engine='tachyon', # long time, optional - sage.plot + sage: G.plot3d(engine='tachyon', # long time # needs sage.plot ....: vertex_colors={(0,0,1): list(G)}).show() :: sage: C = graphs.CubeGraph(4) - sage: C.plot3d(engine='tachyon', # long time, optional - sage.plot + sage: C.plot3d(engine='tachyon', # long time # needs sage.plot ....: edge_colors={(0,1,0): C.edges(sort=False)}, ....: vertex_colors={(1,1,1): list(C)}, bgcolor=(0,0,0)).show() :: sage: K = graphs.CompleteGraph(3) - sage: K.plot3d(engine='tachyon', # long time, optional - sage.plot + sage: K.plot3d(engine='tachyon', # long time # needs sage.plot ....: edge_colors={(1,0,0): [(0,1,None)], ....: (0,1,0): [(0,2,None)], ....: (0,0,1): [(1,2,None)]}).show() @@ -21055,7 +21065,7 @@ def plot3d(self, bgcolor=(1, 1, 1), ....: 8: [9], 9: [10, 13], 10: [11], 11: [12, 18], ....: 12: [16, 13], 13: [14], 14: [15], 15: [16], 16: [17], ....: 17: [18], 18: [19], 19: []}) - sage: D.plot3d().show() # long time, optional - sage.plot + sage: D.plot3d().show() # long time # needs sage.plot :: @@ -21063,7 +21073,7 @@ def plot3d(self, bgcolor=(1, 1, 1), sage: from sage.plot.colors import rainbow sage: R = rainbow(P.size(), 'rgbtuple') sage: edge_colors = {R[i]: [e] for i, e in enumerate(P.edge_iterator())} - sage: P.plot3d(engine='tachyon', edge_colors=edge_colors).show() # long time, optional - sage.plot + sage: P.plot3d(engine='tachyon', edge_colors=edge_colors).show() # long time, needs sage.plot :: @@ -21078,24 +21088,24 @@ def plot3d(self, bgcolor=(1, 1, 1), Using the ``partition`` keyword:: sage: G = graphs.WheelGraph(7) - sage: G.plot3d(partition=[[0], [1, 2, 3, 4, 5, 6]]) # optional - sage.plot + sage: G.plot3d(partition=[[0], [1, 2, 3, 4, 5, 6]]) # needs sage.plot Graphics3d Object TESTS:: sage: G = DiGraph({0: {1: 'a', 2: 'a'}, 1: {0: 'b'}, 2: {0: 'c'}}) - sage: p = G.plot3d(edge_labels=True, # optional - sage.plot + sage: p = G.plot3d(edge_labels=True, # needs sage.plot ....: color_by_label={'a': 'yellow', 'b': 'cyan'}) - sage: s = p.x3d_str() # optional - sage.plot + sage: s = p.x3d_str() # needs sage.plot This 3D plot contains four yellow objects (two cylinders and two cones), two black objects and 2 cyan objects:: - sage: s.count("Material diffuseColor='1.0 1.0 0.0'") # optional - sage.plot + sage: s.count("Material diffuseColor='1.0 1.0 0.0'") # needs sage.plot 4 - sage: s.count("Material diffuseColor='0.0 0.0 0.0'") # optional - sage.plot + sage: s.count("Material diffuseColor='0.0 0.0 0.0'") # needs sage.plot 2 - sage: s.count("Material diffuseColor='0.0 1.0 1.0'") # optional - sage.plot + sage: s.count("Material diffuseColor='0.0 1.0 1.0'") # needs sage.plot 2 .. SEEALSO:: @@ -21264,14 +21274,14 @@ def show3d(self, bgcolor=(1, 1, 1), vertex_colors=None, vertex_size=0.06, EXAMPLES:: sage: G = graphs.CubeGraph(5) - sage: G.show3d(iterations=500, edge_size=None, vertex_size=0.04) # long time, optional - sage.plot + sage: G.show3d(iterations=500, edge_size=None, vertex_size=0.04) # long time, needs sage.plot We plot a fairly complicated Cayley graph:: - sage: A5 = AlternatingGroup(5); A5 # optional - sage.groups + sage: A5 = AlternatingGroup(5); A5 # needs sage.groups Alternating group of order 5!/2 as a permutation group - sage: G = A5.cayley_graph() # optional - sage.groups - sage: G.show3d(vertex_size=0.03, # long time, optional - sage.groups sage.plot + sage: G = A5.cayley_graph() # needs sage.groups + sage: G.show3d(vertex_size=0.03, # long time # needs sage.groups sage.plot ....: edge_size=0.01, edge_size2=0.02, ....: vertex_colors={(1,1,1): list(G)}, bgcolor=(0,0,0), ....: color_by_label=True, iterations=200) @@ -21279,25 +21289,25 @@ def show3d(self, bgcolor=(1, 1, 1), vertex_colors=None, vertex_size=0.06, Some :class:`~sage.plot.plot3d.tachyon.Tachyon` examples:: sage: D = graphs.DodecahedralGraph() - sage: D.show3d(engine='tachyon') # long time, optional - sage.plot + sage: D.show3d(engine='tachyon') # long time # needs sage.plot :: sage: G = graphs.PetersenGraph() - sage: G.show3d(engine='tachyon', # long time, optional - sage.plot + sage: G.show3d(engine='tachyon', # long time # needs sage.plot ....: vertex_colors={(0,0,1): list(G)}) :: sage: C = graphs.CubeGraph(4) - sage: C.show3d(engine='tachyon', # long time, optional - sage.plot + sage: C.show3d(engine='tachyon', # long time # needs sage.plot ....: edge_colors={(0,1,0): C.edges(sort=False)}, ....: vertex_colors={(1,1,1): list(C)}, bgcolor=(0,0,0)) :: sage: K = graphs.CompleteGraph(3) - sage: K.show3d(engine='tachyon', # long time, optional - sage.plot + sage: K.show3d(engine='tachyon', # long time # needs sage.plot ....: edge_colors={(1,0,0): [(0, 1, None)], ....: (0, 1, 0): [(0, 2, None)], ....: (0, 0, 1): [(1, 2, None)]}) @@ -21460,12 +21470,13 @@ def graphviz_string(self, **options): A digraph using latex labels for vertices and edges:: - sage: f(x) = -1 / x # optional - sage.symbolic - sage: g(x) = 1 / (x + 1) # optional - sage.symbolic - sage: G = DiGraph() # optional - sage.symbolic - sage: G.add_edges((i, f(i), f) for i in (1, 2, 1/2, 1/4)) # optional - sage.symbolic - sage: G.add_edges((i, g(i), g) for i in (1, 2, 1/2, 1/4)) # optional - sage.symbolic - sage: print(G.graphviz_string(labels="latex", # random # optional - sage.symbolic + sage: # needs sage.symbolic + sage: f(x) = -1 / x + sage: g(x) = 1 / (x + 1) + sage: G = DiGraph() + sage: G.add_edges((i, f(i), f) for i in (1, 2, 1/2, 1/4)) + sage: G.add_edges((i, g(i), g) for i in (1, 2, 1/2, 1/4)) + sage: print(G.graphviz_string(labels="latex", # random ....: edge_labels=True)) digraph { node [shape="plaintext"]; @@ -21492,7 +21503,7 @@ def graphviz_string(self, **options): node_4 -> node_9 [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"]; } - sage: print(G.graphviz_string(labels="latex", # random # optional - sage.symbolic + sage: print(G.graphviz_string(labels="latex", # random # needs sage.symbolic ....: color_by_label=True)) digraph { node [shape="plaintext"]; @@ -21519,7 +21530,7 @@ def graphviz_string(self, **options): node_4 -> node_9 [color = "#00ffff"]; } - sage: print(G.graphviz_string(labels="latex", # random # optional - sage.symbolic + sage: print(G.graphviz_string(labels="latex", # random # needs sage.symbolic ....: color_by_label={f: "red", g: "blue"})) digraph { node [shape="plaintext"]; @@ -21607,7 +21618,7 @@ def graphviz_string(self, **options): sage: def edge_options(data): ....: u, v, label = data ....: return {"dir":"back"} if u == 1 else {} - sage: print(G.graphviz_string(edge_options=edge_options)) # random # optional - sage.symbolic + sage: print(G.graphviz_string(edge_options=edge_options)) # random # needs sage.symbolic digraph { node_0 [label="-1"]; node_1 [label="-1/2"]; @@ -21641,7 +21652,7 @@ def graphviz_string(self, **options): ....: if (u,v) == (1, -1): options["label_style"] = "latex" ....: if (u,v) == (1, 1/2): options["dir"] = "back" ....: return options - sage: print(G.graphviz_string(edge_options=edge_options)) # random # optional - sage.symbolic + sage: print(G.graphviz_string(edge_options=edge_options)) # random # needs sage.symbolic digraph { node_0 [label="-1"]; node_1 [label="-1/2"]; @@ -21737,12 +21748,13 @@ def graphviz_string(self, **options): The following digraph has vertices with newlines in their string representations:: - sage: m1 = matrix(3, 3) # optional - sage.modules - sage: m2 = matrix(3, 3, 1) # optional - sage.modules - sage: m1.set_immutable() # optional - sage.modules - sage: m2.set_immutable() # optional - sage.modules - sage: g = DiGraph({m1: [m2]}) # optional - sage.modules - sage: print(g.graphviz_string()) # optional - sage.modules + sage: # needs sage.modules + sage: m1 = matrix(3, 3) + sage: m2 = matrix(3, 3, 1) + sage: m1.set_immutable() + sage: m2.set_immutable() + sage: g = DiGraph({m1: [m2]}) + sage: print(g.graphviz_string()) digraph { node_0 [label="[0 0 0]\n\ [0 0 0]\n\ @@ -22053,26 +22065,26 @@ def spectrum(self, laplacian=False): EXAMPLES:: sage: P = graphs.PetersenGraph() - sage: P.spectrum() # optional - sage.modules + sage: P.spectrum() # needs sage.modules [3, 1, 1, 1, 1, 1, -2, -2, -2, -2] - sage: P.spectrum(laplacian=True) # optional - sage.modules + sage: P.spectrum(laplacian=True) # needs sage.modules [5, 5, 5, 5, 2, 2, 2, 2, 2, 0] sage: D = P.to_directed() sage: D.delete_edge(7, 9) - sage: D.spectrum() # optional - sage.modules + sage: D.spectrum() # needs sage.modules [2.9032119259..., 1, 1, 1, 1, 0.8060634335..., -1.7092753594..., -2, -2, -2] :: sage: C = graphs.CycleGraph(8) - sage: C.spectrum() # optional - sage.modules + sage: C.spectrum() # needs sage.modules [2, 1.4142135623..., 1.4142135623..., 0, 0, -1.4142135623..., -1.4142135623..., -2] A digraph may have complex eigenvalues. Previously, the complex parts of graph eigenvalues were being dropped. For a 3-cycle, we have:: sage: T = DiGraph({0: [1], 1: [2], 2: [0]}) - sage: T.spectrum() # optional - sage.modules + sage: T.spectrum() # needs sage.modules [1, -0.5000000000... + 0.8660254037...*I, -0.5000000000... - 0.8660254037...*I] TESTS: @@ -22086,10 +22098,10 @@ def spectrum(self, laplacian=False): eigenvalues. :: sage: H = graphs.HoffmanSingletonGraph() - sage: evals = H.spectrum() # optional - sage.modules - sage: lap = [7 - x for x in evals] # optional - sage.modules - sage: lap.sort(reverse=True) # optional - sage.modules - sage: lap == H.spectrum(laplacian=True) # optional - sage.modules + sage: evals = H.spectrum() # needs sage.modules + sage: lap = [7 - x for x in evals] # needs sage.modules + sage: lap.sort(reverse=True) # needs sage.modules + sage: lap == H.spectrum(laplacian=True) # needs sage.modules True """ # Ideally the spectrum should return something like a Factorization object @@ -22135,11 +22147,11 @@ def characteristic_polynomial(self, var='x', laplacian=False): EXAMPLES:: sage: P = graphs.PetersenGraph() - sage: P.characteristic_polynomial() # optional - sage.modules + sage: P.characteristic_polynomial() # needs sage.modules x^10 - 15*x^8 + 75*x^6 - 24*x^5 - 165*x^4 + 120*x^3 + 120*x^2 - 160*x + 48 - sage: P.charpoly() # optional - sage.modules + sage: P.charpoly() # needs sage.modules x^10 - 15*x^8 + 75*x^6 - 24*x^5 - 165*x^4 + 120*x^3 + 120*x^2 - 160*x + 48 - sage: P.characteristic_polynomial(laplacian=True) # optional - sage.modules + sage: P.characteristic_polynomial(laplacian=True) # needs sage.modules x^10 - 30*x^9 + 390*x^8 - 2880*x^7 + 13305*x^6 - 39882*x^5 + 77640*x^4 - 94800*x^3 + 66000*x^2 - 20000*x """ @@ -22175,7 +22187,7 @@ def eigenvectors(self, laplacian=False): EXAMPLES:: sage: P = graphs.PetersenGraph() - sage: P.eigenvectors() # optional - sage.modules + sage: P.eigenvectors() # needs sage.modules [(3, [ (1, 1, 1, 1, 1, 1, 1, 1, 1, 1) ], 1), (-2, [ @@ -22195,7 +22207,7 @@ def eigenvectors(self, laplacian=False): graph is regular. However, since the output also contains the eigenvalues, the two outputs are slightly different:: - sage: P.eigenvectors(laplacian=True) # optional - sage.modules + sage: P.eigenvectors(laplacian=True) # needs sage.modules [(0, [ (1, 1, 1, 1, 1, 1, 1, 1, 1, 1) ], 1), (5, [ @@ -22214,7 +22226,7 @@ def eigenvectors(self, laplacian=False): :: sage: C = graphs.CycleGraph(8) - sage: C.eigenvectors() # optional - sage.modules + sage: C.eigenvectors() # needs sage.modules [(2, [ (1, 1, 1, 1, 1, 1, 1, 1) @@ -22244,7 +22256,7 @@ def eigenvectors(self, laplacian=False): graph eigenvalues were being dropped. For a 3-cycle, we have:: sage: T = DiGraph({0:[1], 1:[2], 2:[0]}) - sage: T.eigenvectors() # optional - sage.modules + sage: T.eigenvectors() # needs sage.modules [(1, [ (1, 1, 1) @@ -22285,7 +22297,7 @@ def eigenspaces(self, laplacian=False): EXAMPLES:: sage: P = graphs.PetersenGraph() - sage: P.eigenspaces() # optional - sage.modules + sage: P.eigenspaces() # needs sage.modules [ (3, Vector space of degree 10 and dimension 1 over Rational Field User basis matrix: @@ -22309,7 +22321,7 @@ def eigenspaces(self, laplacian=False): graph is regular. However, since the output also contains the eigenvalues, the two outputs are slightly different:: - sage: P.eigenspaces(laplacian=True) # optional - sage.modules + sage: P.eigenspaces(laplacian=True) # needs sage.modules [ (0, Vector space of degree 10 and dimension 1 over Rational Field User basis matrix: @@ -22334,7 +22346,7 @@ def eigenspaces(self, laplacian=False): corresponding eigenspace:: sage: C = graphs.CycleGraph(8) - sage: C.eigenspaces() # optional - sage.modules + sage: C.eigenspaces() # needs sage.modules [ (2, Vector space of degree 8 and dimension 1 over Rational Field User basis matrix: @@ -22357,7 +22369,7 @@ def eigenspaces(self, laplacian=False): we have:: sage: T = DiGraph({0: [1], 1: [2], 2: [0]}) - sage: T.eigenspaces() # optional - sage.modules + sage: T.eigenspaces() # needs sage.modules [ (1, Vector space of degree 3 and dimension 1 over Rational Field User basis matrix: @@ -22435,7 +22447,7 @@ def relabel(self, perm=None, inplace=True, return_map=False, check_input=True, c EXAMPLES:: sage: G = graphs.PathGraph(3) - sage: G.am() # optional - sage.modules + sage: G.am() # needs sage.modules [0 1 0] [1 0 1] [0 1 0] @@ -22443,7 +22455,7 @@ def relabel(self, perm=None, inplace=True, return_map=False, check_input=True, c Relabeling using a dictionary. Note that the dictionary does not define the new label of vertex `0`:: - sage: G.relabel({1:2,2:1}, inplace=False).am() # optional - sage.modules + sage: G.relabel({1:2,2:1}, inplace=False).am() # needs sage.modules [0 0 1] [0 0 1] [1 1 0] @@ -22453,7 +22465,7 @@ def relabel(self, perm=None, inplace=True, return_map=False, check_input=True, c vertices have an image can require some time, and this feature can be disabled (at your own risk):: - sage: G.relabel({1:2,2:1}, inplace=False, # optional - sage.modules + sage: G.relabel({1:2,2:1}, inplace=False, # needs sage.modules ....: complete_partial_function=False).am() Traceback (most recent call last): ... @@ -22461,14 +22473,14 @@ def relabel(self, perm=None, inplace=True, return_map=False, check_input=True, c Relabeling using a list:: - sage: G.relabel([0,2,1], inplace=False).am() # optional - sage.modules + sage: G.relabel([0,2,1], inplace=False).am() # needs sage.modules [0 0 1] [0 0 1] [1 1 0] Relabeling using an iterable:: - sage: G.relabel(iter((0,2,1)), inplace=False).am() # optional - sage.modules + sage: G.relabel(iter((0,2,1)), inplace=False).am() # needs sage.modules [0 0 1] [0 0 1] [1 1 0] @@ -22476,10 +22488,10 @@ def relabel(self, perm=None, inplace=True, return_map=False, check_input=True, c Relabeling using a Sage permutation:: sage: G = graphs.PathGraph(3) - sage: from sage.groups.perm_gps.permgroup_named import SymmetricGroup # optional - sage.groups - sage: S = SymmetricGroup(3) # optional - sage.groups - sage: gamma = S('(1,2)') # optional - sage.groups - sage: G.relabel(gamma, inplace=False).am() # optional - sage.groups sage.modules + sage: from sage.groups.perm_gps.permgroup_named import SymmetricGroup # needs sage.groups + sage: S = SymmetricGroup(3) # needs sage.groups + sage: gamma = S('(1,2)') # needs sage.groups + sage: G.relabel(gamma, inplace=False).am() # needs sage.groups sage.modules [0 0 1] [0 0 1] [1 1 0] @@ -22762,7 +22774,7 @@ def is_equitable(self, partition, quotient_matrix=False): False sage: G.is_equitable([[0,4],[1,3,5,9],[2,6,8,7]]) True - sage: G.is_equitable([[0,4],[1,3,5,9],[2,6,8,7]], quotient_matrix=True) # optional - sage.modules + sage: G.is_equitable([[0,4],[1,3,5,9],[2,6,8,7]], quotient_matrix=True) # needs sage.modules [1 2 0] [1 0 2] [0 2 1] @@ -22948,7 +22960,7 @@ def automorphism_group(self, partition=None, verbosity=0, sage: graphs_query = GraphQuery(display_cols=['graph6'],num_vertices=4) sage: L = graphs_query.get_graphs_list() - sage: graphs_list.show_graphs(L) # optional - sage.plot + sage: graphs_list.show_graphs(L) # needs sage.plot sage: for g in L: ....: G = g.automorphism_group() ....: G.order(), G.gens() @@ -23051,10 +23063,11 @@ def automorphism_group(self, partition=None, verbosity=0, One can also use the faster algorithm for computing the automorphism group of the graph - bliss:: - sage: G = graphs.HallJankoGraph() # optional - bliss - sage: A1 = G.automorphism_group() # optional - bliss - sage: A2 = G.automorphism_group(algorithm='bliss') # optional - bliss - sage: A1.is_isomorphic(A2) # optional - bliss + sage: # optional - bliss + sage: G = graphs.HallJankoGraph() + sage: A1 = G.automorphism_group() + sage: A2 = G.automorphism_group(algorithm='bliss') + sage: A1.is_isomorphic(A2) True TESTS: @@ -23286,13 +23299,13 @@ def is_vertex_transitive(self, partition=None, verbosity=0, sage: G.is_vertex_transitive() False sage: P = graphs.PetersenGraph() - sage: P.is_vertex_transitive() # optional - sage.groups + sage: P.is_vertex_transitive() # needs sage.groups True sage: D = graphs.DodecahedralGraph() - sage: D.is_vertex_transitive() # optional - sage.groups + sage: D.is_vertex_transitive() # needs sage.groups True - sage: R = graphs.RandomGNP(2000, .01) # optional - networkx - sage: R.is_vertex_transitive() # optional - networkx + sage: R = graphs.RandomGNP(2000, .01) # needs networkx + sage: R.is_vertex_transitive() # needs networkx False """ if partition is None: @@ -23424,21 +23437,21 @@ def is_isomorphic(self, other, certificate=False, verbosity=0, edge_labels=False Graphs:: - sage: from sage.groups.perm_gps.permgroup_named import SymmetricGroup # optional - sage.groups + sage: from sage.groups.perm_gps.permgroup_named import SymmetricGroup # needs sage.groups sage: D = graphs.DodecahedralGraph() sage: E = copy(D) - sage: gamma = SymmetricGroup(20).random_element() # optional - sage.groups - sage: E.relabel(gamma) # optional - sage.groups + sage: gamma = SymmetricGroup(20).random_element() # needs sage.groups + sage: E.relabel(gamma) # needs sage.groups sage: D.is_isomorphic(E) True :: sage: D = graphs.DodecahedralGraph() - sage: S = SymmetricGroup(20) # optional - sage.groups - sage: gamma = S.random_element() # optional - sage.groups - sage: E = copy(D) # optional - sage.groups - sage: E.relabel(gamma) # optional - sage.groups + sage: S = SymmetricGroup(20) # needs sage.groups + sage: gamma = S.random_element() # needs sage.groups + sage: E = copy(D) # needs sage.groups + sage: E.relabel(gamma) # needs sage.groups sage: a,b = D.is_isomorphic(E, certificate=True); a True sage: from sage.graphs.generic_graph_pyx import spring_layout_fast @@ -23803,7 +23816,7 @@ class by some canonization function `c`. If `G` and `H` are graphs, sage: P = graphs.PetersenGraph() sage: DP = P.to_directed() - sage: DP.canonical_label(algorithm='sage').adjacency_matrix() # optional - sage.modules + sage: DP.canonical_label(algorithm='sage').adjacency_matrix() # needs sage.modules [0 0 0 0 0 0 0 1 1 1] [0 0 0 0 1 0 1 0 0 1] [0 0 0 1 0 0 1 0 1 0] @@ -24027,15 +24040,15 @@ def is_cayley(self, return_group=False, mapping=False, A Petersen Graph is not a Cayley graph:: sage: g = graphs.PetersenGraph() - sage: g.is_cayley() # optional - sage.groups + sage: g.is_cayley() # needs sage.groups False A Cayley digraph is a Cayley graph:: - sage: C7 = groups.permutation.Cyclic(7) # optional - sage.groups + sage: C7 = groups.permutation.Cyclic(7) # needs sage.groups sage: S = [(1,2,3,4,5,6,7), (1,3,5,7,2,4,6), (1,5,2,6,3,7,4)] - sage: d = C7.cayley_graph(generators=S) # optional - sage.groups - sage: d.is_cayley() # optional - sage.groups + sage: d = C7.cayley_graph(generators=S) # needs sage.groups + sage: d.is_cayley() # needs sage.groups True Graphs with loops and multiedges will have identity and repeated @@ -24309,7 +24322,7 @@ def katz_matrix(self, alpha, nonedgesonly=False, vertices=None): We find the Katz matrix of an undirected 4-cycle. :: sage: G = graphs.CycleGraph(4) - sage: G.katz_matrix(1/20) # optional - sage.modules + sage: G.katz_matrix(1/20) # needs sage.modules [1/198 5/99 1/198 5/99] [ 5/99 1/198 5/99 1/198] [1/198 5/99 1/198 5/99] @@ -24318,7 +24331,7 @@ def katz_matrix(self, alpha, nonedgesonly=False, vertices=None): We find the Katz matrix of an undirected 4-cycle with all entries other than those which correspond to non-edges zeroed out. :: - sage: G.katz_matrix(1/20, True) # optional - sage.modules + sage: G.katz_matrix(1/20, True) # needs sage.modules [ 0 0 1/198 0] [ 0 0 0 1/198] [1/198 0 0 0] @@ -24330,7 +24343,7 @@ def katz_matrix(self, alpha, nonedgesonly=False, vertices=None): We find the Katz matrix in a fan on 6 vertices. :: sage: H = Graph([(0,1),(0,2),(0,3),(0,4),(0,5),(0,6),(1,2),(2,3),(3,4),(4,5)]) - sage: H.katz_matrix(1/10) # optional - sage.modules + sage: H.katz_matrix(1/10) # needs sage.modules [ 169/2256 545/4512 25/188 605/4512 25/188 545/4512 485/4512] [ 545/4512 7081/297792 4355/37224 229/9024 595/37224 4073/297792 109/9024] [ 25/188 4355/37224 172/4653 45/376 125/4653 595/37224 5/376] @@ -24346,22 +24359,23 @@ def katz_matrix(self, alpha, nonedgesonly=False, vertices=None): TESTS:: - sage: (graphs.CompleteGraph(4)).katz_matrix(1/4) # optional - sage.modules + sage: # needs sage.modules + sage: (graphs.CompleteGraph(4)).katz_matrix(1/4) [3/5 4/5 4/5 4/5] [4/5 3/5 4/5 4/5] [4/5 4/5 3/5 4/5] [4/5 4/5 4/5 3/5] - sage: (graphs.CompleteGraph(4)).katz_matrix(1/4, nonedgesonly=True) # optional - sage.modules + sage: (graphs.CompleteGraph(4)).katz_matrix(1/4, nonedgesonly=True) [0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0] - sage: (graphs.PathGraph(4)).katz_matrix(1/4, nonedgesonly=False) # optional - sage.modules + sage: (graphs.PathGraph(4)).katz_matrix(1/4, nonedgesonly=False) [15/209 60/209 16/209 4/209] [60/209 31/209 64/209 16/209] [16/209 64/209 31/209 60/209] [ 4/209 16/209 60/209 15/209] - sage: (graphs.PathGraph(4)).katz_matrix(1/4, nonedgesonly=True) # optional - sage.modules + sage: (graphs.PathGraph(4)).katz_matrix(1/4, nonedgesonly=True) [ 0 0 16/209 4/209] [ 0 0 0 16/209] [16/209 0 0 0] @@ -24426,7 +24440,7 @@ def katz_centrality(self, alpha, u=None): all 4 vertices have the same centrality) :: sage: G = graphs.CycleGraph(4) - sage: G.katz_centrality(1/20) # optional - sage.modules + sage: G.katz_centrality(1/20) # needs sage.modules {0: 1/9, 1: 1/9, 2: 1/9, 3: 1/9} Note that in the below example the nodes having indegree `0` also have @@ -24435,7 +24449,7 @@ def katz_centrality(self, alpha, u=None): sage: G = DiGraph({1: [10], 2:[10,11], 3:[10,11], 4:[], 5:[11, 4], 6:[11], ....: 7:[10,11], 8:[10,11], 9:[10], 10:[11, 5, 8], 11:[6]}) - sage: G.katz_centrality(.85) # rel tol 1e-14 # optional - sage.modules + sage: G.katz_centrality(.85) # rel tol 1e-14 # needs sage.modules {1: 0.000000000000000, 2: 0.000000000000000, 3: 0.000000000000000, @@ -24456,15 +24470,16 @@ def katz_centrality(self, alpha, u=None): TESTS:: - sage: graphs.PathGraph(3).katz_centrality(1/20) # optional - sage.modules + sage: # needs sage.modules + sage: graphs.PathGraph(3).katz_centrality(1/20) {0: 11/199, 1: 21/199, 2: 11/199} - sage: graphs.PathGraph(4).katz_centrality(1/20) # optional - sage.modules + sage: graphs.PathGraph(4).katz_centrality(1/20) {0: 21/379, 1: 41/379, 2: 41/379, 3: 21/379} - sage: graphs.PathGraph(3).katz_centrality(1/20,2) # optional - sage.modules + sage: graphs.PathGraph(3).katz_centrality(1/20,2) 11/199 - sage: graphs.PathGraph(4).katz_centrality(1/20,3) # optional - sage.modules + sage: graphs.PathGraph(4).katz_centrality(1/20,3) 21/379 - sage: (graphs.PathGraph(3) + graphs.PathGraph(4)).katz_centrality(1/20) # optional - sage.modules + sage: (graphs.PathGraph(3) + graphs.PathGraph(4)).katz_centrality(1/20) {0: 11/199, 1: 21/199, 2: 11/199, 3: 21/379, 4: 41/379, 5: 41/379, 6: 21/379} """ @@ -24515,29 +24530,29 @@ def edge_polytope(self, backend=None): The EP of a `4`-cycle is a square:: sage: G = graphs.CycleGraph(4) - sage: P = G.edge_polytope(); P # optional - sage.geometry.polyhedron + sage: P = G.edge_polytope(); P # needs sage.geometry.polyhedron A 2-dimensional polyhedron in ZZ^4 defined as the convex hull of 4 vertices The EP of a complete graph on `4` vertices is cross polytope:: sage: G = graphs.CompleteGraph(4) - sage: P = G.edge_polytope(); P # optional - sage.geometry.polyhedron + sage: P = G.edge_polytope(); P # needs sage.geometry.polyhedron A 3-dimensional polyhedron in ZZ^4 defined as the convex hull of 6 vertices - sage: P.is_combinatorially_isomorphic(polytopes.cross_polytope(3)) # optional - sage.geometry.polyhedron + sage: P.is_combinatorially_isomorphic(polytopes.cross_polytope(3)) # needs sage.geometry.polyhedron True The EP of a graph is isomorphic to the subdirect sum of its connected components EPs:: sage: n = randint(3, 6) - sage: G1 = graphs.RandomGNP(n, 0.2) # optional - networkx + sage: G1 = graphs.RandomGNP(n, 0.2) # needs networkx sage: n = randint(3, 6) - sage: G2 = graphs.RandomGNP(n, 0.2) # optional - networkx - sage: G = G1.disjoint_union(G2) # optional - networkx - sage: P = G.edge_polytope() # optional - networkx sage.geometry.polyhedron - sage: P1 = G1.edge_polytope() # optional - networkx sage.geometry.polyhedron - sage: P2 = G2.edge_polytope() # optional - networkx sage.geometry.polyhedron - sage: P.is_combinatorially_isomorphic(P1.subdirect_sum(P2)) # optional - networkx sage.geometry.polyhedron + sage: G2 = graphs.RandomGNP(n, 0.2) # needs networkx + sage: G = G1.disjoint_union(G2) # needs networkx + sage: P = G.edge_polytope() # needs networkx sage.geometry.polyhedron + sage: P1 = G1.edge_polytope() # needs networkx sage.geometry.polyhedron + sage: P2 = G2.edge_polytope() # needs networkx sage.geometry.polyhedron + sage: P.is_combinatorially_isomorphic(P1.subdirect_sum(P2)) # needs networkx sage.geometry.polyhedron True All trees on `n` vertices have isomorphic EPs:: @@ -24545,9 +24560,9 @@ def edge_polytope(self, backend=None): sage: n = randint(4, 10) sage: G1 = graphs.RandomTree(n) sage: G2 = graphs.RandomTree(n) - sage: P1 = G1.edge_polytope() # optional - sage.geometry.polyhedron - sage: P2 = G2.edge_polytope() # optional - sage.geometry.polyhedron - sage: P1.is_combinatorially_isomorphic(P2) # optional - sage.geometry.polyhedron + sage: P1 = G1.edge_polytope() # needs sage.geometry.polyhedron + sage: P2 = G2.edge_polytope() # needs sage.geometry.polyhedron + sage: P1.is_combinatorially_isomorphic(P2) # needs sage.geometry.polyhedron True However, there are still many different EPs:: @@ -24555,14 +24570,14 @@ def edge_polytope(self, backend=None): sage: len(list(graphs(5))) 34 sage: polys = [] - sage: for G in graphs(5): # optional - sage.geometry.polyhedron + sage: for G in graphs(5): # needs sage.geometry.polyhedron ....: P = G.edge_polytope() ....: for P1 in polys: ....: if P.is_combinatorially_isomorphic(P1): ....: break ....: else: ....: polys.append(P) - sage: len(polys) # optional - sage.geometry.polyhedron + sage: len(polys) # needs sage.geometry.polyhedron 19 TESTS: @@ -24570,7 +24585,7 @@ def edge_polytope(self, backend=None): Obtain the EP with unsortable vertices:: sage: G = Graph([[1, (1, 2)]]) - sage: G.edge_polytope() # optional - sage.geometry.polyhedron + sage: G.edge_polytope() # needs sage.geometry.polyhedron A 0-dimensional polyhedron in ZZ^2 defined as the convex hull of 1 vertex """ from sage.matrix.special import identity_matrix @@ -24601,17 +24616,17 @@ def symmetric_edge_polytope(self, backend=None): The SEP of a `4`-cycle is a cube:: sage: G = graphs.CycleGraph(4) - sage: P = G.symmetric_edge_polytope(); P # optional - sage.geometry.polyhedron + sage: P = G.symmetric_edge_polytope(); P # needs sage.geometry.polyhedron A 3-dimensional polyhedron in ZZ^4 defined as the convex hull of 8 vertices - sage: P.is_combinatorially_isomorphic(polytopes.cube()) # optional - sage.geometry.polyhedron + sage: P.is_combinatorially_isomorphic(polytopes.cube()) # needs sage.geometry.polyhedron True The SEP of a complete graph on `4` vertices is a cuboctahedron:: sage: G = graphs.CompleteGraph(4) - sage: P = G.symmetric_edge_polytope(); P # optional - sage.geometry.polyhedron + sage: P = G.symmetric_edge_polytope(); P # needs sage.geometry.polyhedron A 3-dimensional polyhedron in ZZ^4 defined as the convex hull of 12 vertices - sage: P.is_combinatorially_isomorphic(polytopes.cuboctahedron()) # optional - sage.geometry.polyhedron + sage: P.is_combinatorially_isomorphic(polytopes.cuboctahedron()) # needs sage.geometry.polyhedron True The SEP of a graph with edges on `n` vertices has dimension `n` @@ -24619,26 +24634,26 @@ def symmetric_edge_polytope(self, backend=None): sage: n = randint(5, 12) sage: G = Graph() - sage: while not G.num_edges(): # optional - networkx + sage: while not G.num_edges(): # needs networkx ....: G = graphs.RandomGNP(n, 0.2) - sage: P = G.symmetric_edge_polytope() # optional - networkx sage.geometry.polyhedron - sage: P.ambient_dim() == n # optional - networkx sage.geometry.polyhedron + sage: P = G.symmetric_edge_polytope() # needs networkx sage.geometry.polyhedron + sage: P.ambient_dim() == n # needs networkx sage.geometry.polyhedron True - sage: P.dim() == n - G.connected_components_number() # optional - networkx sage.geometry.polyhedron + sage: P.dim() == n - G.connected_components_number() # needs networkx sage.geometry.polyhedron True The SEP of a graph is isomorphic to the subdirect sum of its connected components SEP's:: sage: n = randint(3, 6) - sage: G1 = graphs.RandomGNP(n, 0.2) # optional - networkx + sage: G1 = graphs.RandomGNP(n, 0.2) # needs networkx sage: n = randint(3, 6) - sage: G2 = graphs.RandomGNP(n, 0.2) # optional - networkx - sage: G = G1.disjoint_union(G2) # optional - networkx - sage: P = G.symmetric_edge_polytope() # optional - networkx sage.geometry.polyhedron - sage: P1 = G1.symmetric_edge_polytope() # optional - networkx sage.geometry.polyhedron - sage: P2 = G2.symmetric_edge_polytope() # optional - networkx sage.geometry.polyhedron - sage: P.is_combinatorially_isomorphic(P1.subdirect_sum(P2)) # optional - networkx sage.geometry.polyhedron + sage: G2 = graphs.RandomGNP(n, 0.2) # needs networkx + sage: G = G1.disjoint_union(G2) # needs networkx + sage: P = G.symmetric_edge_polytope() # needs networkx sage.geometry.polyhedron + sage: P1 = G1.symmetric_edge_polytope() # needs networkx sage.geometry.polyhedron + sage: P2 = G2.symmetric_edge_polytope() # needs networkx sage.geometry.polyhedron + sage: P.is_combinatorially_isomorphic(P1.subdirect_sum(P2)) # needs networkx sage.geometry.polyhedron True All trees on `n` vertices have isomorphic SEPs:: @@ -24646,9 +24661,9 @@ def symmetric_edge_polytope(self, backend=None): sage: n = randint(4, 10) sage: G1 = graphs.RandomTree(n) sage: G2 = graphs.RandomTree(n) - sage: P1 = G1.symmetric_edge_polytope() # optional - sage.geometry.polyhedron - sage: P2 = G2.symmetric_edge_polytope() # optional - sage.geometry.polyhedron - sage: P1.is_combinatorially_isomorphic(P2) # optional - sage.geometry.polyhedron + sage: P1 = G1.symmetric_edge_polytope() # needs sage.geometry.polyhedron + sage: P2 = G2.symmetric_edge_polytope() # needs sage.geometry.polyhedron + sage: P1.is_combinatorially_isomorphic(P2) # needs sage.geometry.polyhedron True However, there are still many different SEPs:: @@ -24656,7 +24671,7 @@ def symmetric_edge_polytope(self, backend=None): sage: len(list(graphs(5))) 34 sage: polys = [] - sage: for G in graphs(5): # optional - sage.geometry.polyhedron + sage: for G in graphs(5): # needs sage.geometry.polyhedron ....: P = G.symmetric_edge_polytope() ....: for P1 in polys: ....: if P.is_combinatorially_isomorphic(P1): @@ -24675,24 +24690,24 @@ def symmetric_edge_polytope(self, backend=None): sage: G2.add_edges([[0, 7], [7, 3]]) sage: G1.is_isomorphic(G2) False - sage: P1 = G1.symmetric_edge_polytope() # optional - sage.geometry.polyhedron - sage: P2 = G2.symmetric_edge_polytope() # optional - sage.geometry.polyhedron - sage: P1.is_combinatorially_isomorphic(P2) # optional - sage.geometry.polyhedron + sage: P1 = G1.symmetric_edge_polytope() # needs sage.geometry.polyhedron + sage: P2 = G2.symmetric_edge_polytope() # needs sage.geometry.polyhedron + sage: P1.is_combinatorially_isomorphic(P2) # needs sage.geometry.polyhedron True Apparently, glueing two graphs together on a vertex gives isomorphic SEPs:: sage: n = randint(3, 7) - sage: g1 = graphs.RandomGNP(n, 0.2) # optional - networkx - sage: g2 = graphs.RandomGNP(n, 0.2) # optional - networkx - sage: G = g1.disjoint_union(g2) # optional - networkx - sage: H = copy(G) # optional - networkx - sage: G.merge_vertices(((0, randrange(n)), (1, randrange(n)))) # optional - networkx - sage: H.merge_vertices(((0, randrange(n)), (1, randrange(n)))) # optional - networkx - sage: PG = G.symmetric_edge_polytope() # optional - networkx sage.geometry.polyhedron - sage: PH = H.symmetric_edge_polytope() # optional - networkx sage.geometry.polyhedron - sage: PG.is_combinatorially_isomorphic(PH) # optional - networkx sage.geometry.polyhedron + sage: g1 = graphs.RandomGNP(n, 0.2) # needs networkx + sage: g2 = graphs.RandomGNP(n, 0.2) # needs networkx + sage: G = g1.disjoint_union(g2) # needs networkx + sage: H = copy(G) # needs networkx + sage: G.merge_vertices(((0, randrange(n)), (1, randrange(n)))) # needs networkx + sage: H.merge_vertices(((0, randrange(n)), (1, randrange(n)))) # needs networkx + sage: PG = G.symmetric_edge_polytope() # needs networkx sage.geometry.polyhedron + sage: PH = H.symmetric_edge_polytope() # needs networkx sage.geometry.polyhedron + sage: PG.is_combinatorially_isomorphic(PH) # needs networkx sage.geometry.polyhedron True TESTS: @@ -24700,7 +24715,7 @@ def symmetric_edge_polytope(self, backend=None): Obtain the SEP with unsortable vertices:: sage: G = Graph([[1, (1, 2)]]) - sage: G.symmetric_edge_polytope() # optional - sage.geometry.polyhedron + sage: G.symmetric_edge_polytope() # needs sage.geometry.polyhedron A 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices """ from itertools import chain @@ -24736,10 +24751,10 @@ def tachyon_vertex_plot(g, bgcolor=(1, 1, 1), sage: G = graphs.TetrahedralGraph() sage: from sage.graphs.generic_graph import tachyon_vertex_plot - sage: T,p = tachyon_vertex_plot(G, pos3d=G.layout(dim=3)) # optional - sage.plot - sage: type(T) # optional - sage.plot + sage: T,p = tachyon_vertex_plot(G, pos3d=G.layout(dim=3)) # needs sage.plot + sage: type(T) # needs sage.plot - sage: type(p) # optional - sage.plot + sage: type(p) # needs sage.plot <... 'dict'> """ assert pos3d is not None diff --git a/src/sage/interfaces/r.py b/src/sage/interfaces/r.py index 144d581b18e..7d18f1e467e 100644 --- a/src/sage/interfaces/r.py +++ b/src/sage/interfaces/r.py @@ -66,13 +66,14 @@ sage: x.var() # optional - rpy2 [1] 53.853 - sage: x.sort() # optional - rpy2 + sage: # optional - rpy2 + sage: x.sort() [1] 3.1 5.6 6.4 10.4 21.7 - sage: x.min() # optional - rpy2 + sage: x.min() [1] 3.1 - sage: x.max() # optional - rpy2 + sage: x.max() [1] 21.7 - sage: x # optional - rpy2 + sage: x [1] 10.4 5.6 3.1 6.4 21.7 sage: r(-17).sqrt() # optional - rpy2 @@ -92,42 +93,44 @@ sage: r.seq(length=10, from_=-1, by=.2) # optional - rpy2 [1] -1.0 -0.8 -0.6 -0.4 -0.2 0.0 0.2 0.4 0.6 0.8 - sage: x = r([10.4,5.6,3.1,6.4,21.7]) # optional - rpy2 - sage: x.rep(2) # optional - rpy2 + sage: # optional - rpy2 + sage: x = r([10.4,5.6,3.1,6.4,21.7]) + sage: x.rep(2) [1] 10.4 5.6 3.1 6.4 21.7 10.4 5.6 3.1 6.4 21.7 - sage: x.rep(times=2) # optional - rpy2 + sage: x.rep(times=2) [1] 10.4 5.6 3.1 6.4 21.7 10.4 5.6 3.1 6.4 21.7 - sage: x.rep(each=2) # optional - rpy2 + sage: x.rep(each=2) [1] 10.4 10.4 5.6 5.6 3.1 3.1 6.4 6.4 21.7 21.7 Missing Values:: - sage: na = r('NA') # optional - rpy2 - sage: z = r([1,2,3,na]) # optional - rpy2 - sage: z # optional - rpy2 + sage: # optional - rpy2 + sage: na = r('NA') + sage: z = r([1,2,3,na]) + sage: z [1] 1 2 3 NA - sage: ind = r.is_na(z) # optional - rpy2 - sage: ind # optional - rpy2 + sage: ind = r.is_na(z) + sage: ind [1] FALSE FALSE FALSE TRUE - sage: zero = r(0) # optional - rpy2 - sage: zero / zero # optional - rpy2 + sage: zero = r(0) + sage: zero / zero [1] NaN - sage: inf = r('Inf') # optional - rpy2 - sage: inf-inf # optional - rpy2 + sage: inf = r('Inf') + sage: inf-inf [1] NaN - sage: r.is_na(inf) # optional - rpy2 + sage: r.is_na(inf) [1] FALSE - sage: r.is_na(inf-inf) # optional - rpy2 + sage: r.is_na(inf-inf) [1] TRUE - sage: r.is_na(zero/zero) # optional - rpy2 + sage: r.is_na(zero/zero) [1] TRUE - sage: r.is_na(na) # optional - rpy2 + sage: r.is_na(na) [1] TRUE - sage: r.is_nan(inf-inf) # optional - rpy2 + sage: r.is_nan(inf-inf) [1] TRUE - sage: r.is_nan(zero/zero) # optional - rpy2 + sage: r.is_nan(zero/zero) [1] TRUE - sage: r.is_nan(na) # optional - rpy2 + sage: r.is_nan(na) [1] FALSE @@ -145,13 +148,14 @@ sage: x['!is.na(self)'] # optional - rpy2 [1] 10.4 5.6 3.1 6.4 21.7 - sage: x = r([10.4,5.6,3.1,6.4,21.7,na]); x # optional - rpy2 + sage: # optional - rpy2 + sage: x = r([10.4,5.6,3.1,6.4,21.7,na]); x [1] 10.4 5.6 3.1 6.4 21.7 NA - sage: (x+1)['(!is.na(self)) & self>0'] # optional - rpy2 + sage: (x+1)['(!is.na(self)) & self>0'] [1] 11.4 6.6 4.1 7.4 22.7 - sage: x = r([10.4,-2,3.1,-0.5,21.7,na]); x # optional - rpy2 + sage: x = r([10.4,-2,3.1,-0.5,21.7,na]); x [1] 10.4 -2.0 3.1 -0.5 21.7 NA - sage: (x+1)['(!is.na(self)) & self>0'] # optional - rpy2 + sage: (x+1)['(!is.na(self)) & self>0'] [1] 11.4 4.1 0.5 22.7 Distributions:: @@ -187,16 +191,17 @@ Or you get a dictionary to be able to access all the information:: - sage: rs = r.summary(r.c(1,4,3,4,3,2,5,1)) # optional - rpy2 - sage: rs # optional - rpy2 + sage: # optional - rpy2 + sage: rs = r.summary(r.c(1,4,3,4,3,2,5,1)) + sage: rs Min. 1st Qu. Median Mean 3rd Qu. Max. 1.000 1.750 3.000 2.875 4.000 5.000 - sage: d = rs._sage_() # optional - rpy2 - sage: d['DATA'] # optional - rpy2 + sage: d = rs._sage_() + sage: d['DATA'] [1, 1.75, 3, 2.875, 4, 5] - sage: d['_Names'] # optional - rpy2 + sage: d['_Names'] ['Min.', '1st Qu.', 'Median', 'Mean', '3rd Qu.', 'Max.'] - sage: d['_r_class'] # optional - rpy2 + sage: d['_r_class'] ['summaryDefault', 'table'] It is also possible to access the plotting capabilities of R @@ -346,13 +351,14 @@ def _setup_r_to_sage_converter(): The conversion can handle "not a number", infinity, imaginary values and missing values:: - sage: r(-17).sqrt().sage() # optional - rpy2 + sage: # optional - rpy2 + sage: r(-17).sqrt().sage() nan - sage: r('-17+0i').sqrt().sage() # optional - rpy2 + sage: r('-17+0i').sqrt().sage() 4.123105625617661j - sage: r('NA').sage() # optional - rpy2 + sage: r('NA').sage() NA - sage: inf = r('Inf'); inf.sage() # optional - rpy2 + sage: inf = r('Inf'); inf.sage() inf Character Vectors are represented by regular python arrays:: @@ -504,30 +510,33 @@ def _lazy_init(self): Initialization happens on eval:: - sage: my_r = R() # optional - rpy2 - sage: my_r._initialized # optional - rpy2 + sage: # optional - rpy2 + sage: my_r = R() + sage: my_r._initialized False - sage: my_r(42) # indirect doctest # optional - rpy2 + sage: my_r(42) # indirect doctest [1] 42 - sage: my_r._initialized # optional - rpy2 + sage: my_r._initialized True And on package import:: - sage: my_r = R() # optional - rpy2 - sage: my_r._initialized # optional - rpy2 + sage: # optional - rpy2 + sage: my_r = R() + sage: my_r._initialized False - sage: my_r.library('grid') # optional - rpy2 - sage: my_r._initialized # optional - rpy2 + sage: my_r.library('grid') + sage: my_r._initialized True And when fetching help pages:: - sage: my_r = R() # optional - rpy2 - sage: my_r._initialized # optional - rpy2 + sage: # optional - rpy2 + sage: my_r = R() + sage: my_r._initialized False - sage: _ = my_r.help('c') # optional - rpy2 - sage: my_r._initialized # optional - rpy2 + sage: _ = my_r.help('c') + sage: my_r._initialized True """ if not self._initialized: @@ -611,15 +620,16 @@ def png(self, *args, **kwds): EXAMPLES:: - sage: filename = tmp_filename() + '.png' # optional - rpy2 - sage: r.png(filename='"%s"'%filename) # optional -- rgraphics # optional - rpy2 + sage: # optional - rpy2 + sage: filename = tmp_filename() + '.png' + sage: r.png(filename='"%s"'%filename) # optional - rgraphics NULL - sage: x = r([1,2,3]) # optional - rpy2 - sage: y = r([4,5,6]) # optional - rpy2 - sage: r.plot(x,y) # This saves to filename, but is not viewable from command line; optional -- rgraphics # optional - rpy2 + sage: x = r([1,2,3]) + sage: y = r([4,5,6]) + sage: r.plot(x,y) # optional - rgraphics null device 1 - sage: import os; os.unlink(filename) # We remove the file for doctesting; optional -- rgraphics # optional - rpy2 + sage: import os; os.unlink(filename) # optional - rgraphics We want to make sure that we actually can view R graphics, which happens differently on different platforms:: @@ -743,12 +753,13 @@ def read(self, filename): EXAMPLES:: - sage: filename = tmp_filename() # optional - rpy2 - sage: f = open(filename, 'w') # optional - rpy2 - sage: _ = f.write('a <- 2+2\n') # optional - rpy2 - sage: f.close() # optional - rpy2 - sage: r.read(filename) # optional - rpy2 - sage: r.get('a') # optional - rpy2 + sage: # optional - rpy2 + sage: filename = tmp_filename() + sage: f = open(filename, 'w') + sage: _ = f.write('a <- 2+2\n') + sage: f.close() + sage: r.read(filename) + sage: r.get('a') '[1] 4' """ self.eval( self._read_in_file_command(filename) ) @@ -804,12 +815,13 @@ def version(self): EXAMPLES:: - sage: r.version() # not tested # optional - rpy2 + sage: # optional - rpy2 + sage: r.version() # not tested ((3, 0, 1), 'R version 3.0.1 (2013-05-16)') - sage: rint, rstr = r.version() # optional - rpy2 - sage: rint[0] >= 3 # optional - rpy2 + sage: rint, rstr = r.version() + sage: rint[0] >= 3 True - sage: rstr.startswith('R version') # optional - rpy2 + sage: rstr.startswith('R version') True """ major_re = re.compile(r'^major\s*(\d.*?)$', re.M) @@ -1271,15 +1283,16 @@ def plot(self, *args, **kwds): the output device to that file. If this is done in the notebook, it must be done in the same cell as the plot itself:: - sage: filename = tmp_filename() + '.png' # optional - rpy2 - sage: r.png(filename='"%s"'%filename) # Note the double quotes in single quotes!; optional -- rgraphics # optional - rpy2 + sage: # optional - rpy2 + sage: filename = tmp_filename() + '.png' + sage: r.png(filename='"%s"'%filename) # optional - rgraphics NULL - sage: x = r([1,2,3]) # optional - rpy2 - sage: y = r([4,5,6]) # optional - rpy2 - sage: r.plot(x,y) # optional -- rgraphics # optional - rpy2 + sage: x = r([1,2,3]) + sage: y = r([4,5,6]) + sage: r.plot(x,y) # optional - rgraphics null device 1 - sage: import os; os.unlink(filename) # For doctesting, we remove the file; optional -- rgraphics # optional - rpy2 + sage: import os; os.unlink(filename) # optional - rgraphics Please note that for more extensive use of R's plotting capabilities (such as the lattices package), it is advisable @@ -1287,23 +1300,25 @@ def plot(self, *args, **kwds): notebook. The following examples are not tested, because they differ depending on operating system:: - sage: r.X11() # not tested - opens interactive device on systems with X11 support # optional - rpy2 - sage: r.quartz() # not tested - opens interactive device on OSX # optional - rpy2 - sage: r.hist("rnorm(100)") # not tested - makes a plot # optional - rpy2 - sage: r.library("lattice") # not tested - loads R lattice plotting package # optional - rpy2 - sage: r.histogram(x = "~ wt | cyl", data="mtcars") # not tested - makes a lattice plot # optional - rpy2 - sage: r.dev_off() # not tested, turns off the interactive viewer # optional - rpy2 + sage: # not tested, optional - rpy2 + sage: r.X11() + sage: r.quartz() + sage: r.hist("rnorm(100)") + sage: r.library("lattice") + sage: r.histogram(x = "~ wt | cyl", data="mtcars") + sage: r.dev_off() In the notebook, one can use r.png() to open the device, but would need to use the following since R lattice graphics do not automatically print away from the command line:: - sage: filename = tmp_filename() + '.png' # Not needed in notebook, used for doctesting # optional - rpy2 - sage: r.png(filename='"%s"'%filename) # filename not needed in notebook, used for doctesting; optional -- rgraphics # optional - rpy2 + sage: # optional - rpy2 + sage: filename = tmp_filename() + '.png' # Not needed in notebook, used for doctesting + sage: r.png(filename='"%s"'%filename) # optional - rgraphics NULL - sage: r.library("lattice") # optional - rpy2 - sage: r("print(histogram(~wt | cyl, data=mtcars))") # plot should appear; optional -- rgraphics # optional - rpy2 - sage: import os; os.unlink(filename) # We remove the file for doctesting, not needed in notebook; optional -- rgraphics # optional - rpy2 + sage: r.library("lattice") + sage: r("print(histogram(~wt | cyl, data=mtcars))") # optional - rgraphics + sage: import os; os.unlink(filename) # optional - rgraphics """ # We have to define this to override the plot function defined in the # superclass. @@ -1337,14 +1352,15 @@ def _r_to_sage_name(self, s): EXAMPLES:: - sage: f = r._r_to_sage_name # optional - rpy2 - sage: f('t.test') # optional - rpy2 + sage: # optional - rpy2 + sage: f = r._r_to_sage_name + sage: f('t.test') 't_test' - sage: f('attr<-') # optional - rpy2 + sage: f('attr<-') 'attr__' - sage: f('parent.env<-') # optional - rpy2 + sage: f('parent.env<-') 'parent_env__' - sage: f('class') # optional - rpy2 + sage: f('class') 'class_' """ from keyword import iskeyword @@ -1360,16 +1376,17 @@ def _sage_to_r_name(self, s): EXAMPLES:: - sage: f = r._sage_to_r_name # optional - rpy2 - sage: f('t_test') # optional - rpy2 + sage: # optional - rpy2 + sage: f = r._sage_to_r_name + sage: f('t_test') 't.test' - sage: f('attr__') # optional - rpy2 + sage: f('attr__') 'attr<-' - sage: f('parent_env__') # optional - rpy2 + sage: f('parent_env__') 'parent.env<-' - sage: r._r_to_sage_name(f('parent_env__')) # optional - rpy2 + sage: r._r_to_sage_name(f('parent_env__')) 'parent_env__' - sage: f('class_') # optional - rpy2 + sage: f('class_') 'class' """ if len(s) > 1 and s[-2:] == "__": @@ -1451,11 +1468,12 @@ def tilde(self, x): EXAMPLES:: - sage: x = r([1,2,3,4,5]) # optional - rpy2 - sage: y = r([3,5,7,9,11]) # optional - rpy2 - sage: a = r.lm( y.tilde(x) ) # lm( y ~ x ) # optional - rpy2 - sage: d = a._sage_() # optional - rpy2 - sage: d['DATA']['coefficients']['DATA'][1] # optional - rpy2 + sage: # optional - rpy2 + sage: x = r([1,2,3,4,5]) + sage: y = r([3,5,7,9,11]) + sage: a = r.lm( y.tilde(x) ) # lm( y ~ x ) + sage: d = a._sage_() + sage: d['DATA']['coefficients']['DATA'][1] 2 """ par = self.parent() @@ -1505,11 +1523,12 @@ def __getattr__(self, attrname): EXAMPLES:: - sage: x = r([1,2,3]) # optional - rpy2 - sage: length = x.length # optional - rpy2 - sage: type(length) # optional - rpy2 + sage: # optional - rpy2 + sage: x = r([1,2,3]) + sage: length = x.length + sage: type(length) - sage: length() # optional - rpy2 + sage: length() [1] 3 """ try: @@ -1535,24 +1554,25 @@ def __getitem__(self, n): EXAMPLES:: - sage: x = r([10.4,5.6,3.1,6.4,21.7]) # optional - rpy2 - sage: x[0] # optional - rpy2 + sage: # optional - rpy2 + sage: x = r([10.4,5.6,3.1,6.4,21.7]) + sage: x[0] numeric(0) - sage: x[1] # optional - rpy2 + sage: x[1] [1] 10.4 - sage: x[-1] # optional - rpy2 + sage: x[-1] [1] 5.6 3.1 6.4 21.7 - sage: x[-2] # optional - rpy2 + sage: x[-2] [1] 10.4 3.1 6.4 21.7 - sage: x[-3] # optional - rpy2 + sage: x[-3] [1] 10.4 5.6 6.4 21.7 - sage: x['c(2,3)'] # optional - rpy2 + sage: x['c(2,3)'] [1] 5.6 3.1 - sage: key = r.c(2,3) # optional - rpy2 - sage: x[key] # optional - rpy2 + sage: key = r.c(2,3) + sage: x[key] [1] 5.6 3.1 - sage: m = r.array('1:3',r.c(2,4,2)) # optional - rpy2 - sage: m # optional - rpy2 + sage: m = r.array('1:3',r.c(2,4,2)) + sage: m , , 1 [,1] [,2] [,3] [,4] [1,] 1 3 2 1 @@ -1561,9 +1581,9 @@ def __getitem__(self, n): [,1] [,2] [,3] [,4] [1,] 3 2 1 3 [2,] 1 3 2 1 - sage: m[1,2,2] # optional - rpy2 + sage: m[1,2,2] [1] 2 - sage: m[1,r.c(1,2),1] # optional - rpy2 + sage: m[1,r.c(1,2),1] [1] 1 3 """ P = self._check_valid() @@ -1593,15 +1613,16 @@ def __bool__(self): EXAMPLES:: - sage: x = r([10.4,5.6,3.1,6.4,21.7]) # optional - rpy2 - sage: bool(x) # optional - rpy2 + sage: # optional - rpy2 + sage: x = r([10.4,5.6,3.1,6.4,21.7]) + sage: bool(x) True - sage: y = r([0,0,0,0]) # optional - rpy2 - sage: bool(y) # optional - rpy2 + sage: y = r([0,0,0,0]) + sage: bool(y) False - sage: bool(r(0)) # optional - rpy2 + sage: bool(r(0)) False - sage: bool(r(1)) # optional - rpy2 + sage: bool(r(1)) True """ return "FALSE" in repr(self == 0) @@ -2049,12 +2070,13 @@ def r_version(): EXAMPLES:: - sage: r_version() # not tested # optional - rpy2 + sage: # optional - rpy2 + sage: r_version() # not tested ((3, 0, 1), 'R version 3.0.1 (2013-05-16)') - sage: rint, rstr = r_version() # optional - rpy2 - sage: rint[0] >= 3 # optional - rpy2 + sage: rint, rstr = r_version() + sage: rint[0] >= 3 True - sage: rstr.startswith('R version') # optional - rpy2 + sage: rstr.startswith('R version') True """ return r.version() diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 15d898d9846..4bf03d29041 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -175,13 +175,14 @@ You can launch web-pages attached to the links:: - sage: K.diagram() # not tested + sage: # not tested + sage: K.diagram() True - sage: L.diagram(single=True) # not tested + sage: L.diagram(single=True) True - sage: L.knot_atlas_webpage() # not tested + sage: L.knot_atlas_webpage() True - sage: K.knotilus_webpage() # not tested + sage: K.knotilus_webpage() True and the description web-pages of the properties:: diff --git a/src/sage/misc/latex_standalone.py b/src/sage/misc/latex_standalone.py index cd42519c5ec..e783c4000a6 100644 --- a/src/sage/misc/latex_standalone.py +++ b/src/sage/misc/latex_standalone.py @@ -205,17 +205,17 @@ Adding a border in the options avoids cropping the vertices of a graph:: - sage: g = graphs.PetersenGraph() # optional - sage.graphs - sage: s = latex(g) # takes 3s but the result is cached # optional latex sage.graphs - sage: t = TikzPicture(s, standalone_config=["border=4mm"], usepackage=['tkz-graph']) # optional latex sage.graphs + sage: g = graphs.PetersenGraph() # needs sage.graphs + sage: s = latex(g) # takes 3s but the result is cached # optional - latex, needs sage.graphs + sage: t = TikzPicture(s, standalone_config=["border=4mm"], usepackage=['tkz-graph']) # optional - latex, needs sage.graphs sage: _ = t.pdf() # not tested The current latex representation of a transducer is a tikzpicture using the tikz library automata. The string can be used as input:: - sage: s = latex(transducers.GrayCode()) # optional sage.combinat - sage: t = TikzPicture(s, usetikzlibrary=['automata']) # optional sage.combinat - sage: _ = t.pdf(view=False) # long time (2s) # optional sage.combinat latex + sage: s = latex(transducers.GrayCode()) # needs sage.combinat + sage: t = TikzPicture(s, usetikzlibrary=['automata']) # needs sage.combinat + sage: _ = t.pdf(view=False) # long time (2s), optional - latex, needs sage.combinat AUTHORS: @@ -1344,19 +1344,21 @@ class TikzPicture(Standalone): below methods return a string providing the path to the filename, which is by default in a temporary folder:: - sage: _ = t.pdf() # not tested - sage: _ = t.png() # not tested - sage: _ = t.svg() # not tested - sage: _ = t.tex() # not tested - sage: _ = t.pdf(filename='abc.pdf') # not tested + sage: # not tested + sage: _ = t.pdf() + sage: _ = t.png() + sage: _ = t.svg() + sage: _ = t.tex() + sage: _ = t.pdf(filename='abc.pdf') Here we create a tikzpicture for the latex representation of a graph. This is using tkz-graph tex library:: - sage: g = graphs.PetersenGraph() # optional sage.graphs - sage: s = latex(g) # optional sage.graphs latex - sage: t = TikzPicture(s, standalone_config=["border=4mm"], usepackage=['tkz-graph']) # optional sage.graphs latex - sage: _ = t.pdf(view=False) # long time (2s) # optional - sage.graphs latex latex_package_tkz_graph + sage: # needs sage.graphs + sage: g = graphs.PetersenGraph() + sage: s = latex(g) # optional - latex + sage: t = TikzPicture(s, standalone_config=["border=4mm"], usepackage=['tkz-graph']) # optional - latex + sage: _ = t.pdf(view=False) # long time (2s), optional - latex latex_package_tkz_graph Here are standalone configurations, packages, tikz libraries and macros that can be set:: @@ -1454,29 +1456,29 @@ def from_dot_string(cls, dotdata, prog='dot'): EXAMPLES:: sage: from sage.misc.latex_standalone import TikzPicture - sage: G = graphs.PetersenGraph() # optional sage.graphs - sage: dotdata = G.graphviz_string() # optional sage.graphs - sage: tikz = TikzPicture.from_dot_string(dotdata) # optional sage.graphs dot2tex graphviz # long time (3s) + sage: G = graphs.PetersenGraph() # needs sage.graphs + sage: dotdata = G.graphviz_string() # needs sage.graphs + sage: tikz = TikzPicture.from_dot_string(dotdata) # long time (3s), optional - dot2tex graphviz, needs sage.graphs sage: _ = tikz.pdf() # not tested :: - sage: dotdata = G.graphviz_string(labels='latex') # optional sage.graphs - sage: tikz = TikzPicture.from_dot_string(dotdata) # optional sage.graphs dot2tex graphviz # long time (3s) + sage: dotdata = G.graphviz_string(labels='latex') # needs sage.graphs + sage: tikz = TikzPicture.from_dot_string(dotdata) # long time (3s), optional - dot2tex graphviz, needs sage.graphs sage: _ = tikz.pdf() # not tested :: sage: W = CoxeterGroup(["A",2]) - sage: G = W.cayley_graph() # optional sage.graphs - sage: dotdata = G.graphviz_string() # optional sage.graphs - sage: tikz = TikzPicture.from_dot_string(dotdata) # optional sage.graphs dot2tex graphviz # long time (3s) + sage: G = W.cayley_graph() # needs sage.graphs + sage: dotdata = G.graphviz_string() # needs sage.graphs + sage: tikz = TikzPicture.from_dot_string(dotdata) # long time (3s), optional - dot2tex graphviz, needs sage.graphs sage: _ = tikz.pdf() # not tested :: - sage: dotdata = G.graphviz_string(labels='latex') # optional sage.graphs - sage: tikz = TikzPicture.from_dot_string(dotdata) # optional sage.graphs dot2tex graphviz # long time (3s) + sage: dotdata = G.graphviz_string(labels='latex') # needs sage.graphs + sage: tikz = TikzPicture.from_dot_string(dotdata) # long time (3s), optional - dot2tex graphviz, needs sage.graphs sage: _ = tikz.pdf() # not tested """ @@ -1539,8 +1541,8 @@ def from_graph(cls, graph, merge_multiedges=True, EXAMPLES:: sage: from sage.misc.latex_standalone import TikzPicture - sage: g = graphs.PetersenGraph() # optional sage.graphs - sage: tikz = TikzPicture.from_graph(g) # optional sage.graphs dot2tex graphviz + sage: g = graphs.PetersenGraph() # needs sage.graphs + sage: tikz = TikzPicture.from_graph(g) # optional - dot2tex graphviz, needs sage.graphs doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. See https://github.com/sagemath/sage/issues/20343 for details. @@ -1548,28 +1550,28 @@ def from_graph(cls, graph, merge_multiedges=True, Using ``prog``:: - sage: tikz = TikzPicture.from_graph(g, prog='neato', color_by_label=True) # optional sage.graphs dot2tex graphviz # long time (3s) + sage: tikz = TikzPicture.from_graph(g, prog='neato', color_by_label=True) # long time (3s), optional - dot2tex graphviz, needs sage.graphs sage: _ = tikz.pdf() # not tested Using ``rankdir``:: - sage: tikz = TikzPicture.from_graph(g, rankdir='right') # optional sage.graphs dot2tex graphviz # long time (3s) + sage: tikz = TikzPicture.from_graph(g, rankdir='right') # long time (3s), optional - dot2tex graphviz, needs sage.graphs sage: _ = tikz.pdf() # not tested Using ``merge_multiedges``:: sage: alpha = var('alpha') sage: m = matrix(2,range(4)); m.set_immutable() - sage: G = DiGraph([(0,1,alpha), (0,1,0), (0,2,9), (0,2,m)], multiedges=True) # optional sage.graphs - sage: tikz = TikzPicture.from_graph(G, merge_multiedges=True) # optional sage.graphs dot2tex graphviz + sage: G = DiGraph([(0,1,alpha), (0,1,0), (0,2,9), (0,2,m)], multiedges=True) # needs sage.graphs + sage: tikz = TikzPicture.from_graph(G, merge_multiedges=True) # optional - dot2tex graphviz, needs sage.graphs sage: _ = tikz.pdf() # not tested Using ``merge_multiedges`` with ``merge_label_function``:: sage: fn = lambda L: LatexExpr(','.join(map(str, L))) sage: edges = [(0,1,'a'), (0,1,'b'), (0,2,'c'), (0,2,'d')] - sage: G = DiGraph(edges, multiedges=True) # optional sage.graphs - sage: tikz = TikzPicture.from_graph(G, # optional sage.graphs dot2tex graphviz + sage: G = DiGraph(edges, multiedges=True) # needs sage.graphs + sage: tikz = TikzPicture.from_graph(G, # optional - dot2tex graphviz, needs sage.graphs ....: merge_multiedges=True, merge_label_function=fn) sage: _ = tikz.pdf() # not tested @@ -1583,22 +1585,22 @@ def from_graph(cls, graph, merge_multiedges=True, sage: roots = [I] sage: succ = lambda v:[v*a,v*b,a*v,b*v] sage: R = RecursivelyEnumeratedSet(roots, succ) - sage: G = R.to_digraph() # optional sage.graphs - sage: G # optional sage.graphs + sage: G = R.to_digraph() # needs sage.graphs + sage: G # needs sage.graphs Looped multi-digraph on 27 vertices - sage: C = G.strongly_connected_components() # optional sage.graphs - sage: tikz = TikzPicture.from_graph(G, # optional sage.graphs dot2tex graphviz + sage: C = G.strongly_connected_components() # needs sage.graphs + sage: tikz = TikzPicture.from_graph(G, # optional - dot2tex graphviz, needs sage.graphs ....: merge_multiedges=False, subgraph_clusters=C) sage: _ = tikz.pdf() # not tested An example coming from ``graphviz_string`` documentation in SageMath:: - sage: f(x) = -1 / x # optional sage.symbolic - sage: g(x) = 1 / (x + 1) # optional sage.symbolic - sage: G = DiGraph() # optional sage.symbolic sage.graphs - sage: G.add_edges((i, f(i), f) for i in (1, 2, 1/2, 1/4)) # optional sage.symbolic sage.graphs - sage: G.add_edges((i, g(i), g) for i in (1, 2, 1/2, 1/4)) # optional sage.symbolic sage.graphs - sage: tikz = TikzPicture.from_graph(G) # optional sage.symbolic sage.graphs dot2tex graphviz + sage: f(x) = -1 / x # needs sage.symbolic + sage: g(x) = 1 / (x + 1) # needs sage.symbolic + sage: G = DiGraph() # needs sage.graphs sage.symbolic + sage: G.add_edges((i, f(i), f) for i in (1, 2, 1/2, 1/4)) # needs sage.graphs sage.symbolic + sage: G.add_edges((i, g(i), g) for i in (1, 2, 1/2, 1/4)) # needs sage.graphs sage.symbolic + sage: tikz = TikzPicture.from_graph(G) # optional - dot2tex graphviz, needs sage.graphs sage.symbolic sage: _ = tikz.pdf() # not tested sage: def edge_options(data): ....: u, v, label = data @@ -1608,7 +1610,7 @@ def from_graph(cls, graph, merge_multiedges=True, ....: if (u,v) == (1, -1): options["label_style"] = "latex" ....: if (u,v) == (1, 1/2): options["dir"] = "back" ....: return options - sage: tikz = TikzPicture.from_graph(G, edge_options=edge_options) # optional sage.symbolic sage.graphs dot2tex graphviz + sage: tikz = TikzPicture.from_graph(G, edge_options=edge_options) # optional - dot2tex graphviz, needs sage.graphs sage.symbolic sage: _ = tikz.pdf() # not tested """ @@ -1668,8 +1670,8 @@ def from_graph_with_pos(cls, graph, scale=1, merge_multiedges=True, EXAMPLES:: sage: from sage.misc.latex_standalone import TikzPicture - sage: g = graphs.PetersenGraph() # optional sage.graphs - sage: tikz = TikzPicture.from_graph_with_pos(g) # optional sage.graphs + sage: g = graphs.PetersenGraph() # needs sage.graphs + sage: tikz = TikzPicture.from_graph_with_pos(g) # needs sage.graphs doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. See https://github.com/sagemath/sage/issues/20343 for details. @@ -1678,10 +1680,10 @@ def from_graph_with_pos(cls, graph, scale=1, merge_multiedges=True, sage: edges = [(0,0,'a'),(0,1,'b'),(0,1,'c')] sage: kwds = dict(format='list_of_edges', loops=True, multiedges=True) - sage: G = DiGraph(edges, **kwds) # optional sage.graphs - sage: G.set_pos({0:(0,0), 1:(1,0)}) # optional sage.graphs - sage: f = lambda label:','.join(label) # optional sage.graphs - sage: TikzPicture.from_graph_with_pos(G, merge_label_function=f) # optional sage.graphs + sage: G = DiGraph(edges, **kwds) # needs sage.graphs + sage: G.set_pos({0:(0,0), 1:(1,0)}) # needs sage.graphs + sage: f = lambda label:','.join(label) # needs sage.graphs + sage: TikzPicture.from_graph_with_pos(G, merge_label_function=f) # needs sage.graphs \documentclass[tikz]{standalone} \standaloneconfig{border=4mm} \begin{document} @@ -1701,8 +1703,8 @@ def from_graph_with_pos(cls, graph, scale=1, merge_multiedges=True, sage: edges = [(0,0,'a'),(0,1,'b'),(0,1,'c')] sage: kwds = dict(format='list_of_edges', loops=True, multiedges=True) - sage: G = DiGraph(edges, **kwds) # optional sage.graphs - sage: TikzPicture.from_graph_with_pos(G) # optional sage.graphs + sage: G = DiGraph(edges, **kwds) # needs sage.graphs + sage: TikzPicture.from_graph_with_pos(G) # needs sage.graphs Traceback (most recent call last): ... ValueError: vertex positions need to be set first @@ -1796,21 +1798,21 @@ def from_poset(cls, poset, **kwds): EXAMPLES:: sage: from sage.misc.latex_standalone import TikzPicture - sage: P = posets.PentagonPoset() # optional sage.combinat - sage: tikz = TikzPicture.from_poset(P) # optional sage.combinat dot2tex graphviz + sage: P = posets.PentagonPoset() # needs sage.combinat + sage: tikz = TikzPicture.from_poset(P) # optional - dot2tex graphviz, needs sage.combinat doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. See https://github.com/sagemath/sage/issues/20343 for details. :: - sage: tikz = TikzPicture.from_poset(P, prog='neato', color_by_label=True) # optional sage.combinat dot2tex # long time (3s) + sage: tikz = TikzPicture.from_poset(P, prog='neato', color_by_label=True) # long time (3s), optional - dot2tex, needs sage.combinat :: - sage: P = posets.SymmetricGroupWeakOrderPoset(4) # optional sage.combinat - sage: tikz = TikzPicture.from_poset(P) # optional sage.combinat dot2tex graphviz # long time (4s) - sage: tikz = TikzPicture.from_poset(P, prog='neato') # optional sage.combinat dot2tex graphviz # long time (4s) + sage: P = posets.SymmetricGroupWeakOrderPoset(4) # needs sage.combinat + sage: tikz = TikzPicture.from_poset(P) # long time (4s), optional - dot2tex graphviz, needs sage.combinat + sage: tikz = TikzPicture.from_poset(P, prog='neato') # long time (4s), optional - dot2tex graphviz, needs sage.combinat """ graph = poset.hasse_diagram() return cls.from_graph(graph, **kwds) diff --git a/src/sage/misc/profiler.py b/src/sage/misc/profiler.py index 48c0d0cbbea..7db5d9d6eec 100644 --- a/src/sage/misc/profiler.py +++ b/src/sage/misc/profiler.py @@ -42,11 +42,12 @@ class Profiler: You can create a checkpoints without a string; ``Profiler`` will use the source code instead:: - sage: p() # not tested - sage: y = factor(25) # not tested - sage: p("last step") # not tested - sage: z = factor(35) # not tested - sage: p() # not tested + sage: # not tested + sage: p() + sage: y = factor(25) + sage: p("last step") + sage: z = factor(35) + sage: p() This will give a nice list of timings between checkpoints:: diff --git a/src/sage/quivers/algebra_elements.pyx b/src/sage/quivers/algebra_elements.pyx index cc1cdab8da6..3d05ba7e270 100644 --- a/src/sage/quivers/algebra_elements.pyx +++ b/src/sage/quivers/algebra_elements.pyx @@ -89,15 +89,16 @@ cdef class PathAlgebraElement(RingElement): faster, but the timing for path algebra elements has improved by about 20%:: - sage: timeit('pF^5+3*pF^3') # not tested + sage: # not tested + sage: timeit('pF^5+3*pF^3') 1 loops, best of 3: 338 ms per loop - sage: timeit('pP^5+3*pP^3') # not tested + sage: timeit('pP^5+3*pP^3') 100 loops, best of 3: 2.55 ms per loop - sage: timeit('pF2^7') # not tested + sage: timeit('pF2^7') 10000 loops, best of 3: 513 ms per loop - sage: timeit('pL2^7') # not tested + sage: timeit('pL2^7') 125 loops, best of 3: 1.99 ms per loop - sage: timeit('pP2^7') # not tested + sage: timeit('pP2^7') 10000 loops, best of 3: 1.54 ms per loop So, if one is merely interested in basic arithmetic operations for diff --git a/src/sage/rings/cfinite_sequence.py b/src/sage/rings/cfinite_sequence.py index 14ae02423e4..733fbcbd6a7 100644 --- a/src/sage/rings/cfinite_sequence.py +++ b/src/sage/rings/cfinite_sequence.py @@ -1265,10 +1265,11 @@ def guess(self, sequence, algorithm='sage'): r""" .. TODO:: - sage: CFiniteSequence(x+x^2+x^3+x^4+x^5+O(x^6)) # not implemented - sage: latex(r) # not implemented + sage: # not implemented + sage: CFiniteSequence(x+x^2+x^3+x^4+x^5+O(x^6)) + sage: latex(r) \big\{a_{n\ge0}\big|a_{n+2}=\sum_{i=0}^{1}c_ia_{n+i}, c=\{1,1\}, a_{n<2}=\{0,0,0,1\}\big\} - sage: r.egf() # not implemented + sage: r.egf() exp(2*x) - sage: r = CFiniteSequence(1/(1-y-x*y), x) # not implemented + sage: r = CFiniteSequence(1/(1-y-x*y), x) """ diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal.py b/src/sage/rings/polynomial/multi_polynomial_ideal.py index 9520251a300..099e72c2c58 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ideal.py +++ b/src/sage/rings/polynomial/multi_polynomial_ideal.py @@ -166,11 +166,11 @@ sage: factor(164878) 2 * 7 * 11777 - sage: I.change_ring(P.change_ring(GF(2))).groebner_basis() # optional - sage.rings.finite_rings + sage: I.change_ring(P.change_ring(GF(2))).groebner_basis() # needs sage.rings.finite_rings [x + y + z, y^2 + y, y*z + y, z^2 + 1] - sage: I.change_ring(P.change_ring(GF(7))).groebner_basis() # optional - sage.rings.finite_rings + sage: I.change_ring(P.change_ring(GF(7))).groebner_basis() # needs sage.rings.finite_rings [x - 1, y + 3, z - 2] - sage: I.change_ring(P.change_ring(GF(11777))).groebner_basis() # optional - sage.rings.finite_rings + sage: I.change_ring(P.change_ring(GF(11777))).groebner_basis() # needs sage.rings.finite_rings [x + 5633, y - 3007, z - 2626] The Groebner basis modulo any product of the prime factors is also non-trivial:: @@ -181,7 +181,7 @@ Modulo any other prime the Groebner basis is trivial so there are no other solutions. For example:: - sage: I.change_ring(P.change_ring(GF(3))).groebner_basis() # optional - sage.rings.finite_rings + sage: I.change_ring(P.change_ring(GF(3))).groebner_basis() # needs sage.rings.finite_rings [1] TESTS:: @@ -343,9 +343,9 @@ def _magma_init_(self, magma): EXAMPLES:: - sage: R. = PolynomialRing(GF(127),10) # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Cyclic(R,4) # indirect doctest # optional - sage.rings.finite_rings - sage: magma(I) # optional - magma # optional - sage.rings.finite_rings + sage: R. = PolynomialRing(GF(127),10) # needs sage.rings.finite_rings + sage: I = sage.rings.ideal.Cyclic(R,4) # indirect doctest # needs sage.rings.finite_rings + sage: magma(I) # optional - magma # needs sage.rings.finite_rings Ideal of Polynomial ring of rank 10 over GF(127) Order: Graded Reverse Lexicographical Variables: a, b, c, d, e, f, g, h, i, j @@ -380,18 +380,20 @@ def _groebner_basis_magma(self, deg_bound=None, prot=False, magma=magma_default) EXAMPLES:: - sage: R. = PolynomialRing(GF(127), 10) # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Cyclic(R, 6) # optional - sage.rings.finite_rings - sage: gb = I.groebner_basis('magma:GroebnerBasis') # indirect doctest; optional - magma sage.rings.finite_rings - sage: len(gb) # optional - magma # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R. = PolynomialRing(GF(127), 10) + sage: I = sage.rings.ideal.Cyclic(R, 6) + sage: gb = I.groebner_basis('magma:GroebnerBasis') # optional - magma + sage: len(gb) # optional - magma 45 We may also pass a degree bound to Magma:: - sage: R. = PolynomialRing(GF(127), 10) # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Cyclic(R, 6) # optional - sage.rings.finite_rings - sage: gb = I.groebner_basis('magma:GroebnerBasis', deg_bound=4) # indirect doctest; optional - magma # optional - sage.rings.finite_rings - sage: len(gb) # optional - magma # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R. = PolynomialRing(GF(127), 10) + sage: I = sage.rings.ideal.Cyclic(R, 6) + sage: gb = I.groebner_basis('magma:GroebnerBasis', deg_bound=4) # optional - magma + sage: len(gb) # optional - magma 5 """ R = self.ring() @@ -1099,9 +1101,9 @@ def triangular_decomposition(self, algorithm=None, singular=singular_default): Check that this method works over QQbar (:trac:`25351`):: - sage: R. = QQbar[] # optional - sage.rings.number_field - sage: J = Ideal(x^2 + y^2 - 2, y^2 - 1) # optional - sage.rings.number_field - sage: J.triangular_decomposition() # optional - sage.rings.number_field + sage: R. = QQbar[] # needs sage.rings.number_field + sage: J = Ideal(x^2 + y^2 - 2, y^2 - 1) # needs sage.rings.number_field + sage: J.triangular_decomposition() # needs sage.rings.number_field [Ideal (y^2 - 1, x^2 - 1) of Multivariate Polynomial Ring in x, y over Algebraic Field] """ P = self.ring() @@ -1157,9 +1159,9 @@ def dimension(self, singular=singular_default): EXAMPLES:: - sage: P. = PolynomialRing(GF(32003), order='degrevlex') # optional - sage.rings.finite_rings - sage: I = ideal(x^2 - y, x^3) # optional - sage.rings.finite_rings - sage: I.dimension() # optional - sage.rings.finite_rings + sage: P. = PolynomialRing(GF(32003), order='degrevlex') # needs sage.rings.finite_rings + sage: I = ideal(x^2 - y, x^3) # needs sage.rings.finite_rings + sage: I.dimension() # needs sage.rings.finite_rings 1 If the ideal is the total ring, the dimension is `-1` by convention. @@ -1171,22 +1173,23 @@ def dimension(self, singular=singular_default): EXAMPLES:: - sage: R. = PolynomialRing(GF(2147483659^2), order='lex') # optional - sage.rings.finite_rings - sage: I = R.ideal([x*y, x*y + 1]) # optional - sage.rings.finite_rings - sage: I.dimension() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R. = PolynomialRing(GF(2147483659^2), order='lex') + sage: I = R.ideal([x*y, x*y + 1]) + sage: I.dimension() verbose 0 (...: multi_polynomial_ideal.py, dimension) Warning: falling back to very slow toy implementation. -1 - sage: I=ideal([x*(x*y+1), y*(x*y+1)]) # optional - sage.rings.finite_rings - sage: I.dimension() # optional - sage.rings.finite_rings + sage: I=ideal([x*(x*y+1), y*(x*y+1)]) + sage: I.dimension() verbose 0 (...: multi_polynomial_ideal.py, dimension) Warning: falling back to very slow toy implementation. 1 - sage: I = R.ideal([x^3*y, x*y^2]) # optional - sage.rings.finite_rings - sage: I.dimension() # optional - sage.rings.finite_rings + sage: I = R.ideal([x^3*y, x*y^2]) + sage: I.dimension() verbose 0 (...: multi_polynomial_ideal.py, dimension) Warning: falling back to very slow toy implementation. 1 - sage: R. = PolynomialRing(GF(2147483659^2), order='lex') # optional - sage.rings.finite_rings - sage: I = R.ideal(0) # optional - sage.rings.finite_rings - sage: I.dimension() # optional - sage.rings.finite_rings + sage: R. = PolynomialRing(GF(2147483659^2), order='lex') + sage: I = R.ideal(0) + sage: I.dimension() verbose 0 (...: multi_polynomial_ideal.py, dimension) Warning: falling back to very slow toy implementation. 2 @@ -1198,9 +1201,9 @@ def dimension(self, singular=singular_default): Check that this method works over QQbar (:trac:`25351`):: - sage: P. = QQbar[] # optional - sage.rings.number_field - sage: I = ideal(x^2-y, x^3-QQbar(-1)) # optional - sage.rings.number_field - sage: I.dimension() # optional - sage.rings.number_field + sage: P. = QQbar[] # needs sage.rings.number_field + sage: I = ideal(x^2-y, x^3-QQbar(-1)) # needs sage.rings.number_field + sage: I.dimension() # needs sage.rings.number_field 1 .. NOTE:: @@ -1299,19 +1302,20 @@ def vector_space_dimension(self): Due to integer overflow, the result is correct only modulo ``2^32``, see :trac:`8586`:: - sage: P. = PolynomialRing(GF(32003), 3) # optional - sage.rings.finite_rings - sage: sage.rings.ideal.FieldIdeal(P).vector_space_dimension() # known bug # optional - sage.rings.finite_rings + sage: P. = PolynomialRing(GF(32003), 3) # needs sage.rings.finite_rings + sage: sage.rings.ideal.FieldIdeal(P).vector_space_dimension() # known bug, needs sage.rings.finite_rings 32777216864027 TESTS: Check that this method works over QQbar (:trac:`25351`):: - sage: P. = QQbar[] # optional - sage.rings.number_field - sage: I = ideal(x^2-y,x^3-QQbar(-1),z-y) # optional - sage.rings.number_field - sage: I.dimension() # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: P. = QQbar[] + sage: I = ideal(x^2-y,x^3-QQbar(-1),z-y) + sage: I.dimension() 0 - sage: I.vector_space_dimension() # optional - sage.rings.number_field + sage: I.vector_space_dimension() 3 """ @@ -1351,9 +1355,9 @@ def _groebner_basis_ginv(self, algorithm="TQ", criteria='CritPartially', divisio sage: I.groebner_basis(algorithm='ginv') # optional - ginv [z^3 - 79/210*z^2 + 1/30*y + 1/70*z, y^2 - 3/5*z^2 - 1/5*y + 1/5*z, y*z + 6/5*z^2 - 1/10*y - 2/5*z, x + 2*y + 2*z - 1] - sage: P. = PolynomialRing(GF(127), order='degrevlex') # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Katsura(P) # optional - sage.rings.finite_rings - sage: I.groebner_basis(algorithm='ginv') # optional - ginv # optional - sage.rings.finite_rings + sage: P. = PolynomialRing(GF(127), order='degrevlex') # needs sage.rings.finite_rings + sage: I = sage.rings.ideal.Katsura(P) # needs sage.rings.finite_rings + sage: I.groebner_basis(algorithm='ginv') # optional - ginv # needs sage.rings.finite_rings ... [z^3 + 22*z^2 - 55*y + 49*z, y^2 - 26*z^2 - 51*y + 51*z, y*z + 52*z^2 + 38*y + 25*z, x + 2*y + 2*z - 1] @@ -1596,9 +1600,9 @@ def genus(self): Check that this method works over QQbar (:trac:`25351`):: - sage: P. = QQbar[] # optional - sage.rings.number_field - sage: I = ideal(y^3*z + x^3*y + x*z^3) # optional - sage.rings.number_field - sage: I.genus() # optional - sage.rings.number_field + sage: P. = QQbar[] # needs sage.rings.number_field + sage: I = ideal(y^3*z + x^3*y + x*z^3) # needs sage.rings.number_field + sage: I.genus() # needs sage.rings.number_field 3 """ from sage.libs.singular.function_factory import ff @@ -1657,10 +1661,11 @@ def intersection(self, *others): Check that this method works over QQbar (:trac:`25351`):: - sage: R. = QQbar[] # optional - sage.rings.number_field - sage: I = x*R # optional - sage.rings.number_field - sage: J = y*R # optional - sage.rings.number_field - sage: I.intersection(J) # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: R. = QQbar[] + sage: I = x*R + sage: J = y*R + sage: I.intersection(J) Ideal (x*y) of Multivariate Polynomial Ring in x, y over Algebraic Field """ R = self.ring() @@ -1741,10 +1746,11 @@ def radical(self): :: - sage: R. = PolynomialRing(GF(37), 3) # optional - sage.rings.finite_rings - sage: p = z^2 + 1; q = z^3 + 2 # optional - sage.rings.finite_rings - sage: I = (p*q^2, y - z^2) * R # optional - sage.rings.finite_rings - sage: I.radical() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R. = PolynomialRing(GF(37), 3) + sage: p = z^2 + 1; q = z^3 + 2 + sage: I = (p*q^2, y - z^2) * R + sage: I.radical() Ideal (z^2 - y, y^2*z + y*z + 2*y + 2) of Multivariate Polynomial Ring in x, y, z over Finite Field of size 37 """ @@ -1829,16 +1835,17 @@ def syzygy_module(self): Check that this method works over QQbar (:trac:`25351`):: - sage: R. = QQbar[] # optional - sage.rings.number_field - sage: f = 2*x^2 + y # optional - sage.rings.number_field - sage: g = y # optional - sage.rings.number_field - sage: h = 2*f + g # optional - sage.rings.number_field - sage: I = Ideal([f,g,h]) # optional - sage.rings.number_field - sage: M = I.syzygy_module(); M # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: R. = QQbar[] + sage: f = 2*x^2 + y + sage: g = y + sage: h = 2*f + g + sage: I = Ideal([f,g,h]) + sage: M = I.syzygy_module(); M [ -2 -1 1] [ -y 2*x^2 + y 0] - sage: G = vector(I.gens()) # optional - sage.rings.number_field - sage: M*G # optional - sage.rings.number_field + sage: G = vector(I.gens()) + sage: M*G (0, 0) """ from sage.libs.singular.function_factory import ff @@ -1967,8 +1974,8 @@ def interreduced_basis(self): The interreduced basis of 0 is 0:: - sage: P. = GF(2)[] # optional - sage.rings.finite_rings - sage: Ideal(P(0)).interreduced_basis() # optional - sage.rings.finite_rings + sage: P. = GF(2)[] # needs sage.rings.finite_rings + sage: Ideal(P(0)).interreduced_basis() # needs sage.rings.finite_rings [0] ALGORITHM: @@ -1981,9 +1988,9 @@ def interreduced_basis(self): Check that this method works over QQbar (:trac:`25351`):: - sage: R. = QQbar[] # optional - sage.rings.number_field - sage: I = Ideal([z*x + y^3, z + y^3, z + x*y]) # optional - sage.rings.number_field - sage: I.interreduced_basis() # optional - sage.rings.number_field + sage: R. = QQbar[] # needs sage.rings.number_field + sage: I = Ideal([z*x + y^3, z + y^3, z + x*y]) # needs sage.rings.number_field + sage: I.interreduced_basis() # needs sage.rings.number_field [y^3 + z, x*y + z, x*z - z] """ return self.basis.reduced() @@ -2011,18 +2018,19 @@ def basis_is_groebner(self, singular=singular_default): EXAMPLES:: - sage: R. = PolynomialRing(GF(127), 10) # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Cyclic(R, 4) # optional - sage.rings.finite_rings - sage: I.basis_is_groebner() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R. = PolynomialRing(GF(127), 10) + sage: I = sage.rings.ideal.Cyclic(R, 4) + sage: I.basis_is_groebner() False - sage: I2 = Ideal(I.groebner_basis()) # optional - sage.rings.finite_rings - sage: I2.basis_is_groebner() # optional - sage.rings.finite_rings + sage: I2 = Ideal(I.groebner_basis()) + sage: I2.basis_is_groebner() True A more complicated example:: - sage: R. = PolynomialRing(GF(7583)) # optional - sage.rings.finite_rings - sage: l = [u6 + u5 + u4 + u3 + u2 - 3791*h, # optional - sage.rings.finite_rings + sage: R. = PolynomialRing(GF(7583)) # needs sage.rings.finite_rings + sage: l = [u6 + u5 + u4 + u3 + u2 - 3791*h, # needs sage.rings.finite_rings ....: U6 + U5 + U4 + U3 + U2 - 3791*h, ....: U2*u2 - h^2, U3*u3 - h^2, U4*u4 - h^2, ....: U5*u4 + U5*u3 + U4*u3 + U5*u2 + U4*u2 + U3*u2 - 3791*U5*h @@ -2064,10 +2072,10 @@ def basis_is_groebner(self, singular=singular_default): ....: - 2*U5*U4*U2^2*h^2 - 2*U5*U3*U2^2*h^2 - 2*U4*U3*U2^2*h^2 ....: - U5*U4*U3*h^3 - U5*U4*U2*h^3 - U5*U3*U2*h^3 - U4*U3*U2*h^3] - sage: Ideal(l).basis_is_groebner() # optional - sage.rings.finite_rings + sage: Ideal(l).basis_is_groebner() # needs sage.rings.finite_rings False - sage: gb = Ideal(l).groebner_basis() # optional - sage.rings.finite_rings - sage: Ideal(gb).basis_is_groebner() # optional - sage.rings.finite_rings + sage: gb = Ideal(l).groebner_basis() # needs sage.rings.finite_rings + sage: Ideal(gb).basis_is_groebner() # needs sage.rings.finite_rings True .. NOTE:: @@ -2085,12 +2093,13 @@ def basis_is_groebner(self, singular=singular_default): Check that this method works over QQbar (:trac:`25351`):: - sage: R. = QQbar[] # optional - sage.rings.number_field - sage: I = sage.rings.ideal.Cyclic(R,4) # optional - sage.rings.number_field - sage: I.basis_is_groebner() # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: R. = QQbar[] + sage: I = sage.rings.ideal.Cyclic(R,4) + sage: I.basis_is_groebner() False - sage: I2 = Ideal(I.groebner_basis()) # optional - sage.rings.number_field - sage: I2.basis_is_groebner() # optional - sage.rings.number_field + sage: I2 = Ideal(I.groebner_basis()) + sage: I2.basis_is_groebner() True """ from sage.matrix.constructor import matrix @@ -2173,9 +2182,9 @@ def transformed_basis(self, algorithm="gwalk", other_ring=None, singular=singula :: - sage: R. = PolynomialRing(GF(32003), 3, order='lex') # optional - sage.rings.finite_rings - sage: I = Ideal([y^3 + x*y*z + y^2*z + x*z^3, 3 + x*y + x^2*y + y^2*z]) # optional - sage.rings.finite_rings - sage: I.transformed_basis('gwalk') # optional - sage.rings.finite_rings + sage: R. = PolynomialRing(GF(32003), 3, order='lex') # needs sage.rings.finite_rings + sage: I = Ideal([y^3 + x*y*z + y^2*z + x*z^3, 3 + x*y + x^2*y + y^2*z]) # needs sage.rings.finite_rings + sage: I.transformed_basis('gwalk') # needs sage.rings.finite_rings [z*y^2 + y*x^2 + y*x + 3, z*x + 8297*y^8*x^2 + 8297*y^8*x + 3556*y^7 - 8297*y^6*x^4 + 15409*y^6*x^3 - 8297*y^6*x^2 - 8297*y^5*x^5 + 15409*y^5*x^4 - 8297*y^5*x^3 + 3556*y^5*x^2 @@ -2195,12 +2204,13 @@ def transformed_basis(self, algorithm="gwalk", other_ring=None, singular=singula Check that this method works over QQbar (:trac:`25351`). We are not currently able to specify other_ring, due to the limitations of @handle_AA_and_QQbar:: - sage: R. = QQbar[] # optional - sage.rings.number_field - sage: I = Ideal([y^3 + x^2, x^2*y + x^2, x^3 - x^2, z^4 - x^2 - y]) # optional - sage.rings.number_field - sage: I = Ideal(I.groebner_basis()) # optional - sage.rings.number_field - sage: S. = PolynomialRing(QQbar, 3, order='lex') # optional - sage.rings.number_field - sage: J = Ideal(I.transformed_basis('fglm', other_ring=S)) # known bug # optional - sage.rings.number_field - sage: J # known bug # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: R. = QQbar[] + sage: I = Ideal([y^3 + x^2, x^2*y + x^2, x^3 - x^2, z^4 - x^2 - y]) + sage: I = Ideal(I.groebner_basis()) + sage: S. = PolynomialRing(QQbar, 3, order='lex') + sage: J = Ideal(I.transformed_basis('fglm', other_ring=S)) # known bug + sage: J # known bug """ from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence R = self.ring() @@ -2287,12 +2297,13 @@ def elimination_ideal(self, variables, algorithm=None, *args, **kwds): Check that this method works over QQbar (:trac:`25351`):: - sage: R. = QQbar[] # optional - sage.rings.number_field - sage: I = R * [x - t, y - t^2, z - t^3, s - x + y^3] # optional - sage.rings.number_field - sage: J = I.elimination_ideal([t, s]); J # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: R. = QQbar[] + sage: I = R * [x - t, y - t^2, z - t^3, s - x + y^3] + sage: J = I.elimination_ideal([t, s]); J Ideal (y^2 - x*z, x*y - z, x^2 - y) of Multivariate Polynomial Ring in x, y, t, s, z over Algebraic Field - sage: print("possible output from giac", flush=True); I.elimination_ideal([t, s], algorithm="giac") == J # optional - sage.rings.number_field + sage: print("possible output from giac", flush=True); I.elimination_ideal([t, s], algorithm="giac") == J possible output... True @@ -2362,15 +2373,16 @@ def quotient(self, J): EXAMPLES:: - sage: R. = PolynomialRing(GF(181), 3) # optional - sage.rings.finite_rings - sage: I = Ideal([x^2 + x*y*z, y^2 - z^3*y, z^3 + y^5*x*z]) # optional - sage.rings.finite_rings - sage: J = Ideal([x]) # optional - sage.rings.finite_rings - sage: Q = I.quotient(J) # optional - sage.rings.finite_rings - sage: y*z + x in I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R. = PolynomialRing(GF(181), 3) + sage: I = Ideal([x^2 + x*y*z, y^2 - z^3*y, z^3 + y^5*x*z]) + sage: J = Ideal([x]) + sage: Q = I.quotient(J) + sage: y*z + x in I False - sage: x in J # optional - sage.rings.finite_rings + sage: x in J True - sage: x * (y*z + x) in I # optional - sage.rings.finite_rings + sage: x * (y*z + x) in I True TESTS: @@ -2385,10 +2397,11 @@ def quotient(self, J): Check that this method works over QQbar (:trac:`25351`):: - sage: R. = QQbar[] # optional - sage.rings.number_field - sage: I = ideal(x, z) # optional - sage.rings.number_field - sage: J = ideal(R(1)) # optional - sage.rings.number_field - sage: I.quotient(J) # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: R. = QQbar[] + sage: I = ideal(x, z) + sage: J = ideal(R(1)) + sage: I.quotient(J) Ideal (z, x) of Multivariate Polynomial Ring in x, y, z over Algebraic Field Check that :trac:`12803` is fixed:: @@ -2439,10 +2452,11 @@ def saturation(self, other): Check that this method works over QQbar (:trac:`25351`):: - sage: R. = QQbar[] # optional - sage.rings.number_field - sage: I = R.ideal(x^5*z^3, x*y*z, y*z^4) # optional - sage.rings.number_field - sage: J = R.ideal(z) # optional - sage.rings.number_field - sage: I.saturation(other = J) # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: R. = QQbar[] + sage: I = R.ideal(x^5*z^3, x*y*z, y*z^4) + sage: J = R.ideal(z) + sage: I.saturation(other = J) (Ideal (y, x^5) of Multivariate Polynomial Ring in x, y, z over Algebraic Field, 4) """ from sage.libs.singular.function_factory import ff @@ -2492,10 +2506,11 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition", proof=True EXAMPLES:: - sage: K. = GF(27) # this example is from the MAGMA handbook # optional - sage.rings.finite_rings - sage: P. = PolynomialRing(K, 2, order='lex') # optional - sage.rings.finite_rings - sage: I = Ideal([x^8 + y + 2, y^6 + x*y^5 + x^2]) # optional - sage.rings.finite_rings - sage: I = Ideal(I.groebner_basis()); I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = GF(27) # this example is from the MAGMA handbook + sage: P. = PolynomialRing(K, 2, order='lex') + sage: I = Ideal([x^8 + y + 2, y^6 + x*y^5 + x^2]) + sage: I = Ideal(I.groebner_basis()); I Ideal (x - y^47 - y^45 + y^44 - y^43 + y^41 - y^39 - y^38 - y^37 - y^36 + y^35 - y^34 - y^33 + y^32 - y^31 + y^30 + y^28 + y^27 + y^26 + y^25 - y^23 + y^22 + y^21 - y^19 - y^18 - y^16 + y^15 + y^13 @@ -2504,19 +2519,20 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition", proof=True - y^25 + y^24 + y^2 + y + 1) of Multivariate Polynomial Ring in x, y over Finite Field in w of size 3^3 - sage: V = I.variety(); # optional - sage.rings.finite_rings - sage: sorted(V, key=str) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: V = I.variety(); + sage: sorted(V, key=str) [{y: w^2 + 2*w, x: 2*w + 2}, {y: w^2 + 2, x: 2*w}, {y: w^2 + w, x: 2*w + 1}] - sage: [f.subs(v) # check that all polynomials vanish # optional - sage.rings.finite_rings + sage: [f.subs(v) # check that all polynomials vanish ....: for f in I.gens() for v in V] [0, 0, 0, 0, 0, 0] - sage: [I.subs(v).is_zero() for v in V] # same test, but nicer syntax # optional - sage.rings.finite_rings + sage: [I.subs(v).is_zero() for v in V] # same test, but nicer syntax [True, True, True] However, we only account for solutions in the ground field and not in the algebraic closure:: - sage: I.vector_space_dimension() # optional - sage.rings.finite_rings + sage: I.vector_space_dimension() # needs sage.rings.finite_rings 48 Here we compute the points of intersection of a hyperbola and a @@ -2538,7 +2554,7 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition", proof=True sage: sorted(I.variety(ring=RR), key=str) [{y: 0.361103080528647, x: 2.76929235423863}, {y: 1.00000000000000, x: 1.00000000000000}] - sage: I.variety(ring=AA) # optional - sage.rings.number_field + sage: I.variety(ring=AA) # needs sage.rings.number_field [{y: 1, x: 1}, {y: 0.3611030805286474?, x: 2.769292354238632?}] @@ -2551,7 +2567,7 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition", proof=True x: 0.11535382288068... + 0.58974280502220...*I}, {y: 0.36110308052864..., x: 2.7692923542386...}, {y: 1.00000000000000, x: 1.00000000000000}] - sage: sorted(I.variety(ring=QQbar), key=str) # optional - sage.rings.number_field + sage: sorted(I.variety(ring=QQbar), key=str) # needs sage.rings.number_field [{y: 0.3194484597356763? + 1.633170240915238?*I, x: 0.11535382288068429? - 0.5897428050222055?*I}, {y: 0.3194484597356763? - 1.633170240915238?*I, @@ -2586,9 +2602,9 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition", proof=True If the ground field's characteristic is too large for Singular, we resort to a toy implementation:: - sage: R. = PolynomialRing(GF(2147483659^3), order='lex') # optional - sage.rings.finite_rings - sage: I = ideal([x^3 - 2*y^2, 3*x + y^4]) # optional - sage.rings.finite_rings - sage: I.variety() # optional - sage.rings.finite_rings + sage: R. = PolynomialRing(GF(2147483659^3), order='lex') # needs sage.rings.finite_rings + sage: I = ideal([x^3 - 2*y^2, 3*x + y^4]) # needs sage.rings.finite_rings + sage: I.variety() # needs sage.rings.finite_rings verbose 0 (...: multi_polynomial_ideal.py, groebner_basis) Warning: falling back to very slow toy implementation. verbose 0 (...: multi_polynomial_ideal.py, dimension) Warning: falling back to very slow toy implementation. verbose 0 (...: multi_polynomial_ideal.py, variety) Warning: falling back to very slow toy implementation. @@ -2601,20 +2617,20 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition", proof=True sage: K. = QQ[] sage: I = ideal([x^2 + 2*y - 5, x + y + 3]) - sage: v = I.variety(AA)[0]; v[x], v[y] # optional - sage.rings.number_field + sage: v = I.variety(AA)[0]; v[x], v[y] # needs sage.rings.number_field (4.464101615137755?, -7.464101615137755?) - sage: list(v)[0].parent() # optional - sage.rings.number_field + sage: list(v)[0].parent() # needs sage.rings.number_field Multivariate Polynomial Ring in x, y over Algebraic Real Field - sage: v[x] # optional - sage.rings.number_field + sage: v[x] # needs sage.rings.number_field 4.464101615137755? - sage: v["y"] # optional - sage.rings.number_field + sage: v["y"] # needs sage.rings.number_field -7.464101615137755? ``msolve`` also works over finite fields:: - sage: R. = PolynomialRing(GF(536870909), 2, order='lex') # optional - sage.rings.finite_rings - sage: I = Ideal([x^2 - 1, y^2 - 1]) # optional - sage.rings.finite_rings - sage: sorted(I.variety(algorithm='msolve', # optional - msolve # optional - sage.rings.finite_rings + sage: R. = PolynomialRing(GF(536870909), 2, order='lex') # needs sage.rings.finite_rings + sage: I = Ideal([x^2 - 1, y^2 - 1]) # needs sage.rings.finite_rings + sage: sorted(I.variety(algorithm='msolve', # optional - msolve # needs sage.rings.finite_rings ....: proof=False), ....: key=str) [{x: 1, y: 1}, @@ -2625,9 +2641,9 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition", proof=True but may fail in small characteristic, especially with ideals of high degree with respect to the characteristic:: - sage: R. = PolynomialRing(GF(3), 2, order='lex') # optional - sage.rings.finite_rings - sage: I = Ideal([x^2 - 1, y^2 - 1]) # optional - sage.rings.finite_rings - sage: I.variety(algorithm='msolve', proof=False) # optional - msolve # optional - sage.rings.finite_rings + sage: R. = PolynomialRing(GF(3), 2, order='lex') # needs sage.rings.finite_rings + sage: I = Ideal([x^2 - 1, y^2 - 1]) # needs sage.rings.finite_rings + sage: I.variety(algorithm='msolve', proof=False) # optional - msolve, needs sage.rings.finite_rings Traceback (most recent call last): ... NotImplementedError: characteristic 3 too small @@ -2662,20 +2678,20 @@ def _variety_triangular_decomposition(self, ring): TESTS:: - sage: K. = GF(27) # optional - sage.rings.finite_rings - sage: P. = PolynomialRing(K, 2, order='lex') # optional - sage.rings.finite_rings - sage: I = Ideal([ x^8 + y + 2, y^6 + x*y^5 + x^2 ]) # optional - sage.rings.finite_rings + sage: K. = GF(27) # needs sage.rings.finite_rings + sage: P. = PolynomialRing(K, 2, order='lex') # needs sage.rings.finite_rings + sage: I = Ideal([ x^8 + y + 2, y^6 + x*y^5 + x^2 ]) # needs sage.rings.finite_rings Testing the robustness of the Singular interface:: - sage: T = I.triangular_decomposition('singular:triangLfak') # optional - sage.rings.finite_rings - sage: sorted(I.variety(), key=str) # optional - sage.rings.finite_rings + sage: T = I.triangular_decomposition('singular:triangLfak') # needs sage.rings.finite_rings + sage: sorted(I.variety(), key=str) # needs sage.rings.finite_rings [{y: w^2 + 2*w, x: 2*w + 2}, {y: w^2 + 2, x: 2*w}, {y: w^2 + w, x: 2*w + 1}] Testing that a bug is indeed fixed :: - sage: R = PolynomialRing(GF(2), 30, ['x%d'%(i+1) for i in range(30)], order='lex') # optional - sage.rings.finite_rings - sage: R.inject_variables() # optional - sage.rings.finite_rings + sage: R = PolynomialRing(GF(2), 30, ['x%d'%(i+1) for i in range(30)], order='lex') # needs sage.rings.finite_rings + sage: R.inject_variables() # needs sage.rings.finite_rings Defining... sage: I = Ideal([x1 + 1, x2, x3 + 1, x5*x10 + x10 + x18, x5*x11 + x11, \ x5*x18, x6, x7 + 1, x9, x10*x11 + x10 + x18, x10*x18 + x18, \ @@ -2687,9 +2703,9 @@ def _variety_triangular_decomposition(self, ring): x16^2 + x16, x17^2 + x17, x18^2 + x18, x19^2 + x19, x20^2 + x20, \ x21^2 + x21, x22^2 + x22, x23^2 + x23, x24^2 + x24, x25^2 + x25, \ x26^2 + x26, x27^2 + x27, x28^2 + x28, x29^2 + x29, x30^2 + x30]) # optional - sage.rings.finite_rings - sage: I.basis_is_groebner() + sage: I.basis_is_groebner() # needs sage.rings.finite_rings True - sage: sorted("".join(str(V[g]) for g in R.gens()) for V in I.variety()) # long time (6s on sage.math, 2011) # optional - sage.rings.finite_rings + sage: sorted("".join(str(V[g]) for g in R.gens()) for V in I.variety()) # long time (6s on sage.math, 2011), needs sage.rings.finite_rings ['101000100000000110001000100110', '101000100000000110001000101110', '101000100100000101001000100110', @@ -2742,7 +2758,7 @@ def _variety_triangular_decomposition(self, ring): sage: R. = PolynomialRing(QQ, order='lex') sage: I = R.ideal(c^2-2, b-c, a) - sage: I.variety(QQbar) # optional - sage.rings.number_field + sage: I.variety(QQbar) # needs sage.rings.number_field [...a: 0...] An early version of :trac:`25351` broke this method by adding the @@ -2750,13 +2766,14 @@ def _variety_triangular_decomposition(self, ring): that this circle and this hyperbola have two real intersections and two more complex ones:: - sage: K. = PolynomialRing(AA) # optional - sage.rings.number_field - sage: I = Ideal([ x*y - 1, (x-2)^2 + (y-1)^2 - 1]) # optional - sage.rings.number_field - sage: len(I.variety()) # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: K. = PolynomialRing(AA) + sage: I = Ideal([ x*y - 1, (x-2)^2 + (y-1)^2 - 1]) + sage: len(I.variety()) 2 - sage: K. = PolynomialRing(QQbar) # optional - sage.rings.number_field - sage: I = Ideal([ x*y - 1, (x-2)^2 + (y-1)^2 - 1]) # optional - sage.rings.number_field - sage: len(I.variety()) # optional - sage.rings.number_field + sage: K. = PolynomialRing(QQbar) + sage: I = Ideal([ x*y - 1, (x-2)^2 + (y-1)^2 - 1]) + sage: len(I.variety()) 4 """ @@ -2842,7 +2859,7 @@ def hilbert_polynomial(self, algorithm='sage'): sage: P. = PolynomialRing(QQ) sage: I = Ideal([x^3*y^2 + 3*x^2*y^2*z + y^3*z^2 + z^5]) - sage: I.hilbert_polynomial() # optional - sage.libs.flint + sage: I.hilbert_polynomial() # needs sage.libs.flint 5*t - 5 Of course, the Hilbert polynomial of a zero-dimensional ideal @@ -2853,14 +2870,14 @@ def hilbert_polynomial(self, algorithm='sage'): sage: J = P*[m.lm() for m in J0.groebner_basis()] sage: J.dimension() 0 - sage: J.hilbert_polynomial() # optional - sage.libs.flint + sage: J.hilbert_polynomial() # needs sage.libs.flint 0 It is possible to request a computation using the Singular library:: - sage: I.hilbert_polynomial(algorithm='singular') == I.hilbert_polynomial() # optional - sage.libs.flint sage.libs.singular + sage: I.hilbert_polynomial(algorithm='singular') == I.hilbert_polynomial() # needs sage.libs.flint True - sage: J.hilbert_polynomial(algorithm='singular') == J.hilbert_polynomial() # optional - sage.libs.flint sage.libs.singular + sage: J.hilbert_polynomial(algorithm='singular') == J.hilbert_polynomial() # needs sage.libs.flint True Here is a bigger examples:: @@ -2868,7 +2885,7 @@ def hilbert_polynomial(self, algorithm='sage'): sage: n = 4; m = 11; P = PolynomialRing(QQ, n * m, "x"); x = P.gens() sage: M = Matrix(n, x) sage: Minors = P.ideal(M.minors(2)) - sage: hp = Minors.hilbert_polynomial(); hp # optional - sage.libs.flint + sage: hp = Minors.hilbert_polynomial(); hp # needs sage.libs.flint 1/21772800*t^13 + 61/21772800*t^12 + 1661/21772800*t^11 + 26681/21772800*t^10 + 93841/7257600*t^9 + 685421/7257600*t^8 + 1524809/3110400*t^7 + 39780323/21772800*t^6 + 6638071/1360800*t^5 @@ -2879,7 +2896,7 @@ def hilbert_polynomial(self, algorithm='sage'): with Singular. We don't test it here, as it has a side-effect on other tests that is not understood yet (see :trac:`26300`):: - sage: Minors.hilbert_polynomial(algorithm='singular') # not tested # optional - sage.libs.singular + sage: Minors.hilbert_polynomial(algorithm='singular') # not tested Traceback (most recent call last): ... RuntimeError: error in Singular function call 'hilbPoly': @@ -2891,8 +2908,8 @@ def hilbert_polynomial(self, algorithm='sage'): coefficients of the Hilbert-Poincaré series in all degrees:: sage: P = PowerSeriesRing(QQ, 't', default_prec=50) - sage: hs = Minors.hilbert_series() # optional - sage.libs.flint - sage: list(P(hs.numerator()) / P(hs.denominator())) == [hp(t=k) # optional - sage.libs.flint + sage: hs = Minors.hilbert_series() # needs sage.libs.flint + sage: list(P(hs.numerator()) / P(hs.denominator())) == [hp(t=k) # needs sage.libs.flint ....: for k in range(50)] True @@ -2902,23 +2919,23 @@ def hilbert_polynomial(self, algorithm='sage'): sage: P. = PolynomialRing(QQ) sage: I = Ideal([x^3, x*y^2, y^4, x^2*y*z, y^3*z, x^2*z^2, x*y*z^2, x*z^3]) - sage: I.hilbert_polynomial(algorithm='singular') # optional - sage.libs.singular + sage: I.hilbert_polynomial(algorithm='singular') 3 - sage: I.hilbert_polynomial() # optional - sage.libs.flint + sage: I.hilbert_polynomial() # needs sage.libs.flint 3 Check that this method works over ``QQbar`` (:trac:`25351`):: - sage: P. = QQbar[] # optional - sage.rings.number_field - sage: I = Ideal([x^3*y^2 + 3*x^2*y^2*z + y^3*z^2 + z^5]) # optional - sage.rings.number_field - sage: I.hilbert_polynomial() # optional - sage.rings.number_field + sage: P. = QQbar[] # needs sage.rings.number_field + sage: I = Ideal([x^3*y^2 + 3*x^2*y^2*z + y^3*z^2 + z^5]) # needs sage.rings.number_field + sage: I.hilbert_polynomial() # needs sage.rings.number_field 5*t - 5 Check for :trac:`33597`:: sage: R. = QQ[] sage: I = R.ideal([X^2*Y^3, X*Z]) - sage: I.hilbert_polynomial() # optional - sage.libs.flint + sage: I.hilbert_polynomial() # needs sage.libs.flint t + 5 """ if not self.is_homogeneous(): @@ -2981,48 +2998,49 @@ def hilbert_series(self, grading=None, algorithm='sage'): sage: P. = PolynomialRing(QQ) sage: I = Ideal([x^3*y^2 + 3*x^2*y^2*z + y^3*z^2 + z^5]) - sage: I.hilbert_series() # optional - sage.libs.flint + sage: I.hilbert_series() # needs sage.libs.flint (t^4 + t^3 + t^2 + t + 1)/(t^2 - 2*t + 1) sage: R. = PolynomialRing(QQ) sage: J = R.ideal([a^2*b, a*b^2]) - sage: J.hilbert_series() # optional - sage.libs.flint + sage: J.hilbert_series() # needs sage.libs.flint (t^3 - t^2 - t - 1)/(t - 1) - sage: J.hilbert_series(grading=(10,3)) # optional - sage.libs.flint + sage: J.hilbert_series(grading=(10,3)) # needs sage.libs.flint (t^25 + t^24 + t^23 - t^15 - t^14 - t^13 - t^12 - t^11 - t^10 - t^9 - t^8 - t^7 - t^6 - t^5 - t^4 - t^3 - t^2 - t - 1)/(t^12 + t^11 + t^10 - t^2 - t - 1) sage: K = R.ideal([a^2*b^3, a*b^4 + a^3*b^2]) - sage: K.hilbert_series(grading=[1,2]) # optional - sage.libs.flint + sage: K.hilbert_series(grading=[1,2]) # needs sage.libs.flint (t^11 + t^8 - t^6 - t^5 - t^4 - t^3 - t^2 - t - 1)/(t^2 - 1) - sage: K.hilbert_series(grading=[2,1]) # optional - sage.libs.flint + sage: K.hilbert_series(grading=[2,1]) # needs sage.libs.flint (2*t^7 - t^6 - t^4 - t^2 - 1)/(t - 1) TESTS:: - sage: I.hilbert_series() == I.hilbert_series(algorithm='singular') # optional - sage.libs.flint sage.libs.singular + sage: # needs sage.libs.flint + sage: I.hilbert_series() == I.hilbert_series(algorithm='singular') True - sage: J.hilbert_series() == J.hilbert_series(algorithm='singular') # optional - sage.libs.flint sage.libs.singular + sage: J.hilbert_series() == J.hilbert_series(algorithm='singular') True - sage: J.hilbert_series(grading=(10,3)) == J.hilbert_series(grading=(10,3), algorithm='singular') # optional - sage.libs.flint sage.libs.singular + sage: J.hilbert_series(grading=(10,3)) == J.hilbert_series(grading=(10,3), algorithm='singular') True - sage: K.hilbert_series(grading=(1,2)) == K.hilbert_series(grading=(1,2), algorithm='singular') # optional - sage.libs.flint sage.libs.singular + sage: K.hilbert_series(grading=(1,2)) == K.hilbert_series(grading=(1,2), algorithm='singular') True - sage: K.hilbert_series(grading=(2,1)) == K.hilbert_series(grading=(2,1), algorithm='singular') # optional - sage.libs.flint sage.libs.singular + sage: K.hilbert_series(grading=(2,1)) == K.hilbert_series(grading=(2,1), algorithm='singular') True sage: P. = PolynomialRing(QQ) sage: I = Ideal([x^3*y^2 + 3*x^2*y^2*z + y^3*z^2 + z^5]) - sage: I.hilbert_series(grading=5) # optional - sage.libs.flint + sage: I.hilbert_series(grading=5) # needs sage.libs.flint Traceback (most recent call last): ... TypeError: grading must be a list or a tuple of integers Check that this method works over QQbar (:trac:`25351`):: - sage: P. = QQbar[] # optional - sage.rings.number_field - sage: I = Ideal([x^3*y^2 + 3*x^2*y^2*z + y^3*z^2 + z^5]) # optional - sage.rings.number_field - sage: I.hilbert_series() # optional - sage.rings.number_field + sage: P. = QQbar[] # needs sage.rings.number_field + sage: I = Ideal([x^3*y^2 + 3*x^2*y^2*z + y^3*z^2 + z^5]) # needs sage.rings.number_field + sage: I.hilbert_series() # needs sage.rings.number_field (t^4 + t^3 + t^2 + t + 1)/(t^2 - 2*t + 1) """ if not self.is_homogeneous(): @@ -3080,42 +3098,43 @@ def hilbert_numerator(self, grading=None, algorithm='sage'): sage: P. = PolynomialRing(QQ) sage: I = Ideal([x^3*y^2 + 3*x^2*y^2*z + y^3*z^2 + z^5]) - sage: I.hilbert_numerator() # optional - sage.libs.flint + sage: I.hilbert_numerator() # needs sage.libs.flint -t^5 + 1 sage: R. = PolynomialRing(QQ) sage: J = R.ideal([a^2*b, a*b^2]) - sage: J.hilbert_numerator() # optional - sage.libs.flint + sage: J.hilbert_numerator() # needs sage.libs.flint t^4 - 2*t^3 + 1 - sage: J.hilbert_numerator(grading=(10,3)) # optional - sage.libs.flint + sage: J.hilbert_numerator(grading=(10,3)) # needs sage.libs.flint t^26 - t^23 - t^16 + 1 TESTS:: - sage: I.hilbert_numerator() == I.hilbert_numerator(algorithm='singular') # optional - sage.libs.flint sage.libs.singular + sage: I.hilbert_numerator() == I.hilbert_numerator(algorithm='singular') # needs sage.libs.flint True - sage: J.hilbert_numerator() == J.hilbert_numerator(algorithm='singular') # optional - sage.libs.flint sage.libs.singular + sage: J.hilbert_numerator() == J.hilbert_numerator(algorithm='singular') # needs sage.libs.flint True - sage: J.hilbert_numerator(grading=(10,3)) == J.hilbert_numerator(grading=(10,3), algorithm='singular') # optional - sage.libs.flint sage.libs.singular + sage: J.hilbert_numerator(grading=(10,3)) == J.hilbert_numerator(grading=(10,3), algorithm='singular') # needs sage.libs.flint True Check that this method works over QQbar (:trac:`25351`):: - sage: P. = QQbar[] # optional - sage.rings.number_field - sage: I = Ideal([x^3*y^2 + 3*x^2*y^2*z + y^3*z^2 + z^5]) # optional - sage.rings.number_field - sage: I.hilbert_numerator() # optional - sage.rings.number_field + sage: P. = QQbar[] # needs sage.rings.number_field + sage: I = Ideal([x^3*y^2 + 3*x^2*y^2*z + y^3*z^2 + z^5]) # needs sage.rings.number_field + sage: I.hilbert_numerator() # needs sage.rings.number_field -t^5 + 1 Our two algorithms should always agree; not tested until :trac:`33178` is fixed:: - sage: m = ZZ.random_element(2,8) # not tested - sage: nvars = m^2 # not tested - sage: R = PolynomialRing(QQ, "x", nvars) # not tested - sage: M = matrix(R, m, R.gens()) # not tested - sage: I = R.ideal(M.minors(2)) # not tested - sage: n1 = I.hilbert_numerator() # not tested - sage: n2 = I.hilbert_numerator(algorithm='singular') # not tested - sage: n1 == n2 # not tested + sage: # not tested + sage: m = ZZ.random_element(2,8) + sage: nvars = m^2 + sage: R = PolynomialRing(QQ, "x", nvars) + sage: M = matrix(R, m, R.gens()) + sage: I = R.ideal(M.minors(2)) + sage: n1 = I.hilbert_numerator() + sage: n2 = I.hilbert_numerator(algorithm='singular') + sage: n1 == n2 True """ @@ -3272,19 +3291,20 @@ def normal_basis(self, degree=None, algorithm='libsingular', Check that this method works over QQbar (:trac:`25351`):: - sage: R. = QQbar[] # optional - sage.rings.number_field - sage: I = R.ideal(x^2+y^2+z^2-4, x^2+2*y^2-5, x*z-1) # optional - sage.rings.number_field - sage: I.normal_basis() # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: R. = QQbar[] + sage: I = R.ideal(x^2+y^2+z^2-4, x^2+2*y^2-5, x*z-1) + sage: I.normal_basis() [y*z^2, z^2, y*z, z, x*y, y, x, 1] - sage: J = R.ideal(x^2+y^2+z^2-4, x^2+2*y^2-5) # optional - sage.rings.number_field - sage: [J.normal_basis(d) for d in (0..3)] # optional - sage.rings.number_field + sage: J = R.ideal(x^2+y^2+z^2-4, x^2+2*y^2-5) + sage: [J.normal_basis(d) for d in (0..3)] [[1], [z, y, x], [z^2, y*z, x*z, x*y], [z^3, y*z^2, x*z^2, x*y*z]] Check the option ``algorithm="singular"`` with a weighted term order:: sage: T = TermOrder('wdegrevlex', (1, 2, 3)) - sage: S. = PolynomialRing(GF(2), order=T) # optional - sage.rings.finite_rings - sage: S.ideal(x^6 + y^3 + z^2).normal_basis(6, algorithm='singular') # optional - sage.rings.finite_rings + sage: S. = PolynomialRing(GF(2), order=T) # needs sage.rings.finite_rings + sage: S.ideal(x^6 + y^3 + z^2).normal_basis(6, algorithm='singular') # needs sage.rings.finite_rings [x^4*y, x^2*y^2, y^3, x^3*z, x*y*z, z^2] """ from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence @@ -3364,14 +3384,15 @@ def _groebner_basis_macaulay2(self, strategy=None): Over finite fields, Macaulay2 supports different algorithms to compute Gröbner bases:: - sage: R = PolynomialRing(GF(101), 'x', 4) # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Cyclic(R) # optional - sage.rings.finite_rings - sage: gb1 = I.groebner_basis('macaulay2:gb') # optional - macaulay2 # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Cyclic(R) # optional - sage.rings.finite_rings - sage: gb2 = I.groebner_basis('macaulay2:mgb') # optional - macaulay2 # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Cyclic(R) # optional - sage.rings.finite_rings - sage: gb3 = I.groebner_basis('macaulay2:f4') # optional - macaulay2 # optional - sage.rings.finite_rings - sage: gb1 == gb2 == gb3 # optional - macaulay2 # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R = PolynomialRing(GF(101), 'x', 4) + sage: I = sage.rings.ideal.Cyclic(R) + sage: gb1 = I.groebner_basis('macaulay2:gb') # optional - macaulay2 + sage: I = sage.rings.ideal.Cyclic(R) + sage: gb2 = I.groebner_basis('macaulay2:mgb') # optional - macaulay2 + sage: I = sage.rings.ideal.Cyclic(R) + sage: gb3 = I.groebner_basis('macaulay2:f4') # optional - macaulay2 + sage: gb1 == gb2 == gb3 # optional - macaulay2 True TESTS:: @@ -3435,26 +3456,27 @@ def __init__(self, ring, gens, coerce=True, side="left"): EXAMPLES:: - sage: A. = FreeAlgebra(QQ, 3) # optional - sage.combinat sage.modules - sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) # optional - sage.combinat sage.modules - sage: H.inject_variables() # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: A. = FreeAlgebra(QQ, 3) + sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) + sage: H.inject_variables() Defining x, y, z - sage: I = H.ideal([y^2, x^2, z^2 - H.one()], # indirect doctest # optional - sage.combinat sage.modules + sage: I = H.ideal([y^2, x^2, z^2 - H.one()], # indirect doctest ....: coerce=False) - sage: I # random # optional - sage.combinat sage.modules + sage: I # random Left Ideal (y^2, x^2, z^2 - 1) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: sorted(I.gens(), key=str) # optional - sage.combinat sage.modules + sage: sorted(I.gens(), key=str) [x^2, y^2, z^2 - 1] - sage: H.ideal([y^2, x^2, z^2 - H.one()], side="twosided") # random # optional - sage.combinat sage.modules + sage: H.ideal([y^2, x^2, z^2 - H.one()], side="twosided") # random Twosided Ideal (y^2, x^2, z^2 - 1) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: sorted(H.ideal([y^2, x^2, z^2 - H.one()], side="twosided").gens(), # optional - sage.combinat sage.modules + sage: sorted(H.ideal([y^2, x^2, z^2 - H.one()], side="twosided").gens(), ....: key=str) [x^2, y^2, z^2 - 1] - sage: H.ideal([y^2, x^2, z^2 - H.one()], side="right") # optional - sage.combinat sage.modules + sage: H.ideal([y^2, x^2, z^2 - H.one()], side="right") Traceback (most recent call last): ... ValueError: Only left and two-sided ideals are allowed. @@ -3479,15 +3501,16 @@ def __call_singular(self, cmd, arg=None): EXAMPLES:: - sage: A. = FreeAlgebra(QQ, 3) # optional - sage.combinat sage.modules - sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) # optional - sage.combinat sage.modules - sage: H.inject_variables() # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: A. = FreeAlgebra(QQ, 3) + sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) + sage: H.inject_variables() Defining x, y, z - sage: id = H.ideal(x + y, y + z) # optional - sage.combinat sage.modules - sage: id.std() # indirect doctest # random # optional - sage.combinat sage.modules + sage: id = H.ideal(x + y, y + z) + sage: id.std() # indirect doctest # random Left Ideal (z, y, x) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: sorted(id.std().gens(), key=str) # optional - sage.combinat sage.modules + sage: sorted(id.std().gens(), key=str) [x, y, z] """ from sage.libs.singular.function import singular_function @@ -3504,16 +3527,17 @@ def std(self): EXAMPLES:: - sage: A. = FreeAlgebra(QQ, 3) # optional - sage.combinat sage.modules - sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) # optional - sage.combinat sage.modules - sage: H.inject_variables() # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: A. = FreeAlgebra(QQ, 3) + sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) + sage: H.inject_variables() Defining x, y, z - sage: I = H.ideal([y^2, x^2, z^2 - H.one()], coerce=False) # optional - sage.combinat sage.modules - sage: I.std() #random # optional - sage.combinat sage.modules + sage: I = H.ideal([y^2, x^2, z^2 - H.one()], coerce=False) + sage: I.std() #random Left Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: sorted(I.std().gens(), key=str) # optional - sage.combinat sage.modules + sage: sorted(I.std().gens(), key=str) [2*x*y - z - 1, x*z + x, x^2, y*z - y, y^2, z^2 - 1] @@ -3521,37 +3545,38 @@ def std(self): Groebner basis. But if it is a two-sided ideal, then the output of :meth:`std` and :meth:`twostd` coincide:: - sage: JL = H.ideal([x^3, y^3, z^3 - 4*z]) # optional - sage.combinat sage.modules - sage: JL #random # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: JL = H.ideal([x^3, y^3, z^3 - 4*z]) + sage: JL #random Left Ideal (x^3, y^3, z^3 - 4*z) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: sorted(JL.gens(), key=str) # optional - sage.combinat sage.modules + sage: sorted(JL.gens(), key=str) [x^3, y^3, z^3 - 4*z] - sage: JL.std() # random # optional - sage.combinat sage.modules + sage: JL.std() # random Left Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, 2*x*y*z - z^2 - 2*z, y^3, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: sorted(JL.std().gens(), key=str) # optional - sage.combinat sage.modules + sage: sorted(JL.std().gens(), key=str) [2*x*y*z - z^2 - 2*z, x*z^2 + 2*x*z, x^3, y*z^2 - 2*y*z, y^3, z^3 - 4*z] - sage: JT = H.ideal([x^3, y^3, z^3 - 4*z], side='twosided') # optional - sage.combinat sage.modules - sage: JT #random # optional - sage.combinat sage.modules + sage: JT = H.ideal([x^3, y^3, z^3 - 4*z], side='twosided') + sage: JT #random Twosided Ideal (x^3, y^3, z^3 - 4*z) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: sorted(JT.gens(), key=str) # optional - sage.combinat sage.modules + sage: sorted(JT.gens(), key=str) [x^3, y^3, z^3 - 4*z] - sage: JT.std() #random # optional - sage.combinat sage.modules + sage: JT.std() #random Twosided Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, y^2*z - 2*y^2, 2*x*y*z - z^2 - 2*z, x^2*z + 2*x^2, y^3, x*y^2 - y*z, x^2*y - x*z - 2*x, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: sorted(JT.std().gens(), key=str) # optional - sage.combinat sage.modules + sage: sorted(JT.std().gens(), key=str) [2*x*y*z - z^2 - 2*z, x*y^2 - y*z, x*z^2 + 2*x*z, x^2*y - x*z - 2*x, x^2*z + 2*x^2, x^3, y*z^2 - 2*y*z, y^2*z - 2*y^2, y^3, z^3 - 4*z] - sage: JT.std() == JL.twostd() # optional - sage.combinat sage.modules + sage: JT.std() == JL.twostd() True ALGORITHM: Uses Singular's ``std`` command @@ -3568,20 +3593,21 @@ def elimination_ideal(self, variables): EXAMPLES:: - sage: A. = FreeAlgebra(QQ, 3) # optional - sage.combinat sage.modules - sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) # optional - sage.combinat sage.modules - sage: H.inject_variables() # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: A. = FreeAlgebra(QQ, 3) + sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) + sage: H.inject_variables() Defining x, y, z - sage: I = H.ideal([y^2, x^2, z^2 - H.one()], coerce=False) # optional - sage.combinat sage.modules - sage: I.elimination_ideal([x, z]) # optional - sage.combinat sage.modules + sage: I = H.ideal([y^2, x^2, z^2 - H.one()], coerce=False) + sage: I.elimination_ideal([x, z]) Left Ideal (y^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {...} - sage: J = I.twostd(); J # optional - sage.combinat sage.modules + sage: J = I.twostd(); J Twosided Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {...} - sage: J.elimination_ideal([x, z]) # optional - sage.combinat sage.modules + sage: J.elimination_ideal([x, z]) Twosided Ideal (y^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {...} @@ -3604,15 +3630,16 @@ def twostd(self): EXAMPLES:: - sage: A. = FreeAlgebra(QQ, 3) # optional - sage.combinat sage.modules - sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) # optional - sage.combinat sage.modules - sage: H.inject_variables() # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: A. = FreeAlgebra(QQ, 3) + sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) + sage: H.inject_variables() Defining x, y, z - sage: I = H.ideal([y^2, x^2, z^2 - H.one()], coerce=False) # optional - sage.combinat sage.modules - sage: I.twostd() #random # optional - sage.combinat sage.modules + sage: I = H.ideal([y^2, x^2, z^2 - H.one()], coerce=False) + sage: I.twostd() #random Twosided Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field... - sage: sorted(I.twostd().gens(), key=str) # optional - sage.combinat sage.modules + sage: sorted(I.twostd().gens(), key=str) [2*x*y - z - 1, x*z + x, x^2, y*z - y, y^2, z^2 - 1] ALGORITHM: Uses Singular's ``twostd`` command @@ -3631,10 +3658,11 @@ def _groebner_strategy(self): EXAMPLES:: - sage: A. = FreeAlgebra(QQ, 3) # optional - sage.combinat sage.modules - sage: H. = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) # optional - sage.combinat sage.modules - sage: I = H.ideal([y^2, x^2, z^2-H.one()], coerce=False) # optional - sage.combinat sage.modules - sage: I._groebner_strategy() # random # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: A. = FreeAlgebra(QQ, 3) + sage: H. = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) + sage: I = H.ideal([y^2, x^2, z^2-H.one()], coerce=False) + sage: I._groebner_strategy() # random Groebner Strategy for ideal generated by 6 elements over Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} @@ -3655,27 +3683,28 @@ def reduce(self,p): EXAMPLES:: - sage: A. = FreeAlgebra(QQ, 3) # optional - sage.combinat sage.modules - sage: H. = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) # optional - sage.combinat sage.modules - sage: I = H.ideal([y^2, x^2, z^2 - H.one()], # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: A. = FreeAlgebra(QQ, 3) + sage: H. = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) + sage: I = H.ideal([y^2, x^2, z^2 - H.one()], ....: coerce=False, side='twosided') - sage: Q = H.quotient(I); Q #random # optional - sage.combinat sage.modules + sage: Q = H.quotient(I); Q #random Quotient of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} by the ideal (y^2, x^2, z^2 - 1) - sage: Q.2^2 == Q.one() # indirect doctest # optional - sage.combinat sage.modules + sage: Q.2^2 == Q.one() # indirect doctest True Here, we see that the relation that we just found in the quotient is actually a consequence of the given relations:: - sage: H.2^2 - H.one() in I.std().gens() # optional - sage.combinat sage.modules + sage: H.2^2 - H.one() in I.std().gens() # needs sage.combinat sage.modules True Here is the corresponding direct test:: - sage: I.reduce(z^2) # optional - sage.combinat sage.modules + sage: I.reduce(z^2) # needs sage.combinat sage.modules 1 """ @@ -3687,15 +3716,16 @@ def _contains_(self,p): We define a left and a two-sided ideal:: - sage: A. = FreeAlgebra(QQ, 3) # optional - sage.combinat sage.modules - sage: H. = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) # optional - sage.combinat sage.modules - sage: JL = H.ideal([x^3, y^3, z^3 - 4*z]) # optional - sage.combinat sage.modules - sage: JL.std() #random # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: A. = FreeAlgebra(QQ, 3) + sage: H. = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) + sage: JL = H.ideal([x^3, y^3, z^3 - 4*z]) + sage: JL.std() #random Left Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, 2*x*y*z - z^2 - 2*z, y^3, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: JT = H.ideal([x^3, y^3, z^3 - 4*z], side='twosided') # optional - sage.combinat sage.modules - sage: JT.std() #random # optional - sage.combinat sage.modules + sage: JT = H.ideal([x^3, y^3, z^3 - 4*z], side='twosided') + sage: JT.std() #random Twosided Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, y^2*z - 2*y^2, 2*x*y*z - z^2 - 2*z, x^2*z + 2*x^2, y^3, x*y^2 - y*z, x^2*y - x*z - 2*x, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, @@ -3704,9 +3734,9 @@ def _contains_(self,p): Apparently, ``x*y^2-y*z`` should be in the two-sided, but not in the left ideal:: - sage: x*y^2-y*z in JL #indirect doctest # optional - sage.combinat sage.modules + sage: x*y^2-y*z in JL #indirect doctest # needs sage.combinat sage.modules False - sage: x*y^2-y*z in JT # optional - sage.combinat sage.modules + sage: x*y^2-y*z in JT # needs sage.combinat sage.modules True """ @@ -3726,12 +3756,13 @@ def syzygy_module(self): EXAMPLES:: - sage: A. = FreeAlgebra(QQ, 3) # optional - sage.combinat sage.modules - sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) # optional - sage.combinat sage.modules - sage: H.inject_variables() # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: A. = FreeAlgebra(QQ, 3) + sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) + sage: H.inject_variables() Defining x, y, z - sage: I = H.ideal([y^2, x^2, z^2-H.one()], coerce=False) # optional - sage.combinat sage.modules - sage: G = vector(I.gens()); G # optional - sage.combinat sage.modules + sage: I = H.ideal([y^2, x^2, z^2-H.one()], coerce=False) + sage: G = vector(I.gens()); G d...: UserWarning: You are constructing a free module over a noncommutative ring. Sage does not have a concept of left/right and both sided modules, so be careful. @@ -3743,7 +3774,7 @@ def syzygy_module(self): It's also not guaranteed that all multiplications are done from the right side. (y^2, x^2, z^2 - 1) - sage: M = I.syzygy_module(); M # optional - sage.combinat sage.modules + sage: M = I.syzygy_module(); M [ -z^2 - 8*z - 15 0 y^2] [ 0 -z^2 + 8*z - 15 x^2] [ x^2*z^2 + 8*x^2*z + 15*x^2 -y^2*z^2 + 8*y^2*z - 15*y^2 -4*x*y*z + 2*z^2 + 2*z] @@ -3754,7 +3785,7 @@ def syzygy_module(self): [ x^4*z + 4*x^4 -x^2*y^2*z + 4*x^2*y^2 - 4*x*y*z^2 + 32*x*y*z - 6*z^3 - 64*x*y + 66*z^2 - 240*z + 288 0] [x^3*y^2*z + 4*x^3*y^2 + 18*x^2*y*z - 36*x*z^3 + 66*x^2*y - 432*x*z^2 - 1656*x*z - 2052*x -x*y^4*z + 4*x*y^4 - 8*y^3*z^2 + 62*y^3*z - 114*y^3 48*y*z^2 - 36*y*z] - sage: M*G # optional - sage.combinat sage.modules + sage: M*G # needs sage.combinat sage.modules (0, 0, 0, 0, 0, 0, 0, 0, 0) ALGORITHM: Uses Singular's ``syz`` command @@ -3781,12 +3812,13 @@ def res(self, length): EXAMPLES:: - sage: A. = FreeAlgebra(QQ, 3) # optional - sage.combinat sage.modules - sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) # optional - sage.combinat sage.modules - sage: H.inject_variables() # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: A. = FreeAlgebra(QQ, 3) + sage: H = A.g_algebra({y*x: x*y-z, z*x: x*z+2*x, z*y: y*z-2*y}) + sage: H.inject_variables() Defining x, y, z - sage: I = H.ideal([y^2, x^2, z^2-H.one()], coerce=False) # optional - sage.combinat sage.modules - sage: I.res(3) # optional - sage.combinat sage.modules + sage: I = H.ideal([y^2, x^2, z^2-H.one()], coerce=False) + sage: I.res(3) """ if self.side() == 'twosided': @@ -3816,8 +3848,8 @@ def __init__(self, ring, gens, coerce=True): sage: R. = PolynomialRing(IntegerRing(), 2, order='lex') sage: R.ideal([x, y]) Ideal (x, y) of Multivariate Polynomial Ring in x, y over Integer Ring - sage: R. = GF(3)[] # optional - sage.rings.finite_rings - sage: R.ideal([x0^2, x1^3]) # optional - sage.rings.finite_rings + sage: R. = GF(3)[] # needs sage.rings.finite_rings + sage: R.ideal([x0^2, x1^3]) # needs sage.rings.finite_rings Ideal (x0^2, x1^3) of Multivariate Polynomial Ring in x0, x1 over Finite Field of size 3 """ Ideal_generic.__init__(self, ring, gens, coerce=coerce) @@ -3903,22 +3935,24 @@ def __richcmp__(self, other, op): :: - sage: R. = GF(32003)[] # optional - sage.rings.finite_rings - sage: I = R*[x^2 + x, y] # optional - sage.rings.finite_rings - sage: J = R*[x + 1, y] # optional - sage.rings.finite_rings - sage: J < I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R. = GF(32003)[] + sage: I = R*[x^2 + x, y] + sage: J = R*[x + 1, y] + sage: J < I False - sage: I < J # optional - sage.rings.finite_rings + sage: I < J True :: - sage: R. = GF(32003)[] # optional - sage.rings.finite_rings - sage: I = R*[x^2 + x, y] # optional - sage.rings.finite_rings - sage: J = R*[x + 1, y] # optional - sage.rings.finite_rings - sage: J > I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R. = GF(32003)[] + sage: I = R*[x^2 + x, y] + sage: J = R*[x + 1, y] + sage: J > I True - sage: I > J # optional - sage.rings.finite_rings + sage: I > J False :: @@ -3956,15 +3990,16 @@ def __richcmp__(self, other, op): We test to make sure that pickling works with the cached Groebner basis:: - sage: R. = GF(32003)[] # optional - sage.rings.finite_rings - sage: I = R*[x^2 + x, y] # optional - sage.rings.finite_rings - sage: J = R*[x + 1, y] # optional - sage.rings.finite_rings - sage: J >= I # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R. = GF(32003)[] + sage: I = R*[x^2 + x, y] + sage: J = R*[x + 1, y] + sage: J >= I True - sage: I >= J # optional - sage.rings.finite_rings + sage: I >= J False - sage: loads(dumps(I)).__getstate__() # optional - sage.rings.finite_rings + sage: loads(dumps(I)).__getstate__() # needs sage.rings.finite_rings (Monoid of ideals of Multivariate Polynomial Ring in x, y over Finite Field of size 32003, {'_Ideal_generic__gens': (x^2 + x, y), '_Ideal_generic__ring': Multivariate Polynomial Ring in x, y over Finite Field of size 32003, @@ -4330,31 +4365,31 @@ def groebner_basis(self, algorithm='', deg_bound=None, mult_bound=None, prot=Fal Here we use Macaulay2 with three different strategies over a finite field. :: - sage: R. = PolynomialRing(GF(101), 3) # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Katsura(R,3) # regenerate to prevent caching # optional - sage.rings.finite_rings - sage: I.groebner_basis('macaulay2:gb') # optional - macaulay2 # optional - sage.rings.finite_rings + sage: R. = PolynomialRing(GF(101), 3) # needs sage.rings.finite_rings + sage: I = sage.rings.ideal.Katsura(R,3) # regenerate to prevent caching # needs sage.rings.finite_rings + sage: I.groebner_basis('macaulay2:gb') # optional - macaulay2 # needs sage.rings.finite_rings [c^3 + 28*c^2 - 37*b + 13*c, b^2 - 41*c^2 + 20*b - 20*c, b*c - 19*c^2 + 10*b + 40*c, a + 2*b + 2*c - 1] - sage: I = sage.rings.ideal.Katsura(R,3) # regenerate to prevent caching # optional - sage.rings.finite_rings - sage: I.groebner_basis('macaulay2:f4') # optional - macaulay2 # optional - sage.rings.finite_rings + sage: I = sage.rings.ideal.Katsura(R,3) # regenerate to prevent caching # needs sage.rings.finite_rings + sage: I.groebner_basis('macaulay2:f4') # optional - macaulay2 # needs sage.rings.finite_rings [c^3 + 28*c^2 - 37*b + 13*c, b^2 - 41*c^2 + 20*b - 20*c, b*c - 19*c^2 + 10*b + 40*c, a + 2*b + 2*c - 1] - sage: I = sage.rings.ideal.Katsura(R,3) # regenerate to prevent caching # optional - sage.rings.finite_rings - sage: I.groebner_basis('macaulay2:mgb') # optional - macaulay2 # optional - sage.rings.finite_rings + sage: I = sage.rings.ideal.Katsura(R,3) # regenerate to prevent caching # needs sage.rings.finite_rings + sage: I.groebner_basis('macaulay2:mgb') # optional - macaulay2 # needs sage.rings.finite_rings [c^3 + 28*c^2 - 37*b + 13*c, b^2 - 41*c^2 + 20*b - 20*c, b*c - 19*c^2 + 10*b + 40*c, a + 2*b + 2*c - 1] Over prime fields of small characteristic, we can also use the `optional package msolve <../spkg/msolve.html>`_:: - sage: R. = PolynomialRing(GF(101), 3) # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Katsura(R,3) # regenerate to prevent caching # optional - sage.rings.finite_rings - sage: I.groebner_basis('msolve') # optional - msolve # optional - sage.rings.finite_rings + sage: R. = PolynomialRing(GF(101), 3) # needs sage.rings.finite_rings + sage: I = sage.rings.ideal.Katsura(R,3) # regenerate to prevent caching # needs sage.rings.finite_rings + sage: I.groebner_basis('msolve') # optional - msolve # needs sage.rings.finite_rings [a + 2*b + 2*c - 1, b*c - 19*c^2 + 10*b + 40*c, b^2 - 41*c^2 + 20*b - 20*c, c^3 + 28*c^2 - 37*b + 13*c] :: - sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # optional - sage.rings.finite_rings - sage: I.groebner_basis('magma:GroebnerBasis') # optional - magma # optional - sage.rings.finite_rings + sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # needs sage.rings.finite_rings + sage: I.groebner_basis('magma:GroebnerBasis') # optional - magma, needs sage.rings.finite_rings [a - 60*c^3 + 158/7*c^2 + 8/7*c - 1, b + 30*c^3 - 79/7*c^2 + 3/7*c, c^4 - 10/21*c^3 + 1/84*c^2 + 1/84*c] Singular and libSingular can compute Groebner basis with degree @@ -4527,54 +4562,55 @@ def groebner_basis(self, algorithm='', deg_bound=None, mult_bound=None, prot=Fal Check that this method works over QQbar (:trac:`25351`):: - sage: P. = PolynomialRing(QQbar, 3, order='lex') # optional - sage.rings.number_field - sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # optional - sage.rings.number_field - sage: I.groebner_basis() # optional - sage.rings.number_field + sage: P. = PolynomialRing(QQbar, 3, order='lex') # needs sage.rings.number_field + sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # needs sage.rings.number_field + sage: I.groebner_basis() # needs sage.rings.number_field [a + (-60)*c^3 + 158/7*c^2 + 8/7*c - 1, b + 30*c^3 + (-79/7)*c^2 + 3/7*c, c^4 + (-10/21)*c^3 + 1/84*c^2 + 1/84*c] - sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # optional - sage.rings.number_field - sage: I.groebner_basis('libsingular:groebner') # optional - sage.rings.number_field + sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # needs sage.rings.number_field + sage: I.groebner_basis('libsingular:groebner') # needs sage.rings.number_field [a + (-60)*c^3 + 158/7*c^2 + 8/7*c - 1, b + 30*c^3 + (-79/7)*c^2 + 3/7*c, c^4 + (-10/21)*c^3 + 1/84*c^2 + 1/84*c] - sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # optional - sage.rings.number_field - sage: I.groebner_basis('libsingular:std') # optional - sage.rings.number_field + sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # needs sage.rings.number_field + sage: I.groebner_basis('libsingular:std') # needs sage.rings.number_field [a + (-60)*c^3 + 158/7*c^2 + 8/7*c - 1, b + 30*c^3 + (-79/7)*c^2 + 3/7*c, c^4 + (-10/21)*c^3 + 1/84*c^2 + 1/84*c] - sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # optional - sage.rings.number_field - sage: I.groebner_basis('libsingular:stdhilb') # optional - sage.rings.number_field + sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # needs sage.rings.number_field + sage: I.groebner_basis('libsingular:stdhilb') # needs sage.rings.number_field [a + (-60)*c^3 + 158/7*c^2 + 8/7*c - 1, b + 30*c^3 + (-79/7)*c^2 + 3/7*c, c^4 + (-10/21)*c^3 + 1/84*c^2 + 1/84*c] - sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # optional - sage.rings.number_field - sage: I.groebner_basis('libsingular:stdfglm') # optional - sage.rings.number_field + sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # needs sage.rings.number_field + sage: I.groebner_basis('libsingular:stdfglm') # needs sage.rings.number_field [a + (-60)*c^3 + 158/7*c^2 + 8/7*c - 1, b + 30*c^3 + (-79/7)*c^2 + 3/7*c, c^4 + (-10/21)*c^3 + 1/84*c^2 + 1/84*c] - sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # optional - sage.rings.number_field - sage: I.groebner_basis('libsingular:slimgb') # optional - sage.rings.number_field + sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # needs sage.rings.number_field + sage: I.groebner_basis('libsingular:slimgb') # needs sage.rings.number_field [a + (-60)*c^3 + 158/7*c^2 + 8/7*c - 1, b + 30*c^3 + (-79/7)*c^2 + 3/7*c, c^4 + (-10/21)*c^3 + 1/84*c^2 + 1/84*c] - sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # optional - sage.rings.number_field - sage: J = I.change_ring(P.change_ring(order='degrevlex')) # optional - sage.rings.number_field - sage: gb = J.groebner_basis('giac') # random # optional - sage.rings.number_field - sage: gb # optional - sage.rings.number_field + sage: # needs sage.rings.number_field + sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching + sage: J = I.change_ring(P.change_ring(order='degrevlex')) + sage: gb = J.groebner_basis('giac') # random + sage: gb [c^3 + (-79/210)*c^2 + 1/30*b + 1/70*c, b^2 + (-3/5)*c^2 + (-1/5)*b + 1/5*c, b*c + 6/5*c^2 + (-1/10)*b + (-2/5)*c, a + 2*b + 2*c - 1] - sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # optional - sage.rings.number_field - sage: I.groebner_basis('toy:buchberger2') # optional - sage.rings.number_field + sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # needs sage.rings.number_field + sage: I.groebner_basis('toy:buchberger2') # needs sage.rings.number_field [a + (-60)*c^3 + 158/7*c^2 + 8/7*c - 1, b + 30*c^3 + (-79/7)*c^2 + 3/7*c, c^4 + (-10/21)*c^3 + 1/84*c^2 + 1/84*c] - sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # optional - sage.rings.number_field - sage: I.groebner_basis('macaulay2:gb') # optional - macaulay2 # optional - sage.rings.number_field + sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # needs sage.rings.number_field + sage: I.groebner_basis('macaulay2:gb') # optional - macaulay2 # needs sage.rings.number_field [a + (-60)*c^3 + 158/7*c^2 + 8/7*c - 1, b + 30*c^3 + (-79/7)*c^2 + 3/7*c, c^4 + (-10/21)*c^3 + 1/84*c^2 + 1/84*c] - sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # optional - sage.rings.number_field - sage: I.groebner_basis('magma:GroebnerBasis') # optional - magma # optional - sage.rings.number_field + sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching # needs sage.rings.number_field + sage: I.groebner_basis('magma:GroebnerBasis') # optional - magma, needs sage.rings.number_field [a + (-60)*c^3 + 158/7*c^2 + 8/7*c - 1, b + 30*c^3 + (-79/7)*c^2 + 3/7*c, c^4 + (-10/21)*c^3 + 1/84*c^2 + 1/84*c] msolve currently supports the degrevlex order only:: - sage: R. = PolynomialRing(GF(101), 3, order='lex') # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Katsura(R,3) # regenerate to prevent caching # optional - sage.rings.finite_rings - sage: I.groebner_basis('msolve') # optional - msolve # optional - sage.rings.finite_rings + sage: R. = PolynomialRing(GF(101), 3, order='lex') # needs sage.rings.finite_rings + sage: I = sage.rings.ideal.Katsura(R,3) # regenerate to prevent caching # needs sage.rings.finite_rings + sage: I.groebner_basis('msolve') # optional - msolve # needs sage.rings.finite_rings Traceback (most recent call last): ... NotImplementedError: msolve only supports the degrevlex order (use transformed_basis()) @@ -4927,28 +4963,28 @@ def homogenize(self, var='h'): EXAMPLES:: - sage: P. = PolynomialRing(GF(2)) # optional - sage.rings.finite_rings - sage: I = Ideal([x^2*y + z + 1, x + y^2 + 1]); I # optional - sage.rings.finite_rings + sage: P. = PolynomialRing(GF(2)) # needs sage.rings.finite_rings + sage: I = Ideal([x^2*y + z + 1, x + y^2 + 1]); I # needs sage.rings.finite_rings Ideal (x^2*y + z + 1, y^2 + x + 1) of Multivariate Polynomial Ring in x, y, z over Finite Field of size 2 :: - sage: I.homogenize() # optional - sage.rings.finite_rings + sage: I.homogenize() # needs sage.rings.finite_rings Ideal (x^2*y + z*h^2 + h^3, y^2 + x*h + h^2) of Multivariate Polynomial Ring in x, y, z, h over Finite Field of size 2 :: - sage: I.homogenize(y) # optional - sage.rings.finite_rings + sage: I.homogenize(y) # needs sage.rings.finite_rings Ideal (x^2*y + y^3 + y^2*z, x*y) of Multivariate Polynomial Ring in x, y, z over Finite Field of size 2 :: - sage: I = Ideal([x^2*y + z^3 + y^2*x, x + y^2 + 1]) # optional - sage.rings.finite_rings - sage: I.homogenize() # optional - sage.rings.finite_rings + sage: I = Ideal([x^2*y + z^3 + y^2*x, x + y^2 + 1]) # needs sage.rings.finite_rings + sage: I.homogenize() # needs sage.rings.finite_rings Ideal (x^2*y + x*y^2 + z^3, y^2 + x*h + h^2) of Multivariate Polynomial Ring in x, y, z, h over Finite Field of size 2 @@ -5024,51 +5060,51 @@ def degree_of_semi_regularity(self): We consider a homogeneous example:: sage: n = 8 - sage: K = GF(127) # optional - sage.rings.finite_rings - sage: P = PolynomialRing(K, n, 'x') # optional - sage.rings.finite_rings - sage: s = [K.random_element() for _ in range(n)] # optional - sage.rings.finite_rings - sage: L = [] # optional - sage.rings.finite_rings - sage: for i in range(2 * n): # optional - sage.rings.finite_rings + sage: K = GF(127) # needs sage.rings.finite_rings + sage: P = PolynomialRing(K, n, 'x') # needs sage.rings.finite_rings + sage: s = [K.random_element() for _ in range(n)] # needs sage.rings.finite_rings + sage: L = [] # needs sage.rings.finite_rings + sage: for i in range(2 * n): # needs sage.rings.finite_rings ....: f = P.random_element(degree=2, terms=binomial(n, 2)) ....: f -= f(*s) ....: L.append(f.homogenize()) - sage: I = Ideal(L) # optional - sage.rings.finite_rings - sage: I.degree_of_semi_regularity() # optional - sage.rings.finite_rings + sage: I = Ideal(L) # needs sage.rings.finite_rings + sage: I.degree_of_semi_regularity() # needs sage.rings.finite_rings 4 From this, we expect a Groebner basis computation to reach at most degree 4. For homogeneous systems this is equivalent to the largest degree in the Groebner basis:: - sage: max(f.degree() for f in I.groebner_basis()) # optional - sage.rings.finite_rings + sage: max(f.degree() for f in I.groebner_basis()) # needs sage.rings.finite_rings 4 We increase the number of polynomials and observe a decrease the degree of regularity:: - sage: for i in range(2 * n): # optional - sage.rings.finite_rings + sage: for i in range(2 * n): # needs sage.rings.finite_rings ....: f = P.random_element(degree=2, terms=binomial(n, 2)) ....: f -= f(*s) ....: L.append(f.homogenize()) - sage: I = Ideal(L) # optional - sage.rings.finite_rings - sage: I.degree_of_semi_regularity() # optional - sage.rings.finite_rings + sage: I = Ideal(L) # needs sage.rings.finite_rings + sage: I.degree_of_semi_regularity() # needs sage.rings.finite_rings 3 - sage: max(f.degree() for f in I.groebner_basis()) # optional - sage.rings.finite_rings + sage: max(f.degree() for f in I.groebner_basis()) # needs sage.rings.finite_rings 3 The degree of regularity approaches 2 for quadratic systems as the number of polynomials approaches `n^2`:: - sage: for i in range((n-4) * n): # optional - sage.rings.finite_rings + sage: for i in range((n-4) * n): # needs sage.rings.finite_rings ....: f = P.random_element(degree=2, terms=binomial(n, 2)) ....: f -= f(*s) ....: L.append(f.homogenize()) - sage: I = Ideal(L) # optional - sage.rings.finite_rings - sage: I.degree_of_semi_regularity() # optional - sage.rings.finite_rings + sage: I = Ideal(L) # needs sage.rings.finite_rings + sage: I.degree_of_semi_regularity() # needs sage.rings.finite_rings 2 - sage: max(f.degree() for f in I.groebner_basis()) # optional - sage.rings.finite_rings + sage: max(f.degree() for f in I.groebner_basis()) # needs sage.rings.finite_rings 2 .. NOTE:: @@ -5117,45 +5153,45 @@ def plot(self, *args, **kwds): sage: R. = PolynomialRing(QQ, 2) sage: I = R.ideal([y^3 - x^2]) - sage: I.plot() # cusp # optional - sage.plot + sage: I.plot() # cusp # needs sage.plot Graphics object consisting of 1 graphics primitive :: sage: I = R.ideal([y^2 - x^2 - 1]) - sage: I.plot((x,-3, 3), (y, -2, 2)) # hyperbola # optional - sage.plot + sage: I.plot((x,-3, 3), (y, -2, 2)) # hyperbola # needs sage.plot Graphics object consisting of 1 graphics primitive :: sage: I = R.ideal([y^2 + x^2*(1/4) - 1]) - sage: I.plot() # ellipse # optional - sage.plot + sage: I.plot() # ellipse # needs sage.plot Graphics object consisting of 1 graphics primitive :: sage: I = R.ideal([y^2-(x^2-1)*(x-2)]) - sage: I.plot() # elliptic curve # optional - sage.plot + sage: I.plot() # elliptic curve # needs sage.plot Graphics object consisting of 1 graphics primitive :: sage: f = ((x+3)^3 + 2*(x+3)^2 - y^2)*(x^3 - y^2)*((x-3)^3-2*(x-3)^2-y^2) sage: I = R.ideal(f) - sage: I.plot() # the Singular logo # optional - sage.plot + sage: I.plot() # the Singular logo # needs sage.plot Graphics object consisting of 1 graphics primitive :: sage: R. = PolynomialRing(QQ, 2) sage: I = R.ideal([x - 1]) - sage: I.plot((y, -2, 2)) # vertical line # optional - sage.plot + sage: I.plot((y, -2, 2)) # vertical line # needs sage.plot Graphics object consisting of 1 graphics primitive :: sage: I = R.ideal([-x^2*y + 1]) - sage: I.plot() # blow up # optional - sage.plot + sage: I.plot() # blow up # needs sage.plot Graphics object consisting of 1 graphics primitive """ @@ -5234,12 +5270,13 @@ def random_element(self, degree, compute_gb=False, *args, **kwds): We compute a uniformly random element up to the provided degree. :: - sage: P. = GF(127)[] # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Katsura(P) # optional - sage.rings.finite_rings - sage: f = I.random_element(degree=4, compute_gb=True, terms=infinity) # optional - sage.rings.finite_rings - sage: f.degree() <= 4 # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: P. = GF(127)[] + sage: I = sage.rings.ideal.Katsura(P) + sage: f = I.random_element(degree=4, compute_gb=True, terms=infinity) + sage: f.degree() <= 4 True - sage: len(list(f)) <= 35 # optional - sage.rings.finite_rings + sage: len(list(f)) <= 35 True Note that sampling uniformly at random from the ideal at some large enough degree is @@ -5247,45 +5284,47 @@ def random_element(self, degree, compute_gb=False, *args, **kwds): basis if we can sample uniformly at random from an ideal:: sage: n = 3; d = 4 - sage: P = PolynomialRing(GF(127), n, 'x') # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Cyclic(P) # optional - sage.rings.finite_rings + sage: P = PolynomialRing(GF(127), n, 'x') # needs sage.rings.finite_rings + sage: I = sage.rings.ideal.Cyclic(P) # needs sage.rings.finite_rings 1. We sample `n^d` uniformly random elements in the ideal:: - sage: F = Sequence(I.random_element(degree=d, compute_gb=True, # optional - sage.rings.finite_rings + sage: F = Sequence(I.random_element(degree=d, compute_gb=True, # needs sage.rings.finite_rings ....: terms=infinity) ....: for _ in range(n^d)) 2. We linearize and compute the echelon form:: - sage: A, v = F.coefficient_matrix() # optional - sage.rings.finite_rings - sage: A.echelonize() # optional - sage.rings.finite_rings + sage: A, v = F.coefficient_matrix() # needs sage.rings.finite_rings + sage: A.echelonize() # needs sage.rings.finite_rings 3. The result is the desired Gröbner basis:: - sage: G = Sequence((A * v).list()) # optional - sage.rings.finite_rings - sage: G.is_groebner() # optional - sage.rings.finite_rings + sage: G = Sequence((A * v).list()) # needs sage.rings.finite_rings + sage: G.is_groebner() # needs sage.rings.finite_rings True - sage: Ideal(G) == I # optional - sage.rings.finite_rings + sage: Ideal(G) == I # needs sage.rings.finite_rings True We return some element in the ideal with no guarantee on the distribution:: - sage: P = PolynomialRing(GF(127), 10, 'x') # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Katsura(P) # optional - sage.rings.finite_rings - sage: f = I.random_element(degree=3) # optional - sage.rings.finite_rings - sage: f # random # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: P = PolynomialRing(GF(127), 10, 'x') + sage: I = sage.rings.ideal.Katsura(P) + sage: f = I.random_element(degree=3) + sage: f # random -25*x0^2*x1 + 14*x1^3 + 57*x0*x1*x2 + ... + 19*x7*x9 + 40*x8*x9 + 49*x1 - sage: f.degree() # optional - sage.rings.finite_rings + sage: f.degree() 3 We show that the default method does not sample uniformly at random from the ideal:: - sage: P. = GF(127)[] # optional - sage.rings.finite_rings - sage: G = Sequence([x + 7, y - 2, z + 110]) # optional - sage.rings.finite_rings - sage: I = Ideal([sum(P.random_element() * g for g in G) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: P. = GF(127)[] + sage: G = Sequence([x + 7, y - 2, z + 110]) + sage: I = Ideal([sum(P.random_element() * g for g in G) ....: for _ in range(4)]) - sage: all(I.random_element(degree=1) == 0 for _ in range(100)) # optional - sage.rings.finite_rings + sage: all(I.random_element(degree=1) == 0 for _ in range(100)) True If ``degree`` equals the degree of the generators, a random linear @@ -5350,33 +5389,35 @@ def weil_restriction(self): EXAMPLES:: - sage: k. = GF(2^2) # optional - sage.rings.finite_rings - sage: P. = PolynomialRing(k, 2) # optional - sage.rings.finite_rings - sage: I = Ideal([x*y + 1, a*x + 1]) # optional - sage.rings.finite_rings - sage: I.variety() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: k. = GF(2^2) + sage: P. = PolynomialRing(k, 2) + sage: I = Ideal([x*y + 1, a*x + 1]) + sage: I.variety() [{y: a, x: a + 1}] - sage: J = I.weil_restriction() # optional - sage.rings.finite_rings - sage: J # optional - sage.rings.finite_rings + sage: J = I.weil_restriction() + sage: J Ideal (x0*y0 + x1*y1 + 1, x1*y0 + x0*y1 + x1*y1, x1 + 1, x0 + x1) of Multivariate Polynomial Ring in x0, x1, y0, y1 over Finite Field of size 2 - sage: J += sage.rings.ideal.FieldIdeal(J.ring()) # ensure radical ideal # optional - sage.rings.finite_rings - sage: J.variety() # optional - sage.rings.finite_rings + sage: J += sage.rings.ideal.FieldIdeal(J.ring()) # ensure radical ideal + sage: J.variety() [{y1: 1, y0: 0, x1: 1, x0: 1}] - sage: J.weil_restriction() # returns J # optional - sage.rings.finite_rings + sage: J.weil_restriction() # returns J # needs sage.rings.finite_rings Ideal (x0*y0 + x1*y1 + 1, x1*y0 + x0*y1 + x1*y1, x1 + 1, x0 + x1, x0^2 + x0, x1^2 + x1, y0^2 + y0, y1^2 + y1) of Multivariate Polynomial Ring in x0, x1, y0, y1 over Finite Field of size 2 - sage: k. = GF(3^5) # optional - sage.rings.finite_rings - sage: P. = PolynomialRing(k) # optional - sage.rings.finite_rings - sage: I = sage.rings.ideal.Katsura(P) # optional - sage.rings.finite_rings - sage: I.dimension() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: k. = GF(3^5) + sage: P. = PolynomialRing(k) + sage: I = sage.rings.ideal.Katsura(P) + sage: I.dimension() 0 - sage: I.variety() # optional - sage.rings.finite_rings + sage: I.variety() [{z: 0, y: 0, x: 1}] - sage: J = I.weil_restriction(); J # optional - sage.rings.finite_rings + sage: J = I.weil_restriction(); J # needs sage.rings.finite_rings Ideal (x0 - y0 - z0 - 1, x1 - y1 - z1, x2 - y2 - z2, x3 - y3 - z3, x4 - y4 - z4, x0^2 + x2*x3 + x1*x4 - y0^2 - y2*y3 - y1*y4 - z0^2 - z2*z3 - z1*z4 - x0, @@ -5401,9 +5442,9 @@ def weil_restriction(self): - y4*z0 - y3*z1 - y2*z2 - y1*z3 - y0*z4 - y4*z4 - y4) of Multivariate Polynomial Ring in x0, x1, x2, x3, x4, y0, y1, y2, y3, y4, z0, z1, z2, z3, z4 over Finite Field of size 3 - sage: J += sage.rings.ideal.FieldIdeal(J.ring()) # ensure radical ideal # optional - sage.rings.finite_rings + sage: J += sage.rings.ideal.FieldIdeal(J.ring()) # ensure radical ideal # needs sage.rings.finite_rings sage: from sage.doctest.fixtures import reproducible_repr - sage: print(reproducible_repr(J.variety())) # optional - sage.rings.finite_rings + sage: print(reproducible_repr(J.variety())) # needs sage.rings.finite_rings [{x0: 1, x1: 0, x2: 0, x3: 0, x4: 0, y0: 0, y1: 0, y2: 0, y3: 0, y4: 0, z0: 0, z1: 0, z2: 0, z3: 0, z4: 0}] @@ -5412,15 +5453,15 @@ def weil_restriction(self): Weil restrictions are often used to study elliptic curves over extension fields so we give a simple example involving those:: - sage: K. = QuadraticField(1/3) # optional - sage.rings.number_field - sage: E = EllipticCurve(K, [1,2,3,4,5]) # optional - sage.rings.number_field + sage: K. = QuadraticField(1/3) # needs sage.rings.number_field + sage: E = EllipticCurve(K, [1,2,3,4,5]) # needs sage.rings.number_field We pick a point on ``E``:: - sage: p = E.lift_x(1); p # optional - sage.rings.number_field + sage: p = E.lift_x(1); p # needs sage.rings.number_field (1 : -6 : 1) - sage: I = E.defining_ideal(); I # optional - sage.rings.number_field + sage: I = E.defining_ideal(); I # needs sage.rings.number_field Ideal (-x^3 - 2*x^2*z + x*y*z + y^2*z - 4*x*z^2 + 3*y*z^2 - 5*z^3) of Multivariate Polynomial Ring in x, y, z over Number Field in a with defining polynomial x^2 - 1/3 @@ -5428,20 +5469,20 @@ def weil_restriction(self): Of course, the point ``p`` is a root of all generators of ``I``:: - sage: I.subs(x=1, y=2, z=1) # optional - sage.rings.number_field + sage: I.subs(x=1, y=2, z=1) # needs sage.rings.number_field Ideal (0) of Multivariate Polynomial Ring in x, y, z over Number Field in a with defining polynomial x^2 - 1/3 with a = 0.5773502691896258? ``I`` is also radical:: - sage: I.radical() == I # optional - sage.rings.number_field + sage: I.radical() == I # needs sage.rings.number_field True So we compute its Weil restriction:: - sage: J = I.weil_restriction() # optional - sage.rings.number_field - sage: J # optional - sage.rings.number_field + sage: J = I.weil_restriction() # needs sage.rings.number_field + sage: J # needs sage.rings.number_field Ideal (-x0^3 - x0*x1^2 - 2*x0^2*z0 - 2/3*x1^2*z0 + x0*y0*z0 + y0^2*z0 + 1/3*x1*y1*z0 + 1/3*y1^2*z0 - 4*x0*z0^2 + 3*y0*z0^2 - 5*z0^3 - 4/3*x0*x1*z1 + 1/3*x1*y0*z1 + 1/3*x0*y1*z1 + 2/3*y0*y1*z1 @@ -5454,19 +5495,19 @@ def weil_restriction(self): We can check that the point ``p`` is still a root of all generators of ``J``:: - sage: J.subs(x0=1, y0=2, z0=1, x1=0, y1=0, z1=0) # optional - sage.rings.number_field + sage: J.subs(x0=1, y0=2, z0=1, x1=0, y1=0, z1=0) # needs sage.rings.number_field Ideal (0, 0) of Multivariate Polynomial Ring in x0, x1, y0, y1, z0, z1 over Rational Field Example for relative number fields:: sage: R. = QQ[] - sage: K. = NumberField(x^5 - 2) # optional - sage.rings.number_field - sage: R. = K[] # optional - sage.rings.number_field - sage: L. = K.extension(x^2 + 1) # optional - sage.rings.number_field - sage: S. = L[] # optional - sage.rings.number_field - sage: I = S.ideal([y^2 - x^3 - 1]) # optional - sage.rings.number_field - sage: I.weil_restriction() # optional - sage.rings.number_field + sage: K. = NumberField(x^5 - 2) # needs sage.rings.number_field + sage: R. = K[] # needs sage.rings.number_field + sage: L. = K.extension(x^2 + 1) # needs sage.rings.number_field + sage: S. = L[] # needs sage.rings.number_field + sage: I = S.ideal([y^2 - x^3 - 1]) # needs sage.rings.number_field + sage: I.weil_restriction() # needs sage.rings.number_field Ideal (-x0^3 + 3*x0*x1^2 + y0^2 - y1^2 - 1, -3*x0^2*x1 + x1^3 + 2*y0*y1) of Multivariate Polynomial Ring in x0, x1, y0, y1 over Number Field in w with defining polynomial x^5 - 2 diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 5e6203a05c3..03c4574bd4f 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -47,17 +47,18 @@ We create a polynomial ring over a quaternion algebra:: - sage: A. = QuaternionAlgebra(QQ, -1,-1) # optional - sage.combinat sage.modules - sage: R. = PolynomialRing(A, sparse=True) # optional - sage.combinat sage.modules - sage: f = w^3 + (i+j)*w + 1 # optional - sage.combinat sage.modules - sage: f # optional - sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: A. = QuaternionAlgebra(QQ, -1,-1) + sage: R. = PolynomialRing(A, sparse=True) + sage: f = w^3 + (i+j)*w + 1 + sage: f w^3 + (i + j)*w + 1 - sage: f^2 # optional - sage.combinat sage.modules + sage: f^2 w^6 + (2*i + 2*j)*w^4 + 2*w^3 - 2*w^2 + (2*i + 2*j)*w + 1 - sage: f = w + i ; g = w + j # optional - sage.combinat sage.modules - sage: f * g # optional - sage.combinat sage.modules + sage: f = w + i ; g = w + j + sage: f * g w^2 + (i + j)*w + k - sage: g * f # optional - sage.combinat sage.modules + sage: g * f w^2 + (i + j)*w - k :trac:`9944` introduced some changes related with @@ -88,28 +89,28 @@ different base rings. In that situation, coercion works by means of the :func:`~sage.categories.pushout.pushout` formalism:: - sage: R. = PolynomialRing(GF(5), sparse=True) # optional - sage.rings.finite_rings + sage: R. = PolynomialRing(GF(5), sparse=True) # needs sage.rings.finite_rings sage: S. = PolynomialRing(ZZ) - sage: R.has_coerce_map_from(S) # optional - sage.rings.finite_rings + sage: R.has_coerce_map_from(S) # needs sage.rings.finite_rings False - sage: S.has_coerce_map_from(R) # optional - sage.rings.finite_rings + sage: S.has_coerce_map_from(R) # needs sage.rings.finite_rings False - sage: S.0 + R.0 # optional - sage.rings.finite_rings + sage: S.0 + R.0 # needs sage.rings.finite_rings 2*x - sage: (S.0 + R.0).parent() # optional - sage.rings.finite_rings + sage: (S.0 + R.0).parent() # needs sage.rings.finite_rings Univariate Polynomial Ring in x over Finite Field of size 5 - sage: (S.0 + R.0).parent().is_sparse() # optional - sage.rings.finite_rings + sage: (S.0 + R.0).parent().is_sparse() # needs sage.rings.finite_rings False Similarly, there is a coercion from the (non-default) NTL implementation for univariate polynomials over the integers to the default FLINT implementation, but not vice versa:: - sage: R. = PolynomialRing(ZZ, implementation='NTL') # optional - sage.libs.ntl - sage: S. = PolynomialRing(ZZ, implementation='FLINT') # optional - sage.libs.flint - sage: (S.0 + R.0).parent() is S # optional - sage.libs.flint sage.libs.ntl + sage: R. = PolynomialRing(ZZ, implementation='NTL') # needs sage.libs.ntl + sage: S. = PolynomialRing(ZZ, implementation='FLINT') # needs sage.libs.flint + sage: (S.0 + R.0).parent() is S # needs sage.libs.flint sage.libs.ntl True - sage: (R.0 + S.0).parent() is S # optional - sage.libs.flint sage.libs.ntl + sage: (R.0 + S.0).parent() is S # needs sage.libs.flint sage.libs.ntl True TESTS:: @@ -122,9 +123,9 @@ Check that :trac:`5562` has been fixed:: sage: R. = PolynomialRing(RDF, 1) - sage: v1 = vector([u]) # optional - sage.modules - sage: v2 = vector([CDF(2)]) # optional - sage.modules - sage: v1 * v2 # optional - sage.modules + sage: v1 = vector([u]) # needs sage.modules + sage: v2 = vector([CDF(2)]) # needs sage.modules + sage: v1 * v2 # needs sage.modules 2.0*u """ @@ -210,11 +211,11 @@ def is_PolynomialRing(x): :: - sage: R. = PolynomialRing(ZZ, implementation="singular"); R # optional - sage.libs.singular + sage: R. = PolynomialRing(ZZ, implementation="singular"); R # needs sage.libs.singular Multivariate Polynomial Ring in w over Integer Ring - sage: is_PolynomialRing(R) # optional - sage.libs.singular + sage: is_PolynomialRing(R) # needs sage.libs.singular False - sage: type(R) # optional - sage.libs.singular + sage: type(R) # needs sage.libs.singular """ return isinstance(x, PolynomialRing_general) @@ -243,7 +244,7 @@ def __init__(self, base_ring, name=None, sparse=False, implementation=None, and Category of commutative algebras over (euclidean domains and infinite enumerated sets and metric spaces) and Category of infinite sets - sage: category(GF(7)['x']) # optional - sage.rings.finite_rings + sage: category(GF(7)['x']) # needs sage.rings.finite_rings Join of Category of euclidean domains and Category of commutative algebras over (finite enumerated fields and subquotients of monoids and quotients of semigroups) and Category of infinite sets @@ -268,13 +269,13 @@ def __init__(self, base_ring, name=None, sparse=False, implementation=None, sage: Zmod(1)['x'].is_finite() True - sage: GF(7)['x'].is_finite() # optional - sage.rings.finite_rings + sage: GF(7)['x'].is_finite() # needs sage.rings.finite_rings False sage: Zmod(1)['x']['y'].is_finite() True - sage: GF(7)['x']['y'].is_finite() # optional - sage.rings.finite_rings + sage: GF(7)['x']['y'].is_finite() # needs sage.rings.finite_rings False """ @@ -357,9 +358,9 @@ def _element_constructor_(self, x=None, check=True, is_gen=False, Coercing in pari elements:: - sage: QQ['x'](pari('[1,2,3/5]')) # optional - sage.libs.pari + sage: QQ['x'](pari('[1,2,3/5]')) # needs sage.libs.pari 3/5*x^2 + 2*x + 1 - sage: QQ['x'](pari('(-1/3)*x^10 + (2/3)*x - 1/5')) # optional - sage.libs.pari + sage: QQ['x'](pari('(-1/3)*x^10 + (2/3)*x - 1/5')) # needs sage.libs.pari -1/3*x^10 + 2/3*x - 1/5 Coercing strings:: @@ -377,10 +378,10 @@ def _element_constructor_(self, x=None, check=True, is_gen=False, This shows that the issue at :trac:`4106` is fixed:: - sage: x = var('x') # optional - sage.symbolic + sage: x = var('x') # needs sage.symbolic sage: R = IntegerModRing(4) - sage: S = R['x'] # optional - sage.symbolic - sage: S(x) # optional - sage.symbolic + sage: S = R['x'] # needs sage.symbolic + sage: S(x) # needs sage.symbolic x Throw a TypeError if any of the coefficients cannot be coerced @@ -393,16 +394,17 @@ def _element_constructor_(self, x=None, check=True, is_gen=False, Check that the bug in :trac:`11239` is fixed:: - sage: K. = GF(5^2, prefix='z') # optional - sage.rings.finite_rings - sage: L. = GF(5^4, prefix='z') # optional - sage.rings.finite_rings - sage: f = K['x'].gen() + a # optional - sage.rings.finite_rings - sage: L['x'](f) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: K. = GF(5^2, prefix='z') + sage: L. = GF(5^4, prefix='z') + sage: f = K['x'].gen() + a + sage: L['x'](f) x + b^3 + b^2 + b + 3 A test from :trac:`14485` :: - sage: x = SR.var('x') # optional - sage.symbolic - sage: QQbar[x](x^6 + x^5 + x^4 - x^3 + x^2 - x + 2/5) # optional - sage.rings.number_field sage.symbolic + sage: x = SR.var('x') # needs sage.symbolic + sage: QQbar[x](x^6 + x^5 + x^4 - x^3 + x^2 - x + 2/5) # needs sage.rings.number_field sage.symbolic x^6 + x^5 + x^4 - x^3 + x^2 - x + 2/5 Check support for unicode characters (:trac:`29280`):: @@ -694,7 +696,7 @@ def _coerce_map_from_base_ring(self): Polynomial base injection morphism: From: Rational Field To: Univariate Polynomial Ring in x over Rational Field - sage: R.coerce_map_from(GF(7)) # optional - sage.rings.finite_rings + sage: R.coerce_map_from(GF(7)) # needs sage.rings.finite_rings """ from .polynomial_element import PolynomialBaseringInjection @@ -762,20 +764,20 @@ def _coerce_map_from_(self, P): Over the integers, there is a coercion from the NTL and generic implementation to the default FLINT implementation:: - sage: R = PolynomialRing(ZZ, 't', implementation="NTL") # optional - sage.libs.ntl - sage: S = PolynomialRing(ZZ, 't', implementation="FLINT") # optional - sage.libs.flint + sage: R = PolynomialRing(ZZ, 't', implementation="NTL") # needs sage.libs.ntl + sage: S = PolynomialRing(ZZ, 't', implementation="FLINT") # needs sage.libs.flint sage: T = PolynomialRing(ZZ, 't', implementation="generic") - sage: R.has_coerce_map_from(S) # optional - sage.libs.flint sage.libs.ntl + sage: R.has_coerce_map_from(S) # needs sage.libs.flint sage.libs.ntl False - sage: R.has_coerce_map_from(T) # optional - sage.libs.ntl + sage: R.has_coerce_map_from(T) # needs sage.libs.ntl False - sage: S.has_coerce_map_from(T) # optional - sage.libs.flint + sage: S.has_coerce_map_from(T) # needs sage.libs.flint True - sage: S.has_coerce_map_from(R) # optional - sage.libs.flint sage.libs.ntl + sage: S.has_coerce_map_from(R) # needs sage.libs.flint sage.libs.ntl True - sage: T.has_coerce_map_from(R) # optional - sage.libs.ntl + sage: T.has_coerce_map_from(R) # needs sage.libs.ntl False - sage: T.has_coerce_map_from(S) # optional - sage.libs.flint + sage: T.has_coerce_map_from(S) # needs sage.libs.flint False """ base_ring = self.base_ring() @@ -844,9 +846,9 @@ def _magma_init_(self, magma): Univariate Polynomial Ring in y over Rational Field sage: S.1 # optional - magma y - sage: magma(PolynomialRing(GF(7), 'x')) # optional - magma # optional - sage.rings.finite_rings + sage: magma(PolynomialRing(GF(7), 'x')) # optional - magma # needs sage.rings.finite_rings Univariate Polynomial Ring in x over GF(7) - sage: magma(PolynomialRing(GF(49,'a'), 'x')) # optional - magma # optional - sage.rings.finite_rings + sage: magma(PolynomialRing(GF(49,'a'), 'x')) # optional - magma, needs sage.rings.finite_rings Univariate Polynomial Ring in x over GF(7^2) sage: magma(PolynomialRing(PolynomialRing(ZZ,'w'), 'x')) # optional - magma Univariate Polynomial Ring in x over Univariate Polynomial Ring in w over Integer Ring @@ -858,19 +860,20 @@ def _magma_init_(self, magma): :: - sage: m = Magma() # new magma session; optional - magma - sage: m(QQ['w']) # optional - magma + sage: # optional - magma + sage: m = Magma() + sage: m(QQ['w']) Univariate Polynomial Ring in w over Rational Field - sage: m(QQ['x']) # optional - magma + sage: m(QQ['x']) Univariate Polynomial Ring in x over Rational Field - sage: m(QQ['w']) # same magma object, now prints as x; optional - magma + sage: m(QQ['w']) Univariate Polynomial Ring in x over Rational Field A nested example over a Givaro finite field:: - sage: k. = GF(9) # optional - sage.rings.finite_rings - sage: R. = k[] # optional - sage.rings.finite_rings - sage: magma(a^2*x^3 + (a+1)*x + a) # optional - magma # optional - sage.rings.finite_rings + sage: k. = GF(9) # needs sage.rings.finite_rings + sage: R. = k[] # needs sage.rings.finite_rings + sage: magma(a^2*x^3 + (a+1)*x + a) # optional - magma # needs sage.rings.finite_rings a^2*x^3 + a^2*x + a """ B = magma(self.base_ring()) @@ -889,23 +892,23 @@ def _gap_init_(self, gap=None): EXAMPLES:: sage: R. = ZZ[] - sage: gap(R) # optional - sage.libs.gap + sage: gap(R) # needs sage.libs.gap PolynomialRing( Integers, ["z"] ) - sage: gap(R) is gap(R) # optional - sage.libs.gap + sage: gap(R) is gap(R) # needs sage.libs.gap True - sage: gap(z^2 + z) # optional - sage.libs.gap + sage: gap(z^2 + z) # needs sage.libs.gap z^2+z A univariate polynomial ring over a multivariate polynomial ring over a number field:: sage: Q. = QQ[] - sage: K. = NumberField(t^2 + t + 1) # optional - sage.rings.number_field - sage: P. = K[] # optional - sage.rings.number_field - sage: S. = P[] # optional - sage.rings.number_field - sage: gap(S) # optional - sage.libs.gap sage.rings.number_field + sage: K. = NumberField(t^2 + t + 1) # needs sage.rings.number_field + sage: P. = K[] # needs sage.rings.number_field + sage: S. = P[] # needs sage.rings.number_field + sage: gap(S) # needs sage.libs.gap sage.rings.number_field PolynomialRing( PolynomialRing( , ["x", "y"] ), ["z"] ) - sage: gap(S) is gap(S) # optional - sage.libs.gap sage.rings.number_field + sage: gap(S) is gap(S) # needs sage.libs.gap sage.rings.number_field True """ if gap is not None: @@ -921,11 +924,11 @@ def _sage_input_(self, sib, coerced): EXAMPLES:: - sage: sage_input(GF(5)['x']['y'], verify=True) # optional - sage.rings.finite_rings + sage: sage_input(GF(5)['x']['y'], verify=True) # needs sage.rings.finite_rings # Verified GF(5)['x']['y'] - sage: from sage.misc.sage_input import SageInputBuilder # optional - sage.rings.finite_rings - sage: ZZ['z']._sage_input_(SageInputBuilder(), False) # optional - sage.rings.finite_rings + sage: from sage.misc.sage_input import SageInputBuilder # needs sage.rings.finite_rings + sage: ZZ['z']._sage_input_(SageInputBuilder(), False) # needs sage.rings.finite_rings {constr_parent: {subscr: {atomic:ZZ}[{atomic:'z'}]} with gens: ('z',)} """ base = sib(self.base_ring()) @@ -964,9 +967,9 @@ def _is_valid_homomorphism_(self, codomain, im_gens, base_map=None): EXAMPLES:: sage: R. = QQ[] - sage: R._is_valid_homomorphism_(GF(7), [5]) # optional - sage.rings.finite_rings + sage: R._is_valid_homomorphism_(GF(7), [5]) # needs sage.rings.finite_rings False - sage: R._is_valid_homomorphism_(Qp(7), [5]) # optional - sage.rings.padics + sage: R._is_valid_homomorphism_(Qp(7), [5]) # needs sage.rings.padics True """ # Since poly rings are free, any image of the gen @@ -1044,7 +1047,7 @@ def change_ring(self, R): sage: R. = RealIntervalField()[]; R Univariate Polynomial Ring in ZZZ over Real Interval Field with 53 bits of precision - sage: R.change_ring(GF(19^2, 'b')) # optional - sage.rings.finite_rings + sage: R.change_ring(GF(19^2, 'b')) # needs sage.rings.finite_rings Univariate Polynomial Ring in ZZZ over Finite Field in b of size 19^2 """ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing @@ -1148,9 +1151,9 @@ def characteristic(self): Univariate Polynomial Ring in ZZZ over Real Interval Field with 53 bits of precision sage: R.characteristic() 0 - sage: S = R.change_ring(GF(19^2, 'b')); S # optional - sage.rings.finite_rings + sage: S = R.change_ring(GF(19^2, 'b')); S # needs sage.rings.finite_rings Univariate Polynomial Ring in ZZZ over Finite Field in b of size 19^2 - sage: S.characteristic() + sage: S.characteristic() # needs sage.rings.finite_rings 19 """ return self.base_ring().characteristic() @@ -1165,23 +1168,23 @@ def cyclotomic_polynomial(self, n): EXAMPLES:: sage: R = ZZ['x'] - sage: R.cyclotomic_polynomial(8) # optional - sage.libs.pari + sage: R.cyclotomic_polynomial(8) # needs sage.libs.pari x^4 + 1 - sage: R.cyclotomic_polynomial(12) # optional - sage.libs.pari + sage: R.cyclotomic_polynomial(12) # needs sage.libs.pari x^4 - x^2 + 1 - sage: S = PolynomialRing(FiniteField(7), 'x') # optional - sage.rings.finite_rings - sage: S.cyclotomic_polynomial(12) # optional - sage.rings.finite_rings + sage: S = PolynomialRing(FiniteField(7), 'x') # needs sage.rings.finite_rings + sage: S.cyclotomic_polynomial(12) # needs sage.rings.finite_rings x^4 + 6*x^2 + 1 - sage: S.cyclotomic_polynomial(1) # optional - sage.rings.finite_rings + sage: S.cyclotomic_polynomial(1) # needs sage.rings.finite_rings x + 6 TESTS: Make sure it agrees with other systems for the trivial case:: - sage: ZZ['x'].cyclotomic_polynomial(1) # optional - sage.libs.pari + sage: ZZ['x'].cyclotomic_polynomial(1) # needs sage.libs.pari x - 1 - sage: gp('polcyclo(1)') # optional - sage.libs.pari + sage: gp('polcyclo(1)') # needs sage.libs.pari x - 1 """ if n <= 0: @@ -1309,16 +1312,16 @@ def krull_dimension(self): sage: R. = QQ[] sage: R.krull_dimension() 1 - sage: R. = GF(9, 'a')[]; R # optional - sage.rings.finite_rings + sage: R. = GF(9, 'a')[]; R # needs sage.rings.finite_rings Univariate Polynomial Ring in z over Finite Field in a of size 3^2 - sage: R.krull_dimension() # optional - sage.rings.finite_rings + sage: R.krull_dimension() # needs sage.rings.finite_rings 1 - sage: S. = R[] # optional - sage.rings.finite_rings - sage: S.krull_dimension() # optional - sage.rings.finite_rings + sage: S. = R[] # needs sage.rings.finite_rings + sage: S.krull_dimension() # needs sage.rings.finite_rings 2 - sage: for n in range(10): # optional - sage.rings.finite_rings + sage: for n in range(10): # needs sage.rings.finite_rings ....: S = PolynomialRing(S, 'w') - sage: S.krull_dimension() # optional - sage.rings.finite_rings + sage: S.krull_dimension() # needs sage.rings.finite_rings 12 """ return self.base_ring().krull_dimension() + 1 @@ -1392,13 +1395,13 @@ def random_element(self, degree=(-1,2), *args, **kwds): Check that :trac:`16682` is fixed:: - sage: R = PolynomialRing(GF(2), 'z') # optional - sage.rings.finite_rings - sage: for _ in range(100): # optional - sage.rings.finite_rings + sage: R = PolynomialRing(GF(2), 'z') # needs sage.rings.finite_rings + sage: for _ in range(100): # needs sage.rings.finite_rings ....: d = randint(-1,20) ....: P = R.random_element(degree=d) ....: assert P.degree() == d, "problem with {} which has not degree {}".format(P,d) - sage: R.random_element(degree=-2) # optional - sage.rings.finite_rings + sage: R.random_element(degree=-2) # needs sage.rings.finite_rings Traceback (most recent call last): ... ValueError: degree should be an integer greater or equal than -1 @@ -1495,12 +1498,12 @@ def _Karatsuba_threshold(self): EXAMPLES:: - sage: R. = QQbar[] # optional - sage.rings.number_field - sage: R._Karatsuba_threshold # optional - sage.rings.number_field + sage: R. = QQbar[] # needs sage.rings.number_field + sage: R._Karatsuba_threshold # needs sage.rings.number_field 8 - sage: MS = MatrixSpace(ZZ, 2, 2) # optional - sage.modules - sage: R. = MS[] # optional - sage.modules - sage: R._Karatsuba_threshold # optional - sage.modules + sage: MS = MatrixSpace(ZZ, 2, 2) # needs sage.modules + sage: R. = MS[] # needs sage.modules + sage: R._Karatsuba_threshold # needs sage.modules 0 """ base_ring = self.base_ring() @@ -1569,8 +1572,9 @@ def polynomials( self, of_degree=None, max_degree=None ): EXAMPLES:: - sage: P = PolynomialRing(GF(3), 'y') # optional - sage.rings.finite_rings - sage: for p in P.polynomials(of_degree=2): print(p) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: P = PolynomialRing(GF(3), 'y') + sage: for p in P.polynomials(of_degree=2): print(p) y^2 y^2 + 1 y^2 + 2 @@ -1589,7 +1593,7 @@ def polynomials( self, of_degree=None, max_degree=None ): 2*y^2 + 2*y 2*y^2 + 2*y + 1 2*y^2 + 2*y + 2 - sage: for p in P.polynomials(max_degree=1): print(p) # optional - sage.rings.finite_rings + sage: for p in P.polynomials(max_degree=1): print(p) 0 1 2 @@ -1599,7 +1603,7 @@ def polynomials( self, of_degree=None, max_degree=None ): 2*y 2*y + 1 2*y + 2 - sage: for p in P.polynomials(max_degree=1, of_degree=3): print(p) # optional - sage.rings.finite_rings + sage: for p in P.polynomials(max_degree=1, of_degree=3): print(p) Traceback (most recent call last): ... ValueError: you should pass exactly one of of_degree and max_degree @@ -1636,8 +1640,9 @@ def monics( self, of_degree=None, max_degree=None ): EXAMPLES:: - sage: P = PolynomialRing(GF(4, 'a'), 'y') # optional - sage.rings.finite_rings - sage: for p in P.monics(of_degree=2): print(p) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: P = PolynomialRing(GF(4, 'a'), 'y') + sage: for p in P.monics(of_degree=2): print(p) y^2 y^2 + a y^2 + a + 1 @@ -1654,13 +1659,13 @@ def monics( self, of_degree=None, max_degree=None ): y^2 + y + a y^2 + y + a + 1 y^2 + y + 1 - sage: for p in P.monics(max_degree=1): print(p) # optional - sage.rings.finite_rings + sage: for p in P.monics(max_degree=1): print(p) 1 y y + a y + a + 1 y + 1 - sage: for p in P.monics(max_degree=1, of_degree=3): print(p) # optional - sage.rings.finite_rings + sage: for p in P.monics(max_degree=1, of_degree=3): print(p) Traceback (most recent call last): ... ValueError: you should pass exactly one of of_degree and max_degree @@ -1712,7 +1717,7 @@ def quotient_by_principal_ideal(self, f, names=None, **kwds): sage: R. = QQ[] sage: I = (x^2 - 1) * R - sage: R.quotient_by_principal_ideal(I) # optional - sage.libs.pari + sage: R.quotient_by_principal_ideal(I) # needs sage.libs.pari Univariate Quotient Polynomial Ring in xbar over Rational Field with modulus x^2 - 1 @@ -1720,7 +1725,7 @@ def quotient_by_principal_ideal(self, f, names=None, **kwds): and customizing the variable name:: sage: R. = QQ[] - sage: R.quotient_by_principal_ideal(x^2 - 1, names=('foo',)) # optional - sage.libs.pari + sage: R.quotient_by_principal_ideal(x^2 - 1, names=('foo',)) # needs sage.libs.pari Univariate Quotient Polynomial Ring in foo over Rational Field with modulus x^2 - 1 @@ -1729,9 +1734,9 @@ def quotient_by_principal_ideal(self, f, names=None, **kwds): Quotienting by the zero ideal returns ``self`` (:trac:`5978`):: sage: R = QQ['x'] - sage: R.quotient_by_principal_ideal(R.zero_ideal()) is R # optional - sage.libs.pari + sage: R.quotient_by_principal_ideal(R.zero_ideal()) is R # needs sage.libs.pari True - sage: R.quotient_by_principal_ideal(0) is R # optional - sage.libs.pari + sage: R.quotient_by_principal_ideal(0) is R # needs sage.libs.pari True """ from sage.rings.ideal import Ideal @@ -1749,9 +1754,9 @@ def weyl_algebra(self): EXAMPLES:: sage: R = QQ['x'] - sage: W = R.weyl_algebra(); W # optional - sage.combinat sage.modules + sage: W = R.weyl_algebra(); W # needs sage.combinat sage.modules Differential Weyl algebra of polynomials in x over Rational Field - sage: W.polynomial_ring() == R # optional - sage.combinat sage.modules + sage: W.polynomial_ring() == R # needs sage.combinat sage.modules True """ from sage.algebras.weyl_algebra import DifferentialWeylAlgebra @@ -1814,12 +1819,12 @@ def __init__(self, base_ring, name="x", sparse=False, implementation=None, sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_integral_domain as PRing sage: R = PRing(ZZ, 'x'); R Univariate Polynomial Ring in x over Integer Ring - sage: type(R.gen()) # optional - sage.libs.flint + sage: type(R.gen()) # needs sage.libs.flint - sage: R = PRing(ZZ, 'x', implementation='NTL'); R # optional - sage.libs.ntl + sage: R = PRing(ZZ, 'x', implementation='NTL'); R # needs sage.libs.ntl Univariate Polynomial Ring in x over Integer Ring (using NTL) - sage: type(R.gen()) # optional - sage.libs.ntl + sage: type(R.gen()) # needs sage.libs.ntl """ self._implementation_repr = '' @@ -1888,19 +1893,19 @@ def weil_polynomials(self, d, q, sign=1, lead=1): EXAMPLES:: sage: R. = ZZ[] - sage: L = R.weil_polynomials(4, 2) # optional - sage.libs.flint - sage: len(L) # optional - sage.libs.flint + sage: L = R.weil_polynomials(4, 2) # needs sage.libs.flint + sage: len(L) # needs sage.libs.flint 35 - sage: L[9] # optional - sage.libs.flint + sage: L[9] # needs sage.libs.flint T^4 + T^3 + 2*T^2 + 2*T + 4 - sage: all(p.is_weil_polynomial() for p in L) # optional - sage.libs.flint + sage: all(p.is_weil_polynomial() for p in L) # needs sage.libs.flint True Setting multiple leading coefficients:: sage: R. = QQ[] - sage: l = R.weil_polynomials(4, 2, lead=((1,0), (2,4), (1,2))) # optional - sage.libs.flint - sage: l # optional - sage.libs.flint + sage: l = R.weil_polynomials(4, 2, lead=((1,0), (2,4), (1,2))) # needs sage.libs.flint + sage: l # needs sage.libs.flint [T^4 + 2*T^3 + 5*T^2 + 4*T + 4, T^4 + 2*T^3 + 3*T^2 + 4*T + 4, T^4 - 2*T^3 + 5*T^2 - 4*T + 4, @@ -1910,43 +1915,43 @@ def weil_polynomials(self, d, q, sign=1, lead=1): polynomials associated to K3 surfaces over `\GF{2}` of Picard number at least 12:: sage: R. = QQ[] - sage: l = R.weil_polynomials(10, 1, lead=2) # optional - sage.libs.flint - sage: len(l) # optional - sage.libs.flint + sage: l = R.weil_polynomials(10, 1, lead=2) # needs sage.libs.flint + sage: len(l) # needs sage.libs.flint 4865 - sage: l[len(l)//2] # optional - sage.libs.flint + sage: l[len(l)//2] # needs sage.libs.flint 2*T^10 + T^8 + T^6 + T^4 + T^2 + 2 TESTS: We check that products of Weil polynomials are also listed as Weil polynomials:: - sage: all((f * g) in R.weil_polynomials(6, q) for q in [3, 4] # optional - sage.libs.flint + sage: all((f * g) in R.weil_polynomials(6, q) for q in [3, 4] # needs sage.libs.flint ....: for f in R.weil_polynomials(2, q) for g in R.weil_polynomials(4, q)) True We check that irreducible Weil polynomials of degree 6 are CM:: - sage: simples = [f for f in R.weil_polynomials(6, 3) if f.is_irreducible()] # optional - sage.libs.flint - sage: len(simples) # optional - sage.libs.flint + sage: simples = [f for f in R.weil_polynomials(6, 3) if f.is_irreducible()] # needs sage.libs.flint + sage: len(simples) # needs sage.libs.flint 348 - sage: reals = [R([f[3+i] + sum((-3)^j * (i+2*j)/(i+j) * binomial(i+j,j) * f[3+i+2*j] # optional - sage.libs.flint + sage: reals = [R([f[3+i] + sum((-3)^j * (i+2*j)/(i+j) * binomial(i+j,j) * f[3+i+2*j] # needs sage.libs.flint ....: for j in range(1, (3+i)//2 + 1)) ....: for i in range(4)]) for f in simples] Check that every polynomial in this list has 3 real roots between `-2 \sqrt{3}` and `2 \sqrt{3}`:: - sage: roots = [f.roots(RR, multiplicities=False) for f in reals] # optional - sage.libs.flint - sage: all(len(L) == 3 and all(x^2 <= 12 for x in L) for L in roots) # optional - sage.libs.flint + sage: roots = [f.roots(RR, multiplicities=False) for f in reals] # needs sage.libs.flint + sage: all(len(L) == 3 and all(x^2 <= 12 for x in L) for L in roots) # needs sage.libs.flint True Finally, check that the original polynomials are reconstructed as CM polynomials:: - sage: all(f == T^3*r(T + 3/T) for (f, r) in zip(simples, reals)) # optional - sage.libs.flint + sage: all(f == T^3*r(T + 3/T) for (f, r) in zip(simples, reals)) # needs sage.libs.flint True A simple check (not sufficient):: - sage: all(f.number_of_real_roots() == 0 for f in simples) # optional - sage.libs.flint + sage: all(f.number_of_real_roots() == 0 for f in simples) # needs sage.libs.flint True """ R = self.base_ring() @@ -1985,7 +1990,7 @@ def _repr_(self): TESTS:: sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_integral_domain as PRing - sage: R = PRing(ZZ, 'x', implementation='NTL'); R # optional - sage.libs.ntl + sage: R = PRing(ZZ, 'x', implementation='NTL'); R # needs sage.libs.ntl Univariate Polynomial Ring in x over Integer Ring (using NTL) """ s = PolynomialRing_commutative._repr_(self) @@ -2005,11 +2010,11 @@ def construction(self): sage: functor.implementation is None True - sage: R = PRing(ZZ, 'x', implementation='NTL'); R # optional - sage.libs.ntl + sage: R = PRing(ZZ, 'x', implementation='NTL'); R # needs sage.libs.ntl Univariate Polynomial Ring in x over Integer Ring (using NTL) - sage: functor, arg = R.construction(); functor, arg # optional - sage.libs.ntl + sage: functor, arg = R.construction(); functor, arg # needs sage.libs.ntl (Poly[x], Integer Ring) - sage: functor.implementation # optional - sage.libs.ntl + sage: functor.implementation # needs sage.libs.ntl 'NTL' """ implementation = None @@ -2034,7 +2039,7 @@ def __init__(self, base_ring, name="x", sparse=False, implementation=None, sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_field as PRing sage: R = PRing(QQ, 'x'); R Univariate Polynomial Ring in x over Rational Field - sage: type(R.gen()) # optional - sage.libs.flint + sage: type(R.gen()) # needs sage.libs.flint sage: R = PRing(QQ, 'x', sparse=True); R Sparse Univariate Polynomial Ring in x over Rational Field @@ -2047,8 +2052,8 @@ def __init__(self, base_ring, name="x", sparse=False, implementation=None, Demonstrate that :trac:`8762` is fixed:: - sage: R. = PolynomialRing(GF(next_prime(10^20)), sparse=True) # optional - sage.rings.finite_rings - sage: x^(10^20) # this should be fast + sage: R. = PolynomialRing(GF(next_prime(10^20)), sparse=True) # needs sage.rings.finite_rings + sage: x^(10^20) # this should be fast # needs sage.rings.finite_rings x^100000000000000000000 """ def _element_class(): @@ -2094,8 +2099,8 @@ def _ideal_class_(self, n=0): EXAMPLES:: - sage: R. = GF(5)[] # optional - sage.rings.finite_rings - sage: R._ideal_class_() # optional - sage.rings.finite_rings + sage: R. = GF(5)[] # needs sage.rings.finite_rings + sage: R._ideal_class_() # needs sage.rings.finite_rings """ from sage.rings.polynomial.ideal import Ideal_1poly_field @@ -2247,15 +2252,15 @@ def lagrange_polynomial(self, points, algorithm="divided_difference", previous_r -2 sage: f(-4) 9 - sage: R = PolynomialRing(GF(2**3, 'a'), 'x') # optional - sage.rings.finite_rings - sage: a = R.base_ring().gen() # optional - sage.rings.finite_rings - sage: f = R.lagrange_polynomial([(a^2+a, a), (a, 1), (a^2, a^2+a+1)]); f # optional - sage.rings.finite_rings + sage: R = PolynomialRing(GF(2**3, 'a'), 'x') # needs sage.rings.finite_rings + sage: a = R.base_ring().gen() # needs sage.rings.finite_rings + sage: f = R.lagrange_polynomial([(a^2+a, a), (a, 1), (a^2, a^2+a+1)]); f # needs sage.rings.finite_rings a^2*x^2 + a^2*x + a^2 - sage: f(a^2 + a) # optional - sage.rings.finite_rings + sage: f(a^2 + a) # needs sage.rings.finite_rings a - sage: f(a) # optional - sage.rings.finite_rings + sage: f(a) # needs sage.rings.finite_rings 1 - sage: f(a^2) # optional - sage.rings.finite_rings + sage: f(a^2) # needs sage.rings.finite_rings a^2 + a + 1 Now use a memory efficient version of Neville's method:: @@ -2267,9 +2272,9 @@ def lagrange_polynomial(self, points, algorithm="divided_difference", previous_r -11/7*x + 19/7, -17/42*x^2 - 83/42*x + 53/7, -23/84*x^3 - 11/84*x^2 + 13/7*x + 1] - sage: R = PolynomialRing(GF(2**3, 'a'), 'x') # optional - sage.rings.finite_rings - sage: a = R.base_ring().gen() # optional - sage.rings.finite_rings - sage: R.lagrange_polynomial([(a^2+a, a), (a, 1), (a^2, a^2+a+1)], # optional - sage.rings.finite_rings + sage: R = PolynomialRing(GF(2**3, 'a'), 'x') # needs sage.rings.finite_rings + sage: a = R.base_ring().gen() # needs sage.rings.finite_rings + sage: R.lagrange_polynomial([(a^2+a, a), (a, 1), (a^2, a^2+a+1)], # needs sage.rings.finite_rings ....: algorithm="neville") [a^2 + a + 1, x + a + 1, a^2*x^2 + a^2*x + a^2] @@ -2281,10 +2286,10 @@ def lagrange_polynomial(self, points, algorithm="divided_difference", previous_r sage: R.lagrange_polynomial([(0,1), (2,2), (3,-2), (-4,9)], ....: algorithm="neville", previous_row=p)[-1] -23/84*x^3 - 11/84*x^2 + 13/7*x + 1 - sage: R = PolynomialRing(GF(2**3, 'a'), 'x') # optional - sage.rings.finite_rings - sage: a = R.base_ring().gen() # optional - sage.rings.finite_rings - sage: p = R.lagrange_polynomial([(a^2+a, a), (a, 1)], algorithm="neville") # optional - sage.rings.finite_rings - sage: R.lagrange_polynomial([(a^2+a, a), (a, 1), (a^2, a^2+a+1)], # optional - sage.rings.finite_rings + sage: R = PolynomialRing(GF(2**3, 'a'), 'x') # needs sage.rings.finite_rings + sage: a = R.base_ring().gen() # needs sage.rings.finite_rings + sage: p = R.lagrange_polynomial([(a^2+a, a), (a, 1)], algorithm="neville") # needs sage.rings.finite_rings + sage: R.lagrange_polynomial([(a^2+a, a), (a, 1), (a^2, a^2+a+1)], # needs sage.rings.finite_rings ....: algorithm="neville", previous_row=p)[-1] a^2*x^2 + a^2*x + a^2 @@ -2327,10 +2332,10 @@ def lagrange_polynomial(self, points, algorithm="divided_difference", previous_r Check that base fields of positive characteristic are treated correctly (see :trac:`9787`):: - sage: R. = GF(101)[] # optional - sage.rings.finite_rings - sage: R.lagrange_polynomial([[1, 0], [2, 0]]) # optional - sage.rings.finite_rings + sage: R. = GF(101)[] # needs sage.rings.finite_rings + sage: R.lagrange_polynomial([[1, 0], [2, 0]]) # needs sage.rings.finite_rings 0 - sage: R.lagrange_polynomial([[1, 0], [2, 0], [3, 0]]) # optional - sage.rings.finite_rings + sage: R.lagrange_polynomial([[1, 0], [2, 0], [3, 0]]) # needs sage.rings.finite_rings 0 """ # Perhaps we should be slightly stricter on the input and use @@ -2416,8 +2421,8 @@ def fraction_field(self): EXAMPLES:: - sage: R. = GF(5)[] # optional - sage.rings.finite_rings - sage: R.fraction_field() # optional - sage.rings.finite_rings + sage: R. = GF(5)[] # needs sage.rings.finite_rings + sage: R.fraction_field() # needs sage.rings.finite_rings Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 @@ -2425,16 +2430,18 @@ def fraction_field(self): Check that :trac:`25449` has been resolved:: - sage: k = GF(25453) # optional - sage.rings.finite_rings - sage: F. = FunctionField(k) # optional - sage.rings.finite_rings - sage: R. = k[] # optional - sage.rings.finite_rings - sage: t(x) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: k = GF(25453) + sage: F. = FunctionField(k) + sage: R. = k[] + sage: t(x) x - sage: k = GF(55667) # optional - sage.rings.finite_rings - sage: F. = FunctionField(k) # optional - sage.rings.finite_rings - sage: R. = k[] # optional - sage.rings.finite_rings - sage: t(x) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: k = GF(55667) + sage: F. = FunctionField(k) + sage: R. = k[] + sage: t(x) x """ @@ -2454,24 +2461,24 @@ class PolynomialRing_dense_finite_field(PolynomialRing_field): EXAMPLES:: - sage: R = PolynomialRing(GF(27, 'a'), 'x') # optional - sage.rings.finite_rings - sage: type(R) # optional - sage.rings.finite_rings + sage: R = PolynomialRing(GF(27, 'a'), 'x') # needs sage.rings.finite_rings + sage: type(R) # needs sage.rings.finite_rings """ def __init__(self, base_ring, name="x", element_class=None, implementation=None): """ TESTS:: - sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_finite_field # optional - sage.rings.finite_rings - sage: R = PolynomialRing_dense_finite_field(GF(5), implementation='generic') # optional - sage.rings.finite_rings - sage: type(R(0)) # optional - sage.rings.finite_rings + sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_finite_field # needs sage.rings.finite_rings + sage: R = PolynomialRing_dense_finite_field(GF(5), implementation='generic') # needs sage.rings.finite_rings + sage: type(R(0)) # needs sage.rings.finite_rings - sage: S = PolynomialRing_dense_finite_field(GF(25, 'a'), implementation='NTL') # optional - sage.rings.finite_rings - sage: type(S(0)) # optional - sage.rings.finite_rings + sage: S = PolynomialRing_dense_finite_field(GF(25, 'a'), implementation='NTL') # needs sage.rings.finite_rings + sage: type(S(0)) # needs sage.rings.finite_rings - sage: S = PolynomialRing_dense_finite_field(GF(64), implementation='superfast') # optional - sage.rings.finite_rings + sage: S = PolynomialRing_dense_finite_field(GF(64), implementation='superfast') # needs sage.rings.finite_rings Traceback (most recent call last): ... ValueError: unknown implementation 'superfast' for dense polynomial rings over Finite Field in z6 of size 2^6 @@ -2500,16 +2507,17 @@ def _implementation_names_impl(implementation, base_ring, sparse): """ TESTS:: - sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_finite_field # optional - sage.rings.finite_rings - sage: PolynomialRing_dense_finite_field._implementation_names_impl("NTL", GF(4), False) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_finite_field + sage: PolynomialRing_dense_finite_field._implementation_names_impl("NTL", GF(4), False) ['NTL', None] - sage: PolynomialRing_dense_finite_field._implementation_names_impl(None, GF(4), False) # optional - sage.rings.finite_rings + sage: PolynomialRing_dense_finite_field._implementation_names_impl(None, GF(4), False) ['NTL', None] - sage: PolynomialRing_dense_finite_field._implementation_names_impl("generic", GF(4), False) # optional - sage.rings.finite_rings + sage: PolynomialRing_dense_finite_field._implementation_names_impl("generic", GF(4), False) ['generic'] - sage: PolynomialRing_dense_finite_field._implementation_names_impl("FLINT", GF(4), False) # optional - sage.rings.finite_rings + sage: PolynomialRing_dense_finite_field._implementation_names_impl("FLINT", GF(4), False) NotImplemented - sage: PolynomialRing_dense_finite_field._implementation_names_impl(None, GF(4), True) # optional - sage.rings.finite_rings + sage: PolynomialRing_dense_finite_field._implementation_names_impl(None, GF(4), True) NotImplemented """ if sparse: @@ -2544,16 +2552,17 @@ def irreducible_element(self, n, algorithm=None): EXAMPLES:: - sage: f = GF(5^3, 'a')['x'].irreducible_element(2) # optional - sage.rings.finite_rings - sage: f.degree() # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: f = GF(5^3, 'a')['x'].irreducible_element(2) + sage: f.degree() 2 - sage: f.is_irreducible() # optional - sage.rings.finite_rings + sage: f.is_irreducible() True - sage: R = GF(19)['x'] # optional - sage.rings.finite_rings - sage: R.irreducible_element(21, algorithm="first_lexicographic") # optional - sage.rings.finite_rings + sage: R = GF(19)['x'] + sage: R.irreducible_element(21, algorithm="first_lexicographic") x^21 + x + 5 - sage: R = GF(5**2, 'a')['x'] # optional - sage.rings.finite_rings - sage: R.irreducible_element(17, algorithm="first_lexicographic") # optional - sage.rings.finite_rings + sage: R = GF(5**2, 'a')['x'] + sage: R.irreducible_element(17, algorithm="first_lexicographic") x^17 + a*x + 4*a + 3 AUTHORS: @@ -2614,15 +2623,16 @@ def _roth_ruckenstein(self, p, degree_bound, precision): EXAMPLES:: - sage: F = GF(17) # optional - sage.rings.finite_rings - sage: Px. = F[] # optional - sage.rings.finite_rings - sage: Pxy. = Px[] # optional - sage.rings.finite_rings - sage: p = (y - (x**2 + x + 1)) * (y**2 - x + 1) * (y - (x**3 + 4*x + 16)) # optional - sage.rings.finite_rings - sage: Px._roth_ruckenstein(p, 3, None) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: F = GF(17) + sage: Px. = F[] + sage: Pxy. = Px[] + sage: p = (y - (x**2 + x + 1)) * (y**2 - x + 1) * (y - (x**3 + 4*x + 16)) + sage: Px._roth_ruckenstein(p, 3, None) [x^3 + 4*x + 16, x^2 + x + 1] - sage: Px._roth_ruckenstein(p, 2, None) # optional - sage.rings.finite_rings + sage: Px._roth_ruckenstein(p, 2, None) [x^2 + x + 1] - sage: Px._roth_ruckenstein(p, 1, 2) # optional - sage.rings.finite_rings + sage: Px._roth_ruckenstein(p, 1, 2) [(4*x + 16, 2), (2*x + 13, 2), (15*x + 4, 2), (x + 1, 2)] """ def roth_rec(p, lam, k, g): @@ -2713,29 +2723,31 @@ def _alekhnovich(self, p, degree_bound, precision=None, dc_threshold=None): EXAMPLES:: - sage: R. = GF(17)[] # optional - sage.rings.finite_rings - sage: S. = R[] # optional - sage.rings.finite_rings - sage: p = (y - 2*x^2 - 3*x - 14) * (y - 3*x + 2) * (y - 1) # optional - sage.rings.finite_rings - sage: R._alekhnovich(p, 2) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R. = GF(17)[] + sage: S. = R[] + sage: p = (y - 2*x^2 - 3*x - 14) * (y - 3*x + 2) * (y - 1) + sage: R._alekhnovich(p, 2) [3*x + 15, 2*x^2 + 3*x + 14, 1] - sage: R._alekhnovich(p, 1) # optional - sage.rings.finite_rings + sage: R._alekhnovich(p, 1) [3*x + 15, 1] - sage: R._alekhnovich(p, 1, precision=2) # optional - sage.rings.finite_rings + sage: R._alekhnovich(p, 1, precision=2) [(3*x + 15, 2), (3*x + 14, 2), (1, 2)] Example of benchmark to check that `dc_threshold = None` is better:: - sage: p = prod(y - R.random_element(20) # not tested # optional - sage.rings.finite_rings + sage: # not tested, needs sage.rings.finite_rings + sage: p = prod(y - R.random_element(20) ....: for _ in range(10)) * S.random_element(10,10) - sage: %timeit _alekhnovich(R, p, 20, dc_threshold = None) # not tested # optional - sage.rings.finite_rings + sage: %timeit _alekhnovich(R, p, 20, dc_threshold = None) 1 loop, best of 3: 418 ms per loop - sage: %timeit _alekhnovich(R, p, 20, dc_threshold = 1) # not tested # optional - sage.rings.finite_rings + sage: %timeit _alekhnovich(R, p, 20, dc_threshold = 1) 1 loop, best of 3: 416 ms per loop - sage: %timeit _alekhnovich(R, p, 20, dc_threshold = 2) # not tested # optional - sage.rings.finite_rings + sage: %timeit _alekhnovich(R, p, 20, dc_threshold = 2) 1 loop, best of 3: 418 ms per loop - sage: %timeit _alekhnovich(R, p, 20, dc_threshold = 3) # not tested # optional - sage.rings.finite_rings + sage: %timeit _alekhnovich(R, p, 20, dc_threshold = 3) 1 loop, best of 3: 454 ms per loop - sage: %timeit _alekhnovich(R, p, 20, dc_threshold = 4) # not tested # optional - sage.rings.finite_rings + sage: %timeit _alekhnovich(R, p, 20, dc_threshold = 4) 1 loop, best of 3: 519 ms per loop AUTHORS: @@ -2825,24 +2837,25 @@ def _roots_univariate_polynomial(self, p, ring=None, multiplicities=False, algor EXAMPLES:: - sage: R. = GF(13)[] # optional - sage.rings.finite_rings - sage: S. = R[] # optional - sage.rings.finite_rings - sage: p = y^2 + (12*x^2 + x + 11)*y + x^3 + 12*x^2 + 12*x + 1 # optional - sage.rings.finite_rings - sage: p.roots(multiplicities=False) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R. = GF(13)[] + sage: S. = R[] + sage: p = y^2 + (12*x^2 + x + 11)*y + x^3 + 12*x^2 + 12*x + 1 + sage: p.roots(multiplicities=False) [x^2 + 11*x + 1, x + 1] - sage: p.roots(multiplicities=False, degree_bound=1) # optional - sage.rings.finite_rings + sage: p.roots(multiplicities=False, degree_bound=1) [x + 1] - sage: p.roots(multiplicities=False, algorithm="Roth-Ruckenstein") # optional - sage.rings.finite_rings + sage: p.roots(multiplicities=False, algorithm="Roth-Ruckenstein") [x^2 + 11*x + 1, x + 1] TESTS: Check that :trac:`23639` is fixed:: - sage: R = GF(3)['x']['y'] # optional - sage.rings.finite_rings - sage: R.one().roots(multiplicities=False) # optional - sage.rings.finite_rings + sage: R = GF(3)['x']['y'] # needs sage.rings.finite_rings + sage: R.one().roots(multiplicities=False) # needs sage.rings.finite_rings [] - sage: R.zero().roots(multiplicities=False) # optional - sage.rings.finite_rings + sage: R.zero().roots(multiplicities=False) # needs sage.rings.finite_rings Traceback (most recent call last): ... ArithmeticError: roots of 0 are not defined @@ -2887,7 +2900,7 @@ def __init__(self, base_ring, name=None, sparse=False, implementation=None, sage: isinstance(S, PolynomialRing_cdvr) False - sage: S. = Zp(5)[] # optional - sage.rings.padics + sage: S. = Zp(5)[] # needs sage.rings.padics sage: isinstance(S, PolynomialRing_cdvr) True """ @@ -2918,8 +2931,8 @@ def __init__(self, base_ring, name=None, sparse=False, implementation=None, sage: isinstance(S, PolynomialRing_cdvf) False - sage: S. = Qp(5)[] # optional - sage.rings.padics - sage: isinstance(S, PolynomialRing_cdvf) # optional - sage.rings.padics + sage: S. = Qp(5)[] # needs sage.rings.padics + sage: isinstance(S, PolynomialRing_cdvf) # needs sage.rings.padics True """ if element_class is None: @@ -2951,11 +2964,11 @@ def _implementation_names_impl(implementation, base_ring, sparse): TESTS:: sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_padic_ring_generic - sage: PolynomialRing_dense_padic_ring_generic._implementation_names_impl(None, Zp(2), False) # optional - sage.rings.padics + sage: PolynomialRing_dense_padic_ring_generic._implementation_names_impl(None, Zp(2), False) # needs sage.rings.padics [None] - sage: PolynomialRing_dense_padic_ring_generic._implementation_names_impl(None, Zp(2), True) # optional - sage.rings.padics + sage: PolynomialRing_dense_padic_ring_generic._implementation_names_impl(None, Zp(2), True) # needs sage.rings.padics NotImplemented - sage: PolynomialRing_dense_padic_ring_generic._implementation_names_impl("generic", Zp(2), False) # optional - sage.rings.padics + sage: PolynomialRing_dense_padic_ring_generic._implementation_names_impl("generic", Zp(2), False) # needs sage.rings.padics NotImplemented """ if implementation is None and not sparse: @@ -2980,11 +2993,11 @@ def _implementation_names_impl(implementation, base_ring, sparse): TESTS:: sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_padic_field_generic - sage: PolynomialRing_dense_padic_field_generic._implementation_names_impl(None, Qp(2), False) # optional - sage.rings.padics + sage: PolynomialRing_dense_padic_field_generic._implementation_names_impl(None, Qp(2), False) # needs sage.rings.padics [None] - sage: PolynomialRing_dense_padic_field_generic._implementation_names_impl(None, Qp(2), True) # optional - sage.rings.padics + sage: PolynomialRing_dense_padic_field_generic._implementation_names_impl(None, Qp(2), True) # needs sage.rings.padics NotImplemented - sage: PolynomialRing_dense_padic_field_generic._implementation_names_impl("generic", Qp(2), False) # optional - sage.rings.padics + sage: PolynomialRing_dense_padic_field_generic._implementation_names_impl("generic", Qp(2), False) # needs sage.rings.padics NotImplemented """ if implementation is None and not sparse: @@ -2998,9 +3011,9 @@ def __init__(self, base_ring, name=None, implementation=None, element_class=None TESTS:: sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_padic_ring_capped_relative as PRing - sage: R = PRing(Zp(13), name='t'); R # optional - sage.rings.padics + sage: R = PRing(Zp(13), name='t'); R # needs sage.rings.padics Univariate Polynomial Ring in t over 13-adic Ring with capped relative precision 20 - sage: type(R.gen()) # optional - sage.rings.padics + sage: type(R.gen()) # needs sage.rings.padics """ if element_class is None: @@ -3019,9 +3032,9 @@ def __init__(self, base_ring, name=None, implementation=None, element_class=None TESTS:: sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_padic_ring_capped_absolute as PRing - sage: R = PRing(Zp(13, type='capped-abs'), name='t'); R # optional - sage.rings.padics + sage: R = PRing(Zp(13, type='capped-abs'), name='t'); R # needs sage.rings.padics Univariate Polynomial Ring in t over 13-adic Ring with capped absolute precision 20 - sage: type(R.gen()) # optional - sage.rings.padics + sage: type(R.gen()) # needs sage.rings.padics """ if element_class is None: @@ -3039,10 +3052,10 @@ def __init__(self, base_ring, name=None, implementation=None, element_class=None TESTS:: sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_padic_ring_fixed_mod as PRing - sage: R = PRing(Zp(13, type='fixed-mod'), name='t'); R # optional - sage.rings.padics + sage: R = PRing(Zp(13, type='fixed-mod'), name='t'); R # needs sage.rings.padics Univariate Polynomial Ring in t over 13-adic Ring of fixed modulus 13^20 - sage: type(R.gen()) # optional - sage.rings.padics + sage: type(R.gen()) # needs sage.rings.padics """ if element_class is None: @@ -3060,9 +3073,9 @@ def __init__(self, base_ring, name=None, implementation=None, element_class=None TESTS:: sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_padic_field_capped_relative as PRing - sage: R = PRing(Qp(13), name='t'); R # optional - sage.rings.padics + sage: R = PRing(Qp(13), name='t'); R # needs sage.rings.padics Univariate Polynomial Ring in t over 13-adic Field with capped relative precision 20 - sage: type(R.gen()) # optional - sage.rings.padics + sage: type(R.gen()) # needs sage.rings.padics """ if element_class is None: @@ -3084,27 +3097,27 @@ def __init__(self, base_ring, name=None, element_class=None, sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_mod_n as PRing sage: R = PRing(Zmod(15), 'x'); R Univariate Polynomial Ring in x over Ring of integers modulo 15 - sage: type(R.gen()) # optional - sage.libs.flint + sage: type(R.gen()) # needs sage.libs.flint - sage: R = PRing(Zmod(15), 'x', implementation='NTL'); R # optional - sage.libs.ntl + sage: R = PRing(Zmod(15), 'x', implementation='NTL'); R # needs sage.libs.ntl Univariate Polynomial Ring in x over Ring of integers modulo 15 (using NTL) - sage: type(R.gen()) # optional - sage.libs.ntl + sage: type(R.gen()) # needs sage.libs.ntl - sage: R = PRing(Zmod(2**63*3), 'x', implementation='NTL'); R # optional - sage.libs.ntl + sage: R = PRing(Zmod(2**63*3), 'x', implementation='NTL'); R # needs sage.libs.ntl Univariate Polynomial Ring in x over Ring of integers modulo 27670116110564327424 (using NTL) - sage: type(R.gen()) # optional - sage.libs.ntl + sage: type(R.gen()) # needs sage.libs.ntl - sage: R = PRing(Zmod(2**63*3), 'x', implementation='FLINT') # optional - sage.libs.flint + sage: R = PRing(Zmod(2**63*3), 'x', implementation='FLINT') # needs sage.libs.flint Traceback (most recent call last): ... ValueError: FLINT does not support modulus 27670116110564327424 - sage: R = PRing(Zmod(2**63*3), 'x'); R # optional - sage.libs.ntl + sage: R = PRing(Zmod(2**63*3), 'x'); R # needs sage.libs.ntl Univariate Polynomial Ring in x over Ring of integers modulo 27670116110564327424 (using NTL) - sage: type(R.gen()) # optional - sage.libs.ntl + sage: type(R.gen()) # needs sage.libs.ntl """ if element_class is None: @@ -3195,7 +3208,7 @@ def _repr_(self): TESTS:: sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_integral_domain as PRing - sage: R = PRing(ZZ, 'x', implementation='NTL'); R # optional - sage.libs.ntl + sage: R = PRing(ZZ, 'x', implementation='NTL'); R # needs sage.libs.ntl Univariate Polynomial Ring in x over Integer Ring (using NTL) """ s = PolynomialRing_commutative._repr_(self) @@ -3207,38 +3220,39 @@ def residue_field(self, ideal, names=None): EXAMPLES:: - sage: R. = GF(2)[] # optional - sage.rings.finite_rings - sage: k. = R.residue_field(t^3 + t + 1); k # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R. = GF(2)[] + sage: k. = R.residue_field(t^3 + t + 1); k Residue field in a of Principal ideal (t^3 + t + 1) of Univariate Polynomial Ring in t over Finite Field of size 2 (using GF2X) - sage: k.list() # optional - sage.rings.finite_rings + sage: k.list() [0, a, a^2, a + 1, a^2 + a, a^2 + a + 1, a^2 + 1, 1] - sage: R.residue_field(t) # optional - sage.rings.finite_rings + sage: R.residue_field(t) Residue field of Principal ideal (t) of Univariate Polynomial Ring in t over Finite Field of size 2 (using GF2X) - sage: P = R.irreducible_element(8) * R # optional - sage.rings.finite_rings - sage: P # optional - sage.rings.finite_rings + sage: P = R.irreducible_element(8) * R + sage: P Principal ideal (t^8 + t^4 + t^3 + t^2 + 1) of Univariate Polynomial Ring in t over Finite Field of size 2 (using GF2X) - sage: k. = R.residue_field(P); k # optional - sage.rings.finite_rings + sage: k. = R.residue_field(P); k Residue field in a of Principal ideal (t^8 + t^4 + t^3 + t^2 + 1) of Univariate Polynomial Ring in t over Finite Field of size 2 (using GF2X) - sage: k.cardinality() # optional - sage.rings.finite_rings + sage: k.cardinality() 256 Non-maximal ideals are not accepted:: - sage: R.residue_field(t^2 + 1) # optional - sage.rings.finite_rings + sage: R.residue_field(t^2 + 1) # needs sage.rings.finite_rings Traceback (most recent call last): ... ArithmeticError: ideal is not maximal - sage: R.residue_field(0) # optional - sage.rings.finite_rings + sage: R.residue_field(0) # needs sage.rings.finite_rings Traceback (most recent call last): ... ArithmeticError: ideal is not maximal - sage: R.residue_field(1) # optional - sage.rings.finite_rings + sage: R.residue_field(1) # needs sage.rings.finite_rings Traceback (most recent call last): ... ArithmeticError: ideal is not maximal @@ -3256,36 +3270,36 @@ def __init__(self, base_ring, name="x", implementation=None, element_class=None, """ TESTS:: - sage: P = GF(2)['x']; P # optional - sage.rings.finite_rings + sage: P = GF(2)['x']; P # needs sage.rings.finite_rings Univariate Polynomial Ring in x over Finite Field of size 2 (using GF2X) - sage: type(P.gen()) # optional - sage.rings.finite_rings + sage: type(P.gen()) # needs sage.rings.finite_rings - sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_mod_p # optional - sage.rings.finite_rings - sage: P = PolynomialRing_dense_mod_p(GF(5), 'x'); P # optional - sage.rings.finite_rings + sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_mod_p # needs sage.rings.finite_rings + sage: P = PolynomialRing_dense_mod_p(GF(5), 'x'); P # needs sage.rings.finite_rings Univariate Polynomial Ring in x over Finite Field of size 5 - sage: type(P.gen()) # optional - sage.rings.finite_rings + sage: type(P.gen()) # needs sage.rings.finite_rings - sage: P = PolynomialRing_dense_mod_p(GF(5), 'x', implementation='NTL'); P # optional - sage.rings.finite_rings + sage: P = PolynomialRing_dense_mod_p(GF(5), 'x', implementation='NTL'); P # needs sage.rings.finite_rings Univariate Polynomial Ring in x over Finite Field of size 5 (using NTL) - sage: type(P.gen()) # optional - sage.rings.finite_rings + sage: type(P.gen()) # needs sage.rings.finite_rings - sage: P = PolynomialRing_dense_mod_p(GF(9223372036854775837), 'x') # optional - sage.rings.finite_rings - sage: P # optional - sage.rings.finite_rings + sage: P = PolynomialRing_dense_mod_p(GF(9223372036854775837), 'x') # needs sage.rings.finite_rings + sage: P # needs sage.rings.finite_rings Univariate Polynomial Ring in x over Finite Field of size 9223372036854775837 (using NTL) - sage: type(P.gen()) # optional - sage.rings.finite_rings + sage: type(P.gen()) # needs sage.rings.finite_rings This caching bug was fixed in :trac:`24264`:: sage: p = 2^64 + 13 - sage: A = GF(p^2) # optional - sage.rings.finite_rings - sage: B = GF(p^3) # optional - sage.rings.finite_rings - sage: R = A.modulus().parent() # optional - sage.rings.finite_rings - sage: S = B.modulus().parent() # optional - sage.rings.finite_rings - sage: R is S # optional - sage.rings.finite_rings + sage: A = GF(p^2) # needs sage.rings.finite_rings + sage: B = GF(p^3) # needs sage.rings.finite_rings + sage: R = A.modulus().parent() # needs sage.rings.finite_rings + sage: S = B.modulus().parent() # needs sage.rings.finite_rings + sage: R is S # needs sage.rings.finite_rings True """ if element_class is None: @@ -3327,15 +3341,16 @@ def _implementation_names_impl(implementation, base_ring, sparse): """ TESTS:: - sage: PolynomialRing(GF(2), 'x', implementation="GF2X") # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: PolynomialRing(GF(2), 'x', implementation="GF2X") Univariate Polynomial Ring in x over Finite Field of size 2 (using GF2X) - sage: PolynomialRing(GF(2), 'x', implementation="NTL") # optional - sage.rings.finite_rings + sage: PolynomialRing(GF(2), 'x', implementation="NTL") Univariate Polynomial Ring in x over Finite Field of size 2 (using GF2X) - sage: PolynomialRing(GF(2), 'x', implementation=None) # optional - sage.rings.finite_rings + sage: PolynomialRing(GF(2), 'x', implementation=None) Univariate Polynomial Ring in x over Finite Field of size 2 (using GF2X) - sage: PolynomialRing(GF(2), 'x', implementation="FLINT") # optional - sage.rings.finite_rings + sage: PolynomialRing(GF(2), 'x', implementation="FLINT") Univariate Polynomial Ring in x over Finite Field of size 2 - sage: PolynomialRing(GF(3), 'x', implementation="GF2X") # optional - sage.rings.finite_rings + sage: PolynomialRing(GF(3), 'x', implementation="GF2X") Traceback (most recent call last): ... ValueError: GF2X only supports modulus 2 @@ -3407,35 +3422,36 @@ def irreducible_element(self, n, algorithm=None): EXAMPLES:: - sage: GF(5)['x'].irreducible_element(2) # optional - sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: GF(5)['x'].irreducible_element(2) x^2 + 4*x + 2 - sage: GF(5)['x'].irreducible_element(2, algorithm="adleman-lenstra") # optional - sage.rings.finite_rings + sage: GF(5)['x'].irreducible_element(2, algorithm="adleman-lenstra") x^2 + x + 1 - sage: GF(5)['x'].irreducible_element(2, algorithm="primitive") # optional - sage.rings.finite_rings + sage: GF(5)['x'].irreducible_element(2, algorithm="primitive") x^2 + 4*x + 2 - sage: GF(5)['x'].irreducible_element(32, algorithm="first_lexicographic") # optional - sage.rings.finite_rings + sage: GF(5)['x'].irreducible_element(32, algorithm="first_lexicographic") x^32 + 2 - sage: GF(5)['x'].irreducible_element(32, algorithm="conway") # optional - sage.rings.finite_rings + sage: GF(5)['x'].irreducible_element(32, algorithm="conway") Traceback (most recent call last): ... RuntimeError: requested Conway polynomial not in database. - sage: GF(5)['x'].irreducible_element(32, algorithm="primitive") # optional - sage.rings.finite_rings + sage: GF(5)['x'].irreducible_element(32, algorithm="primitive") x^32 + ... In characteristic 2:: - sage: GF(2)['x'].irreducible_element(33) # optional - sage.rings.finite_rings + sage: GF(2)['x'].irreducible_element(33) # needs sage.rings.finite_rings x^33 + x^13 + x^12 + x^11 + x^10 + x^8 + x^6 + x^3 + 1 - sage: GF(2)['x'].irreducible_element(33, algorithm="minimal_weight") # optional - sage.rings.finite_rings + sage: GF(2)['x'].irreducible_element(33, algorithm="minimal_weight") # needs sage.rings.finite_rings x^33 + x^10 + 1 In degree 1:: - sage: GF(97)['x'].irreducible_element(1) # optional - sage.rings.finite_rings + sage: GF(97)['x'].irreducible_element(1) # needs sage.rings.finite_rings x + 96 - sage: GF(97)['x'].irreducible_element(1, algorithm="conway") # optional - sage.rings.finite_rings + sage: GF(97)['x'].irreducible_element(1, algorithm="conway") # needs sage.rings.finite_rings x + 92 - sage: GF(97)['x'].irreducible_element(1, algorithm="adleman-lenstra") # optional - sage.rings.finite_rings + sage: GF(97)['x'].irreducible_element(1, algorithm="adleman-lenstra") # needs sage.rings.finite_rings x AUTHORS: From 8623513db42bad4233fdd6a6bf772d3f1b3d8a0c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 7 Jul 2023 10:36:53 -0700 Subject: [PATCH 127/150] src/sage/doctest/sources.py: Mark _test_enough_doctests test as # not tested --- src/sage/doctest/sources.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index b49bd1b674b..466ccf12181 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -809,9 +809,11 @@ def create_doctests(self, namespace): def _test_enough_doctests(self, check_extras=True, verbose=True): r""" This function checks to see that the doctests are not getting - unexpectedly skipped. It uses a different (and simpler) code - path than the doctest creation functions, so there are a few - files in Sage that it counts incorrectly. + unexpectedly skipped. + + It uses a different (and simpler) code path than the doctest + creation functions. In particular, it does not understand + file-level and block-level # optional / needs tags. INPUT: @@ -824,13 +826,14 @@ def _test_enough_doctests(self, check_extras=True, verbose=True): TESTS:: + sage: # not tested (because the output will change when source files are changed) sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: from sage.env import SAGE_SRC sage: cwd = os.getcwd() sage: os.chdir(SAGE_SRC) sage: import itertools - sage: for path, dirs, files in itertools.chain(os.walk('sage'), os.walk('doc')): # long time + sage: for path, dirs, files in itertools.chain(os.walk('sage'), os.walk('doc')): ....: path = os.path.relpath(path) ....: dirs.sort(); files.sort() ....: for F in files: From 31097fc4766d2d1317e3145b99b5d1ce02afc14e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 7 Jul 2023 16:28:48 -0700 Subject: [PATCH 128/150] src/sage/doctest/test.py: Update doctests --- src/sage/doctest/test.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/sage/doctest/test.py b/src/sage/doctest/test.py index 60075a53578..aa64974fd72 100644 --- a/src/sage/doctest/test.py +++ b/src/sage/doctest/test.py @@ -253,11 +253,12 @@ is still alive, but it should be killed automatically after the ``die_timeout`` given above (10 seconds):: - sage: pid = int(open(F).read()) # long time - sage: time.sleep(2) # long time - sage: os.kill(pid, signal.SIGQUIT) # long time; 2 seconds passed => still alive - sage: time.sleep(8) # long time - sage: os.kill(pid, signal.SIGQUIT) # long time; 10 seconds passed => dead # random + sage: # long time + sage: pid = int(open(F).read()) + sage: time.sleep(2) + sage: os.kill(pid, signal.SIGQUIT) # 2 seconds passed => still alive + sage: time.sleep(8) + sage: os.kill(pid, signal.SIGQUIT) # 10 seconds passed => dead # random Traceback (most recent call last): ... ProcessLookupError: ... @@ -464,13 +465,12 @@ Running doctests ... Doctesting 1 file. sage -t --warn-long 0.0 --random-seed=0 show_skipped.rst - 1 unlabeled test not run 2 tests not run due to known bugs 1 gap test not run 1 long test not run 1 not tested test not run 0 tests not run because we ran out of time - [1 test, ... s] + [2 tests, ... s] ---------------------------------------------------------------------- All tests passed! ---------------------------------------------------------------------- @@ -484,11 +484,10 @@ Running doctests ... Doctesting 1 file. sage -t --long --warn-long 0.0 --random-seed=0 show_skipped.rst - 1 unlabeled test not run 2 tests not run due to known bugs 1 not tested test not run 0 tests not run because we ran out of time - [3 tests, ... s] + [4 tests, ... s] ---------------------------------------------------------------------- All tests passed! ---------------------------------------------------------------------- @@ -500,10 +499,9 @@ Running doctests ... Doctesting 1 file. sage -t --long --warn-long 0.0 --random-seed=0 show_skipped.rst - 1 unlabeled test not run 2 tests not run due to known bugs 1 not tested test not run - 1 sage test not run + 2 sage tests not run 0 tests not run because we ran out of time [2 tests, ... s] ---------------------------------------------------------------------- From c6d1b89f0b906c73a103b979c4bb0ecd40736f17 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 8 Jul 2023 22:44:01 -0700 Subject: [PATCH 129/150] src/doc/en/developer: s/codeblock-scoped/block-scoped/g --- src/doc/en/developer/coding_basics.rst | 2 +- src/doc/en/developer/doctesting.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 5b4d56a549a..72d783b0d4d 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -1267,7 +1267,7 @@ framework. Here is a comprehensive list: chOMP``. If ``# optional`` or ``# needs`` is placed right after the ``sage:`` prompt, - it is a codeblock-scoped tag, which applies to all doctest lines until + it is a block-scoped tag, which applies to all doctest lines until a blank line is encountered. These tags can also be applied to an entire file. If one of the first 10 lines diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index d1fb2da53ae..c585f7e1afd 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -1516,7 +1516,7 @@ Managing ``# optional`` and ``# needs`` tags When a file uses a ``# sage.doctest: optional/needs FEATURE`` directive, the doctest fixer automatically removes the redundant ``# optional/needs FEATURE`` -tags from all ``sage:`` lines. Likewise, when a codeblock-scoped tag +tags from all ``sage:`` lines. Likewise, when a block-scoped tag ``sage: # optional/needs FEATURE`` is used, then the doctest fixer removes redundant tags from all doctests in this scope. For example:: From 636a05dd5b11c27170b13ef7fe3a0d232f83f258 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 8 Jul 2023 22:46:08 -0700 Subject: [PATCH 130/150] src/doc/en/developer/doctesting.rst: Fix typo --- src/doc/en/developer/doctesting.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index c585f7e1afd..98d9588d029 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -921,7 +921,7 @@ If you want Sage to detect external software or other capabilities (such as magma, latex, internet) automatically and run all of the relevant tests, then add ``external``:: - [roed@sage sage-6.0]$ $ ./sage -t --optional=external src/sage/rings/real_mpfr.pyx + [roed@sage sage-6.0]$ ./sage -t --optional=external src/sage/rings/real_mpfr.pyx Running doctests with ID 2016-03-16-14-10-21-af2ebb67. Using --optional=external External software to be detected: cplex,gurobi,internet,latex,macaulay2,magma,maple,mathematica,matlab,octave,scilab From 2625d2af7e702533c573f0bc08d66aa90c80f132 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 8 Jul 2023 23:19:14 -0700 Subject: [PATCH 131/150] src/sage/rings/polynomial/polynomial_ring.py: Use some more block-scoped tags --- src/sage/rings/polynomial/polynomial_ring.py | 104 ++++++++++--------- 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 03c4574bd4f..5e4a52943fc 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -89,17 +89,18 @@ different base rings. In that situation, coercion works by means of the :func:`~sage.categories.pushout.pushout` formalism:: - sage: R. = PolynomialRing(GF(5), sparse=True) # needs sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: R. = PolynomialRing(GF(5), sparse=True) sage: S. = PolynomialRing(ZZ) - sage: R.has_coerce_map_from(S) # needs sage.rings.finite_rings + sage: R.has_coerce_map_from(S) False - sage: S.has_coerce_map_from(R) # needs sage.rings.finite_rings + sage: S.has_coerce_map_from(R) False - sage: S.0 + R.0 # needs sage.rings.finite_rings + sage: S.0 + R.0 2*x - sage: (S.0 + R.0).parent() # needs sage.rings.finite_rings + sage: (S.0 + R.0).parent() Univariate Polynomial Ring in x over Finite Field of size 5 - sage: (S.0 + R.0).parent().is_sparse() # needs sage.rings.finite_rings + sage: (S.0 + R.0).parent().is_sparse() False Similarly, there is a coercion from the (non-default) NTL @@ -838,19 +839,20 @@ def _magma_init_(self, magma): EXAMPLES:: + sage: # optional - magma sage: R = QQ['y'] - sage: R._magma_init_(magma) # optional - magma + sage: R._magma_init_(magma) 'SageCreateWithNames(PolynomialRing(_sage_ref...),["y"])' - sage: S = magma(R) # optional - magma - sage: S # optional - magma + sage: S = magma(R) + sage: S Univariate Polynomial Ring in y over Rational Field - sage: S.1 # optional - magma + sage: S.1 y - sage: magma(PolynomialRing(GF(7), 'x')) # optional - magma # needs sage.rings.finite_rings + sage: magma(PolynomialRing(GF(7), 'x')) # needs sage.rings.finite_rings Univariate Polynomial Ring in x over GF(7) - sage: magma(PolynomialRing(GF(49,'a'), 'x')) # optional - magma, needs sage.rings.finite_rings + sage: magma(PolynomialRing(GF(49,'a'), 'x')) # needs sage.rings.finite_rings Univariate Polynomial Ring in x over GF(7^2) - sage: magma(PolynomialRing(PolynomialRing(ZZ,'w'), 'x')) # optional - magma + sage: magma(PolynomialRing(PolynomialRing(ZZ,'w'), 'x')) Univariate Polynomial Ring in x over Univariate Polynomial Ring in w over Integer Ring Watch out, Magma has different semantics from Sage, i.e., in Magma @@ -873,7 +875,7 @@ def _magma_init_(self, magma): sage: k. = GF(9) # needs sage.rings.finite_rings sage: R. = k[] # needs sage.rings.finite_rings - sage: magma(a^2*x^3 + (a+1)*x + a) # optional - magma # needs sage.rings.finite_rings + sage: magma(a^2*x^3 + (a+1)*x + a) # optional - magma, needs sage.rings.finite_rings a^2*x^3 + a^2*x + a """ B = magma(self.base_ring()) @@ -902,13 +904,14 @@ def _gap_init_(self, gap=None): A univariate polynomial ring over a multivariate polynomial ring over a number field:: + sage: # needs sage.rings.number_field sage: Q. = QQ[] - sage: K. = NumberField(t^2 + t + 1) # needs sage.rings.number_field - sage: P. = K[] # needs sage.rings.number_field - sage: S. = P[] # needs sage.rings.number_field - sage: gap(S) # needs sage.libs.gap sage.rings.number_field + sage: K. = NumberField(t^2 + t + 1) + sage: P. = K[] + sage: S. = P[] + sage: gap(S) # needs sage.libs.gap PolynomialRing( PolynomialRing( , ["x", "y"] ), ["z"] ) - sage: gap(S) is gap(S) # needs sage.libs.gap sage.rings.number_field + sage: gap(S) is gap(S) # needs sage.libs.gap True """ if gap is not None: @@ -1172,6 +1175,7 @@ def cyclotomic_polynomial(self, n): x^4 + 1 sage: R.cyclotomic_polynomial(12) # needs sage.libs.pari x^4 - x^2 + 1 + sage: S = PolynomialRing(FiniteField(7), 'x') # needs sage.rings.finite_rings sage: S.cyclotomic_polynomial(12) # needs sage.rings.finite_rings x^4 + 6*x^2 + 1 @@ -1312,16 +1316,18 @@ def krull_dimension(self): sage: R. = QQ[] sage: R.krull_dimension() 1 - sage: R. = GF(9, 'a')[]; R # needs sage.rings.finite_rings + + sage: # needs sage.rings.finite_rings + sage: R. = GF(9, 'a')[]; R Univariate Polynomial Ring in z over Finite Field in a of size 3^2 - sage: R.krull_dimension() # needs sage.rings.finite_rings + sage: R.krull_dimension() 1 - sage: S. = R[] # needs sage.rings.finite_rings - sage: S.krull_dimension() # needs sage.rings.finite_rings + sage: S. = R[] + sage: S.krull_dimension() 2 - sage: for n in range(10): # needs sage.rings.finite_rings + sage: for n in range(10): ....: S = PolynomialRing(S, 'w') - sage: S.krull_dimension() # needs sage.rings.finite_rings + sage: S.krull_dimension() 12 """ return self.base_ring().krull_dimension() + 1 @@ -1892,20 +1898,20 @@ def weil_polynomials(self, d, q, sign=1, lead=1): EXAMPLES:: + sage: # needs sage.libs.flint sage: R. = ZZ[] - sage: L = R.weil_polynomials(4, 2) # needs sage.libs.flint - sage: len(L) # needs sage.libs.flint + sage: L = R.weil_polynomials(4, 2) + sage: len(L) 35 - sage: L[9] # needs sage.libs.flint + sage: L[9] T^4 + T^3 + 2*T^2 + 2*T + 4 - sage: all(p.is_weil_polynomial() for p in L) # needs sage.libs.flint + sage: all(p.is_weil_polynomial() for p in L) True Setting multiple leading coefficients:: sage: R. = QQ[] - sage: l = R.weil_polynomials(4, 2, lead=((1,0), (2,4), (1,2))) # needs sage.libs.flint - sage: l # needs sage.libs.flint + sage: l = R.weil_polynomials(4, 2, lead=((1,0), (2,4), (1,2))); l # needs sage.libs.flint [T^4 + 2*T^3 + 5*T^2 + 4*T + 4, T^4 + 2*T^3 + 3*T^2 + 4*T + 4, T^4 - 2*T^3 + 5*T^2 - 4*T + 4, @@ -2252,15 +2258,17 @@ def lagrange_polynomial(self, points, algorithm="divided_difference", previous_r -2 sage: f(-4) 9 - sage: R = PolynomialRing(GF(2**3, 'a'), 'x') # needs sage.rings.finite_rings - sage: a = R.base_ring().gen() # needs sage.rings.finite_rings - sage: f = R.lagrange_polynomial([(a^2+a, a), (a, 1), (a^2, a^2+a+1)]); f # needs sage.rings.finite_rings + + sage: # needs sage.rings.finite_rings + sage: R = PolynomialRing(GF(2**3, 'a'), 'x') + sage: a = R.base_ring().gen() + sage: f = R.lagrange_polynomial([(a^2+a, a), (a, 1), (a^2, a^2+a+1)]); f a^2*x^2 + a^2*x + a^2 - sage: f(a^2 + a) # needs sage.rings.finite_rings + sage: f(a^2 + a) a - sage: f(a) # needs sage.rings.finite_rings + sage: f(a) 1 - sage: f(a^2) # needs sage.rings.finite_rings + sage: f(a^2) a^2 + a + 1 Now use a memory efficient version of Neville's method:: @@ -2272,6 +2280,7 @@ def lagrange_polynomial(self, points, algorithm="divided_difference", previous_r -11/7*x + 19/7, -17/42*x^2 - 83/42*x + 53/7, -23/84*x^3 - 11/84*x^2 + 13/7*x + 1] + sage: R = PolynomialRing(GF(2**3, 'a'), 'x') # needs sage.rings.finite_rings sage: a = R.base_ring().gen() # needs sage.rings.finite_rings sage: R.lagrange_polynomial([(a^2+a, a), (a, 1), (a^2, a^2+a+1)], # needs sage.rings.finite_rings @@ -2286,10 +2295,12 @@ def lagrange_polynomial(self, points, algorithm="divided_difference", previous_r sage: R.lagrange_polynomial([(0,1), (2,2), (3,-2), (-4,9)], ....: algorithm="neville", previous_row=p)[-1] -23/84*x^3 - 11/84*x^2 + 13/7*x + 1 - sage: R = PolynomialRing(GF(2**3, 'a'), 'x') # needs sage.rings.finite_rings - sage: a = R.base_ring().gen() # needs sage.rings.finite_rings - sage: p = R.lagrange_polynomial([(a^2+a, a), (a, 1)], algorithm="neville") # needs sage.rings.finite_rings - sage: R.lagrange_polynomial([(a^2+a, a), (a, 1), (a^2, a^2+a+1)], # needs sage.rings.finite_rings + + sage: # needs sage.rings.finite_rings + sage: R = PolynomialRing(GF(2**3, 'a'), 'x') + sage: a = R.base_ring().gen() + sage: p = R.lagrange_polynomial([(a^2+a, a), (a, 1)], algorithm="neville") + sage: R.lagrange_polynomial([(a^2+a, a), (a, 1), (a^2, a^2+a+1)], ....: algorithm="neville", previous_row=p)[-1] a^2*x^2 + a^2*x + a^2 @@ -2469,13 +2480,13 @@ def __init__(self, base_ring, name="x", element_class=None, implementation=None) """ TESTS:: - sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_finite_field # needs sage.rings.finite_rings - sage: R = PolynomialRing_dense_finite_field(GF(5), implementation='generic') # needs sage.rings.finite_rings - sage: type(R(0)) # needs sage.rings.finite_rings + sage: from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_finite_field + sage: R = PolynomialRing_dense_finite_field(GF(5), implementation='generic') + sage: type(R(0)) sage: S = PolynomialRing_dense_finite_field(GF(25, 'a'), implementation='NTL') # needs sage.rings.finite_rings - sage: type(S(0)) # needs sage.rings.finite_rings + sage: type(S(0)) # needs sage.rings.finite_rings sage: S = PolynomialRing_dense_finite_field(GF(64), implementation='superfast') # needs sage.rings.finite_rings @@ -3286,8 +3297,7 @@ def __init__(self, base_ring, name="x", implementation=None, element_class=None, sage: type(P.gen()) # needs sage.rings.finite_rings - sage: P = PolynomialRing_dense_mod_p(GF(9223372036854775837), 'x') # needs sage.rings.finite_rings - sage: P # needs sage.rings.finite_rings + sage: P = PolynomialRing_dense_mod_p(GF(9223372036854775837), 'x'); P # needs sage.rings.finite_rings Univariate Polynomial Ring in x over Finite Field of size 9223372036854775837 (using NTL) sage: type(P.gen()) # needs sage.rings.finite_rings From d5672a12d1e1e36bd732efb3125fc5d35d48ca69 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 9 Jul 2023 09:51:20 -0700 Subject: [PATCH 132/150] src/sage/combinat/finite_state_machine.py: Move import of QQbar into method --- src/sage/combinat/finite_state_machine.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index decef1d4479..35de4581bd0 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -944,7 +944,6 @@ from sage.misc.latex import latex from sage.misc.verbose import verbose from sage.misc.sageinspect import sage_getargspec -from sage.rings.qqbar import QQbar from sage.rings.integer_ring import ZZ from sage.rings.real_mpfr import RR from sage.structure.sage_object import SageObject @@ -9885,7 +9884,7 @@ def number_of_words(self, variable=None, from sage.arith.misc import binomial from sage.symbolic.ring import SR if base_ring is None: - base_ring = QQbar + from sage.rings.qqbar import QQbar as base_ring if variable is None: variable = SR.symbol('n') From 9132250a2d49428c687883d58416958f726c2ef5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 9 Jul 2023 09:52:00 -0700 Subject: [PATCH 133/150] src/sage/misc/latex_standalone.py: Use more block-scoped tags, align better --- src/sage/misc/latex_standalone.py | 207 +++++++++++++++++------------- 1 file changed, 118 insertions(+), 89 deletions(-) diff --git a/src/sage/misc/latex_standalone.py b/src/sage/misc/latex_standalone.py index e783c4000a6..3ed2476fe9a 100644 --- a/src/sage/misc/latex_standalone.py +++ b/src/sage/misc/latex_standalone.py @@ -156,35 +156,36 @@ tikzpicture code generated by Sage from some polyhedron:: sage: from sage.misc.latex_standalone import TikzPicture - sage: V = [[1,0,1],[1,0,0],[1,1,0],[0,0,-1],[0,1,0],[-1,0,0],[0,1,1],[0,0,1],[0,-1,0]] - sage: P = Polyhedron(vertices=V).polar() - sage: s = P.projection().tikz([674,108,-731],112, output_type='LatexExpr') - sage: t = TikzPicture(s) + sage: V = [[1,0,1], [1,0,0], [1,1,0], [0,0,-1], + ....: [0,1,0], [-1,0,0], [0,1,1], [0,0,1], [0,-1,0]] + sage: P = Polyhedron(vertices=V).polar() # needs sage.geometry.polyhedron + sage: s1 = P.projection().tikz([674,108,-731],112, output_type='LatexExpr') # needs sage.geometry.polyhedron sage.plot + sage: t1 = TikzPicture(s1) # needs sage.geometry.polyhedron sage.plot Open the image in a viewer (the returned value is a string giving the absolute path to the file in some temporary directory):: - sage: path_to_file = t.pdf() # not tested + sage: path_to_file = t1.pdf() # not tested # needs sage.geometry.polyhedron sage.plot Instead, you may save a pdf of the tikzpicture into a file of your choice (but this does not open the viewer):: - sage: _ = t.pdf('tikz_polytope.pdf') # not tested + sage: _ = t1.pdf('tikz_polytope.pdf') # not tested # needs sage.geometry.polyhedron sage.plot Opening the image in a viewer can be turned off:: - sage: _ = t.pdf(view=False) # long time (2s) # optional latex + sage: _ = t1.pdf(view=False) # long time (2s), optional - latex # needs sage.geometry.polyhedron sage.plot The same can be done with png format (translated from pdf with convert command which needs the installation of imagemagick):: - sage: _ = t.png(view=False) # long time (2s) # optional latex imagemagick + sage: _ = t1.png(view=False) # long time (2s), optional - imagemagick latex, needs sage.geometry.polyhedron sage.plot The string representation gives the header (5 lines) and tail (5 lines) of the tikzpicture. In Jupyter, it will instead use rich representation and show the image directly below the cell in png or svg format:: - sage: t + sage: t1 # needs sage.geometry.polyhedron sage.plot \documentclass[tikz]{standalone} \begin{document} \begin{tikzpicture}% @@ -201,21 +202,24 @@ Use ``print(t)`` to see the complete content of the file:: - sage: print(t) # not tested + sage: print(t1) # not tested # needs sage.geometry.polyhedron sage.plot Adding a border in the options avoids cropping the vertices of a graph:: - sage: g = graphs.PetersenGraph() # needs sage.graphs - sage: s = latex(g) # takes 3s but the result is cached # optional - latex, needs sage.graphs - sage: t = TikzPicture(s, standalone_config=["border=4mm"], usepackage=['tkz-graph']) # optional - latex, needs sage.graphs - sage: _ = t.pdf() # not tested + sage: # needs sage.graphs + sage: g = graphs.PetersenGraph() + sage: s2 = latex(g) # takes 3s but the result is cached # optional - latex + sage: t2 = TikzPicture(s2, standalone_config=["border=4mm"], # optional - latex + ....: usepackage=['tkz-graph']) + sage: _ = t2.pdf() # not tested The current latex representation of a transducer is a tikzpicture using the tikz library automata. The string can be used as input:: - sage: s = latex(transducers.GrayCode()) # needs sage.combinat - sage: t = TikzPicture(s, usetikzlibrary=['automata']) # needs sage.combinat - sage: _ = t.pdf(view=False) # long time (2s), optional - latex, needs sage.combinat + sage: # needs sage.graphs sage.modules + sage: s3 = latex(transducers.GrayCode()) + sage: t3 = TikzPicture(s3, usetikzlibrary=['automata']) + sage: _ = t3.pdf(view=False) # long time (2s) # optional - latex AUTHORS: @@ -274,7 +278,8 @@ class Standalone(SageObject): :: - sage: t = Standalone(content, standalone_config=["border=4mm"], usepackage=['amsmath']) + sage: t = Standalone(content, standalone_config=["border=4mm"], + ....: usepackage=['amsmath']) sage: t \documentclass{standalone} \standaloneconfig{border=4mm} @@ -645,28 +650,28 @@ def pdf(self, filename=None, view=True, program=None): sage: from sage.misc.latex_standalone import Standalone sage: t = Standalone('Hello World') - sage: _ = t.pdf(view=False) # long time (1s) # optional latex + sage: _ = t.pdf(view=False) # long time (1s) # optional - latex Same for instances of :class:`TikzPicture`:: sage: from sage.misc.latex_standalone import TikzPicture sage: s = "\\begin{tikzpicture}\n\\draw (0,0) -- (1,1);\n\\end{tikzpicture}" sage: t = TikzPicture(s) - sage: _ = t.pdf(view=False) # not tested + sage: _ = t.pdf(view=False) # not tested A filename may be provided where to save the file, in which case the viewer does not open the file:: sage: from sage.misc.temporary_file import tmp_filename sage: filename = tmp_filename('temp','.pdf') - sage: path_to_file = t.pdf(filename) # long time (1s) # optional latex - sage: path_to_file[-4:] # long time (fast) # optional latex + sage: path_to_file = t.pdf(filename) # long time (1s) # optional - latex + sage: path_to_file[-4:] # long time (fast) # optional - latex '.pdf' The filename may contain spaces:: sage: filename = tmp_filename('filename with spaces','.pdf') - sage: path_to_file = t.pdf(filename) # long time (1s) # optional latex + sage: path_to_file = t.pdf(filename) # long time (1s) # optional - latex TESTS: @@ -675,7 +680,7 @@ def pdf(self, filename=None, view=True, program=None): sage: s = "\\begin{tikzpicture}\n\\draw (0,0) -- (1,1);\n\\end{tikzpicture}" sage: s_missing_last_character = s[:-1] sage: t = TikzPicture(s_missing_last_character) - sage: _ = t.pdf() # optional latex + sage: _ = t.pdf() # optional - latex Traceback (most recent call last): ... CalledProcessError: Command '['...latex', '-interaction=nonstopmode', @@ -769,28 +774,28 @@ def dvi(self, filename=None, view=True, program='latex'): sage: from sage.misc.latex_standalone import Standalone sage: t = Standalone('Hello World') - sage: _ = t.dvi(view=False) # long time (1s) # optional latex + sage: _ = t.dvi(view=False) # long time (1s) # optional - latex Same for instances of :class:`TikzPicture`:: sage: from sage.misc.latex_standalone import TikzPicture sage: s = "\\begin{tikzpicture}\n\\draw (0,0) -- (1,1);\n\\end{tikzpicture}" sage: t = TikzPicture(s) - sage: _ = t.dvi(view=False) # not tested + sage: _ = t.dvi(view=False) # not tested A filename may be provided where to save the file, in which case the viewer does not open the file:: sage: from sage.misc.temporary_file import tmp_filename sage: filename = tmp_filename('temp','.dvi') - sage: path_to_file = t.dvi(filename) # long time (1s) # optional latex - sage: path_to_file[-4:] # long time (fast) # optional latex + sage: path_to_file = t.dvi(filename) # long time (1s) # optional - latex + sage: path_to_file[-4:] # long time (fast) # optional - latex '.dvi' The filename may contain spaces:: sage: filename = tmp_filename('filename with spaces','.dvi') - sage: path_to_file = t.dvi(filename) # long time (1s) # optional latex + sage: path_to_file = t.dvi(filename) # long time (1s) # optional - latex TESTS: @@ -799,7 +804,7 @@ def dvi(self, filename=None, view=True, program='latex'): sage: s = "\\begin{tikzpicture}\n\\draw (0,0) -- (1,1);\n\\end{tikzpicture}" sage: s_missing_last_character = s[:-1] sage: t = TikzPicture(s_missing_last_character) - sage: _ = t.dvi() # optional latex + sage: _ = t.dvi() # optional - latex Traceback (most recent call last): ... CalledProcessError: Command '['latex', '-interaction=nonstopmode', @@ -897,21 +902,21 @@ def png(self, filename=None, density=150, view=True): sage: from sage.misc.latex_standalone import Standalone sage: t = Standalone('Hello World') - sage: _ = t.png(view=False) # long time (1s) # optional latex imagemagick + sage: _ = t.png(view=False) # long time (1s) # optional - latex imagemagick Same for instances of :class:`TikzPicture`:: sage: from sage.misc.latex_standalone import TikzPicture sage: s = "\\begin{tikzpicture}\n\\draw (0,0) -- (1,1);\n\\end{tikzpicture}" sage: t = TikzPicture(s) - sage: _ = t.png(view=False) # not tested + sage: _ = t.png(view=False) # not tested :: sage: from sage.misc.temporary_file import tmp_filename sage: filename = tmp_filename('temp','.png') - sage: path_to_file = t.png(filename) # long time (1s) # optional latex imagemagick - sage: path_to_file[-4:] # long time (fast) # optional latex imagemagick + sage: path_to_file = t.png(filename) # long time (1s) # optional - latex imagemagick + sage: path_to_file[-4:] # long time (fast) # optional - latex imagemagick '.png' """ @@ -999,11 +1004,13 @@ def svg(self, filename=None, view=True, program='pdftocairo'): sage: from sage.misc.temporary_file import tmp_filename sage: filename = tmp_filename('temp', '.svg') - sage: path_to_file = t.svg(filename, program='pdf2svg') # long time (1s) # optional latex pdf2svg - sage: path_to_file[-4:] # long time (fast) # optional latex pdf2svg + sage: path_to_file = t.svg(filename, # long time (1s) # optional - latex pdf2svg + ....: program='pdf2svg') + sage: path_to_file[-4:] # long time (fast) # optional - latex pdf2svg '.svg' - sage: path_to_file = t.svg(filename, program='pdftocairo') # long time (1s) # optional latex pdftocairo - sage: path_to_file[-4:] # long time (fast) # optional latex pdftocairo + sage: path_to_file = t.svg(filename, # long time (1s) # optional - latex pdftocairo + ....: program='pdftocairo') + sage: path_to_file[-4:] # long time (fast) # optional - latex pdftocairo '.svg' """ @@ -1099,11 +1106,13 @@ def eps(self, filename=None, view=True, program='dvips'): sage: from sage.misc.temporary_file import tmp_filename sage: filename = tmp_filename('temp', '.eps') - sage: path_to_file = t.eps(filename, program='dvips') # long time (1s) # optional latex dvips - sage: path_to_file[-4:] # long time (fast) # optional latex dvips + sage: path_to_file = t.eps(filename, # long time (1s) # optional - latex dvips + ....: program='dvips') + sage: path_to_file[-4:] # long time (fast) # optional - latex dvips '.eps' - sage: path_to_file = t.eps(filename, program='pdftocairo') # long time (1s) # optional latex pdftocairo - sage: path_to_file[-4:] # long time (fast) # optional latex pdftocairo + sage: path_to_file = t.eps(filename, # long time (1s) # optional - latex pdftocairo + ....: program='pdftocairo') + sage: path_to_file[-4:] # long time (fast) # optional - latex pdftocairo '.eps' TESTS: @@ -1278,9 +1287,9 @@ def save(self, filename, **kwds): sage: from sage.misc.latex_standalone import Standalone sage: t = Standalone('Hello World') sage: filename = tmp_filename('temp','.pdf') - sage: t.save(filename) # long time (1s) # optional latex + sage: t.save(filename) # long time (1s) # optional - latex sage: filename = tmp_filename('temp','.eps') - sage: t.save(filename) # long time (1s) # optional latex dvips + sage: t.save(filename) # long time (1s) # optional - latex dvips """ ext = os.path.splitext(filename)[1].lower() @@ -1356,9 +1365,10 @@ class TikzPicture(Standalone): sage: # needs sage.graphs sage: g = graphs.PetersenGraph() - sage: s = latex(g) # optional - latex - sage: t = TikzPicture(s, standalone_config=["border=4mm"], usepackage=['tkz-graph']) # optional - latex - sage: _ = t.pdf(view=False) # long time (2s), optional - latex latex_package_tkz_graph + sage: s = latex(g) # optional - latex + sage: t = TikzPicture(s, standalone_config=["border=4mm"], # optional - latex + ....: usepackage=['tkz-graph']) + sage: _ = t.pdf(view=False) # long time (2s), optional - latex latex_package_tkz_graph Here are standalone configurations, packages, tikz libraries and macros that can be set:: @@ -1373,7 +1383,7 @@ class TikzPicture(Standalone): sage: s = "\\begin{tikzpicture}\n\\draw (0,0) -- (1,1);\n\\end{tikzpicture}" sage: t = TikzPicture(s, standalone_config=options, usepackage=usepackage, ....: usetikzlibrary=tikzlib, macros=macros) - sage: _ = t.pdf(view=False) # long time (2s) # optional latex + sage: _ = t.pdf(view=False) # long time (2s), optional - latex """ def __init__(self, content, standalone_config=None, usepackage=None, usetikzlibrary=None, macros=None, use_sage_preamble=False): @@ -1455,30 +1465,34 @@ def from_dot_string(cls, dotdata, prog='dot'): EXAMPLES:: + sage: # needs sage.graphs sage: from sage.misc.latex_standalone import TikzPicture - sage: G = graphs.PetersenGraph() # needs sage.graphs - sage: dotdata = G.graphviz_string() # needs sage.graphs - sage: tikz = TikzPicture.from_dot_string(dotdata) # long time (3s), optional - dot2tex graphviz, needs sage.graphs + sage: G = graphs.PetersenGraph() + sage: dotdata = G.graphviz_string() + sage: tikz = TikzPicture.from_dot_string(dotdata) # long time (3s), optional - dot2tex graphviz sage: _ = tikz.pdf() # not tested :: - sage: dotdata = G.graphviz_string(labels='latex') # needs sage.graphs - sage: tikz = TikzPicture.from_dot_string(dotdata) # long time (3s), optional - dot2tex graphviz, needs sage.graphs + sage: # needs sage.graphs + sage: dotdata = G.graphviz_string(labels='latex') + sage: tikz = TikzPicture.from_dot_string(dotdata) # long time (3s), optional - dot2tex graphviz sage: _ = tikz.pdf() # not tested :: + sage: # needs sage.combinat sage.graphs sage.groups sage: W = CoxeterGroup(["A",2]) - sage: G = W.cayley_graph() # needs sage.graphs - sage: dotdata = G.graphviz_string() # needs sage.graphs - sage: tikz = TikzPicture.from_dot_string(dotdata) # long time (3s), optional - dot2tex graphviz, needs sage.graphs + sage: G = W.cayley_graph() + sage: dotdata = G.graphviz_string() + sage: tikz = TikzPicture.from_dot_string(dotdata) # long time (3s), optional - dot2tex graphviz sage: _ = tikz.pdf() # not tested :: - sage: dotdata = G.graphviz_string(labels='latex') # needs sage.graphs - sage: tikz = TikzPicture.from_dot_string(dotdata) # long time (3s), optional - dot2tex graphviz, needs sage.graphs + sage: # needs sage.combinat sage.graphs sage.groups + sage: dotdata = G.graphviz_string(labels='latex') + sage: tikz = TikzPicture.from_dot_string(dotdata) # long time (3s), optional - dot2tex graphviz sage: _ = tikz.pdf() # not tested """ @@ -1540,9 +1554,10 @@ def from_graph(cls, graph, merge_multiedges=True, EXAMPLES:: + sage: # needs sage.graphs sage: from sage.misc.latex_standalone import TikzPicture - sage: g = graphs.PetersenGraph() # needs sage.graphs - sage: tikz = TikzPicture.from_graph(g) # optional - dot2tex graphviz, needs sage.graphs + sage: g = graphs.PetersenGraph() + sage: tikz = TikzPicture.from_graph(g) # optional - dot2tex graphviz doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. See https://github.com/sagemath/sage/issues/20343 for details. @@ -1550,7 +1565,9 @@ def from_graph(cls, graph, merge_multiedges=True, Using ``prog``:: - sage: tikz = TikzPicture.from_graph(g, prog='neato', color_by_label=True) # long time (3s), optional - dot2tex graphviz, needs sage.graphs + sage: # needs sage.graphs + sage: tikz = TikzPicture.from_graph(g, prog='neato', # long time (3s), optional - dot2tex graphviz + ....: color_by_label=True) sage: _ = tikz.pdf() # not tested Using ``rankdir``:: @@ -1560,18 +1577,21 @@ def from_graph(cls, graph, merge_multiedges=True, Using ``merge_multiedges``:: + sage: # needs sage.graphs sage.modules sage.symbolic sage: alpha = var('alpha') - sage: m = matrix(2,range(4)); m.set_immutable() - sage: G = DiGraph([(0,1,alpha), (0,1,0), (0,2,9), (0,2,m)], multiedges=True) # needs sage.graphs - sage: tikz = TikzPicture.from_graph(G, merge_multiedges=True) # optional - dot2tex graphviz, needs sage.graphs + sage: m = matrix(2, range(4)); m.set_immutable() + sage: G = DiGraph([(0,1,alpha), (0,1,0), (0,2,9), (0,2,m)], + ....: multiedges=True) + sage: tikz = TikzPicture.from_graph(G, merge_multiedges=True) # optional - dot2tex graphviz sage: _ = tikz.pdf() # not tested Using ``merge_multiedges`` with ``merge_label_function``:: + sage: # needs sage.graphs sage: fn = lambda L: LatexExpr(','.join(map(str, L))) sage: edges = [(0,1,'a'), (0,1,'b'), (0,2,'c'), (0,2,'d')] - sage: G = DiGraph(edges, multiedges=True) # needs sage.graphs - sage: tikz = TikzPicture.from_graph(G, # optional - dot2tex graphviz, needs sage.graphs + sage: G = DiGraph(edges, multiedges=True) + sage: tikz = TikzPicture.from_graph(G, # optional - dot2tex graphviz ....: merge_multiedges=True, merge_label_function=fn) sage: _ = tikz.pdf() # not tested @@ -1583,24 +1603,25 @@ def from_graph(cls, graph, merge_multiedges=True, sage: a = S((0,1,3,0,0)) sage: b = S((0,2,4,1,0)) sage: roots = [I] - sage: succ = lambda v:[v*a,v*b,a*v,b*v] + sage: succ = lambda v: [v*a,v*b,a*v,b*v] sage: R = RecursivelyEnumeratedSet(roots, succ) sage: G = R.to_digraph() # needs sage.graphs sage: G # needs sage.graphs Looped multi-digraph on 27 vertices sage: C = G.strongly_connected_components() # needs sage.graphs - sage: tikz = TikzPicture.from_graph(G, # optional - dot2tex graphviz, needs sage.graphs + sage: tikz = TikzPicture.from_graph(G, # optional - dot2tex graphviz, needs sage.graphs ....: merge_multiedges=False, subgraph_clusters=C) sage: _ = tikz.pdf() # not tested An example coming from ``graphviz_string`` documentation in SageMath:: - sage: f(x) = -1 / x # needs sage.symbolic - sage: g(x) = 1 / (x + 1) # needs sage.symbolic - sage: G = DiGraph() # needs sage.graphs sage.symbolic - sage: G.add_edges((i, f(i), f) for i in (1, 2, 1/2, 1/4)) # needs sage.graphs sage.symbolic - sage: G.add_edges((i, g(i), g) for i in (1, 2, 1/2, 1/4)) # needs sage.graphs sage.symbolic - sage: tikz = TikzPicture.from_graph(G) # optional - dot2tex graphviz, needs sage.graphs sage.symbolic + sage: # needs sage.graphs sage.symbolic + sage: f(x) = -1 / x + sage: g(x) = 1 / (x + 1) + sage: G = DiGraph() + sage: G.add_edges((i, f(i), f) for i in (1, 2, 1/2, 1/4)) + sage: G.add_edges((i, g(i), g) for i in (1, 2, 1/2, 1/4)) + sage: tikz = TikzPicture.from_graph(G) # optional - dot2tex graphviz sage: _ = tikz.pdf() # not tested sage: def edge_options(data): ....: u, v, label = data @@ -1610,7 +1631,7 @@ def from_graph(cls, graph, merge_multiedges=True, ....: if (u,v) == (1, -1): options["label_style"] = "latex" ....: if (u,v) == (1, 1/2): options["dir"] = "back" ....: return options - sage: tikz = TikzPicture.from_graph(G, edge_options=edge_options) # optional - dot2tex graphviz, needs sage.graphs sage.symbolic + sage: tikz = TikzPicture.from_graph(G, edge_options=edge_options) # optional - dot2tex graphviz sage: _ = tikz.pdf() # not tested """ @@ -1670,20 +1691,23 @@ def from_graph_with_pos(cls, graph, scale=1, merge_multiedges=True, EXAMPLES:: sage: from sage.misc.latex_standalone import TikzPicture - sage: g = graphs.PetersenGraph() # needs sage.graphs - sage: tikz = TikzPicture.from_graph_with_pos(g) # needs sage.graphs + + sage: # needs sage.graphs + sage: g = graphs.PetersenGraph() + sage: tikz = TikzPicture.from_graph_with_pos(g) doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. See https://github.com/sagemath/sage/issues/20343 for details. :: + sage: # needs sage.graphs sage: edges = [(0,0,'a'),(0,1,'b'),(0,1,'c')] sage: kwds = dict(format='list_of_edges', loops=True, multiedges=True) - sage: G = DiGraph(edges, **kwds) # needs sage.graphs - sage: G.set_pos({0:(0,0), 1:(1,0)}) # needs sage.graphs - sage: f = lambda label:','.join(label) # needs sage.graphs - sage: TikzPicture.from_graph_with_pos(G, merge_label_function=f) # needs sage.graphs + sage: G = DiGraph(edges, **kwds) + sage: G.set_pos({0:(0,0), 1:(1,0)}) + sage: f = lambda label:','.join(label) + sage: TikzPicture.from_graph_with_pos(G, merge_label_function=f) \documentclass[tikz]{standalone} \standaloneconfig{border=4mm} \begin{document} @@ -1701,10 +1725,11 @@ def from_graph_with_pos(cls, graph, scale=1, merge_multiedges=True, TESTS:: + sage: # needs sage.graphs sage: edges = [(0,0,'a'),(0,1,'b'),(0,1,'c')] sage: kwds = dict(format='list_of_edges', loops=True, multiedges=True) - sage: G = DiGraph(edges, **kwds) # needs sage.graphs - sage: TikzPicture.from_graph_with_pos(G) # needs sage.graphs + sage: G = DiGraph(edges, **kwds) + sage: TikzPicture.from_graph_with_pos(G) Traceback (most recent call last): ... ValueError: vertex positions need to be set first @@ -1798,21 +1823,25 @@ def from_poset(cls, poset, **kwds): EXAMPLES:: sage: from sage.misc.latex_standalone import TikzPicture - sage: P = posets.PentagonPoset() # needs sage.combinat - sage: tikz = TikzPicture.from_poset(P) # optional - dot2tex graphviz, needs sage.combinat + + sage: # needs sage.graphs sage.modules + sage: P = posets.PentagonPoset() + sage: tikz = TikzPicture.from_poset(P) # optional - dot2tex graphviz doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. See https://github.com/sagemath/sage/issues/20343 for details. :: - sage: tikz = TikzPicture.from_poset(P, prog='neato', color_by_label=True) # long time (3s), optional - dot2tex, needs sage.combinat + sage: tikz = TikzPicture.from_poset(P, prog='neato', # long time (3s), optional - dot2tex, needs sage.graphs sage.modules + ....: color_by_label=True) :: - sage: P = posets.SymmetricGroupWeakOrderPoset(4) # needs sage.combinat - sage: tikz = TikzPicture.from_poset(P) # long time (4s), optional - dot2tex graphviz, needs sage.combinat - sage: tikz = TikzPicture.from_poset(P, prog='neato') # long time (4s), optional - dot2tex graphviz, needs sage.combinat + sage: # needs sage.graphs + sage: P = posets.SymmetricGroupWeakOrderPoset(4) + sage: tikz = TikzPicture.from_poset(P) # long time (4s), optional - dot2tex graphviz + sage: tikz = TikzPicture.from_poset(P, prog='neato') # long time (4s), optional - dot2tex graphviz """ graph = poset.hasse_diagram() return cls.from_graph(graph, **kwds) From eba5b8015bf7f83cbaac7683884613a7beb4b7e4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 9 Jul 2023 09:53:33 -0700 Subject: [PATCH 134/150] src/sage/features/sagemath.py: Decapitalize some terms --- src/sage/features/sagemath.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index d3c9d33132d..f7c3c0749ee 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -729,8 +729,8 @@ class sage__rings__number_field(JoinFeature): sage: CC(zeta) 0.913545457642601 + 0.406736643075800*I - Doctests that make use of the Algebraic Field ``QQbar``, the Algebraic Real Field ``AA``, - or the Universal Cyclotomic Field should be marked likewise:: + Doctests that make use of the algebraic field ``QQbar``, the algebraic real field ``AA``, + or the universal cyclotomic field should be marked likewise:: sage: # needs sage.rings.number_field sage: AA(-1)^(1/3) From 4ad63457bba55a15e3347704bc3fe22b7f75b1e8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 9 Jul 2023 15:37:02 -0700 Subject: [PATCH 135/150] src/doc/en/developer/coding_basics.rst: Add example for unbreakable doctest output --- src/doc/en/developer/coding_basics.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 72d783b0d4d..091cb47283b 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -1000,9 +1000,6 @@ written. sage: from sage.rings.polynomial.multi_polynomial_ring import MPolynomialRing_polydict, MPolynomialRing_polydict_domain # this is fine - - If there is no whitespace in the doctest output where you could wrap the line, - do not add such whitespace. Just don't wrap the line. - - Wrap and indent long output to maximize readability in the source code and in the HTML output. But do not wrap strings:: @@ -1018,6 +1015,14 @@ written. sage: U._repr_() # this is fine 'Quasi-projective subscheme X - Y of Projective Space of dimension 2 over Integer Ring, where X is defined by:\n (no polynomials)\nand Y is defined by:\n x - y' + Also, if there is no whitespace in the doctest output where you could wrap the line, + do not add such whitespace. Just don't wrap the line:: + + sage: B47 = RibbonGraph(4,7, bipartite=True); B47 + Ribbon graph of genus 9 and 1 boundary components + sage: B47.sigma() # this is fine + (1,2,3,4,5,6,7)(8,9,10,11,12,13,14)(15,16,17,18,19,20,21)(22,23,24,25,26,27,28)(29,30,31,32)(33,34,35,36)(37,38,39,40)(41,42,43,44)(45,46,47,48)(49,50,51,52)(53,54,55,56) + - Doctest tags for modularization purposes such as ``# needs sage.modules`` (see :ref:`section-further_conventions`) should be aligned at column 88. Clean lines from consistent alignment help reduce visual clutter. From 627c9c0d0ef5ff01349cb2f4c804a54d2cee64d6 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 9 Jul 2023 19:07:01 -0700 Subject: [PATCH 136/150] git grep -l annotation src/sage/doctest src/bin | xargs sed -i.bak 's/annotation/tag/g' --- src/bin/sage-fixdoctests | 4 ++-- src/sage/doctest/forker.py | 6 +++--- src/sage/doctest/parsing.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index f26eefd769e..79bb1ab78d8 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -158,7 +158,7 @@ def process_block(block, src_in_lines, file_optional_tags): src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], add_tags=optional) - if m := re.search(r"annotation '# (optional|needs)[-: ]([^;']*)' may no longer be needed", block): + if m := re.search(r"tag '# (optional|needs)[-: ]([^;']*)' may no longer be needed", block): optional = m.group(2).split() src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], remove_tags=optional) @@ -376,7 +376,7 @@ for input, output in zip(inputs, outputs): if explanation or tag not in file_optional_tags} line = update_optional_tags(line, tags=persistent_optional_tags, force_rewrite='standard') if not line.rstrip(): - # persistent (block-scoped or file-scoped) annotation was removed, so remove the whole line + # persistent (block-scoped or file-scoped) tag was removed, so remove the whole line line = None else: tags = {tag: explanation diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 538a11442a5..b5521868dd5 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -737,7 +737,7 @@ def compiler(example): if check(example.want, got, self.optionflags): if probed_tags and probed_tags is not True: example.warnings.append( - f"The annotation '{unparse_optional_tags(probed_tags)}' " + f"The tag '{unparse_optional_tags(probed_tags)}' " f"may no longer be needed; these features are not present, " f"but we ran the doctest anyway as requested by --probe, " f"and it succeeded.") @@ -778,7 +778,7 @@ def compiler(example): elif check(example.exc_msg, exc_msg, self.optionflags): if probed_tags and probed_tags is not True: example.warnings.append( - f"The annotation '{unparse_optional_tags(example.probed_tags)}' " + f"The tag '{unparse_optional_tags(example.probed_tags)}' " f"may no longer be needed; these features are not present, " f"but we ran the doctest anyway as requested by --probe, " f"and it succeeded (raised the expected exception).") @@ -792,7 +792,7 @@ def compiler(example): self.optionflags): if probed_tags and probed_tags is not True: example.warnings.append( - f"The annotation '{unparse_optional_tags(example.probed_tags)}' " + f"The tag '{unparse_optional_tags(example.probed_tags)}' " f"may no longer be needed; these features are not present, " f"but we ran the doctest anyway as requested by --probe, " f"and it succeeded (raised an exception as expected).") diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index b876961ab1a..d61ce4b4060 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -228,7 +228,7 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): for m in tag_with_explanation_regex.finditer(m.group('tags'))}) if return_string_sans_tags: - is_persistent = tags and first_line_sans_comments.strip() == 'sage:' and not rest # persistent (block-scoped) annotation + is_persistent = tags and first_line_sans_comments.strip() == 'sage:' and not rest # persistent (block-scoped) tag return tags, (first_line + '\n' + rest%literals if rest is not None else first_line), is_persistent else: @@ -451,7 +451,7 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo | sage: aligned_with_above() # optional - 4ti2 | sage: also_already_aligned() # needs scipy - Rewriting a persistent (block-scoped) annotation:: + Rewriting a persistent (block-scoped) tag:: sage: print_with_ruler([ ....: update_optional_tags(' sage: #opt' 'ional:magma sage.symbolic', From 44e57525453c2854c2ac8abd48f10889862a102d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 9 Jul 2023 19:11:05 -0700 Subject: [PATCH 137/150] src/sage/doctest/parsing.py: Update docstring --- src/sage/doctest/parsing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index d61ce4b4060..17664ab8e96 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -118,7 +118,8 @@ def parse_optional_tags(string, *, return_string_sans_tags=False): - ``'py2'`` - ``'arb216'`` - ``'arb218'`` - - ``'optional - PKG_NAME...'`` -- the dictionary will just have the key ``'PKG_NAME'`` + - ``'optional - FEATURE...'`` or ``'needs FEATURE...'`` -- + the dictionary will just have the key ``'FEATURE'`` The values, if non-``None``, are strings with optional explanations for a tag, which may appear in parentheses after the tag in ``string``. From a3e61ad4e402a4f9fc2a69bc4aa11ae900ad3492 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 10 Jul 2023 09:14:36 -0700 Subject: [PATCH 138/150] sage -fixdoctests: Actually handle --long --- src/bin/sage-fixdoctests | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 79bb1ab78d8..35d7c2df96f 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -316,12 +316,13 @@ else: if not args.no_test: executable = f'{os.path.relpath(args.venv)}/bin/sage' if args.venv else 'sage' environment_args = f'--environment {args.environment} ' if args.environment != runtest_default_environment else '' + long_args = f'--long ' if args.long else '' probe_args = f'--probe {shlex.quote(args.probe)} ' if args.probe else '' lib_args = f'--only-lib ' if args.venv else '' doc_file = tmp_filename() if args.venv or environment_args: input = os.path.join(os.path.relpath(SAGE_ROOT), 'src', 'sage', 'version.py') - cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{lib_args}{shlex.quote(input)}' + cmdline = f'{shlex.quote(executable)} -t {environment_args}{long_args}{probe_args}{lib_args}{shlex.quote(input)}' print(f'Running "{cmdline}"') if status := os.waitstatus_to_exitcode(os.system(f'{cmdline} > {shlex.quote(doc_file)}')): print(f'Doctester exited with error status {status}') @@ -335,7 +336,7 @@ for input, output in zip(inputs, outputs): doc_out = '' else: # Run the doctester, putting the output of the test into sage's temporary directory - cmdline = f'{shlex.quote(executable)} -t {environment_args}{probe_args}{lib_args}{shlex.quote(input)}' + cmdline = f'{shlex.quote(executable)} -t {environment_args}{long_args}{probe_args}{lib_args}{shlex.quote(input)}' print(f'Running "{cmdline}"') os.system(f'{cmdline} > {shlex.quote(doc_file)}') From 601918156192229db940a83526e2d484213fbd1f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 10 Jul 2023 09:20:01 -0700 Subject: [PATCH 139/150] src/sage/doctest/parsing.py: Fix docstring markup --- src/sage/doctest/parsing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 17664ab8e96..ad4db8beaf8 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -426,7 +426,7 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo When no tags are changed, by default, the unchanged input is returned. We can force a rewrite; unconditionally or whenever standard tags are involved. But even when forced, if comments are already aligned at one of the standard alignment columns, - this alignment is kept even if we would normally realign farther to the left. + this alignment is kept even if we would normally realign farther to the left:: sage: print_with_ruler([ ....: update_optional_tags(' sage: unforced() # opt' 'ional - latte_int'), From 37d2303bc4a0828f7e57a02144b99b52938b0b9d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 10 Jul 2023 09:25:03 -0700 Subject: [PATCH 140/150] src/sage/doctest/parsing.py (SageDocTestParser.__init__ docstring): Remove redundant doctest tags --- src/sage/doctest/parsing.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index ad4db8beaf8..dfae349d0df 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -1011,7 +1011,6 @@ def parse(self, string, *args): ....: else (getattr(x, 'warnings', None), x.sage_source, x.source) ....: for x in DTP.parse(test_string)] - sage: # optional - guava sage: parse('sage: 1 # optional guava mango\nsage: 2 # optional guava\nsage: 3 # optional guava\nsage: 4 # optional guava\nsage: 5 # optional guava\n\nsage: 11 # optional guava') ['', (["Consider using a block-scoped tag by inserting the line 'sage: # optional - guava' just before this line to avoid repeating the tag 5 times"], @@ -1029,7 +1028,6 @@ def parse(self, string, *args): (None, '11 # optional guava\n', 'Integer(11) # optional guava\n'), ''] - sage: # optional - guava sage: parse('sage: 1 # optional guava\nsage: 2 # optional guava mango\nsage: 3 # optional guava\nsage: 4 # optional guava\nsage: 5 # optional guava\n') ['', (["Consider using a block-scoped tag by inserting the line 'sage: # optional - guava' just before this line to avoid repeating the tag 5 times"], From 26d676665e4b1c8c519da254fd82132741e21d4e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 10 Jul 2023 10:14:26 -0700 Subject: [PATCH 141/150] src/doc/en/developer/doctesting.rst: Explain prompt engineering for the doctest fixer --- src/doc/en/developer/doctesting.rst | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index 98d9588d029..5a4778de25d 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -1547,6 +1547,54 @@ is automatically transformed to:: The doctest fixer also aligns the ``# optional/needs FEATURE`` tags on individual doctests at a fixed set of tab stops. +The doctester may issue style warnings when ``# optional/needs`` tags are +repeated on a whole block of doctests, suggesting to use a block-scoped tag +instead. The doctest fixer makes these changes automatically. + +There are situations in which the doctester and doctest fixer show too +much restraint and a manual intervention would improve the formatting +of the doctests. In the example below, the doctester does not issue a +style warning because the first doctest line does not carry the ``# needs`` +tag:: + + | EXAMPLES:: + | + | sage: set_verbose(-1) + | sage: P. = ProjectiveSpace(QQbar, 2) # needs sage.rings.number_field + | sage: C = Curve([x^3*y + 2*x^2*y^2 + x*y^3 # needs sage.rings.number_field + | ....: + x^3*z + 7*x^2*y*z + | ....: + 14*x*y^2*z + 9*y^3*z], P) + | sage: Q = P([0,0,1]) # needs sage.rings.number_field + | sage: C.tangents(Q) # needs sage.rings.number_field + | [x + 4.147899035704788?*y, + | x + (1.426050482147607? + 0.3689894074818041?*I)*y, + | x + (1.426050482147607? - 0.3689894074818041?*I)*y] + +To change this example, there are two approaches: + +#. Just add the line ``sage: # needs sage.rings.number_field`` at + the beginning and run the doctest fixer, which will remove the tags on the individual + doctests that have now become redundant. + +#. Insert a blank line after the first doctest line, splitting the block into two. + Now the ``# needs`` tag is repeated on the whole second block, so running the doctest + fixer will add a block-scoped tag and remove the individual tags:: + + | EXAMPLES:: + | + | sage: set_verbose(-1) + | + | sage: # needs sage.rings.number_field + | sage: P. = ProjectiveSpace(QQbar, 2) + | sage: C = Curve([x^3*y + 2*x^2*y^2 + x*y^3 + | ....: + x^3*z + 7*x^2*y*z + | ....: + 14*x*y^2*z + 9*y^3*z], P) + | sage: Q = P([0,0,1]) + | sage: C.tangents(Q) + | [x + 4.147899035704788?*y, + | x + (1.426050482147607? + 0.3689894074818041?*I)*y, + | x + (1.426050482147607? - 0.3689894074818041?*I)*y] + In places where the doctester issues a doctest dataflow warning (``Variable ... referenced here was set only in doctest marked '# optional - FEATURE'``), the doctest fixer automatically adds the missing ``# optional/needs`` tags. From bf270c7362bf795b366695fe300dc0c4a90712ae Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 10 Jul 2023 18:56:56 -0700 Subject: [PATCH 142/150] src/sage/misc/sagedoc.py (process_optional_doctest_tags): Rename from process_optional_annotations; remove empty block-level tags --- src/sage/misc/sagedoc.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index 00d39507524..78a6e1fe3d3 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -590,21 +590,33 @@ def process_mathtt(s): return s -def process_optional_annotations(s): +def process_optional_doctest_tags(s): r""" - Remove ``# optional`` annotations for present features from docstring ``s``. - """ - lines = s.split('\n') + Remove ``# optional/needs`` doctest tags for present features from docstring ``s``. + + EXAMPLES: + sage: from sage.misc.sagedoc import process_optional_doctest_tags + sage: process_optional_doctest_tags("sage: # needs sage.rings.finite_rings\nsage: K. = FunctionField(GF(5^2,'a')); K\nRational function field in x over Finite Field in a of size 5^2") # needs sage.rings.finite_rings + "sage: K. = FunctionField(GF(5^2,'a')); K\nRational function field in x over Finite Field in a of size 5^2" + """ + import io from sage.doctest.external import available_software from sage.doctest.parsing import parse_optional_tags, update_optional_tags - for i, line in enumerate(lines): - if re.match(' *sage: .*#', line): - tags = parse_optional_tags(line) - lines[i] = update_optional_tags(line, remove_tags=[tag for tag in tags - if tag in available_software]) - return '\n'.join(lines) + start = 0 + with io.StringIO() as output: + for m in re.finditer('( *sage: *.*#.*)\n', s): + output.write(s[start:m.start(0)]) + line = m.group(1) + tags = [tag for tag in parse_optional_tags(line) + if tag not in available_software] + line = update_optional_tags(line, tags=tags) + if not re.fullmatch(' *sage: *', line): + print(line, file=output) + start = m.end(0) + output.write(s[start:]) + return output.getvalue() def format(s, embedded=False): @@ -788,7 +800,7 @@ def format(s, embedded=False): s = detex(s, embedded=embedded) if not embedded: - s = process_optional_annotations(s) + s = process_optional_doctest_tags(s) return s From 19eee80126bc2186acfc2f347c6761512fddddd8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 11 Jul 2023 09:33:17 -0700 Subject: [PATCH 143/150] src/bin/sage-fixdoctests: Append block tag line to previous line instead of prepending to current line --- src/bin/sage-fixdoctests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 35d7c2df96f..fbd9082a1b6 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -143,7 +143,7 @@ def process_block(block, src_in_lines, file_optional_tags): if m := re.search(r"using.*block-scoped tag.*'(sage: .*)'.*to avoid repeating the tag", block): indent = (len(src_in_lines[first_line_num - 1]) - len(src_in_lines[first_line_num - 1].lstrip())) - src_in_lines[line_num - 1] = ' ' * indent + m.group(1) + '\n' + src_in_lines[line_num - 1] + src_in_lines[line_num - 2] += '\n' + ' ' * indent + m.group(1) if m := re.search(r"updating.*block-scoped tag.*'sage: (.*)'.*to avoid repeating the tag", block): src_in_lines[first_line_num - 1] = update_optional_tags(src_in_lines[first_line_num - 1], From 3c44670ef525cb3dbeefbdcffc333a7fa59a9035 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 11 Jul 2023 09:47:25 -0700 Subject: [PATCH 144/150] ./sage -fixdoctests: Issue a deprecation warning when 2 filenames are passed --- src/bin/sage-fixdoctests | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index fbd9082a1b6..5328a527051 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -307,6 +307,8 @@ def process_block(block, src_in_lines, file_optional_tags): # set input and output files if len(args.filename) == 2 and not args.overwrite and not args.no_overwrite: inputs, outputs = [args.filename[0]], [args.filename[1]] + print("sage-fixdoctests: When passing two filenames, the second one is taken as an output filename; " + "this is deprecated. To pass two input filenames, use the option --overwrite.") elif args.no_overwrite: inputs, outputs = args.filename, [input + ".fixed" for input in args.filename] else: From 30e7dae978cdf1e454975915c08ebb6697e01ef6 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 11 Jul 2023 15:36:02 -0700 Subject: [PATCH 145/150] src/sage/doctest/parsing.py (update_optional_tags): Preserve two-column tag layout --- src/sage/doctest/parsing.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index dfae349d0df..a7b9833cda3 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -442,6 +442,10 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo ....: force_rewrite=True), ....: update_optional_tags(' sage: also_already_aligned() # ne' 'eds scipy', ....: force_rewrite='standard'), + ....: update_optional_tags(' sage: two_columns_first_preserved() # long time # ne' 'eds scipy', + ....: force_rewrite='standard'), + ....: update_optional_tags(' sage: two_columns_first_preserved() # long time # ne' 'eds scipy', + ....: force_rewrite='standard'), ....: ]) | V V V V V V v v v v | sage: unforced() # optional - latte_int @@ -451,6 +455,8 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo | sage: aligned_with_below() # optional - 4ti2 | sage: aligned_with_above() # optional - 4ti2 | sage: also_already_aligned() # needs scipy + | sage: two_columns_first_preserved() # long time # needs scipy + | sage: two_columns_first_preserved() # long time # needs scipy Rewriting a persistent (block-scoped) tag:: @@ -517,21 +523,21 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo break line += ' ' - if (group['optional'] or group['special']) and (group['standard'] or group['sage']): - # Try if two-column mode works better - first_part = unparse_optional_tags({tag: explanation - for tag, explanation in new_tags.items() - if (tag in group['optional'] - or tag in group['special'])}) - column = standard_tag_columns[0] - if len(line + first_part) + 8 <= column: - line += first_part - line += ' ' * (column - len(line)) - line += unparse_optional_tags({tag: explanation - for tag, explanation in new_tags.items() - if not (tag in group['optional'] - or tag in group['special'])}) - return line.rstrip() + if (group['optional'] or group['special']) and (group['standard'] or group['sage']): + # Try if two-column mode works better + first_part = unparse_optional_tags({tag: explanation + for tag, explanation in new_tags.items() + if (tag in group['optional'] + or tag in group['special'])}) + column = standard_tag_columns[0] + if len(line + first_part) + 8 <= column: + line += first_part + line += ' ' * (column - len(line)) + line += unparse_optional_tags({tag: explanation + for tag, explanation in new_tags.items() + if not (tag in group['optional'] + or tag in group['special'])}) + return line.rstrip() line += unparse_optional_tags(new_tags) return line From b801474dc20c4db389bdfa20588adfba39d46d73 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 11 Jul 2023 16:59:28 -0700 Subject: [PATCH 146/150] src/sage/doctest/parsing.py: Highlight keywords from relint --- src/sage/doctest/parsing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index a7b9833cda3..86d7aa71174 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -442,9 +442,9 @@ def update_optional_tags(line, tags=None, *, add_tags=None, remove_tags=None, fo ....: force_rewrite=True), ....: update_optional_tags(' sage: also_already_aligned() # ne' 'eds scipy', ....: force_rewrite='standard'), - ....: update_optional_tags(' sage: two_columns_first_preserved() # long time # ne' 'eds scipy', + ....: update_optional_tags(' sage: two_columns_first_preserved() # lo' 'ng time # ne' 'eds scipy', ....: force_rewrite='standard'), - ....: update_optional_tags(' sage: two_columns_first_preserved() # long time # ne' 'eds scipy', + ....: update_optional_tags(' sage: two_columns_first_preserved() # lo' 'ng time # ne' 'eds scipy', ....: force_rewrite='standard'), ....: ]) | V V V V V V v v v v From fafaaf25bcc2f63fb01d44305f4f749e357d425d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 11 Jul 2023 17:03:55 -0700 Subject: [PATCH 147/150] sage -t, sage -fixdoctests: Rename option --only-lib to --if-installed --- src/bin/sage-fixdoctests | 2 +- src/bin/sage-runtests | 2 +- src/doc/en/developer/doctesting.rst | 8 ++++---- src/sage/doctest/control.py | 14 +++++++------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 5328a527051..3c198dd2737 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -320,7 +320,7 @@ if not args.no_test: environment_args = f'--environment {args.environment} ' if args.environment != runtest_default_environment else '' long_args = f'--long ' if args.long else '' probe_args = f'--probe {shlex.quote(args.probe)} ' if args.probe else '' - lib_args = f'--only-lib ' if args.venv else '' + lib_args = f'--if-installed ' if args.venv else '' doc_file = tmp_filename() if args.venv or environment_args: input = os.path.join(os.path.relpath(SAGE_ROOT), 'src', 'sage', 'version.py') diff --git a/src/bin/sage-runtests b/src/bin/sage-runtests index 35591632155..4d606c5a3a8 100755 --- a/src/bin/sage-runtests +++ b/src/bin/sage-runtests @@ -69,7 +69,7 @@ if __name__ == "__main__": parser.add_argument("-i", "--initial", action="store_true", default=False, help="only show the first failure in each file") parser.add_argument("--exitfirst", action="store_true", default=False, help="end the test run immediately after the first failure or unexpected exception") parser.add_argument("--force_lib", "--force-lib", action="store_true", default=False, help="do not import anything from the tested file(s)") - parser.add_argument("--only-lib", action="store_true", default=False, help="skip Python/Cython files that are not installed as modules") + parser.add_argument("--if-installed", action="store_true", default=False, help="skip Python/Cython files that are not installed as modules") parser.add_argument("--abspath", action="store_true", default=False, help="print absolute paths rather than relative paths") parser.add_argument("--verbose", action="store_true", default=False, help="print debugging output during the test") parser.add_argument("-d", "--debug", action="store_true", default=False, help="drop into a python debugger when an unexpected error is raised") diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index 5a4778de25d..40436699653 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -1392,17 +1392,17 @@ tree (``src/sage/...``):: Note that testing all doctests as they appear in the source tree does not make sense because many of the source files may not be installed in the virtual environment. -Use the option ``--only-lib`` to skip the source files of all Python/Cython modules +Use the option ``--if-installed`` to skip the source files of all Python/Cython modules that are not installed in the virtual environment:: [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -t \ - -p4 --environment sage.all__sagemath_categories --only-lib \ + -p4 --environment sage.all__sagemath_categories --if-installed \ src/sage/schemes This option can also be combined with ``--all``:: [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -t \ - -p4 --environment sage.all__sagemath_categories --only-lib \ + -p4 --environment sage.all__sagemath_categories --if-installed \ --all @@ -1645,7 +1645,7 @@ that uses the more specific options ``--venv`` and ``--environment``:: src/sage/geometry/schemes/generic/*.py Either way, the options ``--keep-both``, ``--full-tracebacks``, and -``--only-lib`` are implied. +``--if-installed`` are implied. In this mode of operation, when the doctester encounters a global name that is unknown in its virtual environment (:class:`NameError`), diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index b1497fee500..4844662073d 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -123,7 +123,7 @@ def __init__(self, **kwds): self.initial = False self.exitfirst = False self.force_lib = False - self.only_lib = False + self.if_installed = False self.abspath = True # sage-runtests default is False self.verbose = False self.debug = False @@ -225,7 +225,7 @@ def skipdir(dirname): return False def skipfile(filename, tested_optional_tags=False, *, - only_lib=False, log=None): + if_installed=False, log=None): """ Return ``True`` if and only if the file ``filename`` should not be doctested. @@ -236,7 +236,7 @@ def skipfile(filename, tested_optional_tags=False, *, - ``tested_optional_tags`` -- a list or tuple or set of optional tags to test, or ``False`` (no optional test) or ``True`` (all optional tests) - - ``only_lib`` -- (boolean, default ``False``) whether to skip Python/Cython files + - ``if_installed`` -- (boolean, default ``False``) whether to skip Python/Cython files that are not installed as modules - ``log`` -- function to call with log messages, or ``None`` @@ -285,7 +285,7 @@ def skipfile(filename, tested_optional_tags=False, *, if log: log(f"Skipping '{filename}' because it is created by the jupyter-sphinx extension for internal use and should not be tested") return True - if only_lib and ext in ('.py', '.pyx', '.pxd'): + if if_installed and ext in ('.py', '.pyx', '.pxd'): module_name = get_basename(filename) try: if not importlib.util.find_spec(module_name): # tries to import the containing package @@ -949,7 +949,7 @@ def all_doc_sources(): filename.endswith(".rst")) and not skipfile(opj(SAGE_ROOT, filename), True if self.options.optional else False, - only_lib=self.options.only_lib)): + if_installed=self.options.if_installed)): self.files.append(os.path.relpath(opj(SAGE_ROOT, filename))) def expand_files_into_sources(self): @@ -1011,11 +1011,11 @@ def expand(): for file in files: if not skipfile(os.path.join(root, file), True if self.options.optional else False, - only_lib=self.options.only_lib): + if_installed=self.options.if_installed): yield os.path.join(root, file) else: if not skipfile(path, True if self.options.optional else False, - only_lib=self.options.only_lib, log=self.log): # log when directly specified filenames are skipped + if_installed=self.options.if_installed, log=self.log): # log when directly specified filenames are skipped yield path self.sources = [FileDocTestSource(path, self.options) for path in expand()] From 8d25508eddc9ee6bfb136760e2912f8c303e4043 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 11 Jul 2023 17:30:58 -0700 Subject: [PATCH 148/150] src/doc/en/developer/doctesting.rst: Standardize prompts --- src/doc/en/developer/doctesting.rst | 168 ++++++++++++++-------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index 40436699653..db4fc7f9a83 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -13,11 +13,11 @@ its documentation. Testing can be performed using one thread or multiple threads. After compiling a source version of Sage, doctesting can be run on the whole Sage library, on all modules under a given directory, or on a specified module only. For the purposes of this -chapter, suppose we have compiled Sage 6.0 from source and the top -level Sage directory is:: +chapter, suppose we have compiled Sage from source and the top +level directory is:: - [jdemeyer@sage sage-6.0]$ pwd - /scratch/jdemeyer/build/sage-6.0 + [jdemeyer@localhost sage]$ pwd + /home/jdemeyer/sage See the section :ref:`chapter-testing` for information on Sage's automated testing process. The general syntax for doctesting is as @@ -43,7 +43,7 @@ Say we want to run all tests in the sudoku module top level Sage directory of our local Sage installation. Now we can start doctesting as demonstrated in the following terminal session:: - [jdemeyer@sage sage-6.0]$ ./sage -t src/sage/games/sudoku.py + [jdemeyer@localhost sage]$ ./sage -t src/sage/games/sudoku.py Running doctests with ID 2012-07-03-03-36-49-d82849c6. Doctesting 1 file. sage -t src/sage/games/sudoku.py @@ -63,7 +63,7 @@ one module so it is not surprising that the total testing time is approximately the same as the time required to test only that one module. Notice that the syntax is:: - [jdemeyer@sage sage-6.0]$ ./sage -t src/sage/games/sudoku.py + [jdemeyer@localhost sage]$ ./sage -t src/sage/games/sudoku.py Running doctests with ID 2012-07-03-03-39-02-da6accbb. Doctesting 1 file. sage -t src/sage/games/sudoku.py @@ -77,7 +77,7 @@ module. Notice that the syntax is:: but not:: - [jdemeyer@sage sage-6.0]$ ./sage -t sage/games/sudoku.py + [jdemeyer@localhost sage]$ ./sage -t sage/games/sudoku.py Running doctests with ID 2012-07-03-03-40-53-6cc4f29f. No files matching sage/games/sudoku.py No files to doctest @@ -85,11 +85,11 @@ but not:: We can also first ``cd`` to the directory containing the module ``sudoku.py`` and doctest that module as follows:: - [jdemeyer@sage sage-6.0]$ cd src/sage/games/ - [jdemeyer@sage games]$ ls + [jdemeyer@localhost sage]$ cd src/sage/games/ + [jdemeyer@localhost games]$ ls __init__.py hexad.py sudoku.py sudoku_backtrack.pyx all.py quantumino.py sudoku_backtrack.c - [jdemeyer@sage games]$ ../../../../sage -t sudoku.py + [jdemeyer@localhost games]$ ../../../../sage -t sudoku.py Running doctests with ID 2012-07-03-03-41-39-95ebd2ff. Doctesting 1 file. sage -t sudoku.py @@ -108,7 +108,7 @@ installation is a recipe for confusion. You can also run the Sage doctester as follows:: - [jdemeyer@sage sage-6.0]$ ./sage -tox -e doctest -- src/sage/games/sudoku.py + [jdemeyer@localhost sage]$ ./sage -tox -e doctest -- src/sage/games/sudoku.py See :ref:`chapter-tools` for more information about tox. @@ -126,7 +126,7 @@ our system has multiple Sage installations. For example, the following syntax is acceptable because we explicitly specify the Sage installation in the current ``SAGE_ROOT``:: - [jdemeyer@sage sage-6.0]$ ./sage -t src/sage/games/sudoku.py + [jdemeyer@localhost sage]$ ./sage -t src/sage/games/sudoku.py Running doctests with ID 2012-07-03-03-43-24-a3449f54. Doctesting 1 file. sage -t src/sage/games/sudoku.py @@ -137,7 +137,7 @@ installation in the current ``SAGE_ROOT``:: Total time for all tests: 4.9 seconds cpu time: 3.6 seconds cumulative wall time: 3.6 seconds - [jdemeyer@sage sage-6.0]$ ./sage -t "src/sage/games/sudoku.py" + [jdemeyer@localhost sage]$ ./sage -t "src/sage/games/sudoku.py" Running doctests with ID 2012-07-03-03-43-54-ac8ca007. Doctesting 1 file. sage -t src/sage/games/sudoku.py @@ -156,10 +156,10 @@ Sage installation (if it exists): :: - [jdemeyer@sage sage-6.0]$ sage -t src/sage/games/sudoku.py + [jdemeyer@localhost sage]$ sage -t src/sage/games/sudoku.py sage -t "src/sage/games/sudoku.py" ********************************************************************** - File "/home/jdemeyer/sage/sage-6.0/src/sage/games/sudoku.py", line 515: + File "/home/jdemeyer/sage/src/sage/games/sudoku.py", line 515: sage: next(h.solve(algorithm='backtrack')) Exception raised: Traceback (most recent call last): @@ -215,7 +215,7 @@ and then using four threads. For this example, suppose we want to test all the modules under ``sage/crypto/``. We can use a syntax similar to that shown above to achieve this:: - [jdemeyer@sage sage-6.0]$ ./sage -t src/sage/crypto + [jdemeyer@localhost sage]$ ./sage -t src/sage/crypto Running doctests with ID 2012-07-03-03-45-40-7f837dcf. Doctesting 24 files. sage -t src/sage/crypto/__init__.py @@ -276,7 +276,7 @@ that shown above to achieve this:: Now we do the same thing, but this time we also use the optional argument ``--long``:: - [jdemeyer@sage sage-6.0]$ ./sage -t --long src/sage/crypto/ + [jdemeyer@localhost sage]$ ./sage -t --long src/sage/crypto/ Running doctests with ID 2012-07-03-03-48-11-c16721e6. Doctesting 24 files. sage -t --long src/sage/crypto/__init__.py @@ -372,7 +372,7 @@ as taking a long time: Now we doctest the same directory in parallel using 4 threads:: - [jdemeyer@sage sage-6.0]$ ./sage -tp 4 src/sage/crypto/ + [jdemeyer@localhost sage]$ ./sage -tp 4 src/sage/crypto/ Running doctests with ID 2012-07-07-00-11-55-9b17765e. Sorting sources by runtime so that slower doctests are run first.... Doctesting 24 files using 4 threads. @@ -430,7 +430,7 @@ Now we doctest the same directory in parallel using 4 threads:: Total time for all tests: 12.9 seconds cpu time: 30.5 seconds cumulative wall time: 31.7 seconds - [jdemeyer@sage sage-6.0]$ ./sage -tp 4 --long src/sage/crypto/ + [jdemeyer@localhost sage]$ ./sage -tp 4 --long src/sage/crypto/ Running doctests with ID 2012-07-07-00-13-04-d71f3cd4. Sorting sources by runtime so that slower doctests are run first.... Doctesting 24 files using 4 threads. @@ -504,7 +504,7 @@ to doctest the main library using multiple threads. When doing release management or patching the main Sage library, a release manager would parallel test the library using 10 threads with the following command:: - [jdemeyer@sage sage-6.0]$ ./sage -tp 10 --long src/ + [jdemeyer@localhost sage]$ ./sage -tp 10 --long src/ Another way is run ``make ptestlong``, which builds Sage (if necessary), builds the Sage documentation (if necessary), and then runs parallel @@ -516,7 +516,7 @@ the number of CPU cores (as determined by the Python function In any case, this will test the Sage library with multiple threads:: - [jdemeyer@sage sage-6.0]$ make ptestlong + [jdemeyer@localhost sage]$ make ptestlong Any of the following commands would also doctest the Sage library or one of its clones: @@ -577,7 +577,7 @@ Doctesting also works fine for files not in the Sage library. For example, suppose we have a Python script called ``my_python_script.py``:: - [mvngu@sage build]$ cat my_python_script.py + [mvngu@localhost sage]$ cat my_python_script.py from sage.all_cmdline import * # import sage library def square(n): @@ -593,7 +593,7 @@ example, suppose we have a Python script called Then we can doctest it just as with Sage library files:: - [mvngu@sage sage-6.0]$ ./sage -t my_python_script.py + [mvngu@localhost sage]$ ./sage -t my_python_script.py Running doctests with ID 2012-07-07-00-17-56-d056f7c0. Doctesting 1 file. sage -t my_python_script.py @@ -608,7 +608,7 @@ Then we can doctest it just as with Sage library files:: Doctesting can also be performed on Sage scripts. Say we have a Sage script called ``my_sage_script.sage`` with the following content:: - [mvngu@sage sage-6.0]$ cat my_sage_script.sage + [mvngu@localhost sage]$ cat my_sage_script.sage def cube(n): r""" Return the cube of n. @@ -622,7 +622,7 @@ script called ``my_sage_script.sage`` with the following content:: Then we can doctest it just as for Python files:: - [mvngu@sage build]$ sage-6.0/sage -t my_sage_script.sage + [mvngu@localhost sage]$ ./sage -t my_sage_script.sage Running doctests with ID 2012-07-07-00-20-06-82ee728c. Doctesting 1 file. sage -t my_sage_script.sage @@ -637,8 +637,8 @@ Then we can doctest it just as for Python files:: Alternatively, we can preparse it to convert it to a Python script, and then doctest that:: - [mvngu@sage build]$ sage-6.0/sage --preparse my_sage_script.sage - [mvngu@sage build]$ cat my_sage_script.sage.py + [mvngu@localhost sage]$ ./sage --preparse my_sage_script.sage + [mvngu@localhost sage]$ cat my_sage_script.sage.py # This file was *autogenerated* from the file my_sage_script.sage. from sage.all_cmdline import * # import sage library _sage_const_3 = Integer(3) @@ -652,7 +652,7 @@ and then doctest that:: 8 """ return n**_sage_const_3 - [mvngu@sage build]$ sage-6.0/sage -t my_sage_script.sage.py + [mvngu@localhost sage]$ ./sage -t my_sage_script.sage.py Running doctests with ID 2012-07-07-00-26-46-2bb00911. Doctesting 1 file. sage -t my_sage_script.sage.py @@ -716,7 +716,7 @@ Use the ``--long`` flag to run doctests that have been marked with the comment ``# long time``. These tests are normally skipped in order to reduce the time spent running tests:: - [roed@sage sage-6.0]$ ./sage -t src/sage/rings/tests.py + [roed@localhost sage]$ ./sage -t src/sage/rings/tests.py Running doctests with ID 2012-06-21-16-00-13-40835825. Doctesting 1 file. sage -t tests.py @@ -730,7 +730,7 @@ reduce the time spent running tests:: In order to run the long tests as well, do the following:: - [roed@sage sage-6.0]$ ./sage -t --long src/sage/rings/tests.py + [roed@localhost sage]$ ./sage -t --long src/sage/rings/tests.py Running doctests with ID 2012-06-21-16-02-05-d13a9a24. Doctesting 1 file. sage -t tests.py @@ -747,7 +747,7 @@ To find tests that take longer than the allowed time use the print a warning if they take longer than 1.0 second. Note that this is a warning, not an error:: - [roed@sage sage-6.0]$ ./sage -t --warn-long src/sage/rings/factorint.pyx + [roed@localhost sage]$ ./sage -t --warn-long src/sage/rings/factorint.pyx Running doctests with ID 2012-07-14-03-27-03-2c952ac1. Doctesting 1 file. sage -t --warn-long src/sage/rings/factorint.pyx @@ -781,7 +781,7 @@ a warning, not an error:: You can also pass in an explicit amount of time:: - [roed@sage sage-6.0]$ ./sage -t --long --warn-long 2.0 src/sage/rings/tests.py + [roed@localhost sage]$ ./sage -t --long --warn-long 2.0 src/sage/rings/tests.py Running doctests with ID 2012-07-14-03-30-13-c9164c9d. Doctesting 1 file. sage -t --long --warn-long 2.0 tests.py @@ -808,7 +808,7 @@ Finally, you can disable any warnings about long tests with Doctests start from a random seed:: - [kliem@sage sage-9.2]$ ./sage -t src/sage/doctest/tests/random_seed.rst + [kliem@localhost sage]$ ./sage -t src/sage/doctest/tests/random_seed.rst Running doctests with ID 2020-06-23-23-22-59-49f37a55. ... Doctesting 1 file. @@ -834,9 +834,9 @@ Doctests start from a random seed:: This seed can be set explicitly to reproduce possible failures:: - [kliem@sage sage-9.2]$ ./sage -t --warn-long 89.5 \ - --random-seed=112986622569797306072457879734474628454 \ - src/sage/doctest/tests/random_seed.rst + [kliem@localhost sage]$ ./sage -t --warn-long 89.5 \ + --random-seed=112986622569797306072457879734474628454 \ + src/sage/doctest/tests/random_seed.rst Running doctests with ID 2020-06-23-23-24-28-14a52269. ... Doctesting 1 file. @@ -875,8 +875,8 @@ necessary optional packages in order for these tests to succeed. By default, Sage only runs doctests that are not marked with the ``optional`` tag. This is equivalent to running :: - [roed@sage sage-6.0]$ ./sage -t --optional=sagemath_doc_html,sage \ - src/sage/rings/real_mpfr.pyx + [roed@localhost sage]$ ./sage -t --optional=sagemath_doc_html,sage \ + src/sage/rings/real_mpfr.pyx Running doctests with ID 2012-06-21-16-18-30-a368a200. Doctesting 1 file. sage -t src/sage/rings/real_mpfr.pyx @@ -890,8 +890,8 @@ By default, Sage only runs doctests that are not marked with the ``optional`` ta If you want to also run tests that require magma, you can do the following:: - [roed@sage sage-6.0]$ ./sage -t --optional=sagemath_doc_html,sage,magma \ - src/sage/rings/real_mpfr.pyx + [roed@localhost sage]$ ./sage -t --optional=sagemath_doc_html,sage,magma \ + src/sage/rings/real_mpfr.pyx Running doctests with ID 2012-06-21-16-18-30-a00a7319 Doctesting 1 file. sage -t src/sage/rings/real_mpfr.pyx @@ -905,7 +905,7 @@ If you want to also run tests that require magma, you can do the following:: In order to just run the tests that are marked as requiring magma, omit ``sage`` and ``sagemath_doc_html``:: - [roed@sage sage-6.0]$ ./sage -t --optional=magma src/sage/rings/real_mpfr.pyx + [roed@localhost sage]$ ./sage -t --optional=magma src/sage/rings/real_mpfr.pyx Running doctests with ID 2012-06-21-16-18-33-a2bc1fdf Doctesting 1 file. sage -t src/sage/rings/real_mpfr.pyx @@ -921,7 +921,7 @@ If you want Sage to detect external software or other capabilities (such as magma, latex, internet) automatically and run all of the relevant tests, then add ``external``:: - [roed@sage sage-6.0]$ ./sage -t --optional=external src/sage/rings/real_mpfr.pyx + [roed@localhost sage]$ ./sage -t --optional=external src/sage/rings/real_mpfr.pyx Running doctests with ID 2016-03-16-14-10-21-af2ebb67. Using --optional=external External software to be detected: cplex,gurobi,internet,latex,macaulay2,magma,maple,mathematica,matlab,octave,scilab @@ -938,7 +938,7 @@ relevant tests, then add ``external``:: To run all tests, regardless of whether they are marked optional, pass ``all`` as the ``optional`` tag:: - [roed@sage sage-6.0]$ ./sage -t --optional=all src/sage/rings/real_mpfr.pyx + [roed@localhost sage]$ ./sage -t --optional=all src/sage/rings/real_mpfr.pyx Running doctests with ID 2012-06-21-16-31-18-8c097f55 Doctesting 1 file. sage -t src/sage/rings/real_mpfr.pyx @@ -959,7 +959,7 @@ than one thread. To run doctests in parallel use the ``--nthreads`` flag (``-p`` is a shortened version). Pass in the number of threads you would like to use (by default Sage just uses 1):: - [roed@sage sage-6.0]$ ./sage -tp 2 src/sage/doctest/ + [roed@localhost sage]$ ./sage -tp 2 src/sage/doctest/ Running doctests with ID 2012-06-22-19-09-25-a3afdb8c. Sorting sources by runtime so that slower doctests are run first.... Doctesting 8 files using 2 threads. @@ -995,12 +995,12 @@ short). In addition to testing the code in Sage's Python and Cython files, this command will run the tests defined in Sage's documentation as well as testing the Sage notebook:: - [roed@sage sage-6.0]$ ./sage -t -a + [roed@localhost sage]$ ./sage -t -a Running doctests with ID 2012-06-22-19-10-27-e26fce6d. Doctesting entire Sage library. Sorting sources by runtime so that slower doctests are run first.... Doctesting 2020 files. - sage -t /Users/roed/sage/sage-5.3/src/sage/plot/plot.py + sage -t /Users/roed/sage/src/sage/plot/plot.py [304 tests, 69.0 s] ... @@ -1016,7 +1016,7 @@ short) then you will drop into an interactive Python debugger whenever a Python exception occurs. As an example, I modified :mod:`sage.schemes.elliptic_curves.constructor` to produce an error:: - [roed@sage sage-6.0]$ ./sage -t --debug \ + [roed@localhost sage]$ ./sage -t --debug \ src/sage/schemes/elliptic_curves/constructor.py Running doctests with ID 2012-06-23-12-09-04-b6352629. Doctesting 1 file. @@ -1078,9 +1078,9 @@ you know what test caused the problem (if you want this output to appear in real time use the ``--verbose`` flag). To have doctests run under the control of gdb, use the ``--gdb`` flag:: - [roed@sage sage-6.0]$ ./sage -t --gdb \ - src/sage/schemes/elliptic_curves/constructor.py - exec gdb --eval-commands="run" --args /home/roed/sage-9.7/local/var/lib/sage/venv-python3.9/bin/python3 sage-runtests --serial --timeout=0 --stats_path=/home/roed/.sage/timings2.json --optional=pip,sage,sage_spkg src/sage/schemes/elliptic_curves/constructor.py + [roed@localhost sage]$ ./sage -t --gdb \ + src/sage/schemes/elliptic_curves/constructor.py + exec gdb --eval-commands="run" --args /home/roed/sage/local/var/lib/sage/venv-python3.9/bin/python3 sage-runtests --serial --timeout=0 --stats_path=/home/roed/.sage/timings2.json --optional=pip,sage,sage_spkg src/sage/schemes/elliptic_curves/constructor.py GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later @@ -1114,7 +1114,7 @@ Once you're done fixing whatever problems where revealed by the doctests, you can rerun just those files that failed their most recent test by using the ``--failed`` flag (``-f`` for short):: - [roed@sage sage-6.0]$ ./sage -t -fa + [roed@localhost sage]$ ./sage -t -fa Running doctests with ID 2012-07-07-00-45-35-d8b5a408. Doctesting entire Sage library. Only doctesting files that failed last test. @@ -1142,8 +1142,8 @@ Show skipped optional tests To print a summary at the end of each file with the number of optional tests skipped, use the ``--show-skipped`` flag:: - [roed@sage sage-6.0]$ ./sage -t --show-skipped \ - src/sage/rings/finite_rings/integer_mod.pyx + [roed@localhost sage]$ ./sage -t --show-skipped \ + src/sage/rings/finite_rings/integer_mod.pyx Running doctests with ID 2013-03-14-15-32-05-8136f5e3. Doctesting 1 file. sage -t sage/rings/finite_rings/integer_mod.pyx @@ -1168,7 +1168,7 @@ you to run tests repeatedly in an attempt to search for Heisenbugs. The flag ``--global-iterations`` takes an integer and runs the whole set of tests that many times serially:: - [roed@sage sage-6.0]$ ./sage -t --global-iterations 2 src/sage/sandpiles + [roed@localhost sage]$ ./sage -t --global-iterations 2 src/sage/sandpiles Running doctests with ID 2012-07-07-00-59-28-e7048ad9. Doctesting 3 files (2 global iterations). sage -t src/sage/sandpiles/__init__.py @@ -1199,7 +1199,7 @@ set of tests that many times serially:: You can also iterate in a different order: the ``--file-iterations`` flag runs the tests in each file ``N`` times before proceeding:: - [roed@sage sage-6.0]$ ./sage -t --file-iterations 2 src/sage/sandpiles + [roed@localhost sage]$ ./sage -t --file-iterations 2 src/sage/sandpiles Running doctests with ID 2012-07-07-01-01-43-8f954206. Doctesting 3 files (2 file iterations). sage -t src/sage/sandpiles/__init__.py @@ -1227,7 +1227,7 @@ On a slow machine the default timeout of 5 minutes may not be enough for the slowest files. Use the ``--timeout`` flag (``-T`` for short) to set it to something else:: - [roed@sage sage-6.0]$ ./sage -tp 2 --all --timeout 1 + [roed@localhost sage]$ ./sage -tp 2 --all --timeout 1 Running doctests with ID 2012-07-07-01-09-37-deb1ab83. Doctesting entire Sage library. Sorting sources by runtime so that slower doctests are run first.... @@ -1242,10 +1242,10 @@ Using absolute paths By default filenames are printed using relative paths. To use absolute paths instead pass in the ``--abspath`` flag:: - [roed@sage sage-6.0]$ ./sage -t --abspath src/sage/doctest/control.py + [roed@localhost sage]$ ./sage -t --abspath src/sage/doctest/control.py Running doctests with ID 2012-07-07-01-13-03-a023e212. Doctesting 1 file. - sage -t /home/roed/sage-6.0/src/sage/doctest/control.py + sage -t /home/roed/sage/src/sage/doctest/control.py [133 tests, 4.7 s] ------------------------------------------------------------------------ All tests passed! @@ -1263,7 +1263,7 @@ convenient to test only the files that have changed. To do so use the ``--new`` flag, which tests files that have been modified or added since the last commit:: - [roed@sage sage-6.0]$ ./sage -t --new + [roed@localhost sage]$ ./sage -t --new Running doctests with ID 2012-07-07-01-15-52-645620ee. Doctesting files changed since last git commit. Doctesting 1 file. @@ -1284,7 +1284,7 @@ By default, tests are run in the order in which they appear in the file. To run tests in a random order (which can reveal subtle bugs), use the ``--randorder`` flag and pass in a random seed:: - [roed@sage sage-6.0]$ ./sage -t --new --randorder 127 + [roed@localhost sage]$ ./sage -t --new --randorder 127 Running doctests with ID 2012-07-07-01-19-06-97c8484e. Doctesting files changed since last git commit. Doctesting 1 file. @@ -1314,7 +1314,7 @@ Auxiliary files To specify a logfile (rather than use the default which is created for ``sage -t --all``), use the ``--logfile`` flag:: - [roed@sage sage-6.0]$ ../sage -t --logfile test1.log src/sage/doctest/control.py + [roed@localhost sage]$ ./sage -t --logfile test1.log src/sage/doctest/control.py Running doctests with ID 2012-07-07-01-25-49-e7c0e52d. Doctesting 1 file. sage -t src/sage/doctest/control.py @@ -1325,7 +1325,7 @@ To specify a logfile (rather than use the default which is created for Total time for all tests: 6.7 seconds cpu time: 0.1 seconds cumulative wall time: 4.3 seconds - [roed@sage sage-6.0]$ cat test1.log + [roed@localhost sage]$ cat test1.log Running doctests with ID 2012-07-07-01-25-49-e7c0e52d. Doctesting 1 file. sage -t src/sage/doctest/control.py @@ -1343,7 +1343,7 @@ To give a json file storing the timings for each file, use the that slower tests are run first (and thus multiple processes are utilized most efficiently):: - [roed@sage sage-6.0]$ ../sage -tp 2 --stats-path ~/.sage/timings2.json --all + [roed@localhost sage]$ ./sage -tp 2 --stats-path ~/.sage/timings2.json --all Running doctests with ID 2012-07-07-01-28-34-2df4251d. Doctesting entire Sage library. Sorting sources by runtime so that slower doctests are run first.... @@ -1367,8 +1367,8 @@ contain installations of built (non-editable) wheels. To test all modules of Sage that are installed in a virtual environment, use the option ``--installed`` (instead of ``--all``):: - [mkoeppe@sage sage]$ pkgs/sagemath-standard/.tox/sagepython-sagewheels-.../sage -t \ - -p4 --installed + [mkoeppe@localhost sage]$ pkgs/sagemath-standard/.tox/sagepython-.../sage -t \ + -p4 --installed This tests against the doctests as they appear in the installed copies of the files (in ``site-packages/sage/...``). @@ -1379,31 +1379,31 @@ When testing a modularized distribution package other than sagemath-standard, the top-level module :mod:`sage.all` is not available. Use the option ``--environment`` to select an appropriate top-level module:: - [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -t \ - -p4 --environment sage.all__sagemath_categories \ - --installed + [mkoeppe@localhost sage]$ pkgs/sagemath-categories/.tox/sagepython-.../sage -t \ + -p4 --environment sage.all__sagemath_categories \ + --installed To test the installed modules against the doctests as they appear in the source tree (``src/sage/...``):: - [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -t \ - -p4 --environment sage.all__sagemath_categories \ - src/sage/structure + [mkoeppe@localhost sage]$ pkgs/sagemath-categories/.tox/sagepython-.../sage -t \ + -p4 --environment sage.all__sagemath_categories \ + src/sage/structure Note that testing all doctests as they appear in the source tree does not make sense because many of the source files may not be installed in the virtual environment. Use the option ``--if-installed`` to skip the source files of all Python/Cython modules that are not installed in the virtual environment:: - [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -t \ - -p4 --environment sage.all__sagemath_categories --if-installed \ - src/sage/schemes + [mkoeppe@localhost sage]$ pkgs/sagemath-categories/.tox/sagepython-.../sage -t \ + -p4 --environment sage.all__sagemath_categories \ + --if-installed src/sage/schemes This option can also be combined with ``--all``:: - [mkoeppe@sage sage]$ pkgs/sagemath-categories/.tox/sagepython-sagewheels-.../sage -t \ - -p4 --environment sage.all__sagemath_categories --if-installed \ - --all + [mkoeppe@localhost sage]$ pkgs/sagemath-categories/.tox/sagepython-.../sage -t \ + -p4 --environment sage.all__sagemath_categories \ + --if-installed --all .. _section-fixdoctests: @@ -1420,7 +1420,7 @@ Updating doctest outputs By default, ``./sage --fixdoctests`` runs the doctester and replaces the expected outputs of all examples by the actual outputs from the current version of Sage:: - [mkoeppe@sage sage]$ ./sage --fixdoctests \ + [mkoeppe@localhost sage]$ ./sage --fixdoctests \ --overwrite src/sage/arith/weird.py For example, when applied to this Python file:: @@ -1469,7 +1469,7 @@ To disable it so that the details of the exception can be inspected, use the option ``--full-tracebacks``. This is particularly useful in combination with ``--keep-both``:: - [mkoeppe@sage sage]$ ./sage --fixdoctests --keep-both --full-tracebacks \ + [mkoeppe@localhost sage]$ ./sage --fixdoctests --keep-both --full-tracebacks \ --overwrite src/sage/arith/weird.py This will give the following result on the above example:: @@ -1498,14 +1498,14 @@ This will give the following result on the above example:: To make sure that all doctests are updated, you may have to use the option ``--long``:: - [mkoeppe@sage sage]$ ./sage --fixdoctests --long \ + [mkoeppe@localhost sage]$ ./sage --fixdoctests --long \ --overwrite src/sage/arith/weird.py If you are not comfortable with allowing this tool to edit your source files, you can use the option ``--no-overwrite``, which will create a new file with the extension ``.fixed`` instead of overwriting the source file:: - [mkoeppe@sage sage]$ ./sage --fixdoctests \ + [mkoeppe@localhost sage]$ ./sage --fixdoctests \ --no-overwrite src/sage/arith/weird.py @@ -1632,14 +1632,14 @@ Use in virtual environments The doctest fixer can also run tests using the Sage doctester installed in a virtual environment:: - [mkoeppe@sage sage]$ ./sage --fixdoctests --overwrite \ - --distribution sagemath-categories \ + [mkoeppe@localhost sage]$ ./sage --fixdoctests --overwrite \ + --distribution sagemath-categories \ src/sage/geometry/schemes/generic/*.py This command, using ``--distribution``, is equivalent to a command that uses the more specific options ``--venv`` and ``--environment``:: - [mkoeppe@sage sage]$ ./sage --fixdoctests --overwrite \ + [mkoeppe@localhost sage]$ ./sage --fixdoctests --overwrite \ --venv pkgs/sagemath-categories/.tox/sagepython-... \ --environment sage.all__sagemath_categories src/sage/geometry/schemes/generic/*.py From 0c4208661d3a94b8792c97e58085759e66914980 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 11 Jul 2023 18:10:31 -0700 Subject: [PATCH 149/150] ./sage -fixdoctests: Update --help --- src/bin/sage-fixdoctests | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bin/sage-fixdoctests b/src/bin/sage-fixdoctests index 3c198dd2737..0c06d448887 100755 --- a/src/bin/sage-fixdoctests +++ b/src/bin/sage-fixdoctests @@ -48,14 +48,14 @@ from sage.features.all import all_features, module_feature, name_feature from sage.misc.temporary_file import tmp_filename parser = ArgumentParser(description="Given an input file with doctests, this creates a modified file that passes the doctests (modulo any raised exceptions). By default, the input file is modified. You can also name an output file.") -parser.add_argument('-l', '--long', - dest='long', action="store_true", default=False) +parser.add_argument('-l', '--long', dest='long', action="store_true", default=False, + help="include tests tagged '# long time'") parser.add_argument("--distribution", type=str, default='', help="distribution package to test, e.g., 'sagemath-graphs', 'sagemath-combinat[modules]'; sets defaults for --venv and --environment") parser.add_argument("--venv", type=str, default='', help="directory name of a venv where 'sage -t' is to be run") parser.add_argument("--environment", type=str, default='', - help="name of a module that provides the global environment for tests; implies --keep-both and --full-tracebacks") + help="name of a module that provides the global environment for tests, e.g., 'sage.all__sagemath_modules'; implies --keep-both and --full-tracebacks") parser.add_argument("--no-test", default=False, action="store_true", help="do not run the doctester, only rewrite '# optional/needs' tags; implies --only-tags") parser.add_argument("--full-tracebacks", default=False, action="store_true", @@ -63,14 +63,14 @@ parser.add_argument("--full-tracebacks", default=False, action="store_true", parser.add_argument("--only-tags", default=False, action="store_true", help="only add '# optional/needs' tags where needed, ignore other failures") parser.add_argument("--probe", metavar="FEATURES", type=str, default='', - help="check whether '# optional - FEATURES' tags are still needed, remove these") + help="check whether '# optional/needs' tags are still needed, remove these") parser.add_argument("--keep-both", default=False, action="store_true", - help="do not replace test results; duplicate the test instead and mark both copies # optional") + help="do not replace test results; duplicate the test instead, showing both results, and mark both copies '# optional'") parser.add_argument("--overwrite", default=False, action="store_true", help="never interpret a second filename as OUTPUT; overwrite the source files") parser.add_argument("--no-overwrite", default=False, action="store_true", help="never interpret a second filename as OUTPUT; output goes to files named INPUT.fixed") -parser.add_argument("filename", nargs='*', help="input filenames; or INPUT_FILENAME OUTPUT_FILENAME if exactly two filenames are given and neither --overwrite nor --no-overwrite is present", +parser.add_argument("filename", nargs='*', help="input filenames; or (deprecated) INPUT_FILENAME OUTPUT_FILENAME if exactly two filenames are given and neither --overwrite nor --no-overwrite is present", type=str) args = parser.parse_args() From 6b5b82c56ee588552f2123ddda0f858890230c85 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Wed, 19 Jul 2023 22:23:27 +0900 Subject: [PATCH 150/150] Fix random doctests in elliptic curves --- src/sage/schemes/elliptic_curves/heegner.py | 28 ++++++++++--------- .../schemes/elliptic_curves/hom_frobenius.py | 12 +++++--- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/heegner.py b/src/sage/schemes/elliptic_curves/heegner.py index 71e620c643a..71f4b86d29b 100644 --- a/src/sage/schemes/elliptic_curves/heegner.py +++ b/src/sage/schemes/elliptic_curves/heegner.py @@ -47,8 +47,8 @@ sage: E = EllipticCurve('43a'); P = E.heegner_point(-7) sage: P.x_poly_exact() x - sage: P.point_exact() - (0 : 0 : 1) + sage: z = P.point_exact(); z == E(0,0,1) or -z == E(0,0,1) + True sage: E = EllipticCurve('997a') sage: E.rank() @@ -58,16 +58,17 @@ sage: P = E.heegner_point(-19) sage: P.x_poly_exact() x - 141/49 - sage: P.point_exact() - (141/49 : -162/343 : 1) + sage: z = P.point_exact(); z == E(141/49, -162/343, 1) or -z == E(141/49, -162/343, 1) + True Here we find that the Heegner point generates a subgroup of index 3:: sage: E = EllipticCurve('92b1') sage: E.heegner_discriminants_list(1) [-7] - sage: P = E.heegner_point(-7); z = P.point_exact(); z - (0 : 1 : 1) + sage: P = E.heegner_point(-7) + sage: z = P.point_exact(); z == E(0, 1, 1) or -z == E(0, 1, 1) + True sage: E.regulator() 0.0498083972980648 sage: z.height() @@ -6421,8 +6422,8 @@ def ell_heegner_point(self, D, c=ZZ(1), f=None, check=True): [-7, -11, -40, -47, -67, -71, -83, -84, -95, -104] sage: P = E.heegner_point(-7); P # indirect doctest Heegner point of discriminant -7 on elliptic curve of conductor 37 - sage: P.point_exact() - (0 : 0 : 1) + sage: z = P.point_exact(); z == E(0, 0, 1) or -z == E(0, 0, 1) + True sage: P.curve() Elliptic Curve defined by y^2 + y = x^3 - x over Rational Field sage: P = E.heegner_point(-40).point_exact(); P @@ -7139,14 +7140,15 @@ def heegner_sha_an(self, D, prec=53): 2.3 in [GZ1986]_ page 311, then that conjecture is false, as the following example shows:: - sage: E = EllipticCurve('65a') # long time - sage: E.heegner_sha_an(-56) # long time + sage: # long time + sage: E = EllipticCurve('65a') + sage: E.heegner_sha_an(-56) 1.00000000000000 - sage: E.torsion_order() # long time + sage: E.torsion_order() 2 - sage: E.tamagawa_product() # long time + sage: E.tamagawa_product() 1 - sage: E.quadratic_twist(-56).rank() # long time + sage: E.quadratic_twist(-56).rank() 2 """ # check conditions, then return from cache if possible. diff --git a/src/sage/schemes/elliptic_curves/hom_frobenius.py b/src/sage/schemes/elliptic_curves/hom_frobenius.py index 38f22f1fcdf..b5f2b51d470 100644 --- a/src/sage/schemes/elliptic_curves/hom_frobenius.py +++ b/src/sage/schemes/elliptic_curves/hom_frobenius.py @@ -269,10 +269,14 @@ def _eval(self, P): sage: from sage.schemes.elliptic_curves.hom_frobenius import EllipticCurveHom_frobenius sage: E = EllipticCurve(GF(11), [1,1]) sage: pi = EllipticCurveHom_frobenius(E) - sage: P = E.change_ring(GF(11^6)).lift_x(GF(11^3).gen()); P - (6*z6^5 + 8*z6^4 + 8*z6^3 + 6*z6^2 + 10*z6 + 5 : 2*z6^5 + 2*z6^4 + 2*z6^3 + 4*z6 + 6 : 1) - sage: pi._eval(P) - (z6^5 + 3*z6^4 + 3*z6^3 + 6*z6^2 + 9 : z6^5 + 10*z6^4 + 10*z6^3 + 5*z6^2 + 4*z6 + 8 : 1) + sage: Ebar = E.change_ring(GF(11^6)) + sage: z6 = GF(11^6).gen() + sage: P = Ebar.lift_x(GF(11^3).gen()) + sage: p = Ebar(6*z6^5 + 8*z6^4 + 8*z6^3 + 6*z6^2 + 10*z6 + 5, 2*z6^5 + 2*z6^4 + 2*z6^3 + 4*z6 + 6, 1) + sage: Q = pi._eval(P) + sage: q = Ebar(z6^5 + 3*z6^4 + 3*z6^3 + 6*z6^2 + 9, z6^5 + 10*z6^4 + 10*z6^3 + 5*z6^2 + 4*z6 + 8, 1) + sage: (P == p and Q == q) or (P == -p and Q == -q) + True """ if self._domain.defining_polynomial()(*P): raise ValueError(f'{P} not on {self._domain}')