From ab060f7edb919a161c00b7935eac0c747b2ecb8f Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Mon, 8 Aug 2016 17:27:48 +0200 Subject: [PATCH 001/232] add file k_regular_sequence --- src/sage/combinat/k_regular_sequence.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/sage/combinat/k_regular_sequence.py diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py new file mode 100644 index 00000000000..e69de29bb2d From 26b96c8bfaa26c60659fd556abaa6f3a37b0b63f Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Mon, 8 Aug 2016 17:29:02 +0200 Subject: [PATCH 002/232] file head --- src/sage/combinat/k_regular_sequence.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index e69de29bb2d..de18e38e31c 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -0,0 +1,6 @@ +r""" +`k`-regular Sequences +""" + +import itertools +from sage.misc.cachefunc import cached_function, cached_method From cd66611afe568c3e6aba0077b03676af8b67506e Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Mon, 8 Aug 2016 17:29:53 +0200 Subject: [PATCH 003/232] element-class kRegularSequence --- src/sage/combinat/k_regular_sequence.py | 71 +++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index de18e38e31c..966d27004ac 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -4,3 +4,74 @@ import itertools from sage.misc.cachefunc import cached_function, cached_method +from sage.structure.element import Element + +class kRegularSequence(Element): + + def __init__(self, parent, matrices, initial=None, selection=None, + output_function=None, transpose=False): + r""" + TESTS:: + + sage: from sage.combinat.k_regular_sequence import kRegularSequences + sage: Seq2 = kRegularSequences(2, ZZ) + sage: Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: Matrix([[0, 1]]), Matrix([[1], [0]]), + ....: lambda o: o[0, 0], transpose=True) + 2-regular sequence 0, 1, 3, 5, 9, 11, 15, 19, 27, 29, ... + """ + super(kRegularSequence, self).__init__(parent=parent) + + def tr(M): + try: + return M.transpose() if transpose else M + except AttributeError: + return M + + self.matrices = tuple(tr(M) for M in matrices) + self.k = len(self.matrices) + self.d = self.matrices[0].nrows() + if not all(M.dimensions() == (self.d, self.d) for M in self.matrices): + raise ValueError + + if not transpose: + self.initial = initial + self.selection = selection + else: + self.initial = tr(selection) + self.selection = tr(initial) + + if output_function is None: + self.output_function = lambda o: o + else: + self.output_function = output_function + + + def _repr_(self): + # TODO + from sage.arith.srange import xsrange + return '{}-regular sequence '.format(self.parent().k) +\ + ', '.join(repr(self[n]) for n in xsrange(10)) + ', ...' + + + @cached_method + def __getitem__(self, n): + result = self.product_of_matrices(n) + if self.initial is not None: + result = self.initial * result + if self.selection is not None: + result = result * self.selection + return self.output_function(result) + + + @cached_method + def product_of_matrices(self, m): + k = self.parent().k + if m < 0: + raise ValueError + if 0 <= m < k: + return self.matrices[m] + n = m // k + r = m - n*k + return self.matrices[r] * self.product_of_matrices(n) + From 3fab5215594a7a19e392bf3391c8d8de3d1fe151 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Mon, 8 Aug 2016 17:30:24 +0200 Subject: [PATCH 004/232] parent-class kRegularSequences --- src/sage/combinat/k_regular_sequence.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 966d27004ac..2a3dc03decf 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -75,3 +75,28 @@ def product_of_matrices(self, m): r = m - n*k return self.matrices[r] * self.product_of_matrices(n) + +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.parent import Parent + +class kRegularSequences(UniqueRepresentation, Parent): + + Element = kRegularSequence + + def __init__(self, k, base, category=None): + r""" + TESTS:: + + sage: from sage.combinat.k_regular_sequence import kRegularSequences + sage: kRegularSequences(2, ZZ) + Set of 2-regular sequences over Integer Ring + """ + from sage.categories.sets_cat import Sets + self.k = k + super(kRegularSequences, self).__init__(category=category or Sets(), + base=base) + + + def _repr_(self): + return 'Set of {}-regular sequences over {}'.format(self.k, self.base()) + From 932535928430b7cf6d17c97b90fe84c6ac872ea4 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Mon, 8 Aug 2016 17:30:40 +0200 Subject: [PATCH 005/232] info-method for sequences --- src/sage/combinat/k_regular_sequence.py | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 2a3dc03decf..88e57688ccc 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -54,6 +54,32 @@ def _repr_(self): ', '.join(repr(self[n]) for n in xsrange(10)) + ', ...' + def info(self): + r""" + EXAMPLES: + + sage: from sage.combinat.k_regular_sequence import kRegularSequences + sage: Seq2 = kRegularSequences(2, ZZ) + sage: Seq2.guess(lambda n: sum(n.digits(2))).info() + matrices: + ( + [1 0] [ 0 -1] + [0 1], [ 1 2] + ) + initial: + (0, 1) + selection: + (1, 0) + """ + from sys import displayhook + print('matrices:') + displayhook(self.matrices) + print('initial:') + displayhook(self.initial) + print('selection:') + displayhook(self.selection) + + @cached_method def __getitem__(self, n): result = self.product_of_matrices(n) From 905a9f4bf603cccf9bafb94cb9de576daabcc586 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Mon, 8 Aug 2016 17:31:02 +0200 Subject: [PATCH 006/232] examples in head of file --- src/sage/combinat/k_regular_sequence.py | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 88e57688ccc..ad016982083 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -1,9 +1,57 @@ r""" `k`-regular Sequences + +EXAMPLES:: + + sage: from sage.combinat.k_regular_sequence import kRegularSequences + +Binary sum of digits:: + + sage: Seq2 = kRegularSequences(2, ZZ) + sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), + ....: initial=vector([0, 1]), selection=vector([1, 0])) + sage: S + 2-regular sequence 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, ... + sage: all(S[n] == sum(n.digits(2)) for n in srange(10)) + True + +Dumas, Example 2:: + + sage: @cached_function + ....: def u(n): + ....: if n <= 1: + ....: return n + ....: elif 2.divides(n): + ....: return 3*u(n//2) + ....: else: + ....: return 2*u(n//2) + u(n//2+1) + sage: tuple(u(n) for n in srange(10)) + (0, 1, 3, 5, 9, 11, 15, 19, 27, 29) + + sage: U = Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: initial=vector([0, 1]), selection=vector([1, 0]), transpose=True) + sage: all(U[n] == u(n) for n in srange(10)) + True """ import itertools from sage.misc.cachefunc import cached_function, cached_method + +def pad_right(T, length, zero=0): + r""" + TESTS:: + + sage: from sage.combinat.k_regular_sequence import pad_right + sage: pad_right((1,2,3), 10) + (1, 2, 3, 0, 0, 0, 0, 0, 0, 0) + sage: pad_right((1,2,3), 2) + (1, 2, 3) + sage: pad_right([1,2,3], 10) + [1, 2, 3, 0, 0, 0, 0, 0, 0, 0] + """ + return T + type(T)(zero for _ in xrange(length - len(T))) + + from sage.structure.element import Element class kRegularSequence(Element): From b954e5e917c47e52269b086a626190a9c9c495ce Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 9 Aug 2016 17:45:39 +0200 Subject: [PATCH 007/232] remove pad_right (only used by .guess in other branch) --- src/sage/combinat/k_regular_sequence.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index ad016982083..fe9490c61a4 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -37,21 +37,6 @@ import itertools from sage.misc.cachefunc import cached_function, cached_method -def pad_right(T, length, zero=0): - r""" - TESTS:: - - sage: from sage.combinat.k_regular_sequence import pad_right - sage: pad_right((1,2,3), 10) - (1, 2, 3, 0, 0, 0, 0, 0, 0, 0) - sage: pad_right((1,2,3), 2) - (1, 2, 3) - sage: pad_right([1,2,3], 10) - [1, 2, 3, 0, 0, 0, 0, 0, 0, 0] - """ - return T + type(T)(zero for _ in xrange(length - len(T))) - - from sage.structure.element import Element class kRegularSequence(Element): From 1d3e6cb433760e9bac20870b215755200d6e4a97 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 9 Aug 2016 17:45:46 +0200 Subject: [PATCH 008/232] cleanup imports --- src/sage/combinat/k_regular_sequence.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index fe9490c61a4..c7540d110f0 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -34,9 +34,7 @@ True """ -import itertools -from sage.misc.cachefunc import cached_function, cached_method - +from sage.misc.cachefunc import cached_method from sage.structure.element import Element class kRegularSequence(Element): From a4c29b6c21fef8efe1daf6a42e232789850d78f1 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Wed, 10 Aug 2016 10:03:27 +0200 Subject: [PATCH 009/232] fix doctests --- src/sage/combinat/k_regular_sequence.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index c7540d110f0..df5b9303bff 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -87,11 +87,11 @@ def _repr_(self): def info(self): r""" - EXAMPLES: + EXAMPLES:: - sage: from sage.combinat.k_regular_sequence import kRegularSequences sage: Seq2 = kRegularSequences(2, ZZ) - sage: Seq2.guess(lambda n: sum(n.digits(2))).info() + sage: Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), + ....: initial=vector([0, 1]), selection=vector([1, 0])).info() matrices: ( [1 0] [ 0 -1] From 7d7fc5b87708ac22f8c4d6fa44bb8df844b32654 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Wed, 10 Aug 2016 10:03:53 +0200 Subject: [PATCH 010/232] lazy import kRegularSequences into global namespace --- src/sage/combinat/all.py | 3 ++- src/sage/combinat/k_regular_sequence.py | 6 +----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 56134dc3a08..5595234c32b 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -176,8 +176,9 @@ ['Automaton', 'Transducer', 'FiniteStateMachine']) lazy_import('sage.combinat.finite_state_machine_generators', ['automata', 'transducers']) -# Binary Recurrence Sequences +# Sequences from binary_recurrence_sequences import BinaryRecurrenceSequence +lazy_import('sage.combinat.k_regular_sequence', 'kRegularSequences') # Six Vertex Model lazy_import('sage.combinat.six_vertex_model', 'SixVertexModel') diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index df5b9303bff..5bffb6fd20d 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -1,9 +1,7 @@ r""" `k`-regular Sequences -EXAMPLES:: - - sage: from sage.combinat.k_regular_sequence import kRegularSequences +EXAMPLES: Binary sum of digits:: @@ -44,7 +42,6 @@ def __init__(self, parent, matrices, initial=None, selection=None, r""" TESTS:: - sage: from sage.combinat.k_regular_sequence import kRegularSequences sage: Seq2 = kRegularSequences(2, ZZ) sage: Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), ....: Matrix([[0, 1]]), Matrix([[1], [0]]), @@ -144,7 +141,6 @@ def __init__(self, k, base, category=None): r""" TESTS:: - sage: from sage.combinat.k_regular_sequence import kRegularSequences sage: kRegularSequences(2, ZZ) Set of 2-regular sequences over Integer Ring """ From 57ca90049d837bd7f646042a0ea74492133f0328 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Wed, 10 Aug 2016 10:04:08 +0200 Subject: [PATCH 011/232] add k_regular_sequence to documentation --- src/doc/en/reference/combinat/module_list.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index 16c87d06180..50147089c88 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -127,6 +127,7 @@ Comprehensive Module list sage/combinat/integer_vector_weighted sage/combinat/integer_vectors_mod_permgroup sage/combinat/interval_posets + sage/combinat/k_regular_sequence sage/combinat/k_tableau sage/combinat/kazhdan_lusztig sage/combinat/knutson_tao_puzzles From d8ae454d8607f6d272872d27eb60786f480eca0b Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Wed, 10 Aug 2016 13:23:40 +0200 Subject: [PATCH 012/232] make product_of_matrices a private method --- src/sage/combinat/k_regular_sequence.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 5bffb6fd20d..900696560bb 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -111,6 +111,7 @@ def info(self): @cached_method def __getitem__(self, n): result = self.product_of_matrices(n) + result = self._product_of_matrices_(n) if self.initial is not None: result = self.initial * result if self.selection is not None: @@ -119,7 +120,7 @@ def __getitem__(self, n): @cached_method - def product_of_matrices(self, m): + def _product_of_matrices_(self, m): k = self.parent().k if m < 0: raise ValueError @@ -127,7 +128,7 @@ def product_of_matrices(self, m): return self.matrices[m] n = m // k r = m - n*k - return self.matrices[r] * self.product_of_matrices(n) + return self.matrices[r] * self._product_of_matrices_(n) from sage.structure.unique_representation import UniqueRepresentation From 03c641860b85535c432fe5b96209b6b544dd1559 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Wed, 10 Aug 2016 13:24:18 +0200 Subject: [PATCH 013/232] docstring of _product_of_matrices_ --- src/sage/combinat/k_regular_sequence.py | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 900696560bb..cec1c49ead3 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -121,6 +121,32 @@ def __getitem__(self, n): @cached_method def _product_of_matrices_(self, m): + r""" + Return the product of matrices according to the `k`-ary + digit expansion of `m`. + + INPUT: + + - ``m`` -- a nonnegative integer. + + OUTPUT: + + A matrix. + + TESTS:: + + sage: Seq2 = kRegularSequences(2, ZZ) + sage: M0 = Matrix([[1, 0], [0, 1]]) + sage: M1 = Matrix([[0, -1], [1, 2]]) + sage: S = Seq2((M0, M1)) + sage: S._product_of_matrices_(0) == M0 + True + sage: S._product_of_matrices_(1) == M1 + True + sage: S._product_of_matrices_(3) == M1^2 + True + + """ k = self.parent().k if m < 0: raise ValueError From 16e041bd42b65cf1aac94b6db23846365bddabf6 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Wed, 10 Aug 2016 13:24:40 +0200 Subject: [PATCH 014/232] phrase error message and test it --- src/sage/combinat/k_regular_sequence.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index cec1c49ead3..299371e5d0c 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -146,10 +146,16 @@ def _product_of_matrices_(self, m): sage: S._product_of_matrices_(3) == M1^2 True + :: + + sage: S._product_of_matrices_(-1) + Traceback (most recent call last): + ... + ValueError: m=-1 is not a nonnegative integer. """ k = self.parent().k if m < 0: - raise ValueError + raise ValueError('m={} is not a nonnegative integer.'.format(m)) if 0 <= m < k: return self.matrices[m] n = m // k From 091a2766fc5c8bc406f7f763823d45c2a17ca5f7 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Wed, 10 Aug 2016 13:25:25 +0200 Subject: [PATCH 015/232] use lazy_list_formatter in _repr_ --- src/sage/combinat/k_regular_sequence.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 299371e5d0c..c7de8ec2f2a 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -76,10 +76,12 @@ def tr(M): def _repr_(self): - # TODO - from sage.arith.srange import xsrange - return '{}-regular sequence '.format(self.parent().k) +\ - ', '.join(repr(self[n]) for n in xsrange(10)) + ', ...' + from sage.misc.lazy_list import lazy_list_formatter + return lazy_list_formatter( + [self[n] for n in xrange(11)], # once slicing works, use self + name='{}-regular sequence'.format(self.parent().k), + opening_delimiter='', closing_delimiter='', + preview=10) def info(self): From e233dfd623872af801341a174ce4ea0e188652bd Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Wed, 10 Aug 2016 13:26:01 +0200 Subject: [PATCH 016/232] write the missing docstrings --- src/sage/combinat/k_regular_sequence.py | 131 ++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 9 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index c7de8ec2f2a..ca4210cc4f8 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -13,36 +13,109 @@ sage: all(S[n] == sum(n.digits(2)) for n in srange(10)) True -Dumas, Example 2:: +Number of odd entries in Pascal's triangle +------------------------------------------ + +:: sage: @cached_function ....: def u(n): ....: if n <= 1: ....: return n - ....: elif 2.divides(n): - ....: return 3*u(n//2) - ....: else: - ....: return 2*u(n//2) + u(n//2+1) + ....: return 2*u(floor(n/2)) + u(ceil(n/2)) sage: tuple(u(n) for n in srange(10)) (0, 1, 3, 5, 9, 11, 15, 19, 27, 29) sage: U = Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), ....: initial=vector([0, 1]), selection=vector([1, 0]), transpose=True) - sage: all(U[n] == u(n) for n in srange(10)) + sage: all(U[n] == u(n) for n in srange(30)) True + + +Various +======= + +.. SEEALSO:: + + :doc:`sage/rings/cfinite_sequence`, + :doc:`sage/combinat/binary_recurrence_sequences`. + +REFERENCES: + +.. [AS2003] Jean-Paul Allouche, Jeffrey Shallit, + *Automatic Sequences: Theory, Applications, Generalizations*, + Cambridge University Press, 2003. + +AUTHORS: + +- Daniel Krenn (2016) + +ACKNOWLEDGEMENT: + +- Daniel Krenn is supported by the + Austrian Science Fund (FWF): P 24644-N26. + + +Classes and Methods +=================== """ +#***************************************************************************** +# Copyright (C) 2016 Daniel Krenn +# +# 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** from sage.misc.cachefunc import cached_method from sage.structure.element import Element + class kRegularSequence(Element): def __init__(self, parent, matrices, initial=None, selection=None, output_function=None, transpose=False): r""" - TESTS:: + A `k`-regular sequence. + + INPUT: + + - ``parent`` -- an instance of :class:`kRegularSequences`. + + - ``matrices`` -- a tuple or other iterable of square matrices, + all of which have the same dimension. + + - ``initial`` -- (default: ``None``) a vector. + When evaluating the sequence, this vector is multiplied + from the left to the matrix product. If ``None``, then this + multiplication is skipped. + + - ``selection`` -- (default: ``None``) a vector. + When evaluating the sequence, this vector is multiplied + from the left to the matrix product. If ``None``, then this + multiplication is skipped. + + - ``output_function`` -- (default: ``None``) a function, which is + applied after evaluating the sequence. This may be used to + extract the value of a 1x1 matrix. + + - ``transpose`` -- (default: ``False``) a boolean. If set, then + each of the ``matrices``. Additionally the vectors ``initial`` + and ``selection`` are switched and (if possible) + transposed as well. + + EXAMPLES:: sage: Seq2 = kRegularSequences(2, ZZ) + sage: Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: vector([0, 1]), vector([1, 0]), + ....: transpose=True) + 2-regular sequence 0, 1, 3, 5, 9, 11, 15, 19, 27, 29, ... + + Using an output function:: + sage: Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), ....: Matrix([[0, 1]]), Matrix([[1], [0]]), ....: lambda o: o[0, 0], transpose=True) @@ -60,7 +133,7 @@ def tr(M): self.k = len(self.matrices) self.d = self.matrices[0].nrows() if not all(M.dimensions() == (self.d, self.d) for M in self.matrices): - raise ValueError + raise ValueError # TODO if not transpose: self.initial = initial @@ -76,6 +149,21 @@ def tr(M): def _repr_(self): + r""" + Return a representation string of this `k`-regular sequence + + OUTPUT: + + A string + + TESTS:: + + sage: Seq2 = kRegularSequences(2, ZZ) + sage: s = Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: vector([0, 1]), vector([1, 0]), transpose=True) + sage: repr(s) # indirect doctest + '2-regular sequence 0, 1, 3, 5, 9, 11, 15, 19, 27, 29, ...' + """ from sage.misc.lazy_list import lazy_list_formatter return lazy_list_formatter( [self[n] for n in xrange(11)], # once slicing works, use self @@ -86,6 +174,13 @@ def _repr_(self): def info(self): r""" + Displays the matrices of the `k`-linear representation, the initial + vector and the selection vector. + + OUTPUT: + + Nothing; printing to standard output. + EXAMPLES:: sage: Seq2 = kRegularSequences(2, ZZ) @@ -112,7 +207,25 @@ def info(self): @cached_method def __getitem__(self, n): - result = self.product_of_matrices(n) + r""" + Return the `n`th entry of this sequence. + + INPUT: + + - ``n`` -- a nonnegative integer. + + OUTPUT: + + An element of the universe of the sequence. + + EXAMPLES:: + + sage: Seq2 = kRegularSequences(2, ZZ) + sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), + ....: initial=vector([0, 1]), selection=vector([1, 0])) + sage: S[7] + 3 + """ result = self._product_of_matrices_(n) if self.initial is not None: result = self.initial * result From 181862819325bcd88b942155c101bcb4ce968fa2 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Wed, 10 Aug 2016 13:26:20 +0200 Subject: [PATCH 017/232] modify docstring at top of file --- src/sage/combinat/k_regular_sequence.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index ca4210cc4f8..622a947b023 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -1,9 +1,18 @@ r""" `k`-regular Sequences -EXAMPLES: +An introduction and formal definition of `k`-regular sequences can be +found, for example, on the :wikipedia:`k-regular_sequence` or in +[AS2003]_. -Binary sum of digits:: + +Examples +======== + +Binary sum of digits +-------------------- + +:: sage: Seq2 = kRegularSequences(2, ZZ) sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), From 1e5efc8e7909240e47d5e997b1e9934d40ba5786 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Wed, 10 Aug 2016 13:47:56 +0200 Subject: [PATCH 018/232] rename to kRegularSequenceSpace --- src/sage/combinat/all.py | 2 +- src/sage/combinat/k_regular_sequence.py | 29 ++++++++++++++----------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 5595234c32b..05744aa779f 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -178,7 +178,7 @@ ['automata', 'transducers']) # Sequences from binary_recurrence_sequences import BinaryRecurrenceSequence -lazy_import('sage.combinat.k_regular_sequence', 'kRegularSequences') +lazy_import('sage.combinat.k_regular_sequence', 'kRegularSequenceSpace') # Six Vertex Model lazy_import('sage.combinat.six_vertex_model', 'SixVertexModel') diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 622a947b023..f3d0748b298 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -14,7 +14,7 @@ :: - sage: Seq2 = kRegularSequences(2, ZZ) + sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), ....: initial=vector([0, 1]), selection=vector([1, 0])) sage: S @@ -91,7 +91,7 @@ def __init__(self, parent, matrices, initial=None, selection=None, INPUT: - - ``parent`` -- an instance of :class:`kRegularSequences`. + - ``parent`` -- an instance of :class:`kRegularSequenceSpace`. - ``matrices`` -- a tuple or other iterable of square matrices, all of which have the same dimension. @@ -117,7 +117,7 @@ def __init__(self, parent, matrices, initial=None, selection=None, EXAMPLES:: - sage: Seq2 = kRegularSequences(2, ZZ) + sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), ....: vector([0, 1]), vector([1, 0]), ....: transpose=True) @@ -167,7 +167,7 @@ def _repr_(self): TESTS:: - sage: Seq2 = kRegularSequences(2, ZZ) + sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: s = Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), ....: vector([0, 1]), vector([1, 0]), transpose=True) sage: repr(s) # indirect doctest @@ -192,7 +192,7 @@ def info(self): EXAMPLES:: - sage: Seq2 = kRegularSequences(2, ZZ) + sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), ....: initial=vector([0, 1]), selection=vector([1, 0])).info() matrices: @@ -229,7 +229,7 @@ def __getitem__(self, n): EXAMPLES:: - sage: Seq2 = kRegularSequences(2, ZZ) + sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), ....: initial=vector([0, 1]), selection=vector([1, 0])) sage: S[7] @@ -259,7 +259,7 @@ def _product_of_matrices_(self, m): TESTS:: - sage: Seq2 = kRegularSequences(2, ZZ) + sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: M0 = Matrix([[1, 0], [0, 1]]) sage: M1 = Matrix([[0, -1], [1, 2]]) sage: S = Seq2((M0, M1)) @@ -290,7 +290,7 @@ def _product_of_matrices_(self, m): from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent -class kRegularSequences(UniqueRepresentation, Parent): +class kRegularSequenceSpace(UniqueRepresentation, Parent): Element = kRegularSequence @@ -298,15 +298,18 @@ def __init__(self, k, base, category=None): r""" TESTS:: - sage: kRegularSequences(2, ZZ) - Set of 2-regular sequences over Integer Ring + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: Seq2 + Space of 2-regular sequences over Integer Ring + sage: Seq2.category() + Category of sets """ from sage.categories.sets_cat import Sets self.k = k - super(kRegularSequences, self).__init__(category=category or Sets(), - base=base) + super(kRegularSequenceSpace, self).__init__( + category=category or Sets(), base=base) def _repr_(self): - return 'Set of {}-regular sequences over {}'.format(self.k, self.base()) + return 'Space of {}-regular sequences over {}'.format(self.k, self.base()) From c14ecebf4d963a86ce058199f30efa57b9925385 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Wed, 10 Aug 2016 15:02:17 +0200 Subject: [PATCH 019/232] __classcall__ and __init__ --- src/sage/combinat/k_regular_sequence.py | 56 +++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index f3d0748b298..f0d8574d990 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -294,20 +294,68 @@ class kRegularSequenceSpace(UniqueRepresentation, Parent): Element = kRegularSequence - def __init__(self, k, base, category=None): + @staticmethod + def __classcall__(cls, k, universe=None, category=None): r""" + Normalizes the input in order to ensure a unique + representation. + + For more information see :class:`kRegularSequenceSpace`. + TESTS:: sage: Seq2 = kRegularSequenceSpace(2, ZZ) - sage: Seq2 - Space of 2-regular sequences over Integer Ring sage: Seq2.category() Category of sets + + :: + + sage: Seq2 is kRegularSequenceSpace(2) + True """ + if universe is None: + from sage.rings.integer_ring import ZZ + universe = ZZ + from sage.categories.sets_cat import Sets + category = category or Sets() + + return super(kRegularSequenceSpace, cls).__classcall__( + cls, k, universe, category) + + + def __init__(self, k, universe, category): + r""" + The space of `k`-regular Sequences over the given ``universe``. + + INPUT: + + - ``k`` -- an integer at least `2` specifying the base. + + - ``universe`` -- (default: ``None``) a object (e.g. a SageMath parent) + in which the entries of a sequence live. + If ``None``, then the integer ring `\ZZ` is used. + + - ``category`` -- (default: ``None``) the category of the + sequence space. If ``None``, then the category of + :class:`~sage.categories.sets_cat.Sets` is used. + + EXAMPLES:: + + sage: kRegularSequenceSpace(2, ZZ) + Space of 2-regular sequences over Integer Ring + sage: kRegularSequenceSpace(3, ZZ) + Space of 3-regular sequences over Integer Ring + + .. SEEALSO:: + + :doc:`k-regular sequence `, + :class:`kRegularSequence`. + """ self.k = k + self.universe = universe super(kRegularSequenceSpace, self).__init__( - category=category or Sets(), base=base) + category=category, base=universe) def _repr_(self): From 9c641804c3ce2ff144e4a2a320b0cab13157872f Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Wed, 10 Aug 2016 15:02:37 +0200 Subject: [PATCH 020/232] write remaining docstrings --- src/sage/combinat/k_regular_sequence.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index f0d8574d990..0f873e7b2ed 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -129,6 +129,11 @@ def __init__(self, parent, matrices, initial=None, selection=None, ....: Matrix([[0, 1]]), Matrix([[1], [0]]), ....: lambda o: o[0, 0], transpose=True) 2-regular sequence 0, 1, 3, 5, 9, 11, 15, 19, 27, 29, ... + + .. SEEALSO:: + + :doc:`k-regular sequence `, + :class:`kRegularSequenceSpace`. """ super(kRegularSequence, self).__init__(parent=parent) @@ -159,7 +164,7 @@ def tr(M): def _repr_(self): r""" - Return a representation string of this `k`-regular sequence + Return a representation string of this `k`-regular sequence. OUTPUT: @@ -359,5 +364,17 @@ def __init__(self, k, universe, category): def _repr_(self): + r""" + Return a representation string of this `k`-regular sequence space. + + OUTPUT: + + A string + + TESTS:: + + sage: repr(kRegularSequenceSpace(2, ZZ)) # indirect doctest + 'Space of 2-regular sequences over Integer Ring' + """ return 'Space of {}-regular sequences over {}'.format(self.k, self.base()) From 4102e47b3b8abddaccece1211a2fedc07e14e4f5 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 11 Aug 2016 08:32:37 +0200 Subject: [PATCH 021/232] __iter__ method --- src/sage/combinat/k_regular_sequence.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 0f873e7b2ed..c826b89f22d 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -292,6 +292,31 @@ def _product_of_matrices_(self, m): return self.matrices[r] * self._product_of_matrices_(n) + def __iter__(self): + r""" + Return an iterator. + + EXAMPLES:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), + ....: initial=vector([0, 1]), selection=vector([1, 0])) + sage: from itertools import islice + sage: tuple(islice(S, 10)) + (0, 1, 1, 2, 1, 2, 2, 3, 1, 2) + + TESTS:: + + sage: it = iter(S) + sage: iter(it) is it + True + sage: iter(S) is not it + True + """ + from itertools import count + return iter(self[n] for n in count()) + + from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent From b92ff4430db1f69cc481ab72f83e5c9b419f5661 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 11 Aug 2016 08:32:50 +0200 Subject: [PATCH 022/232] simplify _repr_ code --- src/sage/combinat/k_regular_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index c826b89f22d..42176d6f23b 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -180,7 +180,7 @@ def _repr_(self): """ from sage.misc.lazy_list import lazy_list_formatter return lazy_list_formatter( - [self[n] for n in xrange(11)], # once slicing works, use self + self, name='{}-regular sequence'.format(self.parent().k), opening_delimiter='', closing_delimiter='', preview=10) From 28149c9225a2482c14bc6713fc2161e0d9e3bb72 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 16 Aug 2016 17:21:42 +0200 Subject: [PATCH 023/232] import of recognizable series --- src/sage/combinat/k_regular_sequence.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 42176d6f23b..d0a5537f994 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -78,10 +78,11 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.combinat.recognizable_series import RecognizableSeries +from sage.combinat.recognizable_series import RecognizableSeriesSpace from sage.misc.cachefunc import cached_method from sage.structure.element import Element - class kRegularSequence(Element): def __init__(self, parent, matrices, initial=None, selection=None, From a26a051c067f5ad03e801a97734f22fdf6619f61 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 16 Aug 2016 17:22:56 +0200 Subject: [PATCH 024/232] matrices --> mu --- src/sage/combinat/k_regular_sequence.py | 32 ++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index d0a5537f994..582c7b20cd2 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -85,7 +85,7 @@ class kRegularSequence(Element): - def __init__(self, parent, matrices, initial=None, selection=None, + def __init__(self, parent, mu, initial=None, selection=None, output_function=None, transpose=False): r""" A `k`-regular sequence. @@ -94,7 +94,7 @@ def __init__(self, parent, matrices, initial=None, selection=None, - ``parent`` -- an instance of :class:`kRegularSequenceSpace`. - - ``matrices`` -- a tuple or other iterable of square matrices, + - ``mu`` -- a tuple or other iterable of square matrices, all of which have the same dimension. - ``initial`` -- (default: ``None``) a vector. @@ -112,7 +112,7 @@ def __init__(self, parent, matrices, initial=None, selection=None, extract the value of a 1x1 matrix. - ``transpose`` -- (default: ``False``) a boolean. If set, then - each of the ``matrices``. Additionally the vectors ``initial`` + each of the ``mu``. Additionally the vectors ``initial`` and ``selection`` are switched and (if possible) transposed as well. @@ -144,10 +144,10 @@ def tr(M): except AttributeError: return M - self.matrices = tuple(tr(M) for M in matrices) - self.k = len(self.matrices) - self.d = self.matrices[0].nrows() - if not all(M.dimensions() == (self.d, self.d) for M in self.matrices): + self.mu = tuple(tr(M) for M in mu) + self.k = len(self.mu) + self.d = self.mu[0].nrows() + if not all(M.dimensions() == (self.d, self.d) for M in self.mu): raise ValueError # TODO if not transpose: @@ -213,7 +213,7 @@ def info(self): """ from sys import displayhook print('matrices:') - displayhook(self.matrices) + displayhook(self.mu) print('initial:') displayhook(self.initial) print('selection:') @@ -241,7 +241,7 @@ def __getitem__(self, n): sage: S[7] 3 """ - result = self._product_of_matrices_(n) + result = self._product_of_mu_(n) if self.initial is not None: result = self.initial * result if self.selection is not None: @@ -250,7 +250,7 @@ def __getitem__(self, n): @cached_method - def _product_of_matrices_(self, m): + def _product_of_mu_(self, m): r""" Return the product of matrices according to the `k`-ary digit expansion of `m`. @@ -269,16 +269,16 @@ def _product_of_matrices_(self, m): sage: M0 = Matrix([[1, 0], [0, 1]]) sage: M1 = Matrix([[0, -1], [1, 2]]) sage: S = Seq2((M0, M1)) - sage: S._product_of_matrices_(0) == M0 + sage: S._product_of_mu_(0) == M0 True - sage: S._product_of_matrices_(1) == M1 + sage: S._product_of_mu_(1) == M1 True - sage: S._product_of_matrices_(3) == M1^2 + sage: S._product_of_mu_(3) == M1^2 True :: - sage: S._product_of_matrices_(-1) + sage: S._product_of_mu_(-1) Traceback (most recent call last): ... ValueError: m=-1 is not a nonnegative integer. @@ -287,10 +287,10 @@ def _product_of_matrices_(self, m): if m < 0: raise ValueError('m={} is not a nonnegative integer.'.format(m)) if 0 <= m < k: - return self.matrices[m] + return self.mu[m] n = m // k r = m - n*k - return self.matrices[r] * self._product_of_matrices_(n) + return self.mu[r] * self._product_of_mu_(n) def __iter__(self): From 9128d28b5da38d3f482fabeef94a5ad1c52d85ed Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 16 Aug 2016 17:23:58 +0200 Subject: [PATCH 025/232] initial, selection --> left, right --- src/sage/combinat/k_regular_sequence.py | 52 ++++++++++++------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 582c7b20cd2..8bfe8b06f03 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -16,7 +16,7 @@ sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), - ....: initial=vector([0, 1]), selection=vector([1, 0])) + ....: left=vector([0, 1]), right=vector([1, 0])) sage: S 2-regular sequence 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, ... sage: all(S[n] == sum(n.digits(2)) for n in srange(10)) @@ -36,7 +36,7 @@ (0, 1, 3, 5, 9, 11, 15, 19, 27, 29) sage: U = Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), - ....: initial=vector([0, 1]), selection=vector([1, 0]), transpose=True) + ....: left=vector([0, 1]), right=vector([1, 0]), transpose=True) sage: all(U[n] == u(n) for n in srange(30)) True @@ -85,7 +85,7 @@ class kRegularSequence(Element): - def __init__(self, parent, mu, initial=None, selection=None, + def __init__(self, parent, mu, left=None, right=None, output_function=None, transpose=False): r""" A `k`-regular sequence. @@ -97,12 +97,12 @@ def __init__(self, parent, mu, initial=None, selection=None, - ``mu`` -- a tuple or other iterable of square matrices, all of which have the same dimension. - - ``initial`` -- (default: ``None``) a vector. + - ``left`` -- (default: ``None``) a vector. When evaluating the sequence, this vector is multiplied from the left to the matrix product. If ``None``, then this multiplication is skipped. - - ``selection`` -- (default: ``None``) a vector. + - ``right`` -- (default: ``None``) a vector. When evaluating the sequence, this vector is multiplied from the left to the matrix product. If ``None``, then this multiplication is skipped. @@ -112,8 +112,8 @@ def __init__(self, parent, mu, initial=None, selection=None, extract the value of a 1x1 matrix. - ``transpose`` -- (default: ``False``) a boolean. If set, then - each of the ``mu``. Additionally the vectors ``initial`` - and ``selection`` are switched and (if possible) + each of the ``mu``. Additionally the vectors ``left`` + and ``right`` are switched and (if possible) transposed as well. EXAMPLES:: @@ -151,11 +151,11 @@ def tr(M): raise ValueError # TODO if not transpose: - self.initial = initial - self.selection = selection + self.left = left + self.right = right else: - self.initial = tr(selection) - self.selection = tr(initial) + self.left = tr(right) + self.right = tr(left) if output_function is None: self.output_function = lambda o: o @@ -189,8 +189,8 @@ def _repr_(self): def info(self): r""" - Displays the matrices of the `k`-linear representation, the initial - vector and the selection vector. + Displays the matrices of the `k`-linear representation, the left + vector and the right vector. OUTPUT: @@ -200,24 +200,24 @@ def info(self): sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), - ....: initial=vector([0, 1]), selection=vector([1, 0])).info() + ....: left=vector([0, 1]), right=vector([1, 0])).info() matrices: ( [1 0] [ 0 -1] [0 1], [ 1 2] ) - initial: + left: (0, 1) - selection: + right: (1, 0) """ from sys import displayhook print('matrices:') displayhook(self.mu) - print('initial:') - displayhook(self.initial) - print('selection:') - displayhook(self.selection) + print('left:') + displayhook(self.left) + print('right:') + displayhook(self.right) @cached_method @@ -237,15 +237,15 @@ def __getitem__(self, n): sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), - ....: initial=vector([0, 1]), selection=vector([1, 0])) + ....: left=vector([0, 1]), right=vector([1, 0])) sage: S[7] 3 """ result = self._product_of_mu_(n) - if self.initial is not None: - result = self.initial * result - if self.selection is not None: - result = result * self.selection + if self.left is not None: + result = self.left * result + if self.right is not None: + result = result * self.right return self.output_function(result) @@ -301,7 +301,7 @@ def __iter__(self): sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), - ....: initial=vector([0, 1]), selection=vector([1, 0])) + ....: left=vector([0, 1]), right=vector([1, 0])) sage: from itertools import islice sage: tuple(islice(S, 10)) (0, 1, 1, 2, 1, 2, 2, 3, 1, 2) From b4d5590c88d50596ea88e3854ef1522551590e5f Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 18 Aug 2016 17:01:05 +0200 Subject: [PATCH 026/232] use new code for RecognizableSeries --- src/sage/combinat/k_regular_sequence.py | 168 ++++++------------------ 1 file changed, 38 insertions(+), 130 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 8bfe8b06f03..c9a75bb0b8a 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -83,10 +83,9 @@ from sage.misc.cachefunc import cached_method from sage.structure.element import Element -class kRegularSequence(Element): +class kRegularSequence(RecognizableSeries): - def __init__(self, parent, mu, left=None, right=None, - output_function=None, transpose=False): + def __init__(self, parent, mu, left=None, right=None): r""" A `k`-regular sequence. @@ -107,15 +106,6 @@ def __init__(self, parent, mu, left=None, right=None, from the left to the matrix product. If ``None``, then this multiplication is skipped. - - ``output_function`` -- (default: ``None``) a function, which is - applied after evaluating the sequence. This may be used to - extract the value of a 1x1 matrix. - - - ``transpose`` -- (default: ``False``) a boolean. If set, then - each of the ``mu``. Additionally the vectors ``left`` - and ``right`` are switched and (if possible) - transposed as well. - EXAMPLES:: sage: Seq2 = kRegularSequenceSpace(2, ZZ) @@ -124,43 +114,13 @@ def __init__(self, parent, mu, left=None, right=None, ....: transpose=True) 2-regular sequence 0, 1, 3, 5, 9, 11, 15, 19, 27, 29, ... - Using an output function:: - - sage: Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), - ....: Matrix([[0, 1]]), Matrix([[1], [0]]), - ....: lambda o: o[0, 0], transpose=True) - 2-regular sequence 0, 1, 3, 5, 9, 11, 15, 19, 27, 29, ... - .. SEEALSO:: :doc:`k-regular sequence `, :class:`kRegularSequenceSpace`. """ - super(kRegularSequence, self).__init__(parent=parent) - - def tr(M): - try: - return M.transpose() if transpose else M - except AttributeError: - return M - - self.mu = tuple(tr(M) for M in mu) - self.k = len(self.mu) - self.d = self.mu[0].nrows() - if not all(M.dimensions() == (self.d, self.d) for M in self.mu): - raise ValueError # TODO - - if not transpose: - self.left = left - self.right = right - else: - self.left = tr(right) - self.right = tr(left) - - if output_function is None: - self.output_function = lambda o: o - else: - self.output_function = output_function + super(kRegularSequence, self).__init__( + parent=parent, mu=mu, left=left, right=right) def _repr_(self): @@ -187,39 +147,6 @@ def _repr_(self): preview=10) - def info(self): - r""" - Displays the matrices of the `k`-linear representation, the left - vector and the right vector. - - OUTPUT: - - Nothing; printing to standard output. - - EXAMPLES:: - - sage: Seq2 = kRegularSequenceSpace(2, ZZ) - sage: Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), - ....: left=vector([0, 1]), right=vector([1, 0])).info() - matrices: - ( - [1 0] [ 0 -1] - [0 1], [ 1 2] - ) - left: - (0, 1) - right: - (1, 0) - """ - from sys import displayhook - print('matrices:') - displayhook(self.mu) - print('left:') - displayhook(self.left) - print('right:') - displayhook(self.right) - - @cached_method def __getitem__(self, n): r""" @@ -240,40 +167,19 @@ def __getitem__(self, n): ....: left=vector([0, 1]), right=vector([1, 0])) sage: S[7] 3 - """ - result = self._product_of_mu_(n) - if self.left is not None: - result = self.left * result - if self.right is not None: - result = result * self.right - return self.output_function(result) - - - @cached_method - def _product_of_mu_(self, m): - r""" - Return the product of matrices according to the `k`-ary - digit expansion of `m`. - - INPUT: - - - ``m`` -- a nonnegative integer. - - OUTPUT: - - A matrix. TESTS:: sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: W = Seq2.indices() sage: M0 = Matrix([[1, 0], [0, 1]]) sage: M1 = Matrix([[0, -1], [1, 2]]) sage: S = Seq2((M0, M1)) - sage: S._product_of_mu_(0) == M0 + sage: S._mu_of_word_(W(0.digits(2))) == M0 True - sage: S._product_of_mu_(1) == M1 + sage: S._mu_of_word_(W(1.digits(2))) == M1 True - sage: S._product_of_mu_(3) == M1^2 + sage: S._mu_of_word_(W(3.digits(2))) == M1^2 True :: @@ -282,15 +188,24 @@ def _product_of_mu_(self, m): Traceback (most recent call last): ... ValueError: m=-1 is not a nonnegative integer. - """ - k = self.parent().k - if m < 0: - raise ValueError('m={} is not a nonnegative integer.'.format(m)) - if 0 <= m < k: - return self.mu[m] - n = m // k - r = m - n*k - return self.mu[r] * self._product_of_mu_(n) + """ + from sage.rings.integer_ring import ZZ + n = ZZ(n) + W = self.parent().indices() + w = W(n.digits(self.parent().k)) + + # We would like the line of code + # return super(kRegularSequence, self).__getitem__(w) + # to determine the return value. Unfortunately, the + # @cached_method of RecognizableSeries.__getitem__ makes + # troubles. (See :trac:`21281` for details). We simply + # copy and use the code of the inherited class: + result = self._mu_of_word_(w) + if self.left is not None: + result = self.left * result + if self.right is not None: + result = result * self.right + return result def __iter__(self): @@ -318,15 +233,12 @@ def __iter__(self): return iter(self[n] for n in count()) -from sage.structure.unique_representation import UniqueRepresentation -from sage.structure.parent import Parent - -class kRegularSequenceSpace(UniqueRepresentation, Parent): +class kRegularSequenceSpace(RecognizableSeriesSpace): Element = kRegularSequence - @staticmethod - def __classcall__(cls, k, universe=None, category=None): + @classmethod + def __normalize__(cls, k, coefficients=None, **kwds): r""" Normalizes the input in order to ensure a unique representation. @@ -344,18 +256,16 @@ def __classcall__(cls, k, universe=None, category=None): sage: Seq2 is kRegularSequenceSpace(2) True """ - if universe is None: - from sage.rings.integer_ring import ZZ - universe = ZZ - - from sage.categories.sets_cat import Sets - category = category or Sets() - - return super(kRegularSequenceSpace, cls).__classcall__( - cls, k, universe, category) + from sage.arith.srange import srange + from sage.rings.integer_ring import ZZ + if coefficients is None: + coefficients = ZZ + nargs = super(kRegularSequenceSpace, cls).__normalize__( + coefficients, alphabet=srange(k), **kwds) + return (k,) + nargs - def __init__(self, k, universe, category): + def __init__(self, k, algebra, category): r""" The space of `k`-regular Sequences over the given ``universe``. @@ -384,9 +294,7 @@ def __init__(self, k, universe, category): :class:`kRegularSequence`. """ self.k = k - self.universe = universe - super(kRegularSequenceSpace, self).__init__( - category=category, base=universe) + super(kRegularSequenceSpace, self).__init__(algebra, category) def _repr_(self): From 2b5097c475d6f3e228e0d72d148fa8278b3f9a81 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 18 Aug 2016 21:19:51 +0200 Subject: [PATCH 027/232] remove cached_function from __getitem__ --- src/sage/combinat/k_regular_sequence.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index c9a75bb0b8a..26563352f75 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -147,7 +147,7 @@ def _repr_(self): preview=10) - @cached_method + def __getitem__(self, n): r""" Return the `n`th entry of this sequence. @@ -193,19 +193,7 @@ def __getitem__(self, n): n = ZZ(n) W = self.parent().indices() w = W(n.digits(self.parent().k)) - - # We would like the line of code - # return super(kRegularSequence, self).__getitem__(w) - # to determine the return value. Unfortunately, the - # @cached_method of RecognizableSeries.__getitem__ makes - # troubles. (See :trac:`21281` for details). We simply - # copy and use the code of the inherited class: - result = self._mu_of_word_(w) - if self.left is not None: - result = self.left * result - if self.right is not None: - result = result * self.right - return result + return super(kRegularSequence, self).__getitem__(w) def __iter__(self): From 8b0e33560922a97be04e4e78d0b9cbc4b18f4628 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 18 Aug 2016 21:27:33 +0200 Subject: [PATCH 028/232] rewrite to follow changes upstream --- src/sage/combinat/k_regular_sequence.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 26563352f75..4a4142f63b3 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -253,7 +253,7 @@ def __normalize__(cls, k, coefficients=None, **kwds): return (k,) + nargs - def __init__(self, k, algebra, category): + def __init__(self, k, coefficients, indices, category): r""" The space of `k`-regular Sequences over the given ``universe``. @@ -261,9 +261,7 @@ def __init__(self, k, algebra, category): - ``k`` -- an integer at least `2` specifying the base. - - ``universe`` -- (default: ``None``) a object (e.g. a SageMath parent) - in which the entries of a sequence live. - If ``None``, then the integer ring `\ZZ` is used. + - ``coefficients`` -- a (semi-)ring. - ``category`` -- (default: ``None``) the category of the sequence space. If ``None``, then the category of @@ -282,7 +280,7 @@ def __init__(self, k, algebra, category): :class:`kRegularSequence`. """ self.k = k - super(kRegularSequenceSpace, self).__init__(algebra, category) + super(kRegularSequenceSpace, self).__init__(coefficients, indices, category) def _repr_(self): From e236a19fa85bcff62158a30302cc468798b6c07c Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 19 Aug 2016 10:00:15 +0200 Subject: [PATCH 029/232] use @cached_method-workaround of #21281 in __getitem__ --- src/sage/combinat/k_regular_sequence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 4a4142f63b3..dd3a7f3a6ee 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -147,7 +147,7 @@ def _repr_(self): preview=10) - + @cached_method def __getitem__(self, n): r""" Return the `n`th entry of this sequence. @@ -193,7 +193,7 @@ def __getitem__(self, n): n = ZZ(n) W = self.parent().indices() w = W(n.digits(self.parent().k)) - return super(kRegularSequence, self).__getitem__(w) + return RecognizableSeries.__getitem__.f(self, w) def __iter__(self): From 00c1ec2dde83175b01b79f6d7551e53ba177cff5 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 19 Aug 2016 10:56:07 +0200 Subject: [PATCH 030/232] small cleanup --- src/sage/combinat/k_regular_sequence.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index dd3a7f3a6ee..9150af979ba 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -81,7 +81,7 @@ from sage.combinat.recognizable_series import RecognizableSeries from sage.combinat.recognizable_series import RecognizableSeriesSpace from sage.misc.cachefunc import cached_method -from sage.structure.element import Element + class kRegularSequence(RecognizableSeries): @@ -93,8 +93,12 @@ def __init__(self, parent, mu, left=None, right=None): - ``parent`` -- an instance of :class:`kRegularSequenceSpace`. - - ``mu`` -- a tuple or other iterable of square matrices, - all of which have the same dimension. + - ``mu`` -- a family of square matrices, all of which have the + same dimension. The indices of this family are `0,...,k-1`. + ``mu`` may be a list or tuple of cardinality `k` + as well. See + :meth:`~sage.combinat.recognizable_series.RecognizableSeries.mu` + for more details. - ``left`` -- (default: ``None``) a vector. When evaluating the sequence, this vector is multiplied From dab4db8f3e397403783ea133ea475949fb1132fd Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 19 Aug 2016 14:19:28 +0200 Subject: [PATCH 031/232] complete docstrings and 100% coverage --- src/sage/combinat/k_regular_sequence.py | 74 ++++++++++++++++++------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 9150af979ba..a1eefee694d 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -110,6 +110,19 @@ def __init__(self, parent, mu, left=None, right=None): from the left to the matrix product. If ``None``, then this multiplication is skipped. + When created via the parent :class:`kRegularSequence`, then + the following option is available. + + - ``transpose`` -- (default: ``False``) a boolean. If set, then + each of the matrices in + :meth:`mu ` + is transposed. Additionally the vectors + :meth`left ` + and + :meth:`right ` + are switched. + (This is done by calling :meth:`~sage.combinat.recognizable_series.RecognizableSeries.transposed`.) + EXAMPLES:: sage: Seq2 = kRegularSequenceSpace(2, ZZ) @@ -133,7 +146,7 @@ def _repr_(self): OUTPUT: - A string + A string. TESTS:: @@ -172,7 +185,14 @@ def __getitem__(self, n): sage: S[7] 3 - TESTS:: + TESTS + + sage: S[-1] + Traceback (most recent call last): + ... + OverflowError: can't convert negative value to unsigned char + + :: sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: W = Seq2.indices() @@ -185,13 +205,6 @@ def __getitem__(self, n): True sage: S._mu_of_word_(W(3.digits(2))) == M1^2 True - - :: - - sage: S._product_of_mu_(-1) - Traceback (most recent call last): - ... - ValueError: m=-1 is not a nonnegative integer. """ from sage.rings.integer_ring import ZZ n = ZZ(n) @@ -226,9 +239,35 @@ def __iter__(self): class kRegularSequenceSpace(RecognizableSeriesSpace): + r""" + The space of `k`-regular Sequences over the given ``coefficients``. + + INPUT: + + - ``k`` -- an integer at least `2` specifying the base. + + - ``coefficients`` -- a (semi-)ring. If not specified (``None``), + then the integer ring is used. + + - ``category`` -- (default: ``None``) the category of this + space. + + EXAMPLES:: + + sage: kRegularSequenceSpace(2, ZZ) + Space of 2-regular sequences over Integer Ring + sage: kRegularSequenceSpace(3, ZZ) + Space of 3-regular sequences over Integer Ring + + .. SEEALSO:: + + :doc:`k-regular sequence `, + :class:`kRegularSequence`. + """ Element = kRegularSequence + @classmethod def __normalize__(cls, k, coefficients=None, **kwds): r""" @@ -257,21 +296,18 @@ def __normalize__(cls, k, coefficients=None, **kwds): return (k,) + nargs - def __init__(self, k, coefficients, indices, category): + def __init__(self, k, *args, **kwds): r""" - The space of `k`-regular Sequences over the given ``universe``. + See :class:`kRegularSequenceSpace` for details. INPUT: - ``k`` -- an integer at least `2` specifying the base. - - ``coefficients`` -- a (semi-)ring. + Other input arguments are passed on to + :meth:`~sage.combinat.recognizable_series.RecognizableSeriesSpace.__init__`. - - ``category`` -- (default: ``None``) the category of the - sequence space. If ``None``, then the category of - :class:`~sage.categories.sets_cat.Sets` is used. - - EXAMPLES:: + TESTS:: sage: kRegularSequenceSpace(2, ZZ) Space of 2-regular sequences over Integer Ring @@ -284,7 +320,7 @@ def __init__(self, k, coefficients, indices, category): :class:`kRegularSequence`. """ self.k = k - super(kRegularSequenceSpace, self).__init__(coefficients, indices, category) + super(kRegularSequenceSpace, self).__init__(*args, **kwds) def _repr_(self): @@ -293,7 +329,7 @@ def _repr_(self): OUTPUT: - A string + A string. TESTS:: From 9afc4ac7aa7271180c6adf2149e2f0bba1dd840e Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 19 Aug 2016 14:24:44 +0200 Subject: [PATCH 032/232] cross-references --- src/sage/combinat/k_regular_sequence.py | 1 + src/sage/combinat/recognizable_series.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index a1eefee694d..c7021f52cb9 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -46,6 +46,7 @@ .. SEEALSO:: + :doc:`sage/combinat/recognizable_series`, :doc:`sage/rings/cfinite_sequence`, :doc:`sage/combinat/binary_recurrence_sequences`. diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index 5b95cd8bac4..2ce7e5e6d3a 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -22,6 +22,12 @@ Various ======= +.. SEEALSO:: + + :doc:`sage/combinat/k_regular_sequence`, + :doc:`sage/rings/cfinite_sequence`, + :doc:`sage/combinat/binary_recurrence_sequences`. + REFERENCES: .. [BR2010] Jean Berstel, Christophe Reutenauer, From cf1b8cc81c82acab3658afd83eca85c2b4c9f1f2 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 19 Aug 2016 14:29:04 +0200 Subject: [PATCH 033/232] correct one link --- src/sage/combinat/k_regular_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index c7021f52cb9..19bbdcc6d87 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -111,7 +111,7 @@ def __init__(self, parent, mu, left=None, right=None): from the left to the matrix product. If ``None``, then this multiplication is skipped. - When created via the parent :class:`kRegularSequence`, then + When created via the parent :class:`kRegularSequenceSpace`, then the following option is available. - ``transpose`` -- (default: ``False``) a boolean. If set, then From 71aef7fe9d33937b6cdbf9a2f2686d420cbcebae Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Mon, 22 Aug 2016 14:55:51 +0200 Subject: [PATCH 034/232] fix doctests --- src/sage/combinat/k_regular_sequence.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 19bbdcc6d87..2bb87e7bcd8 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -6,6 +6,21 @@ [AS2003]_. +.. WARNING:: + + As this code is experimental, warnings are thrown when a + `k`-regular sequence space is created for the first time in a + session (see :class:`sage.misc.superseded.experimental`). + + TESTS:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + doctest:...: FutureWarning: This class/method/function is + marked as experimental. It, its functionality or its interface + might change without a formal deprecation. + See http://trac.sagemath.org/21202 for details. + + Examples ======== @@ -199,7 +214,7 @@ def __getitem__(self, n): sage: W = Seq2.indices() sage: M0 = Matrix([[1, 0], [0, 1]]) sage: M1 = Matrix([[0, -1], [1, 2]]) - sage: S = Seq2((M0, M1)) + sage: S = Seq2((M0, M1), [0, 1], [1, 1]) sage: S._mu_of_word_(W(0.digits(2))) == M0 True sage: S._mu_of_word_(W(1.digits(2))) == M1 From 1b34c92f3c6ac0ee020e314fcfe7c1e760581e6f Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Mon, 22 Aug 2016 15:10:04 +0200 Subject: [PATCH 035/232] put conversion code n --> index/word into separat method --- src/sage/combinat/k_regular_sequence.py | 37 +++++++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 2bb87e7bcd8..8baa0298648 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -201,7 +201,7 @@ def __getitem__(self, n): sage: S[7] 3 - TESTS + TESTS:: sage: S[-1] Traceback (most recent call last): @@ -222,11 +222,8 @@ def __getitem__(self, n): sage: S._mu_of_word_(W(3.digits(2))) == M1^2 True """ - from sage.rings.integer_ring import ZZ - n = ZZ(n) - W = self.parent().indices() - w = W(n.digits(self.parent().k)) - return RecognizableSeries.__getitem__.f(self, w) + return RecognizableSeries.__getitem__.f( + self, self.parent()._n_to_index_(n)) def __iter__(self): @@ -354,3 +351,31 @@ def _repr_(self): """ return 'Space of {}-regular sequences over {}'.format(self.k, self.base()) + + def _n_to_index_(self, n): + r""" + Convert `n` to an index usable by the underlying + recognizable series. + + INPUT: + + - ``n`` -- a nonnegative integer. + + OUTPUT: + + A word. + + TESTS:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: Seq2._n_to_index_(6) + word: 011 + sage: Seq2._n_to_index_(-1) + Traceback (most recent call last): + ... + OverflowError: can't convert negative value to unsigned char + """ + from sage.rings.integer_ring import ZZ + n = ZZ(n) + W = self.indices() + return W(n.digits(self.k)) From 0f538a9a4d18267d6cf11f17eab6838d5a4d85b3 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Mon, 22 Aug 2016 17:42:27 +0200 Subject: [PATCH 036/232] rearrange __getitem__ --- src/sage/combinat/recognizable_series.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index 02459d74ca3..46397bcaba5 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -430,7 +430,7 @@ def _latex_(self): @cached_method - def __getitem__(self, w): + def coefficient_of_word(self, w): r""" Return the coefficient to word `w` of this series. @@ -456,6 +456,9 @@ def __getitem__(self, w): return self.left * self._mu_of_word_(w) * self.right + __getitem__ = coefficient_of_word + + @cached_method def _mu_of_empty_word_(self): r""" @@ -860,7 +863,7 @@ def mu_prime_entry(a, p, q, iq): mu_prime.append(Matrix(M)) left_prime = vector([ZZ(1)] + (len(P)-1)*[ZZ(0)]) - right_prime = vector(self[p] for p in P) + right_prime = vector(self.coefficient_of_word(p) for p in P) return self.parent().element_class( self.parent(), mu_prime, left_prime, right_prime) From e53b9065bc688c407f6a104d6cbecda63816bfd4 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Mon, 22 Aug 2016 17:44:12 +0200 Subject: [PATCH 037/232] update __getitem__ to follow recognizable series --- src/sage/combinat/k_regular_sequence.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 8baa0298648..7cc4d24cfe3 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -222,8 +222,7 @@ def __getitem__(self, n): sage: S._mu_of_word_(W(3.digits(2))) == M1^2 True """ - return RecognizableSeries.__getitem__.f( - self, self.parent()._n_to_index_(n)) + return self.coefficient_of_word(self.parent()._n_to_index_(n)) def __iter__(self): From 1794766cf4e0e1b05bbc5c0b16612063672ee6e5 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 23 Aug 2016 13:32:21 +0200 Subject: [PATCH 038/232] flag to skip multiplication with left/right --- src/sage/combinat/recognizable_series.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index 46397bcaba5..c8ce5806a40 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -430,7 +430,7 @@ def _latex_(self): @cached_method - def coefficient_of_word(self, w): + def coefficient_of_word(self, w, multiply_left=True, multiply_right=True): r""" Return the coefficient to word `w` of this series. @@ -439,6 +439,12 @@ def coefficient_of_word(self, w): - ``w`` -- a word over the parent's :meth:`~RecognizableSeriesSpace.alphabet`. + - ``multiply_left`` -- (default: ``True``) a boolean. If ``False``, + then multiplication by :meth:`left ` is skipped. + + - ``multiply_right`` -- (default: ``True``) a boolean. If ``False``, + then multiplication by :meth:`right ` is skipped. + OUTPUT: An element in the parent's @@ -453,7 +459,12 @@ def coefficient_of_word(self, w): sage: S[W(7.digits(2))] 3 """ - return self.left * self._mu_of_word_(w) * self.right + result = self._mu_of_word_(w) + if multiply_left: + result = self.left * result + if multiply_right: + result = result * self.right + return result __getitem__ = coefficient_of_word @@ -826,12 +837,12 @@ def _minimized_left_(self): from sage.rings.integer_ring import ZZ pcs = PrefixClosedSet(self.parent().indices()) - left = self.left * self._mu_of_word_(pcs.elements[0]) + left = self.coefficient_of_word(pcs.elements[0], multiply_right=False) if left.is_zero(): return self.parent().zero() Left = [left] for p in pcs.populate_interactive(): - left = self.left * self._mu_of_word_(p) + left = self.coefficient_of_word(p, multiply_right=False) try: Matrix(Left).solve_left(left) except ValueError: @@ -844,7 +855,7 @@ def _minimized_left_(self): ML = Matrix(Left) def alpha(c): - return ML.solve_left(self.left * self._mu_of_word_(c)) + return ML.solve_left(self.coefficient_of_word(c, multiply_right=False)) def mu_prime_entry(a, p, q, iq): c = p + a From b060652800113870446dcd6148dd85321dd44d4d Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 23 Aug 2016 13:39:41 +0200 Subject: [PATCH 039/232] minor rewrite in minimized --- src/sage/combinat/recognizable_series.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index c8ce5806a40..3c781eb676c 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -876,8 +876,8 @@ def mu_prime_entry(a, p, q, iq): left_prime = vector([ZZ(1)] + (len(P)-1)*[ZZ(0)]) right_prime = vector(self.coefficient_of_word(p) for p in P) - return self.parent().element_class( - self.parent(), mu_prime, left_prime, right_prime) + P = self.parent() + return P.element_class(P, mu_prime, left_prime, right_prime) class RecognizableSeriesSpace(UniqueRepresentation, Parent): From 893bd5cca141e3fa8ef3caa354306dc46b75d9cd Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 23 Aug 2016 13:36:24 +0200 Subject: [PATCH 040/232] experimental warning in k_regular_sequence --- src/sage/combinat/recognizable_series.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index f5a22bcfc34..163e68f985a 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -33,6 +33,11 @@ marked as experimental. It, its functionality or its interface might change without a formal deprecation. See http://trac.sagemath.org/21202 for details. + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + doctest:...: FutureWarning: This class/method/function is + marked as experimental. It, its functionality or its interface + might change without a formal deprecation. + See http://trac.sagemath.org/21202 for details. Various From 189ce50d5f5c2fd7cf8bed7732d651e03110f9e5 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 23 Aug 2016 13:35:58 +0200 Subject: [PATCH 041/232] pass keywords to coefficient_of_word --- src/sage/combinat/k_regular_sequence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 7cc4d24cfe3..d5f2378c3e3 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -181,7 +181,7 @@ def _repr_(self): @cached_method - def __getitem__(self, n): + def __getitem__(self, n, **kwds): r""" Return the `n`th entry of this sequence. @@ -222,7 +222,7 @@ def __getitem__(self, n): sage: S._mu_of_word_(W(3.digits(2))) == M1^2 True """ - return self.coefficient_of_word(self.parent()._n_to_index_(n)) + return self.coefficient_of_word(self.parent()._n_to_index_(n), **kwds) def __iter__(self): From 59191200dbc773146b9396ba4e216ec4ed0b1cdf Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 23 Aug 2016 19:10:05 +0200 Subject: [PATCH 042/232] doctesting all parameters in coefficient_of_word --- src/sage/combinat/recognizable_series.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index 163e68f985a..3861691d4e0 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -467,8 +467,21 @@ def coefficient_of_word(self, w, multiply_left=True, multiply_right=True): sage: W = Rec.indices() sage: S = Rec((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), ....: left=vector([0, 1]), right=vector([1, 0])) - sage: S[W(7.digits(2))] + sage: S[W(7.digits(2))] # indirect doctest 3 + + TESTS:: + + sage: w = W(6.digits(2)) + sage: S.coefficient_of_word(w) + 2 + sage: S.coefficient_of_word(w, multiply_left=False) + (-1, 2) + sage: S.coefficient_of_word(w, multiply_right=False) + (2, 3) + sage: S.coefficient_of_word(w, multiply_left=False, multiply_right=False) + [-1 -2] + [ 2 3] """ result = self._mu_of_word_(w) if multiply_left: From 5dbedab079a7cd94d7f7784ac06ea918849ba4e5 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 25 Aug 2016 13:07:00 +0200 Subject: [PATCH 043/232] fix doctests (experimental warning) --- src/sage/combinat/recognizable_series.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index 3861691d4e0..33db98a1e25 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -33,11 +33,6 @@ marked as experimental. It, its functionality or its interface might change without a formal deprecation. See http://trac.sagemath.org/21202 for details. - sage: Seq2 = kRegularSequenceSpace(2, ZZ) - doctest:...: FutureWarning: This class/method/function is - marked as experimental. It, its functionality or its interface - might change without a formal deprecation. - See http://trac.sagemath.org/21202 for details. Various From 143ea731ca93c79ee9cdb177293b1862f84abc47 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 25 Aug 2016 13:13:37 +0200 Subject: [PATCH 044/232] fix building of docs (no idea why it failed) --- src/sage/combinat/k_regular_sequence.py | 2 +- src/sage/combinat/recognizable_series.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index d5f2378c3e3..f9fb8bc8656 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -61,7 +61,7 @@ .. SEEALSO:: - :doc:`sage/combinat/recognizable_series`, + :mod:`recognizable series `, :doc:`sage/rings/cfinite_sequence`, :doc:`sage/combinat/binary_recurrence_sequences`. diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index 33db98a1e25..96c285ae89b 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -40,7 +40,7 @@ .. SEEALSO:: - :doc:`sage/combinat/k_regular_sequence`, + :mod:`k-regular sequence `, :doc:`sage/rings/cfinite_sequence`, :doc:`sage/combinat/binary_recurrence_sequences`. From d5c378d50f4f94421e1b664ca66da350d13cc884 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 26 Aug 2016 13:22:05 +0200 Subject: [PATCH 045/232] solve troubles with doc-building --- src/sage/combinat/k_regular_sequence.py | 4 ++-- src/sage/combinat/recognizable_series.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index f9fb8bc8656..d8af3e202c7 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -62,8 +62,8 @@ .. SEEALSO:: :mod:`recognizable series `, - :doc:`sage/rings/cfinite_sequence`, - :doc:`sage/combinat/binary_recurrence_sequences`. + :mod:`sage.rings.cfinite_sequence`, + :mod:`sage.combinat.binary_recurrence_sequences`. REFERENCES: diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index 96c285ae89b..46c08013edf 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -41,8 +41,8 @@ .. SEEALSO:: :mod:`k-regular sequence `, - :doc:`sage/rings/cfinite_sequence`, - :doc:`sage/combinat/binary_recurrence_sequences`. + :mod:`sage.rings.cfinite_sequence`, + :mod:`sage.combinat.binary_recurrence_sequences`. REFERENCES: From 13b536e30ff5527f942f2430e4f59447c6ef2f0a Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 24 Jan 2017 14:08:13 +0100 Subject: [PATCH 046/232] Python3: absolut import --- src/sage/combinat/k_regular_sequence.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index d8af3e202c7..e5e1421168e 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -93,9 +93,10 @@ # (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** +from __future__ import absolute_import -from sage.combinat.recognizable_series import RecognizableSeries -from sage.combinat.recognizable_series import RecognizableSeriesSpace +from .recognizable_series import RecognizableSeries +from .recognizable_series import RecognizableSeriesSpace from sage.misc.cachefunc import cached_method From 1b556388b7af75dd239b2e183458222831da7f12 Mon Sep 17 00:00:00 2001 From: David Loeffler Date: Sun, 15 Apr 2018 17:39:15 +0100 Subject: [PATCH 047/232] Add a minimal twist function for newform objects --- src/sage/modular/hecke/element.py | 16 ++- src/sage/modular/local_comp/local_comp.py | 8 +- src/sage/modular/local_comp/type_space.py | 16 +-- src/sage/modular/modform/element.py | 136 ++++++++++++++++++++-- 4 files changed, 144 insertions(+), 32 deletions(-) diff --git a/src/sage/modular/hecke/element.py b/src/sage/modular/hecke/element.py index f8af57a63d4..b9f13aac060 100644 --- a/src/sage/modular/hecke/element.py +++ b/src/sage/modular/hecke/element.py @@ -89,21 +89,19 @@ def _compute_element(self): EXAMPLES:: - sage: f = EllipticCurve('11a').modular_form() - sage: hasattr(f, '_HeckeModuleElement__element') - False + sage: f = CuspForms(11, 2).gen(0) sage: f._compute_element() - (1, 0) - sage: f.element() - (1, 0) - sage: hasattr(f, '_HeckeModuleElement__element') - True + Traceback (most recent call last): + ... + NotImplementedError: _compute_element *must* be defined... """ # You have to define this in the derived class if you ever set # x=None in __init__ for your element class. # The main reason for this is it allows for lazy constructors who # compute the representation of an element (e.g., a q-expansion) in # terms of the basis only when needed. + + # Not in use at present. raise NotImplementedError("_compute_element *must* be defined in the derived class if element is set to None in constructor") def element(self): @@ -267,7 +265,7 @@ def is_eisenstein(self): True sage: ModularSymbols(19,4).0.is_eisenstein() False - sage: EllipticCurve('37a1').newform().is_eisenstein() + sage: EllipticCurve('37a1').newform().element().is_eisenstein() False """ return (self in self.parent().ambient().eisenstein_submodule()) diff --git a/src/sage/modular/local_comp/local_comp.py b/src/sage/modular/local_comp/local_comp.py index 9f3edb5c523..67bab1e9a32 100644 --- a/src/sage/modular/local_comp/local_comp.py +++ b/src/sage/modular/local_comp/local_comp.py @@ -99,12 +99,12 @@ def LocalComponent(f, p, twist_factor=None): return PrimitivePrincipalSeries(f, p, twist_factor) if c == 0 and r == 1: return PrimitiveSpecial(f, p, twist_factor) - Xf = TypeSpace(f, p) - if Xf.is_minimal(): - return PrimitiveSupercuspidal(f, p, twist_factor) - else: + + if not f.minimal_twist(p)[0] == f: raise NotImplementedError( "Form %s is not %s-primitive" % (f, p) ) + return PrimitiveSupercuspidal(f, p, twist_factor) + class LocalComponentBase(SageObject): r""" Base class for local components of newforms. Not to be directly instantiated; use the :func:`~LocalComponent` constructor function. diff --git a/src/sage/modular/local_comp/type_space.py b/src/sage/modular/local_comp/type_space.py index 0f1a8426e4a..02362e28824 100644 --- a/src/sage/modular/local_comp/type_space.py +++ b/src/sage/modular/local_comp/type_space.py @@ -309,15 +309,17 @@ def group(self): sage: from sage.modular.local_comp.type_space import example_type_space sage: example_type_space().group() - Congruence Subgroup Gamma_H(98) with H generated by [43] + Congruence Subgroup Gamma_H(98) with H generated by [15, 29, 43] """ + # Implementation here is not the most efficient but this is heavily not + # time-critical, and getting it wrong can lead to subtle bugs. p = self.prime() r = self.conductor() d = max(self.character_conductor(), r//2) n = self.tame_level() chi = self.form().character() tame_H = [i for i in chi.kernel() if (i % p**r) == 1] - wild_H = [crt(1 + p**d, 1, p**r, n)] + wild_H = [crt(x, 1, p**r, n) for x in xrange(p**r) if x% (p**d) == 1] return GammaH(n * p**r, tame_H + wild_H) ############################################################################### @@ -392,11 +394,11 @@ def minimal_twist(self): V = A.submodule(VV, check=False) D = V.decomposition()[0] - if len(D.star_eigenvalues()) == 2: - D = D.sign_submodule(1) - D._set_sign(D.star_eigenvalues()[0]) - M = ModularForms(D.group(), D.weight()) - ff = Newform(M, D, names='a') + #if len(D.star_eigenvalues()) == 2: + # D = D.sign_submodule(1) + D1 = D.modular_symbols_of_sign(1) + M = ModularForms(D1.group(), D1.weight(), D1.base_ring()) + ff = Newform(M, D1, names='a') return ff ##################################### diff --git a/src/sage/modular/modform/element.py b/src/sage/modular/modform/element.py index 31b2ca6b97f..5d1b291a582 100644 --- a/src/sage/modular/modform/element.py +++ b/src/sage/modular/modform/element.py @@ -8,10 +8,10 @@ - :class:`Newform` - - :class:`ModularFormElement` - - :class:`ModularFormElement_elliptic_curve` + - :class:`ModularFormElement` + - :class:`EisensteinSeries` """ @@ -1261,6 +1261,9 @@ def cm_discriminant(self): return -self.__cm_char.conductor() class Newform(ModularForm_abstract): + # The reasons why Newform does not inherit from ModularFormElement + # should really be documented somewhere. + def __init__(self, parent, component, names, check=True): r""" Initialize a Newform object. @@ -2071,14 +2074,19 @@ def twist(self, chi, level=None, check=True): determine a common base field into which both the Hecke eigenvalue field of self, and the field of values of ``chi``, can be embedded. - - ``level`` -- (optional) the level `N` of the twisted form. - By default, the algorithm tries to compute `N` using - [AL1978]_, Theorem 3.1. + - ``level`` -- (optional) the level `N` of the twisted form. If `N` is + not given, the algorithm tries to compute `N` using [AL1978]_, + Theorem 3.1; if this is not possible, it returns an error. If `N` is + given but incorrect, i.e.~the twisted form does not have level `N`, + then this function will attempt to detect this and return an error, + but it may sometimes return an incorrect answer (a newform of level + `N` whose first few coefficients agree with those of `f \otimes + \chi`). - ``check`` -- (optional) boolean; if ``True`` (default), ensure that the space of modular symbols that is computed is genuinely simple and - new. This makes it less likely that a wrong result is returned if an - incorrect ``level`` is specified. + new. This makes it less likely, but not impossible, that a wrong + result is returned if an incorrect ``level`` is specified. OUTPUT: @@ -2167,7 +2175,7 @@ def twist(self, chi, level=None, check=True): # pull out the eigenspace for p in prime_range(500): - if p.divides(N) or p.divides(chi.level()): + if p.divides(chi.level()): continue D = (D.hecke_operator(p) - self[p]*chi(p)).kernel() if D.rank() == 1: break @@ -2177,6 +2185,112 @@ def twist(self, chi, level=None, check=True): raise RuntimeError('unable to identify modular symbols for twist of %s by %s' % (self, chi)) return Newform(S, D, names='_', check=check) + def minimal_twist(self, p=None): + r""" + Compute a pair `(g, chi)` such that `g = f \otimes \chi`, where `f` is + this newform and `\chi` is a Dirichlet character, such that `g` has + level as small as possible. If the optional argument `p` is given, + consider only twists by Dirichlet characters of `p`-power conductor. + + EXAMPLES:: + + sage: f = Newforms(575, 2, names='a')[4] + sage: g, chi = f.minimal_twist(5) + sage: g + q + a*q^2 - a*q^3 - 2*q^4 + (1/2*a + 2)*q^5 + O(q^6) + sage: chi + Dirichlet character modulo 5 of conductor 5 mapping 2 |--> 1/2*a + sage: f.twist(chi, level=g.level()) == g + True + """ + if p is None: + # test local minimality at all primes + for p in self.level().prime_divisors(): + (g, chi) = self.minimal_twist(p) + if g.level() < self.level(): + h, tau = g.minimal_twist(p=None) + M = chi.modulus().lcm(tau.modulus()) + return (h, chi.extend(M)*tau.extend(M)) + else: + # f locally minimal at all p, hence globally minimal + return (self, DirichletGroup(1, self.base_ring())(1)) + + p = ZZ(p) + N = self.level() + r = N.valuation(p) + c = self.character().conductor().valuation(p) + if not (p.is_prime() and p.divides(N)): + raise ValueError("p should be prime factor of N") + + if (r==c) or (r==1 and c==0): + # easy cases + return (self, DirichletGroup(1, self.base_ring())(1)) + elif r < 2*c: + # In this case we know that there is a unique chi of conductor p^u + # such that self x chi has level N/p^u, where u = r-c, and this + # twist is minimal. + candidates = [] + for chi in DirichletGroup(p**(r-c), self.base_ring()): + if not chi.is_primitive(): continue + try: + g = self.twist(chi, level=N//p**(r-c)) + candidates.append( (g, chi) ) + except ValueError: + continue + + l = ZZ(1) + while len(candidates) > 1: + l = l.next_prime() + if l==p: continue + candidates = [(g, chi) for (g, chi) in candidates if g[l] == chi(l)*self[l] ] + if l > 10000 or len(candidates) == 0: + raise RuntimeError("bug finding minimal twist") + return candidates[0] + else: + # The hard case. Now f might be ramified principal series, twist of + # Steinberg, or supercuspidal, and the minimal twist is not unique + # any more. So we use the slow, but very general, type-space + # algorithm. + from sage.modular.local_comp.type_space import TypeSpace + T = TypeSpace(self, p) + if T.is_minimal(): + return (self, DirichletGroup(1, self.base_ring())(1)) + else: + g = T.minimal_twist() + epsg = g.character().extend(N) + chisq = (epsg / self.character()).restrict(p**(r//2)) + K = coercion_model.common_parent(self.base_ring(), g.base_ring()) + chis = [chi for chi in DirichletGroup(p**(r//2), K) if chi**2 == chisq] + + if g.has_cm() and g.cm_discriminant().prime_divisors() == [p]: + # Quicker to test g than self, because g has smaller level. + t = 2 + else: + t = 1 + l = ZZ(1) + while len(chis) > t: + l = l.next_prime() + if l == p: continue + chis = [chi for chi in chis if g[l] == chi(l) * self[l] ] + if l > 10000 or len(chis) == 0: + raise RuntimeError("bug finding minimal twist") + return (g, chis[0]) + + def local_component(self, p): + """ + Calculate the local component at the prime `p` of the automorphic + representation attached to this newform. For more information, see the + documentation of the :func:`LocalComponent` function. + + EXAMPLE:: + + sage: f = Newform("49a") + sage: f.local_component(7) + Smooth representation of GL_2(Q_7) with conductor 7^2 + """ + from sage.modular.local_comp.local_comp import LocalComponent + return LocalComponent(self, p) + class ModularFormElement(ModularForm_abstract, element.HeckeModuleElement): def __init__(self, parent, x, check=True): r""" @@ -2489,7 +2603,7 @@ def twist(self, chi, level=None): return M(f_twist) -class ModularFormElement_elliptic_curve(ModularFormElement): +class ModularFormElement_elliptic_curve(Newform): r""" A modular form attached to an elliptic curve over `\QQ`. """ @@ -2515,11 +2629,9 @@ def __init__(self, parent, E): sage: f == loads(dumps(f)) True """ - ModularFormElement.__init__(self, parent, None) -## parent.find_in_space( E.q_expansion(parent.hecke_bound()) )) + Newform.__init__(self, parent, E.modular_symbol_space(), names=None) self.__E = E - def elliptic_curve(self): """ Return elliptic curve associated to self. From 37ce61fee64a96db57abeaf5a41ac24ed3c00e98 Mon Sep 17 00:00:00 2001 From: David Loeffler Date: Sun, 4 Nov 2018 17:20:22 +0000 Subject: [PATCH 048/232] Local components: handle many previously-unimplemented cases --- src/sage/modular/local_comp/local_comp.py | 447 +++++++++++++--- src/sage/modular/local_comp/smoothchar.py | 621 ++++++++++++++-------- src/sage/modular/local_comp/type_space.py | 21 +- 3 files changed, 787 insertions(+), 302 deletions(-) diff --git a/src/sage/modular/local_comp/local_comp.py b/src/sage/modular/local_comp/local_comp.py index 67bab1e9a32..a88f3dfcb35 100644 --- a/src/sage/modular/local_comp/local_comp.py +++ b/src/sage/modular/local_comp/local_comp.py @@ -22,14 +22,16 @@ from sage.structure.sage_object import SageObject from sage.rings.all import ZZ, Zmod, QQbar, PolynomialRing, polygen -from sage.modular.modform.element import Newform -from sage.modular.dirichlet import DirichletGroup -from sage.misc.cachefunc import cached_method from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.misc.misc import verbose +from sage.modular.dirichlet import DirichletGroup +from sage.modular.modform.element import Newform +from sage.sets.set import Set from sage.structure.sequence import Sequence from .type_space import TypeSpace -from .smoothchar import SmoothCharacterGroupQp, SmoothCharacterGroupUnramifiedQuadratic +from .smoothchar import SmoothCharacterGroupQp, SmoothCharacterGroupUnramifiedQuadratic, SmoothCharacterGroupRamifiedQuadratic def LocalComponent(f, p, twist_factor=None): r""" @@ -59,7 +61,7 @@ def LocalComponent(f, p, twist_factor=None): We also adopt a slightly unusual definition of the principal series: we define `\pi(\chi_1, \chi_2)` to be the induction from the Borel subgroup of the character of the maximal torus `\begin{pmatrix} x & \\ & y - \end{pmatrix} \mapsto \chi_1(a) \chi_2(b) |b|`, so its central character is + \end{pmatrix} \mapsto \chi_1(a) \chi_2(b) |a|`, so its central character is `z \mapsto \chi_1(z) \chi_2(z) |z|`. Thus `\chi_1 \chi_2` is the restriction to `\QQ_p^\times` of the unique character of the id\'ele class group mapping `\ell` to `\ell^{k-1} \varepsilon(\ell)` for almost all `\ell`. @@ -100,10 +102,12 @@ def LocalComponent(f, p, twist_factor=None): if c == 0 and r == 1: return PrimitiveSpecial(f, p, twist_factor) - if not f.minimal_twist(p)[0] == f: - raise NotImplementedError( "Form %s is not %s-primitive" % (f, p) ) + g, chi = f.minimal_twist(p) + if g == f: + return PrimitiveSupercuspidal(f, p, twist_factor) - return PrimitiveSupercuspidal(f, p, twist_factor) + mintwist = LocalComponent(g, p, twist_factor) + return ImprimitiveLocalComponent(f, p, twist_factor, mintwist, chi) class LocalComponentBase(SageObject): r""" @@ -259,18 +263,9 @@ def central_character(self): sage: LocalComponent(Newforms(DirichletGroup(24)([1, -1,-1]), 3, names='a')[0], 2).central_character() Character of Q_2*, of level 3, mapping 7 |--> 1, 5 |--> -1, 2 |--> -2 """ - from sage.arith.all import crt - chi = self.newform().character() - f = self.prime() ** self.conductor() - N = self.newform().level() // f - G = DirichletGroup(f, self.coefficient_field()) - chip = G([chi(crt(ZZ(x), 1, f, N)) for x in G.unit_gens()]).primitive_character() - a = crt(1, self.prime(), f, N) - - if chip.conductor() == 1: - return SmoothCharacterGroupQp(self.prime(), self.coefficient_field()).character(0, [chi(a) * self.prime()**self.twist_factor()]) - else: - return SmoothCharacterGroupQp(self.prime(), self.coefficient_field()).character(chip.conductor().valuation(self.prime()), list((~chip).values_on_gens()) + [chi(a) * self.prime()**self.twist_factor()]) + G = SmoothCharacterGroupQp(self.prime(), self.coefficient_field()) + eps = G.from_dirichlet(self.newform().character()) + return eps / G.norm_character()**self.twist_factor() def __eq__(self, other): r""" @@ -315,8 +310,37 @@ def __ne__(self, other): """ return not (self == other) +class PrimitiveLocalComponent(LocalComponentBase): + r""" + Base class for primitive (twist-minimal) local components. + """ + + def is_primitive(self): + r""" + Return True if this local component is primitive (has minimal level + among its character twists). + + EXAMPLES:: + + sage: Newform("50a").local_component(5).is_primitive() + True + """ + return True + + def minimal_twist(self): + r""" + Return a twist of this local component which has the minimal possible + conductor. + + EXAMPLES:: -class PrincipalSeries(LocalComponentBase): + sage: Pi = Newform("50a").local_component(5) + sage: Pi.minimal_twist() == Pi + True + """ + return self + +class PrincipalSeries(PrimitiveLocalComponent): r""" A principal series representation. This is an abstract base class, not to be instantiated directly; see the subclasses @@ -465,11 +489,12 @@ def characters(self): ] """ G = SmoothCharacterGroupQp(self.prime(), self.coefficient_field()) - chi1 = G.character(0, [self.newform()[self.prime()]]) + t = ZZ( (self.newform().weight() - 2 - self.twist_factor()) / 2 ) + chi1 = G.character(0, [self.newform()[self.prime()]]) * G.norm_character()**t chi2 = G.character(0, [self.prime()]) * self.central_character() / chi1 return Sequence([chi1, chi2], cr=True, universe=G) -class PrimitiveSpecial(LocalComponentBase): +class PrimitiveSpecial(PrimitiveLocalComponent): r""" A primitive special representation: that is, the Steinberg representation twisted by an unramified character. All such representations have conductor @@ -555,14 +580,13 @@ def check_tempered(self): for sigma in K.embeddings(QQbar): assert sigma(c1(p)).abs() == w -class PrimitiveSupercuspidal(LocalComponentBase): +class PrimitiveSupercuspidal(PrimitiveLocalComponent): r""" A primitive supercuspidal representation. - Except for some exceptional cases - when `p = 2` which we do not implement here, such representations are - parametrized by smooth characters of tamely ramified quadratic extensions - of `\QQ_p`. + Except for some exceptional cases when `p = 2` which we do not implement + here, such representations are parametrized by smooth characters of tamely + ramified quadratic extensions of `\QQ_p`. EXAMPLES:: @@ -604,10 +628,9 @@ def type_space(self): def characters(self): r""" Return the two conjugate characters of `K^\times`, where `K` is some - quadratic extension of `\QQ_p`, defining this representation. This is - fully implemented only in the case where the power of `p` dividing the - level of the form is even, in which case `K` is the unique unramified - quadratic extension of `\QQ_p`. + quadratic extension of `\QQ_p`, defining this representation. An error + will be raised in some 2-adic cases, since not all 2-adic supercuspidal + representations arise in this way. EXAMPLES: @@ -654,26 +677,55 @@ def characters(self): q + j0*q^2 + q^4 - j0*q^5 + O(q^6) sage: LocalComponent(f, 3).characters() # long time (12s on sage.math, 2012) [ - Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> -2*d - j0, 4 |--> 1, 3*s + 1 |--> -j0*d - 2, 3 |--> 1, - Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> 2*d + j0, 4 |--> 1, 3*s + 1 |--> j0*d + 1, 3 |--> 1 + Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> 2*d + j0, 4 |--> 1, 3*s + 1 |--> j0*d + 1, 3 |--> 1, + Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> -2*d - j0, 4 |--> 1, 3*s + 1 |--> -j0*d - 2, 3 |--> 1 ] - In the ramified case, it's not fully implemented, and just returns a - string indicating which ramified extension is being considered:: + Some ramified examples:: - sage: Pi = LocalComponent(Newform('27a'), 3) - sage: Pi.characters() - 'Character of Q_3(sqrt(-3))' - sage: Pi = LocalComponent(Newform('54a'), 3) - sage: Pi.characters() - 'Character of Q_3(sqrt(3))' + sage: Newform('27a').local_component(3).characters() + [ + Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 2, mapping 2 |--> 1, s + 1 |--> -d, s |--> -1, + Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 2, mapping 2 |--> 1, s + 1 |--> d - 1, s |--> -1 + ] + sage: LocalComponent(Newform('54a'), 3, twist_factor=4).characters() + [ + Character of ramified extension Q_3(s)* (s^2 - 3 = 0), of level 2, mapping 2 |--> 1, s + 1 |--> -1/9*d, s |--> -9, + Character of ramified extension Q_3(s)* (s^2 - 3 = 0), of level 2, mapping 2 |--> 1, s + 1 |--> 1/9*d - 1, s |--> -9 + ] + + A 2-adic non-example:: + + sage: Newform('24a').local_component(2).characters() + Traceback (most recent call last): + ... + ValueError: Totally ramified 2-adic representations are not classified by characters + + Examples where `K^\times / \QQ_p^\times` is not topologically cyclic + (which complicates the computations greatly):: + + sage: Newforms(DirichletGroup(64, QQ).1, 2, names='a')[0].local_component(2).characters() # long time + [ + Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> -1/4*a0, 4*s + 1 |--> -1, -1 |--> 1, 2 |--> 1, + Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> -1/4*a0, 4*s + 1 |--> 1, -1 |--> 1, 2 |--> 1 + ] + sage: Newform('243a',names='a').local_component(3).characters() # long time + [ + Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 4, mapping -s - 1 |--> 1, 4 |--> 1, 3*s + 1 |--> d, s |--> 1, + Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 4, mapping -s - 1 |--> 1, 4 |--> 1, 3*s + 1 |--> -d - 1, s |--> 1 + ] """ T = self.type_space() + p = self.prime() if self.conductor() % 2 == 0: G = SmoothCharacterGroupUnramifiedQuadratic(self.prime(), self.coefficient_field()) n = self.conductor() // 2 - g = G.quotient_gen(n) + + gs = G.quotient_gens(n) + g = gs[-1] + + assert g.valuation(G.ideal(1)) == 0 m = g.matrix().change_ring(ZZ).list() tr = (~T.rho(m)).trace() @@ -682,10 +734,74 @@ def characters(self): X = polygen(self.coefficient_field()) theta_poly = X**2 - (-1)**n*tr*X + self.central_character()(g.norm()) + verbose("theta_poly for %s is %s" % (g, theta_poly), level=1) if theta_poly.is_irreducible(): F = self.coefficient_field().extension(theta_poly, "d") G = G.base_extend(F) - chi1, chi2 = [G.extend_character(n, self.central_character(), x[0]) for x in theta_poly.roots(G.base_ring())] + + gvals = [x[0] for x in theta_poly.roots(G.base_ring())] + if len(gs) == 1: + chi1, chi2 = [G.extend_character(n, self.central_character(), [x]) for x in gvals] + else: + # 2-adic cases, conductor >= 64. Here life is complicated + # because the quotient (O_K* / p^n)^* / (image of Z_2^*) is not + # cyclic. + chis = [] + g0 = gs[0] + try: + G._reduce_Qp(1, g0) + raise ZeroDivisionError + except ValueError: + pass + + tr = (~T.rho(g0.matrix().list())).trace() + X = polygen(G.base_ring()) + theta_poly = X**2 - (-1)**n*tr*X + self.central_character()(g0.norm()) + verbose("theta_poly for %s is %s" % (g0, theta_poly), level=1) + if theta_poly.is_irreducible(): + F = theta_poly.base_ring().extension(theta_poly, "e") + G = G.base_extend(F) + g0vals = [y[0] for y in theta_poly.roots(G.base_ring())] + + pairA = [ [g0vals[0], gvals[0]], [g0vals[1], gvals[1]] ] + pairB = [ [g0vals[0], gvals[1]], [g0vals[1], gvals[0]] ] + + A_fail = 0 + B_fail = 0 + try: + chisA = [G.extend_character(n, self.central_character(), [y, x]) for (y, x) in pairA] + except ValueError: + A_fail = 1 + try: + chisB = [G.extend_character(n, self.central_character(), [y, x]) for (y, x) in pairB] + except ValueError: + B_fail = 1 + + # check the character relation from LW12 + if (not A_fail and not B_fail): + for x in G.ideal(n).invertible_residues(): + try: + # test if G mod p is in Fp + G._reduce_Qp(1, x) + except ValueError: + verbose("testing x = %s" % x, level=1) + ti = (-1)**n * (~T.rho(x.matrix().list())).trace() + verbose(" trace of matrix is %s" % ti, level=1) + if ti != chisA[0](x) + chisA[1](x): + verbose(" chisA FAILED", level=1) + A_fail = 1 + break + if ti != chisB[0](x) + chisB[1](x): + verbose(" chisB FAILED", level=1) + B_fail = 1 + break + else: + verbose(" Trace identity check works for both", level=1) + + if B_fail and not A_fail: chi1, chi2 = chisA + elif A_fail and not B_fail: chi1, chi2 = chisB + else: + raise ValueError("Something went wrong: can't identify the characters") # Consistency checks assert chi1.restrict_to_Qp() == chi2.restrict_to_Qp() == self.central_character() @@ -696,33 +812,28 @@ def characters(self): else: # The ramified case. - p = self.prime() - + n = self.conductor() - 1 if p == 2: # The ramified 2-adic representations aren't classified by admissible pairs. Die. - raise NotImplementedError( "Computation with ramified 2-adic representations not implemented" ) - - if p % 4 == 3: - a = ZZ(-1) - else: - a = ZZ(Zmod(self.prime()).quadratic_nonresidue()) - - tr1 = (~T.rho([0,1,a*p, 0])).trace() - tr2 = (~T.rho([0,1,p,0])).trace() - - if tr1 == tr2 == 0: - # This *can* happen. E.g. if the central character satisfies - # chi(-1) = -1, then we have theta(pi) + theta(-pi) = theta(pi) - # * (1 + -1) = 0. In this case, one can presumably identify - # the character and the extension by some more subtle argument - # but I don't know of a good way to automate the process. - raise NotImplementedError( "Can't identify ramified quadratic extension -- both traces zero" ) - elif tr1 == 0: - return "Character of Q_%s(sqrt(%s))" % (p, p) - - elif tr2 == 0: - return "Character of Q_%s(sqrt(%s))" % (p, a*p) - + raise ValueError("Totally ramified 2-adic representations are not classified by characters") + + G0 = SmoothCharacterGroupRamifiedQuadratic(p, 0, self.coefficient_field()) + G1 = SmoothCharacterGroupRamifiedQuadratic(p, 1, self.coefficient_field()) + q0 = G0.quotient_gens(n) + assert all([x.valuation(G0.ideal(1)) == 1 for x in q0]) + q1 = G1.quotient_gens(n) + assert all([x.valuation(G1.ideal(1)) == 1 for x in q1]) + + t0 = [(~T.rho(q.matrix().list())).trace() for q in q0] + t1 = [(~T.rho(q.matrix().list())).trace() for q in q1] + + if all([x == 0 for x in t0 + t1]): + # Can't happen? + raise NotImplementedError( "Can't identify ramified quadratic extension -- all traces zero" ) + elif all([x ==0 for x in t1]): + G,qs,ts = G0, q0, t0 + elif all([x==0 for x in t0]): + G,qs,ts = G1, q1, t1 else: # At least one of the traces is *always* 0, since the type # space has to be isomorphic to its twist by the (ramified @@ -730,6 +841,79 @@ def characters(self): # extension. raise RuntimeError( "Can't get here!" ) + q = qs[0] + t = ts[0] + k = self.newform().weight() + t *= p**ZZ( (k - 2 + self.twist_factor() ) / 2) + + X = polygen(self.coefficient_field()) + theta_poly = X**2 - X * t + self.central_character()(q.norm()) + verbose("theta_poly is %s" % theta_poly, level=1) + if theta_poly.is_irreducible(): + F = self.coefficient_field().extension(theta_poly, "d") + G = G.base_extend(F) + from sage.misc.flatten import flatten + c1q, c2q = flatten([[x]*e for x,e in theta_poly.roots(G.base_ring())]) + + if len(qs) == 1: + chi1, chi2 = [G.extend_character(n, self.central_character(), [x]) for x in [c1q, c2q]] + + else: + assert p == 3 + q = qs[1] + t = ts[1] + t *= p**ZZ( (k - 2 + self.twist_factor() ) / 2) + + X = polygen(G.base_ring()) + theta_poly = X**2 - X * t + self.central_character()(q.norm()) + verbose("theta_poly is %s" % theta_poly, level=1) + if theta_poly.is_irreducible(): + F = G.base_ring().extension(theta_poly, "e") + G = G.base_extend(F) + c1q2, c2q2 = flatten([[x]*e for x,e in theta_poly.roots(G.base_ring())]) + + + pairA = [ [c1q, c1q2], [c2q,c2q2] ] + pairB = [ [c1q, c2q2], [c2q, c1q2] ] + + A_fail = 0 + B_fail = 0 + try: + chisA = [G.extend_character(n, self.central_character(), [x, y]) for (x, y) in pairA] + except ValueError: + verbose('A failed to create', level=1) + A_fail = 1 + try: + chisB = [G.extend_character(n, self.central_character(), [x, y]) for (x, y) in pairB] + except ValueError: + verbose('A failed to create', level=1) + B_fail = 1 + + if c1q == c2q or c1q2 == c2q2: + B_fail = 1 + + for u in G.ideal(n).invertible_residues(): + if A_fail or B_fail: break + x = q*u + verbose("testing x = %s" % x, level=1) + ti = (~T.rho(x.matrix().list())).trace() * p**ZZ((k-2+self.twist_factor())/2) + verbose("trace of matrix is %s" % ti, level=1) + if chisA[0](x) + chisA[1](x) != ti: + A_fail = 1 + if chisB[0](x) + chisB[1](x) != ti: + B_fail = 1 + + if B_fail and not A_fail: chi1, chi2 = chisA + elif A_fail and not B_fail: chi1, chi2 = chisB + else: + raise ValueError("Something went wrong: can't identify the characters") + + # Consistency checks + assert chi1.restrict_to_Qp() == chi2.restrict_to_Qp() == self.central_character() + assert chi1*chi2 == chi1.parent().compose_with_norm(self.central_character()) + + return Sequence([chi1, chi2], check=False, cr=True) + def check_tempered(self): r""" Check that this representation is tempered (after twisting by @@ -744,13 +928,128 @@ def check_tempered(self): EXAMPLES:: sage: LocalComponent(Newform("50a"), 5).check_tempered() - sage: LocalComponent(Newform("27a"), 3).check_tempered() # not tested + sage: LocalComponent(Newform("27a"), 3).check_tempered() """ - if self.conductor() % 2: - raise NotImplementedError c1, c2 = self.characters() K = c1.base_ring() p = self.prime() - w = QQbar(p)**(self.twist_factor() / ZZ(2)) + w = QQbar(p)**self.twist_factor() for sigma in K.embeddings(QQbar): - assert c1(p).abs() == c2(p).abs() == w + assert sigma(c1(p)).abs() == sigma(c2(p)).abs() == w + +class ImprimitiveLocalComponent(LocalComponentBase): + r""" + A smooth representation which is not of minimal level among its character + twists. Internally, this is stored as a pair consisting of a minimal local + component and a character to twist by. + """ + + def __init__(self,newform, prime, twist_factor, min_twist, chi): + r""" + EXAMPLES:: + + sage: Newform("45a").local_component(3) # indirect doctest + Smooth representation of GL_2(Q_3) with conductor 3^2, twist of representation of conductor 3^1 + """ + LocalComponentBase.__init__(self, newform, prime, twist_factor) + self._min_twist = min_twist + self._chi = chi + + def is_primitive(self): + r""" + Return True if this local component is primitive (has minimal level + among its character twists). + + EXAMPLES:: + + sage: Newform("45a").local_component(3).is_primitive() + False + """ + return False + + def minimal_twist(self): + r""" + Return a twist of this local component which has the minimal possible + conductor. + + EXAMPLES:: + + sage: Pi = Newform("75b").local_component(5) + sage: Pi.minimal_twist() + Smooth representation of GL_2(Q_5) with conductor 5^1 + """ + return self._min_twist + + def twisting_character(self): + r""" + Return the character giving the minimal twist of this representation. + + EXAMPLES:: + + sage: Pi = Newform("45a").local_component(3) + sage: Pi.twisting_character() + Dirichlet character modulo 3 of conductor 3 mapping 2 |--> -1 + """ + return self._chi + + def species(self): + r""" + The species of this local component, which is either 'Principal + Series', 'Special' or 'Supercuspidal'. + + EXAMPLES:: + + sage: Pi = Newform("45a").local_component(3) + sage: Pi.species() + 'Special' + """ + return self._min_twist.species() + + def _repr_(self): + r""" + EXAMPLES:: + + sage: Pi = Newform("45a").local_component(3) + sage: Pi # indirect doctest + Smooth representation of GL_2(Q_3) with conductor 3^2, twist of representation of conductor 3^1 + """ + return LocalComponentBase._repr_(self) + ', twist of representation of conductor %s^%s' % (self.prime(), self._min_twist.conductor()) + + def characters(self): + r""" + Return the pair of characters (either of `\QQ_p^*` or of some quadratic + extension) corresponding to this representation. + + EXAMPLES:: + + sage: f = [f for f in Newforms(63, 4, names='a') if f[2] == 1][0] + sage: f.local_component(3).characters() + [ + Character of Q_3*, of level 1, mapping 2 |--> -1, 3 |--> d, + Character of Q_3*, of level 1, mapping 2 |--> -1, 3 |--> -d - 2 + ] + """ + minchars = self._min_twist.characters() + G = minchars[0].parent() + chi = self._chi + if self.species() == "Supercuspidal": + H = SmoothCharacterGroupQp(self.prime(), chi.base_ring()) + Hchi = H.from_dirichlet(~chi) + Gchi = G.compose_with_norm(Hchi) + else: + Gchi = G.from_dirichlet(~chi) + return Sequence([c*Gchi for c in minchars], cr=True, universe=G) + + def check_tempered(self): + r""" + Check that this representation is quasi-tempered, i.e. `\pi \otimes + |\det|^{j/2}` is tempered. It is well known that local components of + modular forms are *always* tempered, so this serves as a useful check + on our computations. + + EXAMPLES:: + + sage: f = [f for f in Newforms(63, 4, names='a') if f[2] == 1][0] + sage: f.local_component(3).check_tempered() + """ + self.minimal_twist().check_tempered() diff --git a/src/sage/modular/local_comp/smoothchar.py b/src/sage/modular/local_comp/smoothchar.py index 68b7ee19ba2..62031bec84c 100644 --- a/src/sage/modular/local_comp/smoothchar.py +++ b/src/sage/modular/local_comp/smoothchar.py @@ -43,18 +43,21 @@ from six.moves import range import operator -from sage.structure.element import MultiplicativeGroupElement, parent -from sage.structure.parent_base import ParentWithBase -from sage.structure.sequence import Sequence -from sage.structure.richcmp import richcmp_not_equal, richcmp -from sage.rings.all import QQ, ZZ, Zmod, NumberField -from sage.rings.ring import is_Ring -from sage.misc.cachefunc import cached_method -from sage.misc.abstract_method import abstract_method -from sage.misc.misc_c import prod +from sage.arith.misc import crt from sage.categories.groups import Groups from sage.functions.other import ceil +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod +from sage.misc.misc import verbose from sage.misc.mrange import xmrange +from sage.modular.dirichlet import DirichletGroup +from sage.rings.all import QQ, ZZ, Zmod, NumberField +from sage.rings.ring import is_Ring +from sage.structure.element import MultiplicativeGroupElement, parent +from sage.structure.parent_base import ParentWithBase +from sage.structure.richcmp import richcmp_not_equal, richcmp +from sage.structure.sequence import Sequence class SmoothCharacterGeneric(MultiplicativeGroupElement): @@ -81,13 +84,13 @@ def __init__(self, parent, c, values_on_gens): """ MultiplicativeGroupElement.__init__(self, parent) self._c = c - self._values_on_gens = values_on_gens + self._values_on_gens = Sequence(values_on_gens, universe=self.base_ring(), immutable=True) self._check_level() def _check_level(self): r""" Checks that this character has the level it claims to have, and if not, - decrement the level by 1. This is called by :meth:`__init__`. + decrement the level appropriately. This is called by :meth:`__init__`. EXAMPLES:: @@ -104,6 +107,17 @@ def _check_level(self): self._c = self._c - 1 self._check_level() + def __hash__(self): + r""" + EXAMPLES:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupQp + sage: chi = SmoothCharacterGroupQp(5, QQ).character(5, [-1, 7]) + sage: D = {chi: 7}; D[chi] # indirect doctest + 7 + """ + return hash( (self._c, self._values_on_gens) ) + def _richcmp_(self, other, op): r""" Compare ``self`` and ``other``. @@ -723,6 +737,21 @@ def character(self, level, values_on_gens): raise ValueError( "value on uniformiser %s (=%s) should be a unit" % (self.unit_gens(level)[i], S[i]) ) return self.element_class(self, level, S) + def norm_character(self): + r""" + Return the normalised absolute value character in this group (mapping a + uniformiser to `1/q` where `q` is the order of the residue field). + + EXAMPLES:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupQp, SmoothCharacterGroupUnramifiedQuadratic + sage: SmoothCharacterGroupQp(5, QQ).norm_character() + Character of Q_5*, of level 0, mapping 5 |--> 1/5 + sage: SmoothCharacterGroupUnramifiedQuadratic(2, QQ).norm_character() + Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 0, mapping 2 |--> 1/4 + """ + return self.character(0, [1/self.ideal(1).residue_field().cardinality()]) + def _an_element_(self): r""" Return an element of this group. Required by the coercion machinery. @@ -737,8 +766,6 @@ def _an_element_(self): """ return self.character(0, [self.base_ring().an_element()]) - - def _test_unitgens(self, **options): r""" Test that the generators returned by ``unit_gens`` are consistent with @@ -1012,7 +1039,350 @@ def subgroup_gens(self, level): else: return [1 + self.prime()**(level - 1)] -class SmoothCharacterGroupUnramifiedQuadratic(SmoothCharacterGroupGeneric): + def from_dirichlet(self, chi): + r""" + Given a Dirichlet character `\chi`, return the factor at p of the + adelic character `\phi` which satisfies `\phi(\varpi_\ell) = + \chi(\ell)` for almost all `\ell`, where `\varpi_\ell` is a uniformizer + at `\ell`. + + More concretely, if we write `\chi = \chi_p \chi_M` as a product of + characters of p-power, resp prime-to-p, conductor, then this function + returns the character of `\QQ_p^\times` sending `p` to `\chi_M(p)` and + agreeing with `\chi_p^{-1}` on integers that are 1 modulo M and coprime + to `p`. + + EXAMPLES:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupQp + sage: G = SmoothCharacterGroupQp(3, CyclotomicField(6)) + sage: G.from_dirichlet(DirichletGroup(9).0) + Character of Q_3*, of level 2, mapping 2 |--> -zeta6 + 1, 3 |--> 1 + """ + p = self.prime() + chi = chi.primitive_character() + c = chi.level().valuation(p) + M = chi.level().prime_to_m_part(p) + return self.character(chi.conductor().valuation(p), [~chi(crt(1, x, M, p**c)) for x in self.unit_gens(c)[:-1]] + [chi(crt(p, 1, M, p**c))]) + + def quadratic_chars(self): + r""" + Return a list of the (non-trivial) quadratic characters in this group. + This will be a list of 3 characters, unless `p = 2` when there are 7. + + EXAMPLES:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupQp + sage: SmoothCharacterGroupQp(7, QQ).quadratic_chars() + [Character of Q_7*, of level 0, mapping 7 |--> -1, + Character of Q_7*, of level 1, mapping 3 |--> -1, 7 |--> -1, + Character of Q_7*, of level 1, mapping 3 |--> -1, 7 |--> 1] + sage: SmoothCharacterGroupQp(2, QQ).quadratic_chars() + [Character of Q_2*, of level 0, mapping 2 |--> -1, + Character of Q_2*, of level 2, mapping 3 |--> -1, 2 |--> -1, + Character of Q_2*, of level 2, mapping 3 |--> -1, 2 |--> 1, + Character of Q_2*, of level 3, mapping 7 |--> -1, 5 |--> -1, 2 |--> -1, + Character of Q_2*, of level 3, mapping 7 |--> -1, 5 |--> -1, 2 |--> 1, + Character of Q_2*, of level 3, mapping 7 |--> 1, 5 |--> -1, 2 |--> -1, + Character of Q_2*, of level 3, mapping 7 |--> 1, 5 |--> -1, 2 |--> 1] + """ + if self.prime() == 2: + q = 3 + else: + q = 1 + ram = [self.from_dirichlet(chi) for chi in DirichletGroup(self.prime() ** q, QQ) if not chi.is_trivial()] + nr = self.character(0, [-1]) + return sorted([nr] + [f for f in ram] + [f*nr for f in ram]) + +class SmoothCharacterGroupQuadratic(SmoothCharacterGroupGeneric): + r""" + The group of smooth characters of `E^\times`, where `E` is a quadratic extension of `\Qp`. + """ + + def discrete_log(self, level, x, gens=None): + r""" + Express the class of `x` in `F^\times / (1 + \mathfrak{p}^c)^\times` in + terms of the generators returned by ``self.unit_gens(level)``, or a + custom set of generators if given. + + EXAMPLES:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupUnramifiedQuadratic + sage: G = SmoothCharacterGroupUnramifiedQuadratic(2, QQ) + sage: G.discrete_log(0, 12) + [2] + sage: G.discrete_log(1, 12) + [0, 2] + sage: v = G.discrete_log(5, 12); v + [0, 2, 0, 1, 2] + sage: g = G.unit_gens(5); prod([g[i]**v[i] for i in [0..4]])/12 - 1 in G.ideal(5) + True + sage: G.discrete_log(3,G.number_field()([1,1])) + [2, 0, 0, 1, 0] + sage: H = SmoothCharacterGroupUnramifiedQuadratic(5, QQ) + sage: x = H.number_field()([1,1]); x + s + 1 + sage: v = H.discrete_log(5, x); v + [22, 263, 379, 0] + sage: h = H.unit_gens(5); prod([h[i]**v[i] for i in [0..3]])/x - 1 in H.ideal(5) + True + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupRamifiedQuadratic + sage: G = SmoothCharacterGroupRamifiedQuadratic(3, 1, QQ) + sage: s = G.number_field().gen() + sage: G.discrete_log(4, 3 + 2*s) + [1, 2, 2, 1] + sage: gs = G.unit_gens(4); gs[0] * gs[1]^2 * gs[2]^2 * gs[3] - (3 + 2*s) in G.ideal(4) + True + + An example with a custom generating set:: + + sage: G.discrete_log(2, s+3, gens=[s, s+1, 2]) + [1, 2, 0] + """ + x = self.number_field().coerce(x) + if x == 0: raise ValueError( "cannot evaluate at zero" ) + if gens is None: + n1 = x.valuation(self.ideal(1)) + x1 = x / self.unit_gens(0)[-1] ** n1 + if level == 0: + return [n1] + else: + return self.ideal(level).ideallog(x1, self.unit_gens(level)[:-1]) + [n1] + else: + P = self.ideal(1) + I = self.ideal(level) + gens = [self.number_field().coerce(g) for g in gens] + i = min([i for i in xrange(len(gens)) if gens[i].valuation(P) == 1]) # lazy! + pi = gens[i] + genvals = [] + genunits = [] + for g in gens: + genvals.append(g.valuation(P)) + gu = g / pi**genvals[-1] + gu *= gu.denominator_ideal().element_1_mod(I) + genunits.append(I.reduce(gu)) + xunit = x / pi**x.valuation(P) + xunit = I.reduce(xunit * xunit.denominator_ideal().element_1_mod(I)) + verbose("computing log of %s in basis %s" % (xunit, genunits), level=1) + dl = I.ideallog(xunit, genunits) + Xunit = prod(genunits[j] ** dl[j] for j in xrange(len(gens))) + pi_term = x.valuation(P) - sum(dl[j] * genvals[j] for j in xrange(len(gens))) + dl[i] += pi_term + X = prod(gens[j] ** dl[j] for j in xrange(len(gens))) + assert (X/x - 1).valuation(P) >= level + return dl + + @cached_method + def quotient_gens(self, n): + r""" + Return a list of elements of `E` which are a generating set for the + quotient `E^\times / \QQ_p^\times`, consisting of elements which are + "minimal" in the sense of [LW12]. + + In the examples we implement here, this quotient is almost always + cyclic: the exceptions are the unramified quadratic extension of + `\QQ_2` for `n \ge 3`, and the extension `\QQ_3(\sqrt{-3})` for `n \ge + 4`. + + EXAMPLES:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupUnramifiedQuadratic + sage: G = SmoothCharacterGroupUnramifiedQuadratic(7,QQ) + sage: G.quotient_gens(1) + [s] + sage: G.quotient_gens(2) + [23*s - 16] + sage: G.quotient_gens(3) + [-124*s - 2] + + A ramified case:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupRamifiedQuadratic + sage: G = SmoothCharacterGroupRamifiedQuadratic(7, 0, QQ) + sage: G.quotient_gens(3) + [22*s + 21] + + An example where the quotient group is not cyclic:: + + sage: G = SmoothCharacterGroupUnramifiedQuadratic(2,QQ) + sage: G.quotient_gens(1) + [s] + sage: G.quotient_gens(2) + [-s + 1] + sage: G.quotient_gens(3) + [7*s + 5, -s + 3] + """ + + # silly special case + if n == 0: + if self.ideal(1).norm().is_prime(): + return [self.unit_gens(0), [2]] + else: + return [[], []] + + p = self.prime() + I = self.ideal(n) + gs = self.unit_gens(n) + es = self.exponents(n) + d = len(es) + + A = ZZ**d + R = [A.gen(i)*es[i] for i in xrange(d)] + r = I.smallest_integer() + S = [] + for s in Zmod(r).unit_gens() + (p,): + S.append( self.discrete_log(n, ZZ(s)) ) + Q = A / A.span(R + S) + qgs = [] + for v in Q.gens(): + # choose a "nice" representative + vv = v.lift() + if vv[-1] < 0: vv *= -1 + while vv[-1] not in [0, 1]: + t = self.discrete_log(n, p) + vv = [vv[i] - t[i] for i in xrange(d)] + assert (Q(A(vv)) == v or Q(A(vv)) == -v) + qgs.append( I.reduce(prod([gs[i] ** (vv[i] % es[i]) for i in xrange(d-1)])) * gs[-1]**vv[-1] ) + + if len(qgs) == 2: + x,y = qgs + return [x*y, y] + else: + return qgs + + def _reduce_Qp(self, level, x): + r""" + Utility function: given an element `x` of the number field of self, + return an element of `\QQ_p^\times` which is congruent to `x` modulo a + given power of the maximal ideal. An error will be raised if no such + element exists. + + EXAMPLES:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupUnramifiedQuadratic + sage: G = SmoothCharacterGroupUnramifiedQuadratic(2, QQ) + sage: s = G.number_field().gen() + sage: G._reduce_Qp(3, -2520*s-1007) + 9 + sage: G._reduce_Qp(3, s) + Traceback (most recent call last): + ... + ValueError: s not congruent mod Fractional ideal (8) to an elt of Qp + """ + p = self.prime() + r = ZZ(x.norm().valuation(p) / 2) + y = x / p ** r + if p==2 and y.trace().valuation(2) < 1: + raise ValueError("%s not congruent mod %s to an elt of Qp" % (x, self.ideal(level))) + Y = (y.trace() / 2) % self.ideal(level).smallest_integer() + X = p**r * Y + if not (X/x - 1).valuation(self.ideal(1)) >= level: + if p != 2: + raise ValueError("%s not congruent mod %s to an elt of Qp" % (x, self.ideal(level))) + else: + X += ZZ(2)**(r + level - 1) + if not (X/x - 1).valuation(self.ideal(1)) >= level: + raise ValueError("%s not congruent mod %s to an elt of Qp" % (x, self.ideal(level))) + return X + + def extend_character(self, level, chi, vals, check=True): + r""" + Return the unique character of `F^\times` which coincides with `\chi` + on `\QQ_p^\times` and maps the generators of the quotient returned by + :meth:`quotient_gens` to ``vals``. + + INPUT: + + - ``chi``: a smooth character of `\QQ_p`, where `p` is the residue + characteristic of `F`, with values in the base ring of self (or some + other ring coercible to it) + - ``level``: the level of the new character (which should be at least + the level of ``chi``) + - ``vals``: a list of elements of the base ring of self (or some other + ring coercible to it), specifying values on the quotients returned by + :meth:`quotient_gens`. + + A ``ValueError`` will be raised if `x^t \ne \chi(\alpha^t)`, where `t` + is the smallest integer such that `\alpha^t` is congruent modulo + `p^{\rm level}` to an element of `\QQ_p`. + + EXAMPLES: + + We extend an unramified character of `\QQ_3^\times` to the unramified + quadratic extension in various ways. + + :: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupQp, SmoothCharacterGroupUnramifiedQuadratic + sage: chi = SmoothCharacterGroupQp(5, QQ).character(0, [7]); chi + Character of Q_5*, of level 0, mapping 5 |--> 7 + sage: G = SmoothCharacterGroupUnramifiedQuadratic(5, QQ) + sage: G.extend_character(1, chi, [-1]) + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -1, 5 |--> 7 + sage: G.extend_character(2, chi, [-1]) + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -1, 5 |--> 7 + sage: G.extend_character(3, chi, [1]) + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 0, mapping 5 |--> 7 + sage: K. = CyclotomicField(6); G.base_extend(K).extend_character(1, chi, [z]) + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> z, 5 |--> 7 + + We extend the nontrivial quadratic character:: + + sage: chi = SmoothCharacterGroupQp(5, QQ).character(1, [-1, 7]) + sage: K. = CyclotomicField(24); G.base_extend(K).extend_character(1, chi, [z^6]) + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> z^6, 5 |--> 7 + + Extensions of higher level:: + + sage: K. = CyclotomicField(20); rho = G.base_extend(K).extend_character(2, chi, [z]); rho + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 2, mapping 11*s - 10 |--> -z^5, 6 |--> 1, 5*s + 1 |--> z^4, 5 |--> 7 + sage: rho(3) + -1 + + Examples where it doesn't work:: + + sage: G.extend_character(1, chi, [1]) + Traceback (most recent call last): + ... + ValueError: Invalid values for extension + + sage: G = SmoothCharacterGroupQp(2, QQ); H = SmoothCharacterGroupUnramifiedQuadratic(2, QQ) + sage: chi = G.character(3, [1, -1, 7]) + sage: H.extend_character(2, chi, [-1]) + Traceback (most recent call last): + ... + ValueError: Level of extended character cannot be smaller than level of character of Qp + """ + chi = chi.base_extend(self.base_ring()) + + qs = self.quotient_gens(level) + assert len(vals) == len(qs) + + # initial sanity checks + r = self.ideal(level).smallest_integer().valuation(self.prime()) + if chi.level() > r: + raise ValueError("Level of extended character cannot be smaller than level of character of Qp") + + # now do the calculation + standard_gens = self.unit_gens(level) + values_on_standard_gens = [] + + custom_gens = qs + chi.parent().unit_gens(r) + values_on_custom_gens = vals + [chi(x) for x in chi.parent().unit_gens(r)] + verbose("want to send %s to %s" % (custom_gens, values_on_custom_gens), level=1) + + for x in standard_gens: + d = self.discrete_log(level, x, custom_gens) + chix = prod(values_on_custom_gens[i]**d[i] for i in xrange(len(d))) + values_on_standard_gens.append(chix) + + chiE = self.character(level, values_on_standard_gens) + if not all( chiE(qs[i]) == vals[i] for i in xrange(len(qs)) ) or chiE.restrict_to_Qp() != chi: + raise ValueError("Invalid values for extension") + return chiE + +class SmoothCharacterGroupUnramifiedQuadratic(SmoothCharacterGroupQuadratic): r""" The group of smooth characters of `\QQ_{p^2}^\times`, where `\QQ_{p^2}` is the unique unramified quadratic extension of `\QQ_p`. We represent @@ -1226,182 +1596,8 @@ def subgroup_gens(self, level): else: return [1 + self.prime()**(level - 1), 1 + self.prime()**(level - 1) * self.number_field().gen()] - def quotient_gen(self, level): - r""" - Find an element generating the quotient - - .. MATH:: - - \mathcal{O}_F^\times / \ZZ_p^\times \cdot (1 + p^c \mathcal{O}_F), - - where `c` is the given level. - - EXAMPLES:: - - sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupUnramifiedQuadratic - sage: G = SmoothCharacterGroupUnramifiedQuadratic(7,QQ) - sage: G.quotient_gen(1) - s - sage: G.quotient_gen(2) - -20*s - 21 - sage: G.quotient_gen(3) - -69*s - 70 - - For `p = 2` an error will be raised for level `\ge 3`, as the quotient is not cyclic:: - - sage: G = SmoothCharacterGroupUnramifiedQuadratic(2,QQ) - sage: G.quotient_gen(1) - s - sage: G.quotient_gen(2) - -s + 2 - sage: G.quotient_gen(3) - Traceback (most recent call last): - ... - ValueError: Quotient group not cyclic - """ - if level == 0: - raise ValueError( "Quotient group is trivial" ) - elif self.prime() == 2 and level >= 3: - raise ValueError( "Quotient group not cyclic" ) - elif level == 1: - return self.unit_gens(level)[0] - else: - return self.ideal(level).reduce(self.unit_gens(level)[0] * (1 + self.prime() * self.number_field().gen())) - - def extend_character(self, level, chi, x, check=True): - r""" - Return the unique character of `F^\times` which coincides with `\chi` - on `\QQ_p^\times` and maps the generator `\alpha` returned by - :meth:`quotient_gen` to `x`. - - INPUT: - - - ``chi``: a smooth character of `\QQ_p`, where `p` is the residue - characteristic of `F`, with values in the base ring of self (or some - other ring coercible to it) - - ``level``: the level of the new character (which should be at least - the level of ``chi``) - - ``x``: an element of the base ring of self (or some other ring - coercible to it). - - A ``ValueError`` will be raised if `x^t \ne \chi(\alpha^t)`, where `t` - is the smallest integer such that `\alpha^t` is congruent modulo - `p^{\rm level}` to an element of `\QQ_p`. - - EXAMPLES: - - We extend an unramified character of `\QQ_3^\times` to the unramified - quadratic extension in various ways. - - :: - - sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupQp, SmoothCharacterGroupUnramifiedQuadratic - sage: chi = SmoothCharacterGroupQp(5, QQ).character(0, [7]); chi - Character of Q_5*, of level 0, mapping 5 |--> 7 - sage: G = SmoothCharacterGroupUnramifiedQuadratic(5, QQ) - sage: G.extend_character(1, chi, -1) - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -1, 5 |--> 7 - sage: G.extend_character(2, chi, -1) - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -1, 5 |--> 7 - sage: G.extend_character(3, chi, 1) - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 0, mapping 5 |--> 7 - sage: K. = CyclotomicField(6); G.base_extend(K).extend_character(1, chi, z) - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> z, 5 |--> 7 - - We extend the nontrivial quadratic character:: - sage: chi = SmoothCharacterGroupQp(5, QQ).character(1, [-1, 7]) - sage: K. = CyclotomicField(24); G.base_extend(K).extend_character(1, chi, z^6) - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> z^6, 5 |--> 7 - - Extensions of higher level:: - - sage: K. = CyclotomicField(20); rho = G.base_extend(K).extend_character(2, chi, z); rho - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 2, mapping 11*s - 10 |--> z^5, 6 |--> 1, 5*s + 1 |--> -z^6, 5 |--> 7 - sage: rho(3) - -1 - - Examples where it doesn't work:: - - sage: G.extend_character(1, chi, 1) - Traceback (most recent call last): - ... - ValueError: Value at s must satisfy x^6 = chi(2) = -1, but it does not - - sage: G = SmoothCharacterGroupQp(2, QQ); H = SmoothCharacterGroupUnramifiedQuadratic(2, QQ) - sage: chi = G.character(3, [1, -1, 7]) - sage: H.extend_character(2, chi, -1) - Traceback (most recent call last): - ... - ValueError: Level of extended character cannot be smaller than level of character of Qp - """ - chi = chi.base_extend(self.base_ring()) - if chi.level() > level: - raise ValueError("Level of extended character cannot be smaller than level of character of Qp") - - # check it makes sense - e = (self.prime() + 1) * (self.prime()**(level - 1)) - v = self.ideal(level).reduce(self.quotient_gen(level) ** e) - - v = QQ(v) - if x**e != chi(v): - raise ValueError( "Value at %s must satisfy x^%s = chi(%s) = %s, but it does not" % (self.quotient_gen(level), e, v, chi(v)) ) - - # now do the calculation - values_on_standard_gens = [] - other_gens = [self.quotient_gen(level)] + [ZZ(z) for z in Zmod(self.prime()**level).unit_gens()] - values_on_other_gens = [x] + [chi(u) for u in other_gens[1:]] - for s in self.unit_gens(level)[:-1]: - t = self.ideal(level).ideallog(s, other_gens) - values_on_standard_gens.append( prod([values_on_other_gens[i] ** t[i] for i in range(len(t))]) ) - values_on_standard_gens.append(chi(self.prime())) - chiE = self.character(level, values_on_standard_gens) - - # check it makes sense (optional but on by default) - if check: - assert chiE(self.quotient_gen(level)) == x - assert chiE.restrict_to_Qp() == chi - - return chiE - - def discrete_log(self, level, x): - r""" - Express the class of `x` in `F^\times / (1 + \mathfrak{p}^c)^\times` in - terms of the generators returned by ``self.unit_gens(level)``. - - EXAMPLES:: - - sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupUnramifiedQuadratic - sage: G = SmoothCharacterGroupUnramifiedQuadratic(2, QQ) - sage: G.discrete_log(0, 12) - [2] - sage: G.discrete_log(1, 12) - [0, 2] - sage: v = G.discrete_log(5, 12); v - [0, 2, 0, 1, 2] - sage: g = G.unit_gens(5); prod([g[i]**v[i] for i in [0..4]])/12 - 1 in G.ideal(5) - True - sage: G.discrete_log(3,G.number_field()([1,1])) - [2, 0, 0, 1, 0] - sage: H = SmoothCharacterGroupUnramifiedQuadratic(5, QQ) - sage: x = H.number_field()([1,1]); x - s + 1 - sage: v = H.discrete_log(5, x); v - [22, 263, 379, 0] - sage: h = H.unit_gens(5); prod([h[i]**v[i] for i in [0..3]])/x - 1 in H.ideal(5) - True - """ - x = self.number_field().coerce(x) - if x == 0: raise ValueError( "cannot evaluate at zero" ) - n1 = x.valuation(self.number_field().ideal(self.prime())) - x1 = x / self.prime() ** n1 - if level == 0: - return [n1] - else: - return self.ideal(level).ideallog(x1, self.unit_gens(level)[:-1]) + [n1] - - -class SmoothCharacterGroupRamifiedQuadratic(SmoothCharacterGroupGeneric): +class SmoothCharacterGroupRamifiedQuadratic(SmoothCharacterGroupQuadratic): r""" The group of smooth characters of `K^\times`, where `K` is a ramified quadratic extension of `\QQ_p`, and `p \ne 2`. @@ -1428,7 +1624,7 @@ def __init__(self, prime, flag, base_ring, names='s'): sage: G1 = SmoothCharacterGroupRamifiedQuadratic(3, 0, QQ); G1 Group of smooth characters of ramified extension Q_3(s)* (s^2 - 3 = 0) with values in Rational Field sage: G2 = SmoothCharacterGroupRamifiedQuadratic(3, 1, QQ); G2 - Group of smooth characters of ramified extension Q_3(s)* (s^2 + 3 = 0) with values in Rational Field + Group of smooth characters of ramified extension Q_3(s)* (s^2 - 6 = 0) with values in Rational Field sage: G3 = SmoothCharacterGroupRamifiedQuadratic(5, 1, QQ); G3 Group of smooth characters of ramified extension Q_5(s)* (s^2 - 10 = 0) with values in Rational Field @@ -1442,19 +1638,23 @@ def __init__(self, prime, flag, base_ring, names='s'): sage: TestSuite(G2).run() sage: TestSuite(G3).run() """ + prime = ZZ(prime) if prime == 2: raise NotImplementedError( "Wildly ramified extensions not supported" ) SmoothCharacterGroupGeneric.__init__(self, prime, base_ring) self._name = names if flag not in [0, 1]: raise ValueError( "Flag must be 0 (for Qp(sqrt(p)) ) or 1 (for the other ramified extension)" ) self._flag = flag - if flag == 0: - self._unif_sqr = self.prime() - else: - if self.prime() % 4 == 3: - self._unif_sqr = -self.prime() - else: - self._unif_sqr = ZZ(Zmod(self.prime()).quadratic_nonresidue()) * self.prime() + + # Find an integer a such that sqrt(a*p) generates the right field and ZZ(sqrt(a*p)) is integrally closed + for a in xrange(4*prime): + if (not a%prime) or (not ZZ(a).is_squarefree()) or ( (a*prime) % 4 == 1): + continue + if (flag == 0 and Zmod(prime)(a).is_square()) or \ + (flag == 1 and not Zmod(prime)(a).is_square()): + self._unif_sqr = a*prime + break + else: raise ValueError("Can't get here") def change_ring(self, ring): r""" @@ -1468,7 +1668,7 @@ def change_ring(self, ring): sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupRamifiedQuadratic sage: SmoothCharacterGroupRamifiedQuadratic(7, 1, Zmod(3), names='foo').change_ring(CC) - Group of smooth characters of ramified extension Q_7(foo)* (foo^2 + 7 = 0) with values in Complex Field with 53 bits of precision + Group of smooth characters of ramified extension Q_7(foo)* (foo^2 - 35 = 0) with values in Complex Field with 53 bits of precision """ return SmoothCharacterGroupRamifiedQuadratic(self.prime(), self._flag, ring, self._name) @@ -1496,7 +1696,7 @@ def number_field(self): sage: SmoothCharacterGroupRamifiedQuadratic(5, 1, QQ, 'b').number_field() Number Field in b with defining polynomial x^2 - 10 sage: SmoothCharacterGroupRamifiedQuadratic(7, 1, Zmod(6), 'c').number_field() - Number Field in c with defining polynomial x^2 + 7 + Number Field in c with defining polynomial x^2 - 35 """ from sage.rings.all import PolynomialRing R, x = PolynomialRing(QQ, 'x').objgen() @@ -1607,26 +1807,3 @@ def subgroup_gens(self, level): return self.unit_gens(level)[:-1] else: return [1 + self.number_field().gen()**(level - 1)] - - def discrete_log(self, level, x): - r""" - Solve the discrete log problem in the unit group. - - EXAMPLES:: - - sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupRamifiedQuadratic - sage: G = SmoothCharacterGroupRamifiedQuadratic(3, 1, QQ) - sage: s = G.number_field().gen() - sage: G.discrete_log(4, 3 + 2*s) - [5, 1, 1, 1] - sage: gs = G.unit_gens(4); gs[0]^5 * gs[1] * gs[2] * gs[3] - (3 + 2*s) in G.ideal(4) - True - """ - x = self.number_field().coerce(x) - if x == 0: raise ValueError("cannot evaluate at zero") - n1 = x.valuation(self.ideal(1)) - x1 = x / self.number_field().gen()**n1 - if level == 0: - return [n1] - else: - return self.ideal(level).ideallog(x1, self.unit_gens(level)[:-1]) + [n1] diff --git a/src/sage/modular/local_comp/type_space.py b/src/sage/modular/local_comp/type_space.py index 02362e28824..ad079d98a12 100644 --- a/src/sage/modular/local_comp/type_space.py +++ b/src/sage/modular/local_comp/type_space.py @@ -615,9 +615,10 @@ def _discover_torus_action(self): [ 0 1 -2 1] """ f = self.prime() ** self.u() - if len(Zmod(f).unit_gens()) != 1: - raise NotImplementedError - a = ZZ(Zmod(f).unit_gens()[0]) + if not (f % 8): + a = ZZ(5) + else: + a = ZZ(Zmod(f).unit_gens()[0]) mats = self._intertwining_basis(a) V = self.t_space.nonembedded_free_module() @@ -683,18 +684,22 @@ def rho(self, g): except AttributeError: self._discover_torus_action() a = self._a + + if not (f % 8): + if d % 4 == 3: return self.rho([-g[0], g[1], -g[2], g[3]]) * self.t_space.star_involution().matrix().transpose() + i = 0 while (d * a**i) % f != 1: i += 1 if i > f: raise ArithmeticError return self._rho_s([a**i*g[0], g[1], a**i*g[2], g[3]]) * self._amat**(-i) - # funny business + # det(g) is not a unit if (self.conductor() % 2 == 0): if all([x.valuation(p) > 0 for x in g]): eps = self.form().character()(crt(1, p, f, self.tame_level())) - return ~eps * self.rho([x // p for x in g]) + return ~eps * p**(self.form().weight() - 2) * self.rho([x // p for x in g]) else: raise ArithmeticError( "g(={0}) not in K".format(g) ) @@ -718,4 +723,8 @@ def _unif_ramified(self): [ 0 -1] """ - return self.t_space.atkin_lehner_operator(self.prime()).matrix().transpose() * self.prime() ** (-1 + self.form().weight() // 2) + p = self.prime() + k = self.form().weight() + return self.t_space.atkin_lehner_operator(p).matrix().transpose() \ + * p ** ( -(k-2)*self.u() ) \ + * self.t_space.diamond_bracket_matrix( crt(1, p**self.u(), p**self.u(), self.tame_level()) ).transpose() From 482e00a6c12d5d87fca8ac23aa1daf5edea3f9b9 Mon Sep 17 00:00:00 2001 From: David Loeffler Date: Sun, 4 Nov 2018 22:01:47 +0000 Subject: [PATCH 049/232] Fix non-ascii chars, non-Py3 code etc picked up by patchbot --- src/sage/modular/local_comp/local_comp.py | 8 +++----- src/sage/modular/local_comp/smoothchar.py | 19 +++++++++---------- src/sage/modular/local_comp/type_space.py | 4 ++-- src/sage/modular/modform/element.py | 8 ++++---- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/sage/modular/local_comp/local_comp.py b/src/sage/modular/local_comp/local_comp.py index a88f3dfcb35..eadeda5b852 100644 --- a/src/sage/modular/local_comp/local_comp.py +++ b/src/sage/modular/local_comp/local_comp.py @@ -21,13 +21,11 @@ from __future__ import absolute_import from sage.structure.sage_object import SageObject -from sage.rings.all import ZZ, Zmod, QQbar, PolynomialRing, polygen +from sage.rings.all import ZZ, QQbar, PolynomialRing, polygen from sage.misc.abstract_method import abstract_method from sage.misc.cachefunc import cached_method from sage.misc.misc import verbose -from sage.modular.dirichlet import DirichletGroup from sage.modular.modform.element import Newform -from sage.sets.set import Set from sage.structure.sequence import Sequence from .type_space import TypeSpace @@ -704,7 +702,7 @@ def characters(self): Examples where `K^\times / \QQ_p^\times` is not topologically cyclic (which complicates the computations greatly):: - sage: Newforms(DirichletGroup(64, QQ).1, 2, names='a')[0].local_component(2).characters() # long time + sage: Newforms(DirichletGroup(64, QQ).1, 2, names='a')[0].local_component(2).characters() # long time [ Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> -1/4*a0, 4*s + 1 |--> -1, -1 |--> 1, 2 |--> 1, Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> -1/4*a0, 4*s + 1 |--> 1, -1 |--> 1, 2 |--> 1 @@ -741,12 +739,12 @@ def characters(self): gvals = [x[0] for x in theta_poly.roots(G.base_ring())] if len(gs) == 1: + # This is always the case if p != 2 chi1, chi2 = [G.extend_character(n, self.central_character(), [x]) for x in gvals] else: # 2-adic cases, conductor >= 64. Here life is complicated # because the quotient (O_K* / p^n)^* / (image of Z_2^*) is not # cyclic. - chis = [] g0 = gs[0] try: G._reduce_Qp(1, g0) diff --git a/src/sage/modular/local_comp/smoothchar.py b/src/sage/modular/local_comp/smoothchar.py index 62031bec84c..ec1de18b58d 100644 --- a/src/sage/modular/local_comp/smoothchar.py +++ b/src/sage/modular/local_comp/smoothchar.py @@ -1153,7 +1153,7 @@ def discrete_log(self, level, x, gens=None): P = self.ideal(1) I = self.ideal(level) gens = [self.number_field().coerce(g) for g in gens] - i = min([i for i in xrange(len(gens)) if gens[i].valuation(P) == 1]) # lazy! + i = min([i for i in range(len(gens)) if gens[i].valuation(P) == 1]) # lazy! pi = gens[i] genvals = [] genunits = [] @@ -1166,10 +1166,9 @@ def discrete_log(self, level, x, gens=None): xunit = I.reduce(xunit * xunit.denominator_ideal().element_1_mod(I)) verbose("computing log of %s in basis %s" % (xunit, genunits), level=1) dl = I.ideallog(xunit, genunits) - Xunit = prod(genunits[j] ** dl[j] for j in xrange(len(gens))) - pi_term = x.valuation(P) - sum(dl[j] * genvals[j] for j in xrange(len(gens))) + pi_term = x.valuation(P) - sum(dl[j] * genvals[j] for j in range(len(gens))) dl[i] += pi_term - X = prod(gens[j] ** dl[j] for j in xrange(len(gens))) + X = prod(gens[j] ** dl[j] for j in range(len(gens))) assert (X/x - 1).valuation(P) >= level return dl @@ -1228,7 +1227,7 @@ def quotient_gens(self, n): d = len(es) A = ZZ**d - R = [A.gen(i)*es[i] for i in xrange(d)] + R = [A.gen(i)*es[i] for i in range(d)] r = I.smallest_integer() S = [] for s in Zmod(r).unit_gens() + (p,): @@ -1241,9 +1240,9 @@ def quotient_gens(self, n): if vv[-1] < 0: vv *= -1 while vv[-1] not in [0, 1]: t = self.discrete_log(n, p) - vv = [vv[i] - t[i] for i in xrange(d)] + vv = [vv[i] - t[i] for i in range(d)] assert (Q(A(vv)) == v or Q(A(vv)) == -v) - qgs.append( I.reduce(prod([gs[i] ** (vv[i] % es[i]) for i in xrange(d-1)])) * gs[-1]**vv[-1] ) + qgs.append( I.reduce(prod([gs[i] ** (vv[i] % es[i]) for i in range(d-1)])) * gs[-1]**vv[-1] ) if len(qgs) == 2: x,y = qgs @@ -1374,11 +1373,11 @@ def extend_character(self, level, chi, vals, check=True): for x in standard_gens: d = self.discrete_log(level, x, custom_gens) - chix = prod(values_on_custom_gens[i]**d[i] for i in xrange(len(d))) + chix = prod(values_on_custom_gens[i]**d[i] for i in range(len(d))) values_on_standard_gens.append(chix) chiE = self.character(level, values_on_standard_gens) - if not all( chiE(qs[i]) == vals[i] for i in xrange(len(qs)) ) or chiE.restrict_to_Qp() != chi: + if not all( chiE(qs[i]) == vals[i] for i in range(len(qs)) ) or chiE.restrict_to_Qp() != chi: raise ValueError("Invalid values for extension") return chiE @@ -1647,7 +1646,7 @@ def __init__(self, prime, flag, base_ring, names='s'): self._flag = flag # Find an integer a such that sqrt(a*p) generates the right field and ZZ(sqrt(a*p)) is integrally closed - for a in xrange(4*prime): + for a in range(4*prime): if (not a%prime) or (not ZZ(a).is_squarefree()) or ( (a*prime) % 4 == 1): continue if (flag == 0 and Zmod(prime)(a).is_square()) or \ diff --git a/src/sage/modular/local_comp/type_space.py b/src/sage/modular/local_comp/type_space.py index ad079d98a12..c1e802e3677 100644 --- a/src/sage/modular/local_comp/type_space.py +++ b/src/sage/modular/local_comp/type_space.py @@ -311,7 +311,7 @@ def group(self): sage: example_type_space().group() Congruence Subgroup Gamma_H(98) with H generated by [15, 29, 43] """ - # Implementation here is not the most efficient but this is heavily not + # Implementation here is not the most efficient but this is heavily not # time-critical, and getting it wrong can lead to subtle bugs. p = self.prime() r = self.conductor() @@ -319,7 +319,7 @@ def group(self): n = self.tame_level() chi = self.form().character() tame_H = [i for i in chi.kernel() if (i % p**r) == 1] - wild_H = [crt(x, 1, p**r, n) for x in xrange(p**r) if x% (p**d) == 1] + wild_H = [crt(x, 1, p**r, n) for x in range(p**r) if x% (p**d) == 1] return GammaH(n * p**r, tame_H + wild_H) ############################################################################### diff --git a/src/sage/modular/modform/element.py b/src/sage/modular/modform/element.py index 5d1b291a582..9f8569af89f 100644 --- a/src/sage/modular/modform/element.py +++ b/src/sage/modular/modform/element.py @@ -2242,7 +2242,7 @@ def minimal_twist(self, p=None): while len(candidates) > 1: l = l.next_prime() if l==p: continue - candidates = [(g, chi) for (g, chi) in candidates if g[l] == chi(l)*self[l] ] + candidates = [(h, chi) for (h, chi) in candidates if h[l] == chi(l)*self[l] ] if l > 10000 or len(candidates) == 0: raise RuntimeError("bug finding minimal twist") return candidates[0] @@ -2276,20 +2276,20 @@ def minimal_twist(self, p=None): raise RuntimeError("bug finding minimal twist") return (g, chis[0]) - def local_component(self, p): + def local_component(self, p, twist_factor=None): """ Calculate the local component at the prime `p` of the automorphic representation attached to this newform. For more information, see the documentation of the :func:`LocalComponent` function. - EXAMPLE:: + EXAMPLES:: sage: f = Newform("49a") sage: f.local_component(7) Smooth representation of GL_2(Q_7) with conductor 7^2 """ from sage.modular.local_comp.local_comp import LocalComponent - return LocalComponent(self, p) + return LocalComponent(self, p, twist_factor) class ModularFormElement(ModularForm_abstract, element.HeckeModuleElement): def __init__(self, parent, x, check=True): From 0d2b3800a4697ae286f9389f36978604d54a1502 Mon Sep 17 00:00:00 2001 From: "Alex J. Best" Date: Thu, 16 Apr 2020 18:32:05 -0400 Subject: [PATCH 050/232] fixes for polylogs --- .../rings/padics/padic_generic_element.pyx | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index 655c60f5f46..c8164821dfc 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -32,6 +32,7 @@ from cysignals.memory cimport sig_malloc, sig_free cimport sage.rings.padics.local_generic_element from sage.libs.gmp.mpz cimport mpz_set_si +from sage.arith.srange import srange from sage.rings.padics.local_generic_element cimport LocalGenericElement from sage.rings.padics.precision_error import PrecisionError from sage.rings.rational cimport Rational @@ -4013,18 +4014,18 @@ cdef class pAdicGenericElement(LocalGenericElement): """ raise NotImplementedError - def _polylog_res_1(self, n): + def _polylog_res_1(self, n, p_branch = 0): """ Return `Li_n(`self`)` , the `n`th `p`-adic polylogarithm of ``self``, assuming that self is congruent to 1 mod p. This is an internal function, used by :meth:`polylog`. INPUT: - - ``n`` -- a non-negative integer + - ``n`` -- a non-negative integer OUTPUT: - - Li_n(self) + - `Li_n(`self`)` EXAMPLES :: @@ -4044,6 +4045,8 @@ cdef class pAdicGenericElement(LocalGenericElement): if self == 1: raise ValueError('Polylogarithm is not defined for 1.') + if n <= 1: + raise ValueError('Polylogarithm only implemented for n at least 2.') p = self.parent().prime() prec = self.precision_absolute() @@ -4051,7 +4054,7 @@ cdef class pAdicGenericElement(LocalGenericElement): K = self.parent().fraction_field() z = K(self) - hsl = max(prec / ((z - 1).valuation()) + 1, prec*(p == 2)) + hsl = max(prec / ((z - 1).valuation()) + 1, prec*(p == 2), 2) N = floor(prec - n*(hsl - 1).log(p)) verbose(hsl, level=3) @@ -4059,7 +4062,7 @@ cdef class pAdicGenericElement(LocalGenericElement): def bound(m): return prec - m + Integer(1-2**(m-1)).valuation(p) - m*(hsl - 1).log(p) - gsl = max([_findprec(1/(p-1), 1, _polylog_c(m,p) + bound(m), p) for m in range(2,n+1)]) + gsl = max([_findprec(1/(p-1), 1, _polylog_c(m,p) + bound(m), p) for m in range(2,n+1)] + [2]) verbose(gsl, level=3) g = _compute_g(p, n, max([bound(m) + m*floor((gsl-1).log(p)) for m in range(2, n+1)]), gsl) verbose(g, level=3) @@ -4076,7 +4079,7 @@ cdef class pAdicGenericElement(LocalGenericElement): verbose(G, level=3) H = (n+1)*[0] - H[2] = -sum([((-t)**i)/Integer(i)**2 for i in range(1,hsl+2)]) + H[2] = -sum([((-t)**i)/i**2 for i in srange(1,hsl+2)]) for i in range(2, n): H[i+1] = (H[i]/(1+t) + G[i]/t).integral() if (i + 1) % 2 == 1: @@ -4086,19 +4089,22 @@ cdef class pAdicGenericElement(LocalGenericElement): H[i+1] += (2**i*H[i+1](K(-2)))/(1 - 2**(i+1)) verbose(H, level=3) - return (H[n](z - 1) - ((z.log(0))**(n-1)*(1 - z).log(0))/Integer(n-1).factorial()).add_bigoh(N) + return (H[n](z - 1) - ((z.log(p_branch))**(n-1)*(1 - z).log(p_branch))/Integer(n-1).factorial()).add_bigoh(N) - def polylog(self, n): + def polylog(self, n, p_branch = 0): """ Return `Li_n(self)` , the `n`th `p`-adic polylogarithm of this element. INPUT: - - ``n`` -- a non-negative integer + - ``n`` -- a non-negative integer + - ``p_branch`` -- an element in the base ring or its fraction + field; the implementation will choose the branch of the + logarithm which sends `p` to ``branch``. OUTPUT: - - `Li_n(self)` + - `Li_n(`self`)` EXAMPLES: @@ -4143,6 +4149,12 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: Qp(11)(0).polylog(7) 0 + Check that :trac:`29222` is fixed :: + + sage: K = Qp(7) + sage: print(K(1 + 7^11).polylog(4)) + 6*7^14 + 3*7^15 + 7^16 + 7^17 + O(7^18) + ALGORITHM: The algorithm of Besser-de Jeu, as described in [BdJ2008]_ is used. @@ -4168,6 +4180,12 @@ cdef class pAdicGenericElement(LocalGenericElement): if self.parent().absolute_degree() != 1: raise NotImplementedError("Polylogarithms are not currently implemented for elements of extensions") # TODO implement this (possibly after the change method for padic generic elements is added). + if n == 0: + return self/(1-self) + if n == 1: + return -(1-self).log(p_branch) + if n < 0: + raise ValueError('Polylogarithm only implemented for n at least 0.') prec = self.precision_absolute() @@ -4179,7 +4197,7 @@ cdef class pAdicGenericElement(LocalGenericElement): if z.valuation() < 0: verbose("residue oo, using functional equation for reciprocal. %d %s"%(n,str(self)), level=2) - return (-1)**(n+1)*(1/z).polylog(n)-(z.log(0)**n)/K(n.factorial()) + return (-1)**(n+1)*(1/z).polylog(n)-(z.log(p_branch)**n)/K(n.factorial()) zeta = K.teichmuller(z) @@ -4202,12 +4220,12 @@ cdef class pAdicGenericElement(LocalGenericElement): if z == 1: raise ValueError("Polylogarithm is not defined for 1.") verbose("residue 1, using _polylog_res_1. %d %s"%(n,str(self)), level=2) - return self._polylog_res_1(n) + return self._polylog_res_1(n, p_branch) # Set up precision bounds tsl = prec / (z - zeta).valuation() + 1 N = floor(prec - n*(tsl - 1).log(p)) - gsl = max([_findprec(1/(p-1), 1, prec - m + _polylog_c(m,p) - m*(tsl - 1).log(p), p) for m in range(1,n+1)]) + gsl = max([_findprec(1/(p-1), 1, prec - m + _polylog_c(m,p) - m*(tsl - 1).log(p), p) for m in range(1,n+1)] + [2]) gtr = _compute_g(p, n, prec + n*(gsl - 1).log(p), gsl) From e983616bfeca554868a277ff9bf99dca2853b002 Mon Sep 17 00:00:00 2001 From: "Alex J. Best" Date: Sun, 17 May 2020 13:27:08 -0400 Subject: [PATCH 051/232] fix doctest --- src/sage/rings/padics/padic_generic_element.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index c8164821dfc..2dd5c4bfc0e 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -4138,7 +4138,7 @@ cdef class pAdicGenericElement(LocalGenericElement): The polylogarithm of 1 is not defined :: - sage: Qp(5)(1).polylog(1) + sage: Qp(5)(1).polylog(2) Traceback (most recent call last): ... ValueError: Polylogarithm is not defined for 1. From f092ee0fb045282e2d5676576485dc95310d28dc Mon Sep 17 00:00:00 2001 From: "Alex J. Best" Date: Wed, 27 May 2020 16:51:26 -0400 Subject: [PATCH 052/232] define polylogs at 1 using duplication formula --- src/sage/rings/padics/padic_generic_element.pyx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index 2dd5c4bfc0e..2c9b0853850 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -4136,13 +4136,17 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: Qp(5)(10).polylog(1) == -Qp(5)(1-10).log(0) True - The polylogarithm of 1 is not defined :: + The dilogarithm of 1 is zero :: sage: Qp(5)(1).polylog(2) - Traceback (most recent call last): - ... - ValueError: Polylogarithm is not defined for 1. + O(5^20) + The cubing relation holds for the trilogarithm at 1 :: + + sage: K = Qp(7) + sage: z = K.zeta(3) + sage: -8*K(1).polylog(3) == 9*(K(z).polylog(3) + K(z^2).polylog(3)) + True The polylogarithm of 0 is 0 :: @@ -4218,7 +4222,7 @@ cdef class pAdicGenericElement(LocalGenericElement): if zeta == 1: if z == 1: - raise ValueError("Polylogarithm is not defined for 1.") + return Integer(2)**(n-1)*K(-1).polylog(n, p_branch=p_branch)/(1-Integer(2)**(n-1)) verbose("residue 1, using _polylog_res_1. %d %s"%(n,str(self)), level=2) return self._polylog_res_1(n, p_branch) From 099e1faa8421c0e23fecf978397165ce89699926 Mon Sep 17 00:00:00 2001 From: "Alex J. Best" Date: Sat, 30 May 2020 17:38:39 -0400 Subject: [PATCH 053/232] add test for new valueerror --- src/sage/rings/padics/padic_generic_element.pyx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index 2c9b0853850..55ce3462706 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -4093,7 +4093,7 @@ cdef class pAdicGenericElement(LocalGenericElement): def polylog(self, n, p_branch = 0): """ - Return `Li_n(self)` , the `n`th `p`-adic polylogarithm of this element. + Return `Li_n(self)`, the `n`th `p`-adic polylogarithm of this element. INPUT: @@ -4153,6 +4153,14 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: Qp(11)(0).polylog(7) 0 + Only positive polylogarithms for positive `n` are defined :: + + + sage: Qp(11)(2).polylog(-1) + Traceback (most recent call last): + ... + ValueError: Polylogarithm only implemented for n at least 0. + Check that :trac:`29222` is fixed :: sage: K = Qp(7) From c699a80a48352a18f856d0f8543c12dc6010385f Mon Sep 17 00:00:00 2001 From: "Alex J. Best" Date: Sat, 30 May 2020 17:46:20 -0400 Subject: [PATCH 054/232] other valueerror --- src/sage/rings/padics/padic_generic_element.pyx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index 55ce3462706..158022a07f1 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -4037,6 +4037,13 @@ cdef class pAdicGenericElement(LocalGenericElement): Traceback (most recent call last): ... ValueError: Polylogarithm is not defined for 1. + + Only polylogarithms for `n` at least two are defined by this function :: + + sage: Qp(11)(2).polylog(-1) + Traceback (most recent call last): + ... + ValueError: Polylogarithm only implemented for n at least 2. """ from sage.rings.power_series_ring import PowerSeriesRing from sage.functions.other import ceil,floor @@ -4153,8 +4160,7 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: Qp(11)(0).polylog(7) 0 - Only positive polylogarithms for positive `n` are defined :: - + Only polylogarithms for positive `n` are defined :: sage: Qp(11)(2).polylog(-1) Traceback (most recent call last): From a79ddf5786906d819287c1125e94188511157912 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Fri, 14 Aug 2020 09:41:16 +0200 Subject: [PATCH 055/232] 30352: initial version providing just the basics --- build/pkgs/knotinfo/SPKG.rst | 33 ++ build/pkgs/knotinfo/checksums.ini | 5 + build/pkgs/knotinfo/dependencies | 5 + build/pkgs/knotinfo/package-version.txt | 1 + build/pkgs/knotinfo/spkg-install.in | 21 + build/pkgs/knotinfo/spkg-install.py | 15 + build/pkgs/knotinfo/type | 1 + src/sage/databases/knotinfo_db.py | 669 +++++++++++++++++++++++ src/sage/knots/knotinfo.py | 697 ++++++++++++++++++++++++ 9 files changed, 1447 insertions(+) create mode 100644 build/pkgs/knotinfo/SPKG.rst create mode 100644 build/pkgs/knotinfo/checksums.ini create mode 100644 build/pkgs/knotinfo/dependencies create mode 100644 build/pkgs/knotinfo/package-version.txt create mode 100644 build/pkgs/knotinfo/spkg-install.in create mode 100644 build/pkgs/knotinfo/spkg-install.py create mode 100644 build/pkgs/knotinfo/type create mode 100644 src/sage/databases/knotinfo_db.py create mode 100644 src/sage/knots/knotinfo.py diff --git a/build/pkgs/knotinfo/SPKG.rst b/build/pkgs/knotinfo/SPKG.rst new file mode 100644 index 00000000000..dfdc8bec6a6 --- /dev/null +++ b/build/pkgs/knotinfo/SPKG.rst @@ -0,0 +1,33 @@ += KnotInfo Database = + +== Description == + +Database for named knots and links provided at + +https://knotinfo.math.indiana.edu/ + +and + +https://linkinfo.sitehost.iu.edu' + +== Dependencies == + + * Sage library + +== Changelog == + +=== knotinfo-20200713.tar.bz2 (Sebastian Oehms, 13 Juli 2020) === + + * #30352: Initial version + + The tarbal has been created from the both download files at the + given date: + + `knotinfo_data_complete.xls` + `linkinfo_data_complete.xlsx` + + exporting them to CSV via LibreOffice. + + The second file has been changed manually deleting one character: + a trailing "}" occuring in the homfly_polynomial column of the last + link `L11n459{1,1,1}`. diff --git a/build/pkgs/knotinfo/checksums.ini b/build/pkgs/knotinfo/checksums.ini new file mode 100644 index 00000000000..985cba4b86c --- /dev/null +++ b/build/pkgs/knotinfo/checksums.ini @@ -0,0 +1,5 @@ +tarball=knotinfo-20200713.tar.bz2 +sha1=ec6c8436d5565fdd140cd3e4b301d215cd62b0d0 +md5=1d84f176290bdd3a752757242512fca2 +cksum=3419853512 +upstream_url=https://trac.sagemath.org/raw-attachment/ticket/30352/knotinfo-20200713.tar.bz2 diff --git a/build/pkgs/knotinfo/dependencies b/build/pkgs/knotinfo/dependencies new file mode 100644 index 00000000000..c1b713883fe --- /dev/null +++ b/build/pkgs/knotinfo/dependencies @@ -0,0 +1,5 @@ +| $(SAGERUNTIME) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/knotinfo/package-version.txt b/build/pkgs/knotinfo/package-version.txt new file mode 100644 index 00000000000..f6f6326f811 --- /dev/null +++ b/build/pkgs/knotinfo/package-version.txt @@ -0,0 +1 @@ +20200713 diff --git a/build/pkgs/knotinfo/spkg-install.in b/build/pkgs/knotinfo/spkg-install.in new file mode 100644 index 00000000000..e987af7791d --- /dev/null +++ b/build/pkgs/knotinfo/spkg-install.in @@ -0,0 +1,21 @@ +INSTALL="yes" +TARGET="${SAGE_SHARE}/knotinfo" +VERSION=`cat package-version.txt` +if [ -d $TARGET ] +then + diff package-version.txt $TARGET > /dev/null 2>&1 + if [ $? -eq 0 ] + then + INSTALL="no" + echo "Version $VERSION of knotinfo already installed" + else + OLD_VERSION=`cat $TARGET/package-version.txt` + echo "Removing former version $OLD_VERSION of knotinfo" + rm -rf $TARGET + fi +fi + +if [ "$INSTALL" = "yes" ] +then + exec sage-python23 spkg-install.py +fi diff --git a/build/pkgs/knotinfo/spkg-install.py b/build/pkgs/knotinfo/spkg-install.py new file mode 100644 index 00000000000..7e9c3fad8f3 --- /dev/null +++ b/build/pkgs/knotinfo/spkg-install.py @@ -0,0 +1,15 @@ +import os +from sage.all import save +from sage.env import SAGE_SHARE +from sage.misc.misc import sage_makedirs +from sage.databases.knotinfo_db import KnotInfoDataBase + +install_root = os.path.join(SAGE_SHARE, 'knotinfo') + +if __name__ == '__main__': + sage_makedirs(install_root) + print("Creating the KnotInfo database.") + ki_db = KnotInfoDataBase() + ki_db.create_col_dict_sobj() + ki_db.create_data_sobj() + os.system('cp package-version.txt %s' %install_root) diff --git a/build/pkgs/knotinfo/type b/build/pkgs/knotinfo/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/knotinfo/type @@ -0,0 +1 @@ +optional diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py new file mode 100644 index 00000000000..404f044c5e8 --- /dev/null +++ b/src/sage/databases/knotinfo_db.py @@ -0,0 +1,669 @@ +# -*- coding: utf-8 -*- +r""" +KontInfo Database + +This module contains the class :class:`KnotInfoDataBase` and auxilary classes for it +which serves as an interface to the lists of named knots and links provided at +https://knotinfo.math.indiana.edu/ + + +AUTHORS: + +- Sebastian Oehms August 2020: initial version +""" + + +############################################################################## +# Copyright (C) 2020 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 +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +############################################################################## + + +import os +import csv +from enum import Enum + +from sage.structure.sage_object import SageObject +from sage.misc.persist import save, load +from sage.misc.verbose import verbose +from sage.misc.cachefunc import cached_method +from sage.env import SAGE_SHARE, SAGE_ROOT + + + +class KnotInfoColumnTypes(Enum): + r""" + Enum class to specify if a column from the table of knots and links provided by http://www.indiana.edu/~knotinfo + is used for knots only, links only or both. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoColumnTypes + sage: [col_type for col_type in KnotInfoColumnTypes] + [, + , + ] + """ + + OnlyKnots = 'K' # column that is only used in the KnotInfo table + OnlyLinks = 'L' # column that is only used in the LinkInfo table + KnotsAndLinks = 'B' # column that is only used in both tables + + +class KnotInfoColumns(Enum): + r""" + Enum class to select a column from the table of knots and links provided by http://www.indiana.edu/~knotinfo + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns, KnotInfoColumnTypes + sage: ki_db = KnotInfoDataBase() + sage: KnotInfoColumns('Columns', ki_db.read_column_dict()) + + sage: [col.column_name() for col in _ if col.column_type() == KnotInfoColumnTypes.OnlyLinks] + ['Name - Unoriented', + 'Orientation', + 'Unoriented Rank', + 'PD Notation (vector)', + 'PD Notation (KnotTheory)', + 'Multivariable Alexander Polynomial', + 'HOMFLYPT Polynomial', + 'Unoriented', + 'Arc Notation', + 'Linking Matrix', + 'Rolfsen Name', + 'Components', + 'DT code', + 'Splitting Number', + 'Nullity', + 'Unlinking Number', + 'Weak Splitting Number'] + """ + def column_name(self): + r""" + Return the name of ``self`` displayed on the KnotInfo web-page. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns + sage: ki_db = KnotInfoDataBase() + sage: KIcols = KnotInfoColumns('Columns', ki_db.read_column_dict()) + sage: KIcols.dt_code.column_name() + 'DT code' + """ + return self.value[0] + + def column_type(self): + r""" + Return the type of ``self``. That is an instance of ``Enum`` + :class:`KnotInfoColumnTypes`. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns, KnotInfoColumnTypes + sage: ki_db = KnotInfoDataBase() + sage: KIcols = KnotInfoColumns('Columns', ki_db.read_column_dict()) + sage: KIcols.homfly_polynomial.column_type() + + sage: KIcols.homflypt_polynomial.column_type() + + sage: KIcols.name.column_type() + + """ + return self.value[1] + + +class KnotInfoFilename(Enum): + r""" + Enum for the different data files. The following choices are possible: + + - ``knots`` -- contains the the data from KnotInfo + - ``links`` -- contains the the data for proper links from LinkInfo + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename + + """ + + def url(self): + """ + Return the URL to download the data from the web-page. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.url() + 'https://knotinfo.math.indiana.edu/' + """ + if self == KnotInfoFilename.knots: + return self.value[0] + else: + return self.value[0] + + def excel(self): + """ + Return the Excel-file name to download the data from the web-page. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.excel() + 'knotinfo_data_complete.xls' + """ + if self == KnotInfoFilename.knots: + return '%s.xls' %(self.value[1]) + else: + return '%s.xlsx' %(self.value[1]) + + def csv(self): + """ + Return the file name under which the data from the web-page + are stored as csv file. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.csv() + 'knotinfo_data_complete.csv' + """ + return '%s.csv' %(self.value[1]) + + def sobj_num_knots(self): + """ + Return the file name under which the number of knots + is stored as in python int in a sobj-file. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.sobj_num_knots() + 'num_knots.sobj' + """ + return 'num_knots.sobj' + + def sobj_row(self): + """ + Return the file name under which the row-data of the csv-File + is stored as python dictionary in a sobj-file. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.sobj_row() + 'row_dict.sobj' + """ + return 'row_dict.sobj' + + def sobj_column(self): + """ + Return the file name under which the column-data of the csv-File + is stored as python dictionary in a sobj-file. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.sobj_column() + 'column_dict.sobj' + """ + return 'column_dict.sobj' + + + def sobj_data(self, column): + """ + Return the file name under which the data of the given + column is stored as python list in a sobj-file. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.sobj_data(ki_db.columns().braid_notation) + 'knotinfo_braid_notation' + """ + if column.column_type() == KnotInfoColumnTypes.OnlyLinks: + return 'linkinfo_%s' %(column.name) + else: + return 'knotinfo_%s' %(column.name) + + knots = ['https://knotinfo.math.indiana.edu/', 'knotinfo_data_complete'] + links = ['https://linkinfo.sitehost.iu.edu', 'linkinfo_data_complete'] + + + + +#---------------------------------------------------------------------------------------------------------------------------- +# Class to provide data for knots and links from the KnotInfo web-page +#---------------------------------------------------------------------------------------------------------------------------- +class KnotInfoDataBase(SageObject): + r""" + Database interface to KnotInfo + + The original data are obtained from KnotInfo web-page (URL see the example below). In order + to have these data installed during the build process as a sage-package they are converted + as csv files into a tarball. This tarball has been created using the method :meth:`create_spkg_tarball`. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots + + """ + + filename = KnotInfoFilename + + def __init__(self): + r""" + Python constructor. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: from sage.env import SAGE_SHARE + sage: ki_db = KnotInfoDataBase() + sage: ki_db._import_path + '/home/sebastian/develop/sage/local/share/knotinfo' + """ + self._package = 'knotinfo' + version_file = os.path.join(SAGE_ROOT, 'build/pkgs/%s/package-version.txt' %self._package) + f = open(version_file) + self._version = f.read().splitlines()[0] + f.close() + + # some constants + self._delimiter = '|' + self._names_column = 'name' + self._components_column = 'components' + self._knot_prefix = 'K' + self._import_path = os.path.join(SAGE_SHARE, self._package) + + from sage.misc.misc import sage_makedirs + sage_makedirs(self._import_path) + + self._knot_list = None + self._link_list = None + + + def _create_csv_file(self, filename, path_for_src): + r""" + Return the data fetched from the web-page as a csv file + such that it can be parsed via pythons ``csv`` class. + + INOUT: + + - ``filename`` - instance of :class:`KnotInfoDataBase.filename` + - ``path_for_src`` - string giving the pathname where to store + the ``csv`` -files + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: import os + sage: pwd = os.environ['PWD'] + sage: ki_db = KnotInfoDataBase() + sage: ki_db._create_csv_file(ki_db.filename.knots, pwd) + """ + # TODO import directly from the internet page and convert to csv via pandoc + return + if not isinstance(filename, KnotInfoDataBase.filename): + raise TypeError('File name must be an instance of enum %s' (KnotInfoDataBase.filename)) + + import_file = '%s/%s' %(self._import_path, filename.csv()) + + from six.moves.urllib.request import urlopen + try: + from urllib.error import HTTPError + except ImportError: + from urllib2 import HTTPError + + try: + url = '%s/%s' %(filename.url(), filename.excel()) + url_data = urlopen(url).read().decode() + except: + pass + + + def create_spkg_tarball(self, path_for_src=None): + r""" + Create a tarball for the sage-package ``knotinfo`` in the ``upstream`` directory. This + utility should only be used by users who know what they do in case of a switch to a new + version of the data files (that is if the original files on KnotInfo web-page have changed). + In that case in invocation of ``sage -package update knotinfo `` and + ``sage -package fix-checksum knotinfo`` will be necessary. + + INPUT: + + -- ``path_for_src`` - string of the path under which the source are stored in a + subdirectory called ``src`` + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.create_spkg_tarball() # not tested (because of internet access) + """ + if not path_for_src: + path_for_src = os.environ['PWD'] + + for filename in KnotInfoDataBase.filename: + self._create_csv_file(filename, path_for_src) + + os.system('cd %s; tar -cvjSf %s/upstream/%s-%s.tar.bz2 src' %(path, SAGE_ROOT, self._package, self._version) ) + + + @cached_method + def version(self): + r""" + Return the current version. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.version() + '20200713' + """ + return self._version + + def knot_list(self): + r""" + Return the KnotInfo table rows as Python list. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: len(ki_db.knot_list()) # not tested because its only used on installation + """ + if self._knot_list: + return self._knot_list + + print('Importing KnotInfo database from SPKG!') + os.system('pwd') + knot_csv = open('src/%s' %self.filename.knots.csv()) + knot_dict = csv.DictReader(knot_csv, delimiter=self._delimiter) + self._knot_list = list(knot_dict) + knot_csv.close() + return self._knot_list + + + def link_list(self): + r""" + Return the LinkInfo table rows as Python list. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: len(ki_db.link_list()) # not tested because its only used on installation + """ + if self._link_list: + return self._link_list + + print('Importing LinkInfo database from SPKG!') + link_csv = open('src/%s' %self.filename.links.csv()) + link_dict = csv.DictReader(link_csv, delimiter=self._delimiter) + self._link_list = list(link_dict) + link_csv.close() + return self._link_list + + def create_col_dict_sobj(self): + r""" + Create ``sobj`` files containing the number of knots and a dictionary + for the columns of the table. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.create_col_dict_sobj() # not tested because its only used on installation + """ + knot_list = self.knot_list() + knot_column_names = knot_list[0] + len_knots = len(knot_list) + + link_list = self.link_list() + link_column_names = link_list[0] + + num_knots = len_knots - 1 + save(num_knots, '%s/%s' %(self._import_path, self.filename.knots.sobj_num_knots())) + + column_dict = {} + + # ---------------------------------------------------------------- + # Columns that exist for knots and links + # ---------------------------------------------------------------- + for col in knot_column_names: + + name = knot_column_names[col] + if not name: + # not of interest + continue + + col_type = KnotInfoColumnTypes.OnlyKnots + if col in link_column_names: + col_type = KnotInfoColumnTypes.KnotsAndLinks + column_dict[col] = [name, col_type] + + # ---------------------------------------------------------------- + # Columns that exist for links only + # ---------------------------------------------------------------- + for col in link_column_names: + + name = link_column_names[col] + if not name: + # not of interest + continue + + if col in knot_column_names: + # already used + continue + + col_type = KnotInfoColumnTypes.OnlyLinks + column_dict[col] = [name, col_type] + + save(column_dict, '%s/%s' %(self._import_path, self.filename.knots.sobj_column())) + + + + def create_data_sobj(self): + r""" + Create ``sobj`` files containing the contents of the whole table. + To each column there is created one file containing a list of + strings giving the entries of the database table. + + The length of these lists depends on the type of the corresponding + column. If a column is used in both tables (``KnotInfoColumnTypes.KnotsAndLinks``) + the list of proper links is appended to the list of knots. + In both other cases the lenght of the list corresponds to + the number of listed knots and proper links respectively. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.create_data_sobj() # not tested because its only used on installation + """ + knot_list = self.knot_list() + link_list = self.link_list() + len_knots = len(knot_list) + len_links = len(link_list) + + row_dict = {} + + # ---------------------------------------------------------------- + # Columns that exist for knots and links + # ---------------------------------------------------------------- + for col in self.columns(): + val_list = [] + + if col.column_type() != KnotInfoColumnTypes.OnlyLinks: + for i in range(1 , len_knots): + if col.name == self._names_column: + row_dict[self._knot_prefix + knot_list[i][col.name]] = [i - 1 , 1] + else: + val_list.append(knot_list[i][col.name]) + + if col.column_type() != KnotInfoColumnTypes.OnlyKnots: + for i in range(1 , len_links): + if col.name == self._names_column: + link_name = link_list[i][col.name] + link_name = link_name.replace('{', '_') + link_name = link_name.replace(',', '_') + link_name = link_name.replace('}', '') + + num_comp = int(link_list[i][self._components_column]) + row_dict[link_name] = [i + len_knots - 2 , num_comp] + + else: + val_list.append(link_list[i][col.name]) + + if val_list: + save(val_list, '%s/%s' %(self._import_path, self.filename.knots.sobj_data(col))) + + save(row_dict, '%s/%s' %(self._import_path, self.filename.knots.sobj_row())) + + + @cached_method + def columns(self): + r""" + Return the columns ot the databese table as instances of enum class + :class:`KnotInfoColumns`. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: cols = ki_db.columns() + sage: [col.column_name() for col in cols if col.name.startswith('pd')] + ['PD Notation', 'PD Notation (vector)', 'PD Notation (KnotTheory)'] + """ + column_dict = self.read_column_dict() + return KnotInfoColumns('Columns', column_dict) + + + # ------------------------------------------------------------------------------------------------------------- + # read the dictionary for the column names from sobj-file + # ------------------------------------------------------------------------------------------------------------- + @cached_method + def read_column_dict(self): + r""" + Read the dictionary for the column names from the according sobj-file + + OUTPUT: + + A python dictionary containing the column names and types + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: len(ki_db.read_column_dict()) + 120 + """ + lib_path = self._import_path + filename = self.filename.knots.sobj_column() + return load('%s/%s' %(lib_path, filename)) + + # ------------------------------------------------------------------------------------------------------------- + # read the dictionary for the row names that is the knot and link names from sobj-file + # ------------------------------------------------------------------------------------------------------------- + @cached_method + def read_row_dict(self): + r""" + Read the dictionary for the row names that is the knot and link names + from the according sobj-file + + OUTPUT: + + A python dictionary containing the names of the knots and links + together with their table index and the corresponding number of + components + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: len(ki_db.read_row_dict()) + 7166 + """ + lib_path = self._import_path + filename = self.filename.knots.sobj_row() + return load('%s/%s' %(lib_path, filename)) + + # ------------------------------------------------------------------------------------------------------------- + # read the dictionary for the row names that is the knot and link names from sobj-file + # ------------------------------------------------------------------------------------------------------------- + @cached_method + def read_num_knots(self): + r""" + Read the number of knots contained in the database (without + proper links) from the according sobj-file. + + OUTPUT: + + Integer + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.read_num_knots() + 2978 + """ + lib_path = self._import_path + filename = self.filename.knots.sobj_num_knots() + return load('%s/%s' %(lib_path, filename)) + + + # ------------------------------------------------------------------------------------------------------------- + # read an sobj-file obtained from KnotInfo database + # ------------------------------------------------------------------------------------------------------------- + @cached_method + def read(self, column): + r""" + Access a column of KnotInfo / LinkInfo + + INPUT: + + ``column`` -- instance of enum :class:`KnotInfoColumns` + to select the data to be read in + + OUTPUT: + + A python list containing the data corresponding to the column. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + """ + if not isinstance(column, KnotInfoColumns): + raise TypeError('column must be an instance of enum %s' (KnotInfoColumns)) + + lib_path = self._import_path + if column.column_type() == KnotInfoColumnTypes.OnlyLinks: + filename = self.filename.links.sobj_data(column) + else: + filename = self.filename.knots.sobj_data(column) + + verbose('loading data library %s ...' %(filename)) + res = load('%s/%s' %(lib_path, filename)) + verbose('... finished!') + + return res + diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py new file mode 100644 index 00000000000..6a684770727 --- /dev/null +++ b/src/sage/knots/knotinfo.py @@ -0,0 +1,697 @@ +# -*- coding: utf-8 -*- +r""" +KontInfo + +This module contains the class :class:`KnotInfoBase` which is derived from :class:`Enum` +and provides all knots and links listed in the databases at https://knotinfo.math.indiana.edu/ +and https://linkinfo.sitehost.iu.edu as its items. + +Be aware that there are a couple of conventions used differently on KnotInfo as in Sage, especially +concerning the selection of the symmetry version of the link. In our transitions to Sage objects +these are translated in order to avoid confusion about exchanged mirror versions. + +Briefly, these differences are: + + - ``pd_notation`` -- KnotInfo: counter clockwise Sage: clockwise, see note in + :meth:`link` + + - ``homfly_polynomial`` -- KnotInfo: ``v`` Sage: `1/a`, see note in :meth:`homfly_polynomial`. + + - ``braid_notation`` -- This is used accordingly: The crossing of the braid generators are positive + in both systems. Here it is listed because there could arise confusion from the source where they are + taken from. There, the braid generators are assumed to have a negative crossing + (see definition 3 of Gittings, T., "Minimum Braids: A Complete Invariant of Knots and Links + https://arxiv.org/abs/math/0401051). + +REFERENCES: + +- https://knotinfo.math.indiana.edu/ +- https://linkinfo.sitehost.iu.edu/ + + + +AUTHORS: + +- Sebastian Oehms August 2020: initial version +""" + + +############################################################################## +# Copyright (C) 2020 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 +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +############################################################################## + + + +from enum import Enum +from sage.misc.cachefunc import cached_method +from sage.misc.sage_eval import sage_eval +from sage.groups.braid import BraidGroup +from sage.knots.knot import Knots +from sage.databases.knotinfo_db import KnotInfoColumnTypes, KnotInfoColumns, KnotInfoDataBase + + +db = KnotInfoDataBase() + + +def eval_knotinfo(string, locals={}, to_tuple=True): + r""" + Preparse a string from the KnotInfo database and evaluate it by ``sage_eval``. + + INPUT: + + - ``string`` -- string that gives a value of some database entry + - ``locals`` -- dictionary of locals passed to ``sage_eval`` + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo, eval_knotinfo + sage: L = KnotInfo.L4a1_0 + sage: L.braid_notation(original=True) + '{4, {1, -2, 3, -2, -1, -2, -3, -2}}' + sage: eval_knotinfo(_) + (4, (1, -2, 3, -2, -1, -2, -3, -2)) + """ + if to_tuple: + new_string = string.replace('{', '(') + new_string = new_string.replace('}', ')') + else: + new_string = string.replace('{', '[') + new_string = new_string.replace('}', ']') + new_string = new_string.replace(';', ',') + return sage_eval(new_string, locals=locals) + + + +class KnotInfoNotation(Enum): + r""" + """ + + braid = 'braid' + PD = 'planar diagram' + DT = 'Docker Thistlewait' + Gauss = 'Gauss code' + + +class KnotInfoBase(Enum): + r""" + Enum class to select the knots and links provided by http://www.indiana.edu/~knotinfo + + EXAMPLES:: + sage: from sage.knots.knotinfo import KnotInfo + sage: [knot.name for knot in KnotInfo if knot.crossing_number() < 5] + ['K0_1', 'K3_1', 'K4_1', 'L2a1_0', 'L2a1_1', 'L4a1_0', 'L4a1_1'] + """ + @property + def items(self): + r""" + Return an Enum class to select a column item of the KnotInfo database. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: it = L.items + sage: [it.name for it in it if it.name.endswith('notation')] + ['dt_notation', + 'conway_notation', + 'two_bridge_notation', + 'gauss_notation', + 'enhanced_gauss_notation', + 'pd_notation', + 'braid_notation', + 'positive_braid_notation', + 'positive_pd_notation', + 'strongly_quasipositive_braid_notation', + 'quasipositive_braid_notation', + 'arc_notation'] + """ + return db.columns() + + @cached_method + def __getitem__(self, item): + """ + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L[L.items.alternating] + 'Y' + sage: L[L.items.arc_notation] + '{{6, 4}, {3, 5}, {4, 2}, {1, 3}, {2, 6}, {5, 1}}' + sage: L[L.items.braid_notation] + '{4, {1, -2, 3, -2, -1, -2, -3, -2}}' + sage: L[0] + Traceback (most recent call last): + ... + KeyError: "Item must be an instance of " + """ + if not isinstance(item, KnotInfoColumns): + raise KeyError('Item must be an instance of %s' %(KnotInfoColumns)) + if item.column_type() == KnotInfoColumnTypes.OnlyLinks and self.is_knot(): + raise KeyError('Item not available for knots' %(KnotInfoColumns)) + if item.column_type() == KnotInfoColumnTypes.OnlyKnots and not self.is_knot(): + raise KeyError('Item not available for links' %(KnotInfoColumns)) + + l = db.read(item) + offset = 0 + if item.column_type() == KnotInfoColumnTypes.OnlyLinks: + offset = self._offset_knots() + + return l[self.value[0]-offset] + + def _offset_knots(self): + r""" + Return the list index of the first proper link in a conbined + list containing knots and proper links together which is the + case for columns used for KnotInfo and LinkInfo in common. + This index is exactly the total number of knots recorded + in KnotInfo. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L._offset_knots() + 2978 + """ + return db.read_num_knots() + + @cached_method + def _braid_group(self): + r""" + Return the braid group corresponding to the braid index + of ``self``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L._braid_group() + Braid group on 4 strands + """ + n = self.braid_index() + if n == 1: + return BraidGroup(2) + else: + return BraidGroup(n) + + + @cached_method + def _homfly_pol_ring(self, var1, var2): + r""" + Return the parent Laurent polynomial ring for the Homfly-PT + polynomial according to Sage's internal one. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_1 + sage: L._homfly_pol_ring('u', 'v') + Multivariate Laurent Polynomial Ring in u, v over Integer Ring + """ + K3_1 = Knots().from_table(3,1) + return K3_1.homfly_polynomial(var1=var1, var2=var2).parent() + + @cached_method + def pd_notation(self, original=False): + r""" + Return the value of column ``pd_notation`` for this + link as a Python list of Python lists. + + INPUT: + + - ``original`` -- boolean (optional, default ``False``) if set to + ``True`` the original table entry is returned as a string + + OUTPUT:: + + Python list of python lists each entry of the outer list + representing a crossing. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L.pd_notation() + [[6, 1, 7, 2], [8, 3, 5, 4], [2, 5, 3, 6], [4, 7, 1, 8]] + sage: L.pd_notation(original=True) + '{{6, 1, 7, 2}, {8, 3, 5, 4}, {2, 5, 3, 6}, {4, 7, 1, 8}}' + sage: K = KnotInfo.K4_1 + sage: K.pd_notation() + [[4, 2, 5, 1], [8, 6, 1, 5], [6, 3, 7, 4], [2, 7, 3, 8]] + """ + if self.is_knot(): + pd_notation = self[self.items.pd_notation] + else: + pd_notation = self[self.items.pd_notation_vector] + + if original: + return pd_notation + + if not pd_notation: + # don't forget the unknot + return [] + + return eval_knotinfo(pd_notation, to_tuple=False) + + @cached_method + def dt_notation(self, original=False): + r""" + Return the value of column ``dt_notation`` for this + link as a Python list of Python lists. + + INPUT: + + - ``original`` -- boolean (optional, default ``False``) if set to + ``True`` the original table entry is returned as a string + + OUTPUT:: + + Python list of python lists each entry of the outer list + representing a crossing. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L.dt_notation() + [[6, 8], [2, 4]] + sage: L.dt_notation(original=True) + '[{6, 8}, {2, 4}]' + sage: L = KnotInfo.L4a1_0 + sage: K = KnotInfo.K4_1 + sage: K.dt_notation() + [4, 6, 8, 2] + """ + if self.is_knot(): + dt_notation = self[self.items.dt_notation] + else: + dt_notation = self[self.items.dt_code] + + if original: + return dt_notation + + if not dt_notation: + # don't forget the unknot + return [] + + return eval_knotinfo(dt_notation, to_tuple=False) + + @cached_method + def gauss_notation(self, original=False): + r""" + Return the value of column ``gauss_notation`` for this + link as a Python list of Python lists. + + INPUT: + + - ``original`` -- boolean (optional, default ``False``) if set to + ``True`` the original table entry is returned as a string + + OUTPUT:: + + Python list of + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L.gauss_notation() + [[1, -3, 2, -4], [3, -1, 4, -2]] + sage: L.gauss_notation(original=True) + '{{1, -3, 2, -4}, {3, -1, 4, -2}}' + """ + gauss_notation = self[self.items.gauss_notation] + if original: + return gauss_notation + + if not gauss_notation: + # don't forget the unknot + return [] + + return eval_knotinfo(gauss_notation, to_tuple=False) + + @cached_method + def braid_notation(self, original=False): + r""" + Return the value of column ``braid_notation`` for this + link as a Python tuple (Tietze form). + + INPUT: + + - ``original`` -- boolean (optional, default ``False``) if set to + ``True`` the original table entry is returned as a string + + OUTPUT:: + + Python tuple representing the braid whose closure is ``self`` + in Tietze form. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L.braid_notation() + (1, -2, 3, -2, -1, -2, -3, -2) + sage: L.braid_notation(original=True) + '{4, {1, -2, 3, -2, -1, -2, -3, -2}}' + """ + braid_notation = self[self.items.braid_notation] + if original: + return braid_notation + + if not braid_notation: + # don't forget the unknot + return (1, -1) + + braid_notation = eval_knotinfo(braid_notation) + if type(braid_notation) is list: + # in some cases there are a pair of braid representations + # in the database. If this is the case we select the + # corresponding to the braid index. + if type(braid_notation[0]) is tuple: + i = self.braid_index() + for b in braid_notation: + if -i < min(b) and max(b) < i: + braid_notation = b + break + + if not self.is_knot(): + # in linkinfo the braid_notation includes the braid_index as first item of a pair + braid_notation = braid_notation[1] + return braid_notation + + @cached_method + def braid_index(self): + r""" + Return the value of column ``braid_index`` for this + link as a Python int. + + OUTPUT:: + + Python int giving the minimum of strands needed to + represent ``self`` as closure of a braid. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L.braid_index() + 4 + """ + if self.is_knot(): + return int(self[self.items.braid_index]) + else: + braid_notation = self[self.items.braid_notation] + braid_notation = eval_knotinfo(braid_notation) + return int(braid_notation[0]) + + @cached_method + def braid_length(self): + r""" + Return the value of column ``braid_length`` for this + link as a Python int. + + OUTPUT:: + + Python int giving the minimum length of a braid word + needed to represent ``self`` as closure of a braid. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K3_1 + sage: K.braid_length() + 3 + """ + return int(self[self.items.braid_length]) + + @cached_method + def braid(self): + r""" + Return the braid notation of self as an instance of :class:`Braid`. + + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K3_1 + sage: K.braid() + s^3 + sage: K.braid_notation() + (1, 1, 1) + """ + return self._braid_group()(self.braid_notation()) + + def num_components(self): + r""" + Return the number of compoents of ``self``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L6a1_0.num_components() + 2 + """ + return self.value[1] + + @cached_method + def crossing_number(self): + r""" + Return the minimal number of crossings. + + .. NOTE:: + + In contrast to the number of crossings displayed for instances + of :class:`Link` this number is the minimum over all possible + diagrams of the link. The number of crossings displayed in + the representation string of :class:`Link` referes to the + special representation which could be larger. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L4a1_0.crossing_number() + 4 + sage: KnotInfo.K3_1.crossing_number() + 3 + sage: Link(KnotInfo.L4a1_0.braid()) + Link with 2 components represented by 8 crossings + """ + return int(self[self.items.crossing_number]) + + def is_knot(self): + r""" + Return wether ``self`` is a knot or a proper link. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L7a1_0.is_knot() + False + sage: KnotInfo.K6_3.is_knot() + True + """ + return self.num_components() == 1 + + @cached_method + def homfly_polynomial(self, var1='L', var2='M', original=False): + r""" + Return the value of column ``homfly_polynomial`` for this + knot or link (in this case the column ``homflypt_polynomial`` + is used) as an instance of the element class according to + the output of :meth:`homfly_polynomial` of :class:`Link`. + + INPUT: + + - ``var1`` -- (default: ``'L'``) the first variable + - ``var2`` -- (default: ``'M'``) the second variable + - ``original`` -- boolean (optional, default ``False``) if set to + ``True`` the original table entry is returned as a string + + OUTPUT:: + + A Laurent polynomial over the integers, more precisely an instance of + :class:`sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`. + + .. NOTE:: + + The skein-relation for the Homfly-PT polynomial given on KnotInfo + differs from the ones used in Sage: + + KnotInfo: P(O) = 1, ~v P(L+) - v P(L-) = z P(L0) + + (see: https://knotinfo.math.indiana.edu/descriptions/jones_homfly_kauffman_description/polynomial_defn.html) + + Using Sage's Homfy-PT polynomials with ``normalization='az'`` + the corresponding skein-relation is (see :meth:`homfly_polynomial` + of :class:`Link`): + + Sage: P(O) = 1, a P(L+) - ~a P(L-) = z P(L0) + + Thus, the Homfly-PT polynomial of KnotInfo compares to the one of Sage + by replacing ``v`` by ``~a``. To keep them comparable this translation is + performed, as well. + + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K3_1 = KnotInfo.K3_1 + sage: PK3_1 = K3_1.homfly_polynomial(); PK3_1 + L^-2*M^2 + 2*L^-2 - L^-4 + sage: PK3_1 == K3_1.link().homfly_polynomial(normalization='az') + True + sage: L4a1_1 = KnotInfo.L4a1_1 + sage: PL4a1_1 = L4a1_1.homfly_polynomial(var1='x', var2='y'); PL4a1_1 + x^-3*y^3 + 3*x^-3*y + x^-3*y^-1 - x^-5*y - x^-5*y^-1 + sage: PL4a1_1 == L4a1_1.link().homfly_polynomial(var1='x', var2='y', normalization='az') + True + + check the skein-relation given in the doc string of :meth:`homfly_polynomial` of + :class:`Link` (applied to one of the positive crossings of the right-handed trefoil):: + + sage: R = PK3_1.parent() + sage: PO = R.one() + sage: L2a1_1 = KnotInfo.L2a1_1 + sage: PL2a1_1 = L2a1_1.homfly_polynomial() + sage: a, z = R.gens() + sage: a*PK3_1 - ~a*PO == z*PL2a1_1 + True + + check the skein-relation from the KnotInfo description page with the original version:: + + sage: pK3_1o = K3_1.homfly_polynomial(original=True); pK3_1o + '(2*v^2-v^4)+ (v^2)*z^2' + sage: pL2a1_1o = L2a1_1.homfly_polynomial(original=True); pL2a1_1o + 'v/z-v^3/z + v*z' + + sage: R. = LaurentPolynomialRing(ZZ) + sage: PO = R.one() + sage: PK3_1o = sage_eval(pK3_1o, locals={'v':v, 'z':z}) + sage: PL2a1_1o = sage_eval(pL2a1_1o, locals={'v':v, 'z':z}) + sage: ~v*PK3_1o - v*PO == z*PL2a1_1o + True + + TESTS:: + + all(L.homfly_polynomial() == L.link().homfly_polynomial(normalization='az') for L in KnotInfo if L.crossing_number() > 0 and L.crossing_number() < 7) + True + """ + if self.is_knot(): + homfly_polynomial = self[self.items.homfly_polynomial] + else: + homfly_polynomial = self[self.items.homflypt_polynomial] + + if original: + return homfly_polynomial + + R = self._homfly_pol_ring(var1, var2) + if not homfly_polynomial and self.crossing_number() == 0: + return R.one() + + L, M = R.gens() + lc = {'z': M, 'v': ~L} # + return eval_knotinfo(homfly_polynomial, locals=lc) + + + @cached_method + def link(self, use_item=db.columns().pd_notation): + r""" + Return ``self`` as in instance of :class:`Link`. + + INPUT: + + - ``use_item`` -- (optional default ``self.items.pd_notation``) + instance of :class:`KnotInfoColumns` to choose the column + that should be used to construct the link. Allowed values + are: + -- self.items.pd_notation + -- self.items.dt_notation (only for knots) + -- self.items.braid_notation + + .. NOTE:: + + We use the PD-notation to construct ``self`` as + default. This ensures that the number of crossings + displayed in representation string of the link + coincides with the crossing number as a topological + invariant. + + But attention: The convention on how the edges are + listed are opposite to each other + + KnotInfo: counter clockwise + Sage: clockwise + + Therefore, we have to take the mirror_image of the + link! + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K3_1 + sage: K.link() + Knot represented by 3 crossings + sage: _.braid() + s^3 + sage: _ == K.braid() + True + + sage: K.link(use_item=K.items.dt_notation) + Knot represented by 3 crossings + sage: _.braid() + s^3 + + sage: L = KnotInfo.L4a1_0 + sage: L.link() + Link with 2 components represented by 4 crossings + + sage: L.link(use_item=L.items.dt_notation) + Traceback (most recent call last): + ... + NotImplementedError: Columns.dt_notation only implemented for knots + + but observe:: + + sage: L.link(use_item=L.items.braid_notation) + Link with 2 components represented by 8 crossings + + sage: K6_1 = KnotInfo.K6_1 + sage: K6_1.link().braid() == K6_1.braid() + False + + sage: K4_1 = KnotInfo.K4_1 + sage: K4_1.link().pd_code() + [[4, 1, 5, 2], [8, 5, 1, 6], [6, 4, 7, 3], [2, 8, 3, 7]] + sage: K4_1.pd_notation() + [[4, 2, 5, 1], [8, 6, 1, 5], [6, 3, 7, 4], [2, 7, 3, 8]] + """ + if not isinstance(use_item, KnotInfoColumns): + raise TypeError('%s must be an instance of %s' %(use_item, KnotInfoColumns)) + + if self.is_knot(): + from sage.knots.knot import Knot as Link + else: + from sage.knots.link import Link + + if use_item == self.items.pd_notation: + return Link(self.pd_notation()).mirror_image() # for mirror_image see note above + elif use_item == self.items.braid_notation: + return Link(self.braid()) + elif use_item == self.items.dt_notation: + if not self.is_knot(): + raise NotImplementedError('%s only implemented for knots' %use_item) + from sage.knots.knot import Knots + return Knots().from_dowker_code(self.dt_notation()) + else: + raise ValueError('Construction using %s not possible' %use_item) + + + + +KnotInfo = KnotInfoBase('KnotInfo', db.read_row_dict()) From a8f1bfc6a1f7647ab8ff3329759f66269c955d2c Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Sat, 15 Aug 2020 11:05:14 +0200 Subject: [PATCH 056/232] 30352: adding Gauss-code and symmetry type --- build/pkgs/knotinfo/SPKG.rst | 2 +- build/pkgs/knotinfo/spkg-check.in | 7 ++ src/sage/databases/knotinfo_db.py | 26 +++++-- src/sage/knots/knotinfo.py | 123 +++++++++++++++++++++++++++--- 4 files changed, 140 insertions(+), 18 deletions(-) create mode 100644 build/pkgs/knotinfo/spkg-check.in diff --git a/build/pkgs/knotinfo/SPKG.rst b/build/pkgs/knotinfo/SPKG.rst index dfdc8bec6a6..7ab6d03281d 100644 --- a/build/pkgs/knotinfo/SPKG.rst +++ b/build/pkgs/knotinfo/SPKG.rst @@ -20,7 +20,7 @@ https://linkinfo.sitehost.iu.edu' * #30352: Initial version - The tarbal has been created from the both download files at the + The tarball has been created from the both download files at the given date: `knotinfo_data_complete.xls` diff --git a/build/pkgs/knotinfo/spkg-check.in b/build/pkgs/knotinfo/spkg-check.in new file mode 100644 index 00000000000..d265e98d24d --- /dev/null +++ b/build/pkgs/knotinfo/spkg-check.in @@ -0,0 +1,7 @@ +cd $SAGE_ROOT/src/sage/ + +echo "Testing databases/knotinfo_db.py" +sage -t databases/knotinfo_db.py || sdh_die "Error testing KnotInfo databases" + +echo "Testing knots/knotinfo.py" +sage -t knots/knotinfo.py || sdh_die "Error testing KnotInfo funcionality" diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py index 404f044c5e8..dfadc2b8e51 100644 --- a/src/sage/databases/knotinfo_db.py +++ b/src/sage/databases/knotinfo_db.py @@ -35,6 +35,23 @@ from sage.env import SAGE_SHARE, SAGE_ROOT +def is_knotinfo_available(): + r""" + Return wether the KnotInfo databases are installed or not. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import is_knotinfo_available + sage: is_knotinfo_available() + True + """ + ki_db = KnotInfoDataBase() + try: + ki_db.read_num_knots() + return True + except FileNotFoundError: + return False + class KnotInfoColumnTypes(Enum): r""" @@ -291,9 +308,6 @@ def __init__(self): self._knot_prefix = 'K' self._import_path = os.path.join(SAGE_SHARE, self._package) - from sage.misc.misc import sage_makedirs - sage_makedirs(self._import_path) - self._knot_list = None self._link_list = None @@ -303,7 +317,7 @@ def _create_csv_file(self, filename, path_for_src): Return the data fetched from the web-page as a csv file such that it can be parsed via pythons ``csv`` class. - INOUT: + INPUT: - ``filename`` - instance of :class:`KnotInfoDataBase.filename` - ``path_for_src`` - string giving the pathname where to store @@ -365,7 +379,6 @@ def create_spkg_tarball(self, path_for_src=None): os.system('cd %s; tar -cvjSf %s/upstream/%s-%s.tar.bz2 src' %(path, SAGE_ROOT, self._package, self._version) ) - @cached_method def version(self): r""" Return the current version. @@ -439,6 +452,9 @@ def create_col_dict_sobj(self): link_list = self.link_list() link_column_names = link_list[0] + from sage.misc.misc import sage_makedirs + sage_makedirs(self._import_path) + num_knots = len_knots - 1 save(num_knots, '%s/%s' %(self._import_path, self.filename.knots.sobj_num_knots())) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 6a684770727..d8d7ee29ad3 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -88,16 +88,6 @@ def eval_knotinfo(string, locals={}, to_tuple=True): -class KnotInfoNotation(Enum): - r""" - """ - - braid = 'braid' - PD = 'planar diagram' - DT = 'Docker Thistlewait' - Gauss = 'Gauss code' - - class KnotInfoBase(Enum): r""" Enum class to select the knots and links provided by http://www.indiana.edu/~knotinfo @@ -117,7 +107,7 @@ def items(self): sage: from sage.knots.knotinfo import KnotInfo sage: L = KnotInfo.L4a1_0 sage: it = L.items - sage: [it.name for it in it if it.name.endswith('notation')] + sage: [i.name for i in it if i.name.endswith('notation')] ['dt_notation', 'conway_notation', 'two_bridge_notation', @@ -498,6 +488,96 @@ def is_knot(self): """ return self.num_components() == 1 + @cached_method + def symmetry_type(self): + r""" + Return the symmetry type of ``self``. + + From the KnotInfo description page: + + If a knot is viewed as the oriented diffeomorphism + class of an oriented pair, `K = (S_3, S_1), with `S_i` + diffeomorphic to `S^i`, there are four oriented knots + associated to any particular knot `K`. In addition to + `K` itself, there is the reverse, `K^r = (S_3, -S_1)`, + the concordance inverse, `-K = (-S_3, -S_1)`, and the + mirror image, `K^m = (-S3, S1)`. A knot is called + reversible if `K = K^r`, negative amphicheiral if + `K = -K`, and positive amphicheiral if `K = K^m`. + + A knot possessing any two of these types of symmetry + has all three. Thus, in the table, a knot is called + reversible if that is the only type of symmetry it has, + and likewise for negative amphicheiral. If it has none + of these types of symmetry it is called chiral, and if + it has all three it is called fully amphicheiral. + + For prime knots with fewer than 12 crossings, all + amphicheiral knots are negative amphicheiral. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: [(L.name, L.symmetry_type()) for L in KnotInfo if L.is_knot() and L.crossing_number() < 6] + [('K0_1', 'fully amphicheiral'), + ('K3_1', 'reversible'), + ('K4_1', 'fully amphicheiral'), + ('K5_1', 'reversible'), + ('K5_2', 'reversible')] + """ + if not self.is_knot(): + raise NotImplementedError('This is only available for knots') + if not self[self.items.symmetry_type] and self.crossing_number() == 0: + return 'fully amphicheiral' + return self[self.items.symmetry_type] + + + def is_reversible(self): + r""" + Return wether ``self`` is reversible. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K6_3.is_reversible() + True + """ + if self.symmetry_type() == 'reversible': + return True + if self.symmetry_type() == 'fully amphicheiral': + return True + return False + + def is_amphicheiral(self, positive=False): + r""" + Return wether ``self`` is amphicheiral. + + INPUT: + + - ``positive`` -- Boolean (default False) wether to check + if ``self`` is positive or negative amphicheiral (see + doctest of :meth:`symmetry_type`) + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K12a_427 + sage: K.is_amphicheiral() + False + sage: K.is_amphicheiral(positive=True) + True + """ + if positive: + if self.symmetry_type() == 'positive amphicheiral': + return True + else: + if self.symmetry_type() == 'negative amphicheiral': + return True + if self.symmetry_type() == 'fully amphicheiral': + return True + return False + + @cached_method def homfly_polynomial(self, var1='L', var2='M', original=False): r""" @@ -611,7 +691,8 @@ def link(self, use_item=db.columns().pd_notation): that should be used to construct the link. Allowed values are: -- self.items.pd_notation - -- self.items.dt_notation (only for knots) + -- self.items.dt_notation (only for knots) + -- self.items.gauss_notation (only for knots) -- self.items.braid_notation .. NOTE:: @@ -631,6 +712,11 @@ def link(self, use_item=db.columns().pd_notation): Therefore, we have to take the mirror_image of the link! + Furthermore, note that the mirror version may depend + on the used KnotInfo-notation. For example for the + knot `5_1` the Gauss- and the DT-notation refer to + the mirror image (see example below). + EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfo @@ -670,6 +756,14 @@ def link(self, use_item=db.columns().pd_notation): [[4, 1, 5, 2], [8, 5, 1, 6], [6, 4, 7, 3], [2, 8, 3, 7]] sage: K4_1.pd_notation() [[4, 2, 5, 1], [8, 6, 1, 5], [6, 3, 7, 4], [2, 7, 3, 8]] + + sage: K5_1 = KnotInfo.K5_1 + sage: K5_1.link().braid() + s^5 + sage: K5_1.link(K5_1.items.dt_notation).braid() + s^-5 + sage: K5_1.link(K5_1.items.gauss_notation).braid() + s^-5 """ if not isinstance(use_item, KnotInfoColumns): raise TypeError('%s must be an instance of %s' %(use_item, KnotInfoColumns)) @@ -688,6 +782,11 @@ def link(self, use_item=db.columns().pd_notation): raise NotImplementedError('%s only implemented for knots' %use_item) from sage.knots.knot import Knots return Knots().from_dowker_code(self.dt_notation()) + elif use_item == self.items.gauss_notation: + if not self.is_knot(): + raise NotImplementedError('%s only implemented for knots' %use_item) + from sage.knots.knot import Knots + return Knots().from_gauss_code(self.gauss_notation()) else: raise ValueError('Construction using %s not possible' %use_item) From 1ec036c08283c02d335b0f56e70b362cb3817bc0 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Sun, 23 Aug 2020 19:20:39 +0200 Subject: [PATCH 057/232] 30352: add demo sample list of 20 links --- build/pkgs/knotinfo/spkg-check.in | 4 +- src/sage/databases/knotinfo_db.py | 270 +++++++++++++++++++++++++++--- src/sage/knots/knotinfo.py | 30 +++- 3 files changed, 266 insertions(+), 38 deletions(-) diff --git a/build/pkgs/knotinfo/spkg-check.in b/build/pkgs/knotinfo/spkg-check.in index d265e98d24d..b655a1ccf18 100644 --- a/build/pkgs/knotinfo/spkg-check.in +++ b/build/pkgs/knotinfo/spkg-check.in @@ -1,7 +1,7 @@ cd $SAGE_ROOT/src/sage/ echo "Testing databases/knotinfo_db.py" -sage -t databases/knotinfo_db.py || sdh_die "Error testing KnotInfo databases" +sage -t --optional="sage,database_knotinfo" databases/knotinfo_db.py || sdh_die "Error testing KnotInfo databases" echo "Testing knots/knotinfo.py" -sage -t knots/knotinfo.py || sdh_die "Error testing KnotInfo funcionality" +sage -t --optional="sage,database_knotinfo" knots/knotinfo.py || sdh_die "Error testing KnotInfo funcionality" diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py index dfadc2b8e51..bc33c3e67d7 100644 --- a/src/sage/databases/knotinfo_db.py +++ b/src/sage/databases/knotinfo_db.py @@ -35,24 +35,6 @@ from sage.env import SAGE_SHARE, SAGE_ROOT -def is_knotinfo_available(): - r""" - Return wether the KnotInfo databases are installed or not. - - EXAMPLES:: - - sage: from sage.databases.knotinfo_db import is_knotinfo_available - sage: is_knotinfo_available() - True - """ - ki_db = KnotInfoDataBase() - try: - ki_db.read_num_knots() - return True - except FileNotFoundError: - return False - - class KnotInfoColumnTypes(Enum): r""" Enum class to specify if a column from the table of knots and links provided by http://www.indiana.edu/~knotinfo @@ -82,7 +64,7 @@ class KnotInfoColumns(Enum): sage: ki_db = KnotInfoDataBase() sage: KnotInfoColumns('Columns', ki_db.read_column_dict()) - sage: [col.column_name() for col in _ if col.column_type() == KnotInfoColumnTypes.OnlyLinks] + sage: [col.column_name() for col in _ if col.column_type() == KnotInfoColumnTypes.OnlyLinks] # optional - database_knotinfo ['Name - Unoriented', 'Orientation', 'Unoriented Rank', @@ -310,6 +292,8 @@ def __init__(self): self._knot_list = None self._link_list = None + self._available = None + self._num_knots = None def _create_csv_file(self, filename, path_for_src): @@ -350,6 +334,28 @@ def _create_csv_file(self, filename, path_for_src): except: pass + def is_available(self): + r""" + Return wether the KnotInfo databases are installed or not. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.is_available() # optional - database_knotinfo + True + """ + if not self._available: + try: + lib_path = self._import_path + filename = self.filename.knots.sobj_num_knots() + self._num_knots = load('%s/%s' %(lib_path, filename)) + self._available = True + except FileNotFoundError: + self._available = False + self._num_knots = len([v for v in row_demo_sample.values() if v[1]==1]) + return self._available + def create_spkg_tarball(self, path_for_src=None): r""" @@ -565,7 +571,7 @@ def columns(self): sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() sage: cols = ki_db.columns() - sage: [col.column_name() for col in cols if col.name.startswith('pd')] + sage: [col.column_name() for col in cols if col.name.startswith('pd')] # optional - database_knotinfo ['PD Notation', 'PD Notation (vector)', 'PD Notation (KnotTheory)'] """ column_dict = self.read_column_dict() @@ -588,9 +594,11 @@ def read_column_dict(self): sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: len(ki_db.read_column_dict()) + sage: len(ki_db.read_column_dict()) # optional - database_knotinfo 120 """ + if not self.is_available(): + return column_demo_sample lib_path = self._import_path filename = self.filename.knots.sobj_column() return load('%s/%s' %(lib_path, filename)) @@ -614,9 +622,11 @@ def read_row_dict(self): sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: len(ki_db.read_row_dict()) + sage: len(ki_db.read_row_dict()) # optional - database_knotinfo 7166 """ + if not self.is_available(): + return row_demo_sample lib_path = self._import_path filename = self.filename.knots.sobj_row() return load('%s/%s' %(lib_path, filename)) @@ -624,7 +634,6 @@ def read_row_dict(self): # ------------------------------------------------------------------------------------------------------------- # read the dictionary for the row names that is the knot and link names from sobj-file # ------------------------------------------------------------------------------------------------------------- - @cached_method def read_num_knots(self): r""" Read the number of knots contained in the database (without @@ -638,12 +647,12 @@ def read_num_knots(self): sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: ki_db.read_num_knots() + sage: ki_db.read_num_knots() # optional - database_knotinfo 2978 """ - lib_path = self._import_path - filename = self.filename.knots.sobj_num_knots() - return load('%s/%s' %(lib_path, filename)) + if not self._num_knots: + self.is_available() + return self._num_knots # ------------------------------------------------------------------------------------------------------------- @@ -671,6 +680,9 @@ def read(self, column): if not isinstance(column, KnotInfoColumns): raise TypeError('column must be an instance of enum %s' (KnotInfoColumns)) + if not self.is_available(): + return data_demo_sample[column] + lib_path = self._import_path if column.column_type() == KnotInfoColumnTypes.OnlyLinks: filename = self.filename.links.sobj_data(column) @@ -683,3 +695,207 @@ def read(self, column): return res + + +column_demo_sample = { + 'name': ['Name', KnotInfoColumnTypes.KnotsAndLinks], + 'dt_notation': ['DT Notation', KnotInfoColumnTypes.OnlyKnots], + 'gauss_notation': ['Gauss Notation', KnotInfoColumnTypes.KnotsAndLinks], + 'pd_notation': ['PD Notation', KnotInfoColumnTypes.OnlyKnots], + 'pd_notation_vector': ['PD Notation (vector)', KnotInfoColumnTypes.OnlyLinks], + 'crossing_number': ['Crossing Number', KnotInfoColumnTypes.KnotsAndLinks], + 'braid_index': ['Braid Index', KnotInfoColumnTypes.OnlyKnots], + 'braid_length': ['Braid Length', KnotInfoColumnTypes.OnlyKnots], + 'braid_notation': ['Braid Notation', KnotInfoColumnTypes.KnotsAndLinks], + 'alternating': ['Alternating', KnotInfoColumnTypes.KnotsAndLinks], + 'alexander_polynomial': ['Alexander', KnotInfoColumnTypes.OnlyKnots], + 'jones_polynomial': ['Jones', KnotInfoColumnTypes.KnotsAndLinks], + 'conway_polynomial': ['Conway', KnotInfoColumnTypes.KnotsAndLinks], + 'homfly_polynomial': ['HOMFLY', KnotInfoColumnTypes.OnlyKnots], + 'kauffman_polynomial': ['Kauffman', KnotInfoColumnTypes.KnotsAndLinks], + 'symmetry_type': ['Symmetry Type', KnotInfoColumnTypes.OnlyKnots], + 'width': ['Width', KnotInfoColumnTypes.OnlyKnots], + 'homflypt_polynomial': ['HOMFLYPT Polynomial', KnotInfoColumnTypes.OnlyLinks], + 'arc_notation': ['Arc Notation', KnotInfoColumnTypes.OnlyLinks], + 'dt_code': ['DT code', KnotInfoColumnTypes.OnlyLinks] +} + + +row_demo_sample = { + 'K0_1': [0, 1], + 'K3_1': [1, 1], + 'K4_1': [2, 1], + 'K5_1': [3, 1], + 'K5_2': [4, 1], + 'K6_1': [5, 1], + 'K6_2': [6, 1], + 'K6_3': [7, 1], + 'K7_1': [8, 1], + 'K7_2': [9, 1], + 'L2a1_0': [10, 2], + 'L2a1_1': [11, 2], + 'L4a1_0': [12, 2], + 'L4a1_1': [13, 2], + 'L5a1_0': [14, 2], + 'L5a1_1': [15, 2], + 'L6a1_0': [16, 2], + 'L6a1_1': [17, 2], + 'L6a2_0': [18, 2], + 'L6a2_1': [19, 2] +} + +db = KnotInfoDataBase() +dc = db.columns() + +data_demo_sample = { + dc.crossing_number: ['0', '3', '4', '5', '5', '6', '6', '6', '7', '7', '2', '2', '4', '4', '5', '5', '6', '6', '6', '6', '6'], + dc.braid_notation: [ + '', + '{1,1,1}', + '{1,-2,1,-2}', + '{1,1,1,1,1}', + '{1,1,1,2,-1,2}', + '{1,1,2,-1,-3,2,-3}', + '{1,1,1,-2,1,-2}', + '{1,1,-2,1,-2,-2}', + '{1,1,1,1,1,1,1}', + '{1,1,1,2,-1,2,3,-2,3}', + '{2, {-1, -1}}', + '{2, {1, 1}}', + '{4, {1, -2, 3, -2, -1, -2, -3, -2}}', + '{2, {1, 1, 1, 1}}', + '{3, {-1, 2, -1, 2, -1}}', + '{3, {-1, 2, -1, 2, -1}}', + '{4, {1, -2, 3, -2, 1, -2, -3, -2}}', + '{4, {1, 2, 3, 2, 2, -1, 2, 2, -3, 2}}', + '{4, {1, -2, -2, -2, 3, -2, -1, -2, -3, -2}}', + '{4, {1, 2, -3, 2, -1, 2, 3, 2, 2, 2}}', + '{2, {-1, -1, -1, -1, -1, -1}}' + ], + dc.braid_index: ['1', '2', '3', '2', '3', '4', '3', '3', '2', '4'], + dc.braid_length: ['', '3', '4', '5', '6', '7', '6', '6', '7', '9'], + dc.pd_notation: [ + '', + '[[1,5,2,4],[3,1,4,6],[5,3,6,2]]', + '[[4,2,5,1],[8,6,1,5],[6,3,7,4],[2,7,3,8]]', + '[[2,8,3,7],[4,10,5,9],[6,2,7,1],[8,4,9,3],[10,6,1,5]]', + '[[1,5,2,4],[3,9,4,8],[5,1,6,10],[7,3,8,2],[9,7,10,6]]', + '[[1,7,2,6],[3,10,4,11],[5,3,6,2],[7,1,8,12],[9,4,10,5],[11,9,12,8]]', + '[[1,8,2,9],[3,11,4,10],[5,1,6,12],[7,2,8,3],[9,7,10,6],[11,5,12,4]]', + '[[4,2,5,1],[8,4,9,3],[12,9,1,10],[10,5,11,6],[6,11,7,12],[2,8,3,7]]', + '[[1,9,2,8],[3,11,4,10],[5,13,6,12],[7,1,8,14],[9,3,10,2],[11,5,12,4],[13,7,14,6]]', + '[[2,10,3,9],[4,14,5,13],[6,12,7,11],[8,2,9,1],[10,8,11,7],[12,6,13,5],[14,4,1,3]]', + ], + dc.pd_notation_vector: [ + '{{4, 1, 3, 2}, {2, 3, 1, 4}}', + '{{4, 2, 3, 1}, {2, 4, 1, 3}}', + '{{6, 1, 7, 2}, {8, 3, 5, 4}, {2, 5, 3, 6}, {4, 7, 1, 8}}', + '{{6, 2, 7, 1}, {8, 4, 5, 3}, {2, 8, 3, 7}, {4, 6, 1, 5}}', + '{{6, 1, 7, 2}, {10, 7, 5, 8}, {4, 5, 1, 6}, {2, 10, 3, 9}, {8, 4, 9, 3}}', + '{{8, 2, 9, 1}, {10, 7, 5, 8}, {4, 10, 1, 9}, {2, 5, 3, 6}, {6, 3, 7, 4}}', + '{{6, 1, 7, 2}, {10, 3, 11, 4}, {12, 8, 5, 7}, {8, 12, 9, 11}, {2, 5, 3, 6}, {4, 9, 1, 10}}', + '{{10, 2, 11, 1}, {6, 4, 7, 3}, {12, 10, 5, 9}, {8, 6, 9, 5}, {2, 12, 3, 11}, {4, 8, 1, 7}}', + '{{8, 1, 9, 2}, {12, 5, 7, 6}, {10, 3, 11, 4}, {4, 11, 5, 12}, {2, 7, 3, 8}, {6, 9, 1, 10}}', + '{{10, 2, 11, 1}, {12, 6, 7, 5}, {8, 4, 9, 3}, {4, 8, 5, 7}, {2, 12, 3, 11}, {6, 10, 1, 9}}', + '{{8, 1, 9, 2}, {2, 9, 3, 10}, {10, 3, 11, 4}, {12, 5, 7, 6}, {6, 7, 1, 8}, {4, 11, 5, 12}}' + ], + dc.dt_notation: [ + '', + '[4, 6, 2]', + '[4, 6, 8, 2]', + '[6, 8, 10, 2, 4]', + '[4, 8, 10, 2, 6]', + '[4, 8, 12, 10, 2, 6]', + '[4, 8, 10, 12, 2, 6]', + '[4, 8, 10, 2, 12, 6]', + '[8, 10, 12, 14, 2, 4, 6]', + '[4, 10, 14, 12, 2, 8, 6]' + ], + dc.dt_code: [ + '[{4}, {2}]', + '[{4}, {2}]', + '[{6, 8}, {2, 4}]', + '[{6, 8}, {4, 2}]', + '[{6, 8}, {4, 10, 2}]', + '[{8, 6}, {2, 10, 4}]', + '[{6, 10}, {2, 12, 4, 8}]', + '[{10, 6}, {8, 4, 12, 2}]', + '[{8, 10, 12}, {2, 6, 4}]', + '[{10, 8, 12}, {4, 6, 2}]', + '[{8, 10, 12}, {6, 2, 4}]' + ], + dc.gauss_notation: [ + '', + '{1, -2, 3, -1, 2, -3}', + '{-1, 2, -3, 1, -4, 3, -2, 4}', + '{-1, 2, -3, 4, -5, 1, -2, 3, -4, 5}', + '{1, -2, 3, -1, 4, -5, 2, -3, 5, -4}', + '{1, -2, 3, -4, 2, -1, 5, -6, 4, -3, 6, -5}', + '{1, -2, 3, -4, 5, -6, 2, -1, 6, -3, 4, -5}', + '{-1, 2, -3, 1, -4, 5, -2, 3, -6, 4, -5, 6}', + '{1, -2, 3, -4, 5, -6, 7, -1, 2, -3, 4, -5, 6, -7}', + '{-1, 2, -3, 4, -5, 6, -7, 1, -2, 7, -6, 5, -4, 3}', + '{{1, -2}, {2, -1}}', + '{{1, -2}, {2, -1}}', + '{{1, -3, 2, -4}, {3, -1, 4, -2}}', + '{{1, -3, 2, -4}, {4, -1, 3, -2}}', + '{{1, -4, 5, -3}, {3, -1, 2, -5, 4, -2}}', + '{{1, -4, 5, -3}, {4, -5, 2, -1, 3, -2}}', + '{{1, -5, 2, -6}, {5, -1, 3, -4, 6, -2, 4, -3}}', + '{{1, -5, 2, -6}, {4, -2, 6, -4, 3, -1, 5, -3}}', + '{{1, -5, 3, -4, 2, -6}, {5, -1, 6, -3, 4, -2}}', + '{{1, -5, 3, -4, 2, -6}, {4, -3, 6, -1, 5, -2}}', + '{{1, -2, 3, -6, 4, -5}, {5, -1, 2, -3, 6, -4}}' + ], + dc.arc_notation: [ + '{{4, 2}, {3, 1}, {4, 2}, {1, 3}}', + '{{2, 4}, {3, 1}, {2, 4}, {3, 1}}', + '{{6, 4}, {3, 5}, {4, 2}, {1, 3}, {2, 6}, {5, 1}}', + '{{3, 6}, {2, 5}, {6, 4}, {1, 3}, {5, 2}, {4, 1}}', + '{{6, 2}, {1, 4}, {3, 5}, {4, 7}, {2, 6}, {7, 3}, {5, 1}}', + '{{3, 5}, {6, 4}, {5, 2}, {7, 3}, {1, 6}, {2, 7}, {4, 1}}', + '{{8, 4}, {3, 5}, {4, 2}, {6, 3}, {5, 7}, {1, 6}, {2, 8}, {7, 1}}', + '{{2, 8}, {1, 7}, {8, 4}, {5, 3}, {4, 2}, {3, 6}, {7, 5}, {6, 1}}', + '{{8, 3}, {2, 7}, {3, 1}, {4, 8}, {5, 2}, {6, 4}, {7, 5}, {1, 6}}', + '{{3, 8}, {2, 7}, {8, 4}, {1, 3}, {5, 2}, {4, 6}, {7, 5}, {6, 1}}', + '{{8, 2}, {1, 3}, {2, 4}, {3, 5}, {4, 6}, {5, 7}, {6, 8}, {7, 1}}' + ], + dc.alternating: ['Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y'], + dc.symmetry_type: [ + '', + 'reversible', + 'fully amphicheiral', + 'reversible', + 'reversible', + 'reversible', + 'reversible', + 'fully amphicheiral', + 'reversible', + 'reversible' + ], + dc.homfly_polynomial: [ + '', + '(2*v^2-v^4)+ (v^2)*z^2', + '(v^(-2)-1+ v^2)+ (-1)*z^2', + '(3*v^4-2*v^6)+ (4*v^4-v^6)*z^2+ (v^4)*z^4', + '(v^2+ v^4-v^6)+ (v^2+ v^4)*z^2', + '(v^(-2)-v^2+ v^4)+ (-1-v^2)*z^2', + '(2-2*v^2+ v^4)+ (1-3*v^2+ v^4)*z^2+ (-v^2)*z^4', + '(-v^(-2)+ 3-v^2)+ (-v^(-2)+ 3-v^2)*z^2+ (1)*z^4', + '(4*v^6-3*v^8)+ (10*v^6-4*v^8)*z^2+ (6*v^6-v^8)*z^4+ (v^6)*z^6', + '(v^2+ v^6-v^8)+ (v^2+ v^4+ v^6)*z^2' + ], + dc.homflypt_polynomial: [ + '1/(v^3*z)-1/(v*z)-z/v', + 'v/z-v^3/z + v*z', + '1/(v^5*z)-1/(v^3*z)-z/v^3-z/v', + 'v^3/z-v^5/z + 3*v^3*z-v^5*z + v^3*z^3', + '1/(v*z)-v/z-z/v^3 + (2*z)/v-v*z + z^3/v', + '1/(v*z)-v/z-z/v^3 + (2*z)/v-v*z + z^3/v', + '1/(v^5*z)-1/(v^3*z)-(2*z)/v^3 + z/v-v*z + z^3/v', + 'v^3/z-v^5/z + 2*v^3*z + v^5*z-v^7*z + v^3*z^3 + v^5*z^3', + '1/(v^7*z)-1/(v^5*z) + z/v^7-(2*z)/v^5-(2*z)/v^3-z^3/v^5-z^3/v^3', + 'v^5/z-v^7/z + 2*v^3*z + 2*v^5*z-v^7*z + v^3*z^3 + v^5*z^3', + '1/(v^7*z)-1/(v^5*z) + (3*z)/v^7-(6*z)/v^5 + z^3/v^7-(5*z^3)/v^5-z^5/v^5' + ] +} diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index d8d7ee29ad3..67785bea087 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -53,10 +53,22 @@ from sage.misc.sage_eval import sage_eval from sage.groups.braid import BraidGroup from sage.knots.knot import Knots -from sage.databases.knotinfo_db import KnotInfoColumnTypes, KnotInfoColumns, KnotInfoDataBase +from sage.databases.knotinfo_db import KnotInfoColumnTypes, KnotInfoColumns, db -db = KnotInfoDataBase() + + +def is_knotinfo_available(): + r""" + Return wether the KnotInfo databases are installed or not. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import is_knotinfo_available + sage: is_knotinfo_available() # optional - database_knotinfo + True + """ + return db.is_available() def eval_knotinfo(string, locals={}, to_tuple=True): @@ -107,7 +119,7 @@ def items(self): sage: from sage.knots.knotinfo import KnotInfo sage: L = KnotInfo.L4a1_0 sage: it = L.items - sage: [i.name for i in it if i.name.endswith('notation')] + sage: [i.name for i in it if i.name.endswith('notation')] # optional - database_knotinfo ['dt_notation', 'conway_notation', 'two_bridge_notation', @@ -165,7 +177,7 @@ def _offset_knots(self): sage: from sage.knots.knotinfo import KnotInfo sage: L = KnotInfo.L4a1_0 - sage: L._offset_knots() + sage: L._offset_knots() # optional - database_knotinfo 2978 """ return db.read_num_knots() @@ -481,9 +493,9 @@ def is_knot(self): EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfo - sage: KnotInfo.L7a1_0.is_knot() + sage: KnotInfo.L7a1_0.is_knot() # optional - database_knotinfo False - sage: KnotInfo.K6_3.is_knot() + sage: KnotInfo.K6_3.is_knot() # optional - database_knotinfo True """ return self.num_components() == 1 @@ -561,10 +573,10 @@ def is_amphicheiral(self, positive=False): EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfo - sage: K = KnotInfo.K12a_427 - sage: K.is_amphicheiral() + sage: K = KnotInfo.K12a_427 # optional - database_knotinfo + sage: K.is_amphicheiral() # optional - database_knotinfo False - sage: K.is_amphicheiral(positive=True) + sage: K.is_amphicheiral(positive=True) # optional - database_knotinfo True """ if positive: From a20c2f01a7098204736228f04094fe243c762742 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Mon, 12 Oct 2020 06:26:08 +0200 Subject: [PATCH 058/232] build/pkgs/sage_sws2rst/src/test/Adding_Pictures_and_screenshots.sws --- .../test/Adding_Pictures_and_screenshots.sws | Bin 281797 -> 281795 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/build/pkgs/sage_sws2rst/src/test/Adding_Pictures_and_screenshots.sws b/build/pkgs/sage_sws2rst/src/test/Adding_Pictures_and_screenshots.sws index 3dff6b3aada1900a931ac92a13222adcf874d512..d742ce4437f14561448399444c09589b9cd026fd 100644 GIT binary patch delta 31 lcmX>)Q}FOi!G+Q}F0a!GaO Date: Tue, 20 Oct 2020 16:39:15 -0400 Subject: [PATCH 059/232] use numerical comparisons for logs, fix doctests --- .../rings/padics/padic_generic_element.pyx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index 158022a07f1..aff73052ce7 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -4040,7 +4040,7 @@ cdef class pAdicGenericElement(LocalGenericElement): Only polylogarithms for `n` at least two are defined by this function :: - sage: Qp(11)(2).polylog(-1) + sage: Qp(11)(2)._polylog_res_1(1) Traceback (most recent call last): ... ValueError: Polylogarithm only implemented for n at least 2. @@ -4062,16 +4062,17 @@ cdef class pAdicGenericElement(LocalGenericElement): z = K(self) hsl = max(prec / ((z - 1).valuation()) + 1, prec*(p == 2), 2) - N = floor(prec - n*(hsl - 1).log(p)) + N = floor(prec - n*(hsl - 1).log(p).n()) verbose(hsl, level=3) def bound(m): - return prec - m + Integer(1-2**(m-1)).valuation(p) - m*(hsl - 1).log(p) + return prec - m + Integer(1-2**(m-1)).valuation(p) - m*(hsl - 1).log(p).n() - gsl = max([_findprec(1/(p-1), 1, _polylog_c(m,p) + bound(m), p) for m in range(2,n+1)] + [2]) + gsl = max(_findprec(1/(p-1), 1, _polylog_c(m,p) + bound(m), p) for m in range(2,n+1)) + gsl = max(gsl, 2) verbose(gsl, level=3) - g = _compute_g(p, n, max([bound(m) + m*floor((gsl-1).log(p)) for m in range(2, n+1)]), gsl) + g = _compute_g(p, n, max(bound(m) + m*floor((gsl-1).log(p).n()) for m in range(2, n+1)), gsl) verbose(g, level=3) S = PowerSeriesRing(K, default_prec = ceil(hsl), names='t') t = S.gen() @@ -4224,7 +4225,7 @@ cdef class pAdicGenericElement(LocalGenericElement): if z.precision_absolute() == PlusInfinity(): return K(0) verbose("residue 0, using series. %d %s"%(n,str(self)), level=2) - M = ceil((prec/z.valuation()).log(p)) + M = ceil((prec/z.valuation()).log(p).n()) N = prec - n*M ret = K(0) for m in range(M + 1): @@ -4242,10 +4243,11 @@ cdef class pAdicGenericElement(LocalGenericElement): # Set up precision bounds tsl = prec / (z - zeta).valuation() + 1 - N = floor(prec - n*(tsl - 1).log(p)) - gsl = max([_findprec(1/(p-1), 1, prec - m + _polylog_c(m,p) - m*(tsl - 1).log(p), p) for m in range(1,n+1)] + [2]) + N = floor(prec - n*(tsl - 1).log(p).n()) + gsl = max(_findprec(1/(p-1), 1, prec - m + _polylog_c(m,p) - m*(tsl - 1).log(p).n(), p) for m in range(1,n+1)) + gsl = max(gsl,2) - gtr = _compute_g(p, n, prec + n*(gsl - 1).log(p), gsl) + gtr = _compute_g(p, n, prec + n*(gsl - 1).log(p).n(), gsl) K = Qp(p, prec) @@ -4373,9 +4375,9 @@ def _polylog_c(n, p): EXAMPLES:: sage: sage.rings.padics.padic_generic_element._polylog_c(1, 2) - log(4/log(2))/log(2) + 2 + 4.52876637294490 """ - return p/(p-1) - (n-1)/p.log() + (n-1)*(n*(p-1)/p.log()).log(p) + (2*p*(p-1)*n/p.log()).log(p) + return p/(p-1) - (n-1)/p.log().n() + (n-1)*(n*(p-1)/p.log().n()).log(p).n() + (2*p*(p-1)*n/p.log().n()).log(p).n() def _findprec(c_1, c_2, c_3, p): """ @@ -4403,7 +4405,7 @@ def _findprec(c_1, c_2, c_3, p): from sage.functions.other import ceil k = Integer(max(ceil(c_2/c_1), 2)) while True: - if c_1*k - c_2*k.log(p) > c_3: + if c_1*k - c_2*k.log(p).n() > c_3: return k k += 1 From 092cd4c74776a3e1027229788b7ed0f4821bd008 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Sun, 1 Nov 2020 08:36:18 +0100 Subject: [PATCH 060/232] 30352: improvements and documentation --- build/pkgs/database_knotinfo/SPKG.rst | 35 + .../checksums.ini | 0 .../dependencies | 0 .../package-version.txt | 0 .../spkg-check.in | 3 + .../spkg-install.in | 0 .../spkg-install.py | 0 .../pkgs/{knotinfo => database_knotinfo}/type | 0 build/pkgs/knotinfo/SPKG.rst | 33 - src/doc/en/reference/databases/index.rst | 1 + src/doc/en/reference/knots/index.rst | 1 + src/sage/databases/knotinfo_db.py | 285 +++- src/sage/knots/knotinfo.py | 1492 +++++++++++++++-- src/sage/knots/link.py | 251 ++- 14 files changed, 1870 insertions(+), 231 deletions(-) create mode 100644 build/pkgs/database_knotinfo/SPKG.rst rename build/pkgs/{knotinfo => database_knotinfo}/checksums.ini (100%) rename build/pkgs/{knotinfo => database_knotinfo}/dependencies (100%) rename build/pkgs/{knotinfo => database_knotinfo}/package-version.txt (100%) rename build/pkgs/{knotinfo => database_knotinfo}/spkg-check.in (70%) rename build/pkgs/{knotinfo => database_knotinfo}/spkg-install.in (100%) rename build/pkgs/{knotinfo => database_knotinfo}/spkg-install.py (100%) rename build/pkgs/{knotinfo => database_knotinfo}/type (100%) delete mode 100644 build/pkgs/knotinfo/SPKG.rst diff --git a/build/pkgs/database_knotinfo/SPKG.rst b/build/pkgs/database_knotinfo/SPKG.rst new file mode 100644 index 00000000000..460416cc621 --- /dev/null +++ b/build/pkgs/database_knotinfo/SPKG.rst @@ -0,0 +1,35 @@ +database_knotinfo +================= + +Description +----------- + +Database for named knots and links provided at + +https://knotinfo.math.indiana.edu/ + +and + +https://linkinfo.sitehost.iu.edu' + +Dependencies +------------ + +- Sage library + +Changelog +--------- + +- 20200713 (Sebastian Oehms, 13 Juli 2020, :trac:`30352`, initial version) + + The tarball has been created from the both download files at the + given date: + + ``knotinfo_data_complete.xls`` + ``linkinfo_data_complete.xlsx`` + + exporting them to CSV via LibreOffice. + + The second file has been changed manually deleting one character: + a trailing "}" occuring in the homfly_polynomial column of the last + link ``L11n459{1,1,1}``. diff --git a/build/pkgs/knotinfo/checksums.ini b/build/pkgs/database_knotinfo/checksums.ini similarity index 100% rename from build/pkgs/knotinfo/checksums.ini rename to build/pkgs/database_knotinfo/checksums.ini diff --git a/build/pkgs/knotinfo/dependencies b/build/pkgs/database_knotinfo/dependencies similarity index 100% rename from build/pkgs/knotinfo/dependencies rename to build/pkgs/database_knotinfo/dependencies diff --git a/build/pkgs/knotinfo/package-version.txt b/build/pkgs/database_knotinfo/package-version.txt similarity index 100% rename from build/pkgs/knotinfo/package-version.txt rename to build/pkgs/database_knotinfo/package-version.txt diff --git a/build/pkgs/knotinfo/spkg-check.in b/build/pkgs/database_knotinfo/spkg-check.in similarity index 70% rename from build/pkgs/knotinfo/spkg-check.in rename to build/pkgs/database_knotinfo/spkg-check.in index b655a1ccf18..94d3e947e00 100644 --- a/build/pkgs/knotinfo/spkg-check.in +++ b/build/pkgs/database_knotinfo/spkg-check.in @@ -5,3 +5,6 @@ sage -t --optional="sage,database_knotinfo" databases/knotinfo_db.py || sdh_die echo "Testing knots/knotinfo.py" sage -t --optional="sage,database_knotinfo" knots/knotinfo.py || sdh_die "Error testing KnotInfo funcionality" + +echo "Testing knots/link.py" +sage -t --optional="sage,database_knotinfo" knots/link.py || sdh_die "Error testing KnotInfo funcionality" diff --git a/build/pkgs/knotinfo/spkg-install.in b/build/pkgs/database_knotinfo/spkg-install.in similarity index 100% rename from build/pkgs/knotinfo/spkg-install.in rename to build/pkgs/database_knotinfo/spkg-install.in diff --git a/build/pkgs/knotinfo/spkg-install.py b/build/pkgs/database_knotinfo/spkg-install.py similarity index 100% rename from build/pkgs/knotinfo/spkg-install.py rename to build/pkgs/database_knotinfo/spkg-install.py diff --git a/build/pkgs/knotinfo/type b/build/pkgs/database_knotinfo/type similarity index 100% rename from build/pkgs/knotinfo/type rename to build/pkgs/database_knotinfo/type diff --git a/build/pkgs/knotinfo/SPKG.rst b/build/pkgs/knotinfo/SPKG.rst deleted file mode 100644 index 7ab6d03281d..00000000000 --- a/build/pkgs/knotinfo/SPKG.rst +++ /dev/null @@ -1,33 +0,0 @@ -= KnotInfo Database = - -== Description == - -Database for named knots and links provided at - -https://knotinfo.math.indiana.edu/ - -and - -https://linkinfo.sitehost.iu.edu' - -== Dependencies == - - * Sage library - -== Changelog == - -=== knotinfo-20200713.tar.bz2 (Sebastian Oehms, 13 Juli 2020) === - - * #30352: Initial version - - The tarball has been created from the both download files at the - given date: - - `knotinfo_data_complete.xls` - `linkinfo_data_complete.xlsx` - - exporting them to CSV via LibreOffice. - - The second file has been changed manually deleting one character: - a trailing "}" occuring in the homfly_polynomial column of the last - link `L11n459{1,1,1}`. diff --git a/src/doc/en/reference/databases/index.rst b/src/doc/en/reference/databases/index.rst index 5c198d9dcdf..34ae3ba3f1f 100644 --- a/src/doc/en/reference/databases/index.rst +++ b/src/doc/en/reference/databases/index.rst @@ -62,5 +62,6 @@ database engine. sage/databases/cunningham_tables sage/databases/db_class_polynomials sage/databases/db_modular_polynomials + sage/databases/knotinfo_db .. include:: ../footer.txt diff --git a/src/doc/en/reference/knots/index.rst b/src/doc/en/reference/knots/index.rst index ecaa3832288..8fc794b4705 100644 --- a/src/doc/en/reference/knots/index.rst +++ b/src/doc/en/reference/knots/index.rst @@ -6,5 +6,6 @@ Knot Theory sage/knots/knot sage/knots/link + sage/knots/knotinfo .. include:: ../footer.txt diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py index bc33c3e67d7..25b472cfff6 100644 --- a/src/sage/databases/knotinfo_db.py +++ b/src/sage/databases/knotinfo_db.py @@ -3,8 +3,8 @@ KontInfo Database This module contains the class :class:`KnotInfoDataBase` and auxilary classes for it -which serves as an interface to the lists of named knots and links provided at -https://knotinfo.math.indiana.edu/ +which serves as an interface to the lists of named knots and links provided at the web-pages +`KnotInfo `__ and `LinkInfo `__. AUTHORS: @@ -37,7 +37,8 @@ class KnotInfoColumnTypes(Enum): r""" - Enum class to specify if a column from the table of knots and links provided by http://www.indiana.edu/~knotinfo + Enum class to specify if a column from the table of knots and links provided at the web-pages + `KnotInfo `__ and `LinkInfo `__. is used for knots only, links only or both. EXAMPLES:: @@ -56,7 +57,8 @@ class KnotInfoColumnTypes(Enum): class KnotInfoColumns(Enum): r""" - Enum class to select a column from the table of knots and links provided by http://www.indiana.edu/~knotinfo + Enum class to select a column from the table of knots and links provided at the web-pages + `KnotInfo `__ and `LinkInfo `__. EXAMPLES:: @@ -64,7 +66,7 @@ class KnotInfoColumns(Enum): sage: ki_db = KnotInfoDataBase() sage: KnotInfoColumns('Columns', ki_db.read_column_dict()) - sage: [col.column_name() for col in _ if col.column_type() == KnotInfoColumnTypes.OnlyLinks] # optional - database_knotinfo + sage: [col.column_name() for col in _ if col.column_type() == col.types.OnlyLinks] # optional - database_knotinfo ['Name - Unoriented', 'Orientation', 'Unoriented Rank', @@ -83,6 +85,21 @@ class KnotInfoColumns(Enum): 'Unlinking Number', 'Weak Splitting Number'] """ + @property + def types(self): + r""" + Return :class:`KnotInfoColumnTypes` to be used for checks. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns + sage: ki_db = KnotInfoDataBase() + sage: KIcols = KnotInfoColumns('Columns', ki_db.read_column_dict()) + sage: KIcols.dt_code.column_type() == KIcols.dt_code.types.OnlyLinks + True + """ + return KnotInfoColumnTypes + def column_name(self): r""" Return the name of ``self`` displayed on the KnotInfo web-page. @@ -116,6 +133,29 @@ def column_type(self): """ return self.value[1] + def description_webpage(self, new=0, autoraise=True): + r""" + Launch the description page of ``self`` in the standard web browser. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns + sage: ki_db = KnotInfoDataBase() + sage: KIcols = KnotInfoColumns('Columns', ki_db.read_column_dict()) + sage: KIcols.pd_notation.description_webpage() # not tested + True + sage: KIcols.homflypt_polynomial.description_webpage() # not tested + True + """ + import webbrowser + if self.column_type() == self.types.OnlyLinks: + url = KnotInfoFilename.links.description_url(self) + else: + url = KnotInfoFilename.knots.description_url(self) + return webbrowser.open(url, new=new, autoraise=autoraise) + + + class KnotInfoFilename(Enum): r""" @@ -133,7 +173,7 @@ class KnotInfoFilename(Enum): """ def url(self): - """ + r""" Return the URL to download the data from the web-page. Examples:: @@ -149,7 +189,7 @@ def url(self): return self.value[0] def excel(self): - """ + r""" Return the Excel-file name to download the data from the web-page. Examples:: @@ -165,7 +205,7 @@ def excel(self): return '%s.xlsx' %(self.value[1]) def csv(self): - """ + r""" Return the file name under which the data from the web-page are stored as csv file. @@ -179,7 +219,7 @@ def csv(self): return '%s.csv' %(self.value[1]) def sobj_num_knots(self): - """ + r""" Return the file name under which the number of knots is stored as in python int in a sobj-file. @@ -193,7 +233,7 @@ def sobj_num_knots(self): return 'num_knots.sobj' def sobj_row(self): - """ + r""" Return the file name under which the row-data of the csv-File is stored as python dictionary in a sobj-file. @@ -207,7 +247,7 @@ def sobj_row(self): return 'row_dict.sobj' def sobj_column(self): - """ + r""" Return the file name under which the column-data of the csv-File is stored as python dictionary in a sobj-file. @@ -222,7 +262,7 @@ def sobj_column(self): def sobj_data(self, column): - """ + r""" Return the file name under which the data of the given column is stored as python list in a sobj-file. @@ -233,13 +273,45 @@ def sobj_data(self, column): sage: ki_db.filename.knots.sobj_data(ki_db.columns().braid_notation) 'knotinfo_braid_notation' """ - if column.column_type() == KnotInfoColumnTypes.OnlyLinks: + if column.column_type() == column.types.OnlyLinks: return 'linkinfo_%s' %(column.name) else: return 'knotinfo_%s' %(column.name) + def description_url(self, column): + r""" + Return the url of the description page of the given column. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.description_url(ki_db.columns().braid_notation) + 'https://knotinfo.math.indiana.edu/descriptions/braid_notation.html' + """ + return '%sdescriptions/%s.html' %(self.url(), column.name) + + def diagram_url(self, fname, single=False): + r""" + Return the url of the diagram page of the given link. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.diagram_url('3_1-50.png') + 'https://knotinfo.math.indiana.edu/diagram_display.php?3_1-50.png' + sage: ki_db.filename.knots.diagram_url('3_1', single=True) + 'https://knotinfo.math.indiana.edu/diagrams/3_1' + """ + if single: + return '%sdiagrams/%s' %(self.url(), fname) + else: + return '%sdiagram_display.php?%s' %(self.url(), fname) + + knots = ['https://knotinfo.math.indiana.edu/', 'knotinfo_data_complete'] - links = ['https://linkinfo.sitehost.iu.edu', 'linkinfo_data_complete'] + links = ['https://linkinfo.sitehost.iu.edu/', 'linkinfo_data_complete'] @@ -274,10 +346,10 @@ def __init__(self): sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: from sage.env import SAGE_SHARE sage: ki_db = KnotInfoDataBase() - sage: ki_db._import_path - '/home/sebastian/develop/sage/local/share/knotinfo' + sage: ki_db.filename.links + """ - self._package = 'knotinfo' + self._package = 'database_knotinfo' version_file = os.path.join(SAGE_ROOT, 'build/pkgs/%s/package-version.txt' %self._package) f = open(version_file) self._version = f.read().splitlines()[0] @@ -288,7 +360,7 @@ def __init__(self): self._names_column = 'name' self._components_column = 'components' self._knot_prefix = 'K' - self._import_path = os.path.join(SAGE_SHARE, self._package) + self._import_path = os.path.join(SAGE_SHARE, 'knotinfo') self._knot_list = None self._link_list = None @@ -296,44 +368,6 @@ def __init__(self): self._num_knots = None - def _create_csv_file(self, filename, path_for_src): - r""" - Return the data fetched from the web-page as a csv file - such that it can be parsed via pythons ``csv`` class. - - INPUT: - - - ``filename`` - instance of :class:`KnotInfoDataBase.filename` - - ``path_for_src`` - string giving the pathname where to store - the ``csv`` -files - - EXAMPLES:: - - sage: from sage.databases.knotinfo_db import KnotInfoDataBase - sage: import os - sage: pwd = os.environ['PWD'] - sage: ki_db = KnotInfoDataBase() - sage: ki_db._create_csv_file(ki_db.filename.knots, pwd) - """ - # TODO import directly from the internet page and convert to csv via pandoc - return - if not isinstance(filename, KnotInfoDataBase.filename): - raise TypeError('File name must be an instance of enum %s' (KnotInfoDataBase.filename)) - - import_file = '%s/%s' %(self._import_path, filename.csv()) - - from six.moves.urllib.request import urlopen - try: - from urllib.error import HTTPError - except ImportError: - from urllib2 import HTTPError - - try: - url = '%s/%s' %(filename.url(), filename.excel()) - url_data = urlopen(url).read().decode() - except: - pass - def is_available(self): r""" Return wether the KnotInfo databases are installed or not. @@ -359,16 +393,17 @@ def is_available(self): def create_spkg_tarball(self, path_for_src=None): r""" - Create a tarball for the sage-package ``knotinfo`` in the ``upstream`` directory. This + Create a tarball for the sage-package ``database_knotinfo`` in the ``upstream`` directory. This utility should only be used by users who know what they do in case of a switch to a new version of the data files (that is if the original files on KnotInfo web-page have changed). - In that case in invocation of ``sage -package update knotinfo `` and - ``sage -package fix-checksum knotinfo`` will be necessary. + In that case an invocation of ``sage -package update database_knotinfo `` and + ``sage -package fix-checksum database_knotinfo`` will be necessary. INPUT: -- ``path_for_src`` - string of the path under which the source are stored in a - subdirectory called ``src`` + subdirectory called ``src``. In that directory there should be the data files in + csv format (for example ``KnotInfoDataBase.filename.knots.csv()``) EXAMPLES:: @@ -379,10 +414,7 @@ def create_spkg_tarball(self, path_for_src=None): if not path_for_src: path_for_src = os.environ['PWD'] - for filename in KnotInfoDataBase.filename: - self._create_csv_file(filename, path_for_src) - - os.system('cd %s; tar -cvjSf %s/upstream/%s-%s.tar.bz2 src' %(path, SAGE_ROOT, self._package, self._version) ) + os.system('cd %s; tar -cvjSf %s/upstream/%s-%s.tar.bz2 src' %(path_for_src, SAGE_ROOT, self._package, self._version) ) def version(self): @@ -472,7 +504,7 @@ def create_col_dict_sobj(self): for col in knot_column_names: name = knot_column_names[col] - if not name: + if not name and col not in ['knot_atlas_anon', 'knotilus_page_anon']: # not of interest continue @@ -487,7 +519,7 @@ def create_col_dict_sobj(self): for col in link_column_names: name = link_column_names[col] - if not name: + if not name and col not in ['knot_atlas_anon', 'knotilus_page_anon']: # not of interest continue @@ -533,14 +565,14 @@ def create_data_sobj(self): for col in self.columns(): val_list = [] - if col.column_type() != KnotInfoColumnTypes.OnlyLinks: + if col.column_type() != col.types.OnlyLinks: for i in range(1 , len_knots): if col.name == self._names_column: row_dict[self._knot_prefix + knot_list[i][col.name]] = [i - 1 , 1] - else: - val_list.append(knot_list[i][col.name]) - if col.column_type() != KnotInfoColumnTypes.OnlyKnots: + val_list.append(knot_list[i][col.name]) + + if col.column_type() != col.types.OnlyKnots: for i in range(1 , len_links): if col.name == self._names_column: link_name = link_list[i][col.name] @@ -551,8 +583,7 @@ def create_data_sobj(self): num_comp = int(link_list[i][self._components_column]) row_dict[link_name] = [i + len_knots - 2 , num_comp] - else: - val_list.append(link_list[i][col.name]) + val_list.append(link_list[i][col.name]) if val_list: save(val_list, '%s/%s' %(self._import_path, self.filename.knots.sobj_data(col))) @@ -595,7 +626,7 @@ def read_column_dict(self): sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() sage: len(ki_db.read_column_dict()) # optional - database_knotinfo - 120 + 122 """ if not self.is_available(): return column_demo_sample @@ -622,8 +653,8 @@ def read_row_dict(self): sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: len(ki_db.read_row_dict()) # optional - database_knotinfo - 7166 + sage: ki_db.read_row_dict()['K7_1'] + [8, 1] """ if not self.is_available(): return row_demo_sample @@ -632,7 +663,32 @@ def read_row_dict(self): return load('%s/%s' %(lib_path, filename)) # ------------------------------------------------------------------------------------------------------------- - # read the dictionary for the row names that is the knot and link names from sobj-file + # return a dictionary to obtain the original name to a row_dict key + # ------------------------------------------------------------------------------------------------------------- + @cached_method + def row_names(self): + r""" + Return a dictionary to obtain the original name to a row_dict key + + OUTPUT: + + A python dictionary containing the names of the knots and links + together with their original names from the database, + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.row_names()['K7_1'] # optional - database_knotinfo + '7_1' + """ + row_dict = self.read_row_dict() + names = self.read(self.columns().name) + return {k:names[v[0]] for k, v in row_dict.items()} + + + # ------------------------------------------------------------------------------------------------------------- + # read the number of knots contained in the database (without proper links) from the according sobj-file. # ------------------------------------------------------------------------------------------------------------- def read_num_knots(self): r""" @@ -684,7 +740,7 @@ def read(self, column): return data_demo_sample[column] lib_path = self._import_path - if column.column_type() == KnotInfoColumnTypes.OnlyLinks: + if column.column_type() == column.types.OnlyLinks: filename = self.filename.links.sobj_data(column) else: filename = self.filename.knots.sobj_data(column) @@ -699,6 +755,7 @@ def read(self, column): column_demo_sample = { 'name': ['Name', KnotInfoColumnTypes.KnotsAndLinks], + 'name_unoriented': ['Name - Unoriented', KnotInfoColumnTypes.OnlyLinks], 'dt_notation': ['DT Notation', KnotInfoColumnTypes.OnlyKnots], 'gauss_notation': ['Gauss Notation', KnotInfoColumnTypes.KnotsAndLinks], 'pd_notation': ['PD Notation', KnotInfoColumnTypes.OnlyKnots], @@ -712,10 +769,14 @@ def read(self, column): 'jones_polynomial': ['Jones', KnotInfoColumnTypes.KnotsAndLinks], 'conway_polynomial': ['Conway', KnotInfoColumnTypes.KnotsAndLinks], 'homfly_polynomial': ['HOMFLY', KnotInfoColumnTypes.OnlyKnots], + 'homflypt_polynomial': ['HOMFLYPT Polynomial', KnotInfoColumnTypes.OnlyLinks], 'kauffman_polynomial': ['Kauffman', KnotInfoColumnTypes.KnotsAndLinks], + 'determinant': ['Determinant', KnotInfoColumnTypes.KnotsAndLinks], + 'positive': ['Positive', KnotInfoColumnTypes.OnlyKnots], + 'fibered': ['Fibered', KnotInfoColumnTypes.OnlyKnots], + 'unoriented': ['Unoriented', KnotInfoColumnTypes.OnlyLinks], 'symmetry_type': ['Symmetry Type', KnotInfoColumnTypes.OnlyKnots], 'width': ['Width', KnotInfoColumnTypes.OnlyKnots], - 'homflypt_polynomial': ['HOMFLYPT Polynomial', KnotInfoColumnTypes.OnlyLinks], 'arc_notation': ['Arc Notation', KnotInfoColumnTypes.OnlyLinks], 'dt_code': ['DT code', KnotInfoColumnTypes.OnlyLinks] } @@ -748,6 +809,11 @@ def read(self, column): dc = db.columns() data_demo_sample = { + dc.name: ['0_1', '3_1', '4_1', '5_1', '5_2', '6_1', '6_2', '6_3', '7_1', '7_2', + 'L2a1{0}', 'L2a1{1}', 'L4a1{0}', 'L4a1{1}', 'L5a1{0}', 'L5a1{1}', + 'L6a1{0}', 'L6a1{1}', 'L6a2{0}', 'L6a2{1}', 'L6a3{0}' + ], + dc.name_unoriented: ['L2a1', 'L2a1', 'L4a1', 'L4a1', 'L5a1', 'L5a1', 'L6a1', 'L6a1', 'L6a2', 'L6a2', 'L6a3'], dc.crossing_number: ['0', '3', '4', '5', '5', '6', '6', '6', '7', '7', '2', '2', '4', '4', '5', '5', '6', '6', '6', '6', '6'], dc.braid_notation: [ '', @@ -774,6 +840,10 @@ def read(self, column): ], dc.braid_index: ['1', '2', '3', '2', '3', '4', '3', '3', '2', '4'], dc.braid_length: ['', '3', '4', '5', '6', '7', '6', '6', '7', '9'], + dc.determinant: ['0', '3', '5', '5', '7', '9', '11', '13', '7', '11', '2', '2', '4', '4', '8', '8', '12', '12', '10', '10', '6'], + dc.positive: ['', 'Y', 'N', 'Y', 'Y', 'N', 'N', 'N', 'Y', 'Y'], + dc.fibered: ['', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N'], + dc.unoriented: ['Y', 'N', 'Y', 'N', 'Y', 'N', 'Y', 'N', 'Y', 'N', 'Y'], dc.pd_notation: [ '', '[[1,5,2,4],[3,1,4,6],[5,3,6,2]]', @@ -897,5 +967,62 @@ def read(self, column): '1/(v^7*z)-1/(v^5*z) + z/v^7-(2*z)/v^5-(2*z)/v^3-z^3/v^5-z^3/v^3', 'v^5/z-v^7/z + 2*v^3*z + 2*v^5*z-v^7*z + v^3*z^3 + v^5*z^3', '1/(v^7*z)-1/(v^5*z) + (3*z)/v^7-(6*z)/v^5 + z^3/v^7-(5*z^3)/v^5-z^5/v^5' - ] + ], + dc.kauffman_polynomial: [ + '', + '(-a^(-4)-2*a^(-2))*z^(0)+ (a^(-5)+ a^(-3))*z^(1)+ (a^(-4)+ a^(-2))*z^(2)', + '(-a^(-2)-1-a^2)*z^(0)+ (-a^(-1)-a)*z^(1)+ (a^(-2)+ 2+ a^2)*z^(2)+ (a^(-1)+ a)*z^(3)', + '(2*a^(-6)+ 3*a^(-4))*z^(0)+ (a^(-9)-a^(-7)-2*a^(-5))*z^(1)+ (a^(-8)-3*a^(-6)-4*a^(-4))*z^(2)+ (a^(-7)+ a^(-5))*z^(3)+ (a^(-6)+ a^(-4))*z^(4)', + '(a^(-6)+ a^(-4)-a^(-2))*z^(0)+ (-2*a^(-7)-2*a^(-5))*z^(1)+ (-2*a^(-6)-a^(-4)+ a^(-2))*z^(2)+ (a^(-7)+ 2*a^(-5)+ a^(-3))*z^(3)+ (a^(-6)+ a^(-4))*z^(4)', + '(a^(-4)+ a^(-2)-a^2)*z^(0)+ (2*a^(-3)+ 2*a^(-1))*z^(1)+ (-3*a^(-4)-4*a^(-2)+ a^2)*z^(2)+ (-3*a^(-3)-2*a^(-1)+ a)*z^(3)+ (a^(-4)+ 2*a^(-2)+ 1)*z^(4)+ (a^(-3)+ a^(-1))*z^(5)', + '(a^(-4)+ 2*a^(-2)+ 2)*z^(0)+ (-a^(-5)-a^(-3))*z^(1)+ (a^(-6)-2*a^(-4)-6*a^(-2)-3)*z^(2)+ (2*a^(-5)-2*a^(-1))*z^(3)+ (2*a^(-4)+ 3*a^(-2)+ 1)*z^(4)+ (a^(-3)+ a^(-1))*z^(5)', + '(a^(-2)+ 3+ a^2)*z^(0)+ (-a^(-3)-2*a^(-1)-2*a-a^3)*z^(1)+ (-3*a^(-2)-6-3*a^2)*z^(2)+ (a^(-3)+ a^(-1)+ a+ a^3)*z^(3)+ (2*a^(-2)+ 4+ 2*a^2)*z^(4)+ (a^(-1)+ a)*z^(5)', + '(-3*a^(-8)-4*a^(-6))*z^(0)+ (a^(-13)-a^(-11)+ a^(-9)+ 3*a^(-7))*z^(1)+ (a^(-12)-2*a^(-10)+ 7*a^(-8)+ 10*a^(-6))*z^(2)+ (a^(-11)-3*a^(-9)-4*a^(-7))*z^(3)+ (a^(-10)-5*a^(-8)-6*a^(-6))*z^(4)+ (a^(-9)+ a^(-7))*z^(5)+ (a^(-8)+ a^(-6))*z^(6)', + '(-a^(-8)-a^(-6)-a^(-2))*z^(0)+ (3*a^(-9)+ 3*a^(-7))*z^(1)+ (4*a^(-8)+ 3*a^(-6)+ a^(-2))*z^(2)+ (-4*a^(-9)-6*a^(-7)-a^(-5)+ a^(-3))*z^(3)+ (-4*a^(-8)-3*a^(-6)+ a^(-4))*z^(4)+ (a^(-9)+ 2*a^(-7)+ a^(-5))*z^(5)+ (a^(-8)+ a^(-6))*z^(6)', + 'a^2-a/z-a^3/z + a*z + a^3*z', + 'a^(-2)-1/(a^3*z)-1/(a*z) + z/a^3 + z/a', + '-a^4 + a^3/z + a^5/z + a*z-2*a^3*z-3*a^5*z + a^2*z^2 + a^4*z^2 + a^3*z^3 + a^5*z^3', + '-a^(-4) + 1/(a^5*z) + 1/(a^3*z) + z/a^7-(2*z)/a^5-(3*z)/a^3 + z^2/a^6 + z^2/a^4 + z^3/a^5 + z^3/a^3', + '-1 + 1/(a*z) + a/z-(2*z)/a-4*a*z-2*a^3*z-z^2 + a^4*z^2 + z^3/a + 3*a*z^3 + 2*a^3*z^3 + z^4 + a^2*z^4', + '-1 + 1/(a*z) + a/z-(2*z)/a-4*a*z-2*a^3*z-z^2 + a^4*z^2 + z^3/a + 3*a*z^3 + 2*a^3*z^3 + z^4 + a^2*z^4', + '-a^4 + a^3/z + a^5/z-z/a-a^3*z-2*a^5*z-3*z^2-3*a^2*z^2 + z^3/a + a^5*z^3 + 2*z^4 + 3*a^2*z^4 + a^4*z^4 + a*z^5 + a^3*z^5', + '-a^(-4) + 1/(a^5*z) + 1/(a^3*z)-z/a^9-z/a^5-(2*z)/a^3-(3*z^2)/a^8-(3*z^2)/a^6 + z^3/a^9 + z^3/a^3 + (2*z^4)/a^8 + (3*z^4)/a^6 + z^4/a^4 + z^5/a^7 + z^5/a^5', + 'a^6-a^5/z-a^7/z-2*a^3*z + 3*a^5*z + 3*a^7*z-2*a^9*z-a^4*z^2-2*a^6*z^2-a^8*z^2 + a^3*z^3-2*a^5*z^3-2*a^7*z^3 + a^9*z^3 + a^4*z^4 + 2*a^6*z^4 + a^8*z^4 + a^5*z^5 + a^7*z^5', + 'a^(-6)-1/(a^7*z)-1/(a^5*z)-(2*z)/a^9 + (3*z)/a^7 + (3*z)/a^5-(2*z)/a^3-z^2/a^8-(2*z^2)/a^6-z^2/a^4 + z^3/a^9-(2*z^3)/a^7-(2*z^3)/a^5 + z^3/a^3 + z^4/a^8 + (2*z^4)/a^6 + z^4/a^4 + z^5/a^7 + z^5/a^5', + 'a^6-a^5/z-a^7/z + 6*a^5*z + 4*a^7*z-a^9*z + a^11*z-3*a^6*z^2-2*a^8*z^2 + a^10*z^2-5*a^5*z^3-4*a^7*z^3 + a^9*z^3 + a^6*z^4 + a^8*z^4 + a^5*z^5 + a^7*z^5' + ], + dc.jones_polynomial: [ + '1', + 't+ t^3-t^4', + 't^(-2)-t^(-1)+ 1-t+ t^2', + 't^2+ t^4-t^5+ t^6-t^7', + 't-t^2+ 2*t^3-t^4+ t^5-t^6', + 't^(-2)-t^(-1)+ 2-2*t+ t^2-t^3+ t^4', + 't^(-1)-1+ 2*t-2*t^2+ 2*t^3-2*t^4+ t^5', + '-t^(-3)+ 2*t^(-2)-2*t^(-1)+ 3-2*t+ 2*t^2-t^3', + 't^3+ t^5-t^6+ t^7-t^8+ t^9-t^10', + 't-t^2+ 2*t^3-2*t^4+ 2*t^5-t^6+ t^7-t^8', + '-x^(-5)-x^(-1)', + '-x-x^5', + '-x^(-9)-x^(-5) + x^(-3)-x^(-1)', + '-x^3-x^7 + x^9-x^11', + 'x^(-7)-2/x^5 + x^(-3)-2/x + x-x^3', + 'x^(-7)-2/x^5 + x^(-3)-2/x + x-x^3', + '-x^(-9) + x^(-7)-3/x^5 + 2/x^3-2/x + 2*x-x^3', + '-x^3 + x^5-3*x^7 + 2*x^9-2*x^11 + 2*x^13-x^15', + '-x^(-15) + x^(-13)-2/x^11 + 2/x^9-2/x^7 + x^(-5)-x^(-3)', + '-x^3 + x^5-2*x^7 + 2*x^9-2*x^11 + x^13-x^15', + '-x^(-17) + x^(-15)-x^(-13) + x^(-11)-x^(-9)-x^(-5)' + ], + dc.alexander_polynomial: [ + '1', + '1-t+ t^2', + '1-3*t+ t^2', + '1-t+ t^2-t^3+ t^4', + '2-3*t+ 2*t^2', + '2-5*t+ 2*t^2', + '1-3*t+ 3*t^2-3*t^3+ t^4', + '1-3*t+ 5*t^2-3*t^3+ t^4', + '1-t+ t^2-t^3+ t^4-t^5+ t^6', + '3-5*t+ 3*t^2'] } diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 67785bea087..b91498cfa41 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -1,32 +1,197 @@ # -*- coding: utf-8 -*- r""" -KontInfo +Access to the KnotInfo database This module contains the class :class:`KnotInfoBase` which is derived from :class:`Enum` -and provides all knots and links listed in the databases at https://knotinfo.math.indiana.edu/ -and https://linkinfo.sitehost.iu.edu as its items. +and provides knots and links listed in the databases at the web-pages `KnotInfo `__ +and `LinkInfo `__ as its items. + +This interface contains a set of about twenty knots and links statically as demonstration cases. The complete +database can be installed as an optional Sage package using + +- ``sage -i database_knotinfo`` (does not install if the current version is already present) +- ``sage -f database_knotinfo`` (installs even if the current version is already present) + +To perform all the doctests concerning the usage of the database on the installation add the option ``-c``. +In this case (for instance ``sage -f -c database_knotinfo``) the installation breaks on failing tests. + +The installation of the complete database will be necessary in order to have access to all the properties +recorded in the databases, as well. Be aware that there are a couple of conventions used differently on KnotInfo as in Sage, especially concerning the selection of the symmetry version of the link. In our transitions to Sage objects -these are translated in order to avoid confusion about exchanged mirror versions. +these are translated (by default) in order to avoid confusion about exchanged mirror versions. Briefly, these differences are: - ``pd_notation`` -- KnotInfo: counter clockwise Sage: clockwise, see note in - :meth:`link` + :meth:`KnotInfoBase.link` - - ``homfly_polynomial`` -- KnotInfo: ``v`` Sage: `1/a`, see note in :meth:`homfly_polynomial`. + - ``homfly_polynomial`` -- KnotInfo: ``v`` Sage: `1/a`, see note in :meth:`KnotInfoBase.homfly_polynomial`. - ``braid_notation`` -- This is used accordingly: The crossing of the braid generators are positive in both systems. Here it is listed because there could arise confusion from the source where they are taken from. There, the braid generators are assumed to have a negative crossing - (see definition 3 of Gittings, T., "Minimum Braids: A Complete Invariant of Knots and Links - https://arxiv.org/abs/math/0401051). + (see definition 3 of `Gittings, T., "Minimum Braids: A Complete Invariant of Knots and Links `__). + + +EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L.pd_notation() + [[6, 1, 7, 2], [8, 3, 5, 4], [2, 5, 3, 6], [4, 7, 1, 8]] + sage: L.pd_notation(original=True) + '{{6, 1, 7, 2}, {8, 3, 5, 4}, {2, 5, 3, 6}, {4, 7, 1, 8}}' + sage: L.is_knot() + False + sage: L.num_components() + 2 + +Items for knots need a leading ``K`` for technical reason:: + + sage: K = KnotInfo.K4_1 + sage: K.is_knot() + True + +Injecting the variable name into the namespace:: + + sage: KnotInfo.K5_1.inject() + Defining K5_1 + sage: K5_1.dt_notation() + [6, 8, 10, 2, 4] + +Defining a link from the original name string:: + + sage: KnotInfo('L6a1{1}').inject() + Defining L6a1_1 + sage: L6a1_1.is_alternating() + True + +Obtaining an instance of :class:`~sage.groups.braid.Braid`:: + + sage: L.braid() + s0*s1^-1*s2*s1^-1*s0^-1*s1^-1*s2^-1*s1^-1 + sage: type(_) + + +Obtaining an instance of :class:`Link`:: + + sage: l = L.link(); l + Link with 2 components represented by 4 crossings + sage: type(l) + + +If you have `SnapPy `__ installed inside Sage +you can obtain an instance of :class:`~spherogram.links.links_base.Link`, too:: + + sage: L6 = KnotInfo.L6a2_0 + sage: l6s = L6.link(snappy=True); l6s # optional - snappy + Plink failed to import tkinter. + + sage: type(l6s) # optional - snappy + + sage: l6 = L6.link() + sage: l6 == l6s.sage_link() # optional - snappy + True + sage: l6sn = L6.link(use_item=L6.items.name, snappy=True); l6sn # optional - snappy + + sage: l6s == l6sn # optional - snappy + False + sage: l6sn.sage_link().is_isotopic(l6) # optional - snappy + True + +But observe that the name conversion to SnapPy does not distingiush orientation types:: + + sage: L6b = KnotInfo.L6a2_1 + sage: l6bsn = L6b.link(use_item=L6b.items.name, snappy=True) # optional - snappy + sage: l6bsn.PD_code() == l6sn.PD_code() # optional - snappy + True + +Obtaining the HOMFLY-PT polynomial:: + + sage: L.homfly_polynomial() + -v^-1*z - v^-3*z - v^-3*z^-1 + v^-5*z^-1 + sage: L.homfly_polynomial(sage_convention=True) + L^5*M^-1 - L^3*M - L^3*M^-1 - L*M + sage: _ == l.homfly_polynomial(normalization='az') + True + + +Obtaining the original string from the database for an arbitrary property:: + + sage: K[K.items.classical_conway_name] # optional - database_knotinfo + '4_1' + +Further methods:: + + sage: K.crossing_number() + 4 + sage: K.gauss_notation() + [-1, 2, -3, 1, -4, 3, -2, 4] + sage: K.dt_notation() + [4, 6, 8, 2] + sage: K.determinant() + 5 + sage: K.symmetry_type() + 'fully amphicheiral' + sage: _ == K[K.items.symmetry_type] + True + sage: K.is_reversible() + True + sage: K.is_amphicheiral() + True + sage: K.jones_polynomial() + t^2 - t - 1/t + 1/t^2 + 1 + sage: K.kauffman_polynomial() + a^2*z^2 + a*z^3 - a^2 - a*z + 2*z^2 + a^-1*z^3 - 1 - a^-1*z + a^-2*z^2 - a^-2 + sage: K.alexander_polynomial() + t^2 - 3*t + 1 + +Using the ``column_type`` of a property:: + + sage: [i.column_name() for i in K.items if i.column_type() != i.types.OnlyLinks and K[i] == 'Y'] # optional - database_knotinfo + ['Alternating', 'Fibered', 'Quasialternating', 'Adequate'] + +You can launch web-pages attached to the links:: + + sage: K.diagram() # not tested + True + sage: L.diagram(single=True) # not tested + True + sage: L.knot_atlas_webpage() # not tested + True + sage: K.knotilus_webpage() # not tested + True + +and the description web-pages of the properties:: + + sage: K.items.positive.description_webpage() # not tested + True + +To see all the properties available in this interface you can use "tab-completion". +For example type ``K.items.`` and than hit the "tab-key". You can select the item +you want from the list. If you know some first letters type them first to obtain a +reduced selection list. + +In a similar way you may select the knots and links. Here you have to type ``KnotInfo.`` +or ``KnotInfo.L7`` before stroking the "tab-key". In the latter case the selection list +will be reduced to proper links with 7 crossings. + +Finally there is a method :meth:`Link.identify_knotinfo` of class :class:`Link` to find an instance +in the KnotInfo database:: + + sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5], + ....: [17,19,8,18], [9,10,11,14], [10,12,13,11], + ....: [12,19,15,13], [20,16,14,15], [16,20,17,2]]) + sage: L.identify_knotinfo() + (, False) + REFERENCES: -- https://knotinfo.math.indiana.edu/ -- https://linkinfo.sitehost.iu.edu/ +- `KnotInfo `__ +- `LinkInfo `__ @@ -49,8 +214,11 @@ from enum import Enum -from sage.misc.cachefunc import cached_method +from sage.misc.cachefunc import cached_method, cached_function from sage.misc.sage_eval import sage_eval +from sage.structure.sage_object import SageObject +from sage.structure.unique_representation import UniqueRepresentation +from sage.rings.integer_ring import ZZ from sage.groups.braid import BraidGroup from sage.knots.knot import Knots from sage.databases.knotinfo_db import KnotInfoColumnTypes, KnotInfoColumns, db @@ -58,9 +226,14 @@ -def is_knotinfo_available(): +def is_knotinfo_available(raise_error=False): r""" - Return wether the KnotInfo databases are installed or not. + Return whether the KnotInfo databases are installed or not. + + INPUT: + + - ``raise_error`` -- boolean (default ``False``) if set to ``True`` + an import error is raised in case KnotInfo is not installed EXAMPLES:: @@ -68,7 +241,56 @@ def is_knotinfo_available(): sage: is_knotinfo_available() # optional - database_knotinfo True """ - return db.is_available() + res = db.is_available() + if not res and raise_error: + raise ImportError('This functionality needs KnotInfo to be installed! Type `sage -i database_knotinfo` to have this done') + return res + +@cached_function +def knotinfo_matching_list(number_of_crossings, num_components, homfly_polynomial=None): + r""" + Return a list of links from the KontInfo and LinkInfo tables with given properties. + + INPUT: + + - ``number_of_crossings`` -- Python ``int`` giving the (not necessarily minimal) + number of crossings to be matched + - ``num_components`` -- Python ``int`` giving the number of components + to be matched + - ``homfly_polynomial`` -- instance of :class:`~sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomial_mpair` + giving the HOMFLY-PT polynomial to be matched + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo, knotinfo_matching_list + sage: knotinfo_matching_list(3,1) + [, ] + sage: [l.name for l in knotinfo_matching_list(4,2)] + ['L2a1_0', 'L2a1_1', 'L4a1_0', 'L4a1_1'] + sage: L = KnotInfo.L6a3_0 # optional - database_knotinfo + sage: h = L.homfly_polynomial(sage_convention=True) # optional - database_knotinfo + sage: l = knotinfo_matching_list(L.crossing_number(), L.num_components(), h) # optional - database_knotinfo + sage: len(l) == 1 and l[0] == L # optional - database_knotinfo + True + """ + res = [] + if homfly_polynomial: + l = knotinfo_matching_list(number_of_crossings, num_components) + for L in l: + if homfly_polynomial: + if L.homfly_polynomial(sage_convention=True) != homfly_polynomial: + continue + res.append(L) + return res + + for L in KnotInfo: + if L.crossing_number() > number_of_crossings: + continue + if L.num_components() != num_components: + continue + res.append(L) + + return res def eval_knotinfo(string, locals={}, to_tuple=True): @@ -98,16 +320,52 @@ def eval_knotinfo(string, locals={}, to_tuple=True): new_string = new_string.replace(';', ',') return sage_eval(new_string, locals=locals) +def knotinfo_bool(string): + r""" + Preparse a string from the KnotInfo database representing a boolean. + + INPUT: + + - ``string`` -- string that gives a value of some database entry + + EXAMPLES:: + + sage: from sage.knots.knotinfo import knotinfo_bool + sage: knotinfo_bool('Y') + True + """ + if string == 'Y': + return True + elif string == 'N': + return False + raise ValueError('%s is not a KnotInfo boolean') + +# --------------------------------------------------------------------------------- +# KnotInfoBase +# --------------------------------------------------------------------------------- class KnotInfoBase(Enum): r""" - Enum class to select the knots and links provided by http://www.indiana.edu/~knotinfo + Enum class to select the knots and links listed in the databases at the web-pages + `KnotInfo `__ and `LinkInfo `__. EXAMPLES:: + sage: from sage.knots.knotinfo import KnotInfo sage: [knot.name for knot in KnotInfo if knot.crossing_number() < 5] ['K0_1', 'K3_1', 'K4_1', 'L2a1_0', 'L2a1_1', 'L4a1_0', 'L4a1_1'] + + More examples and information can be seen in the module header :mod:`~sage.knots.knotinfo` (by typing):: + + sage: import sage.knots.knotinfo # not tested + sage: sage.knots.knotinfo? # not tested + + TESTS: + + sage: KnotInfo.K7_1.inject() + Defining K7_1 + sage: TestSuite(K7_1).run() """ @property def items(self): @@ -132,12 +390,26 @@ def items(self): 'strongly_quasipositive_braid_notation', 'quasipositive_braid_notation', 'arc_notation'] + sage: L.items.dt_notation.column_name() + 'DT Notation' + + To check if the item is available for proper links or only knots type:: + + sage: it.gauss_notation.column_type() + + sage: it.dt_notation.column_type() + + + To see the description of the item in your web browser type:: + + sage: it.gauss_notation.description_webpage() # not tested + True """ return db.columns() @cached_method def __getitem__(self, item): - """ + r""" sage: from sage.knots.knotinfo import KnotInfo sage: L = KnotInfo.L4a1_0 sage: L[L.items.alternating] @@ -153,21 +425,22 @@ def __getitem__(self, item): """ if not isinstance(item, KnotInfoColumns): raise KeyError('Item must be an instance of %s' %(KnotInfoColumns)) - if item.column_type() == KnotInfoColumnTypes.OnlyLinks and self.is_knot(): + if item.column_type() == item.types.OnlyLinks and self.is_knot(): raise KeyError('Item not available for knots' %(KnotInfoColumns)) - if item.column_type() == KnotInfoColumnTypes.OnlyKnots and not self.is_knot(): + if item.column_type() == item.types.OnlyKnots and not self.is_knot(): raise KeyError('Item not available for links' %(KnotInfoColumns)) l = db.read(item) + ind = db.read_row_dict()[self.name][0] offset = 0 - if item.column_type() == KnotInfoColumnTypes.OnlyLinks: + if item.column_type() == item.types.OnlyLinks: offset = self._offset_knots() - return l[self.value[0]-offset] + return l[ind-offset] def _offset_knots(self): r""" - Return the list index of the first proper link in a conbined + Return the list index of the first proper link in a combined list containing knots and proper links together which is the case for columns used for KnotInfo and LinkInfo in common. This index is exactly the total number of knots recorded @@ -205,7 +478,7 @@ def _braid_group(self): @cached_method def _homfly_pol_ring(self, var1, var2): r""" - Return the parent Laurent polynomial ring for the Homfly-PT + Return the parent Laurent polynomial ring for the HOMFLY-PT polynomial according to Sage's internal one. EXAMPLES:: @@ -222,14 +495,15 @@ def _homfly_pol_ring(self, var1, var2): def pd_notation(self, original=False): r""" Return the value of column ``pd_notation`` for this - link as a Python list of Python lists. + link as a Python list of Python lists. For more information + type ``KnotInfo.K0_1.items.pd_notation.description_webpage()``. INPUT: - ``original`` -- boolean (optional, default ``False``) if set to ``True`` the original table entry is returned as a string - OUTPUT:: + OUTPUT: Python list of python lists each entry of the outer list representing a crossing. @@ -264,14 +538,15 @@ def pd_notation(self, original=False): def dt_notation(self, original=False): r""" Return the value of column ``dt_notation`` for this - link as a Python list of Python lists. + link as a Python list of Python lists. For more information + type ``KnotInfo.K0_1.items.dt_notation.description_webpage()``. INPUT: - ``original`` -- boolean (optional, default ``False``) if set to ``True`` the original table entry is returned as a string - OUTPUT:: + OUTPUT: Python list of python lists each entry of the outer list representing a crossing. @@ -307,14 +582,15 @@ def dt_notation(self, original=False): def gauss_notation(self, original=False): r""" Return the value of column ``gauss_notation`` for this - link as a Python list of Python lists. + link as a Python list of Python lists. For more information + type ``KnotInfo.K0_1.items.gauss_notation.description_webpage()``. INPUT: - ``original`` -- boolean (optional, default ``False``) if set to ``True`` the original table entry is returned as a string - OUTPUT:: + OUTPUT: Python list of @@ -341,14 +617,15 @@ def gauss_notation(self, original=False): def braid_notation(self, original=False): r""" Return the value of column ``braid_notation`` for this - link as a Python tuple (Tietze form). + link as a Python tuple (Tietze form). For more information + type ``KnotInfo.K0_1.items.braid_notation.description_webpage()``. INPUT: - ``original`` -- boolean (optional, default ``False``) if set to ``True`` the original table entry is returned as a string - OUTPUT:: + OUTPUT: Python tuple representing the braid whose closure is ``self`` in Tietze form. @@ -393,7 +670,7 @@ def braid_index(self): Return the value of column ``braid_index`` for this link as a Python int. - OUTPUT:: + OUTPUT: Python int giving the minimum of strands needed to represent ``self`` as closure of a braid. @@ -418,7 +695,7 @@ def braid_length(self): Return the value of column ``braid_length`` for this link as a Python int. - OUTPUT:: + OUTPUT: Python int giving the minimum length of a braid word needed to represent ``self`` as closure of a braid. @@ -435,7 +712,7 @@ def braid_length(self): @cached_method def braid(self): r""" - Return the braid notation of self as an instance of :class:`Braid`. + Return the braid notation of self as an instance of :class:`~sage.groups.braid.Braid`. EXAMPLES:: @@ -449,9 +726,10 @@ def braid(self): """ return self._braid_group()(self.braid_notation()) + @cached_method def num_components(self): r""" - Return the number of compoents of ``self``. + Return the number of components of ``self``. EXAMPLES:: @@ -459,20 +737,20 @@ def num_components(self): sage: KnotInfo.L6a1_0.num_components() 2 """ - return self.value[1] + return db.read_row_dict()[self.name][1] @cached_method def crossing_number(self): r""" - Return the minimal number of crossings. + Return the minimal number of crossings of ``self``. .. NOTE:: In contrast to the number of crossings displayed for instances of :class:`Link` this number is the minimum over all possible diagrams of the link. The number of crossings displayed in - the representation string of :class:`Link` referes to the - special representation which could be larger. + the representation string of :class:`Link` refers to the + special diagram which could be larger. EXAMPLES:: @@ -486,20 +764,49 @@ def crossing_number(self): """ return int(self[self.items.crossing_number]) + @cached_method + def determinant(self): + r""" + Return the determinant of ``self``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L4a1_0.determinant() + 4 + sage: KnotInfo.K3_1.determinant() + 3 + """ + return int(self[self.items.determinant]) + + @cached_method def is_knot(self): r""" - Return wether ``self`` is a knot or a proper link. + Return whether ``self`` is a knot or a proper link. EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfo sage: KnotInfo.L7a1_0.is_knot() # optional - database_knotinfo False - sage: KnotInfo.K6_3.is_knot() # optional - database_knotinfo + sage: KnotInfo.K6_3.is_knot() True """ return self.num_components() == 1 + @cached_method + def name_unoriented(self): + r""" + Return the the part of the name of ``self`` which is independent on the orientation. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L10a122_1_0.name_unoriented() # optional - database_knotinfo + 'L10a122' + """ + return self[self.items.name_unoriented] + @cached_method def symmetry_type(self): r""" @@ -508,12 +815,12 @@ def symmetry_type(self): From the KnotInfo description page: If a knot is viewed as the oriented diffeomorphism - class of an oriented pair, `K = (S_3, S_1), with `S_i` + class of an oriented pair, `K = (S_3, S_1)`, with `S_i` diffeomorphic to `S^i`, there are four oriented knots associated to any particular knot `K`. In addition to `K` itself, there is the reverse, `K^r = (S_3, -S_1)`, the concordance inverse, `-K = (-S_3, -S_1)`, and the - mirror image, `K^m = (-S3, S1)`. A knot is called + mirror image, `K^m = (-S_3, S_1)`. A knot is called reversible if `K = K^r`, negative amphicheiral if `K = -K`, and positive amphicheiral if `K = K^m`. @@ -543,10 +850,10 @@ class of an oriented pair, `K = (S_3, S_1), with `S_i` return 'fully amphicheiral' return self[self.items.symmetry_type] - + @cached_method def is_reversible(self): r""" - Return wether ``self`` is reversible. + Return whether ``self`` is reversible. EXAMPLES:: @@ -560,15 +867,16 @@ def is_reversible(self): return True return False + @cached_method def is_amphicheiral(self, positive=False): r""" - Return wether ``self`` is amphicheiral. + Return whether ``self`` is amphicheiral. INPUT: - - ``positive`` -- Boolean (default False) wether to check - if ``self`` is positive or negative amphicheiral (see - doctest of :meth:`symmetry_type`) + - ``positive`` -- boolean (optional, default False) whether to check + if ``self`` is positive or negative amphicheiral (see documentation + of :meth:`symmetry_type`) EXAMPLES:: @@ -589,45 +897,192 @@ def is_amphicheiral(self, positive=False): return True return False + @cached_method + def is_alternating(self): + r""" + Return whether ``self`` is alternating. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_alternating() + True + """ + return knotinfo_bool(self[self.items.alternating]) + + @cached_method + def is_almost_alternating(self): + r""" + Return whether ``self`` is almost alternating. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_almost_alternating() # optional - database_knotinfo + False + """ + is_knotinfo_available(raise_error=True) # column not available in demo-version + return knotinfo_bool(self[self.items.almost_alternating]) + + @cached_method + def is_quasi_alternating(self): + r""" + Return whether ``self`` is quasi alternating. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_quasi_alternating() # optional - database_knotinfo + True + """ + is_knotinfo_available(raise_error=True) # column not available in demo-version + return knotinfo_bool(self[self.items.quasi_alternating]) + + @cached_method + def is_adequate(self): + r""" + Return whether ``self`` is adequate. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_adequate() # optional - database_knotinfo + True + """ + is_knotinfo_available(raise_error=True) # column not available in demo-version + return knotinfo_bool(self[self.items.adequate]) + + @cached_method + def is_positive(self): + r""" + Return whether ``self`` is positive. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_positive() + True + """ + return knotinfo_bool(self[self.items.positive]) + + @cached_method + def is_quasipositive(self): + r""" + Return whether ``self`` is quasi-positive. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_quasipositive() # optional - database_knotinfo + True + """ + is_knotinfo_available(raise_error=True) # column not available in demo-version + return knotinfo_bool(self[self.items.quasipositive]) + + @cached_method + def is_strongly_quasipositive(self): + r""" + Return whether ``self`` is strongly quasi-positive. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_strongly_quasipositive() # optional - database_knotinfo + True + """ + is_knotinfo_available(raise_error=True) # column not available in demo-version + return knotinfo_bool(self[self.items.strongly_quasipositive]) + + @cached_method + def is_positive_braid(self): + r""" + Return whether ``self`` is a positive braid. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_positive_braid() # optional - database_knotinfo + False + """ + is_knotinfo_available(raise_error=True) # column not available in demo-version + return knotinfo_bool(self[self.items.positive_braid]) + + @cached_method + def is_fibered(self): + r""" + Return whether ``self`` is fibered. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K6_3.is_fibered() + True + """ + return knotinfo_bool(self[self.items.fibered]) + + @cached_method + def is_oriented(self): + r""" + Return whether ``self`` is oriented. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L6a2_1.is_oriented() + True + """ + return not knotinfo_bool(self[self.items.unoriented]) + @cached_method - def homfly_polynomial(self, var1='L', var2='M', original=False): + def homfly_polynomial(self, var1=None, var2=None, original=False, sage_convention=False): r""" - Return the value of column ``homfly_polynomial`` for this - knot or link (in this case the column ``homflypt_polynomial`` - is used) as an instance of the element class according to - the output of :meth:`homfly_polynomial` of :class:`Link`. + Return the HOMFLY-PT polynomial according to the value of column + ``homfly_polynomial`` for this knot or link (in the latter case the + column ``homflypt_polynomial`` is used) as an instance of the + element class according to the output of :meth:`Link.homfly_polynomial` + of :class:`Link`. + + The HOMFLY-PT polynomial `P(L)` of a link `L` satisfies the following skein relation + (see the corresponding `KnotInfo description page `__): + + .. MATH:: + + P(O) = 1,\,\,\, v^{-1} P(L_+) - v P(L_-) = z P(L_0) INPUT: - - ``var1`` -- (default: ``'L'``) the first variable - - ``var2`` -- (default: ``'M'``) the second variable - - ``original`` -- boolean (optional, default ``False``) if set to + - ``var1`` -- string for the name of the first variable (default depending + on keyword ``sage_convention``: ``'v'`` or ``'L'`` if ``sage_convention == True``) + - ``var2`` -- string for the name of the second variable (default depending + on keyword ``sage_convention``: ``'z'`` or ``'M'`` if ``sage_convention == True``) + - ``original`` -- boolean (default ``False``) if set to ``True`` the original table entry is returned as a string + - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` the conversion + to Sage's conventions (see the note below) is performed - OUTPUT:: + OUTPUT: A Laurent polynomial over the integers, more precisely an instance of - :class:`sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`. + :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`. + If ``original`` is set to ``True`` then a string is returned. .. NOTE:: - The skein-relation for the Homfly-PT polynomial given on KnotInfo - differs from the ones used in Sage: - - KnotInfo: P(O) = 1, ~v P(L+) - v P(L-) = z P(L0) + The skein-relation for the HOMFLY-PT polynomial given on KnotInfo + differs from the ones used in Sage. - (see: https://knotinfo.math.indiana.edu/descriptions/jones_homfly_kauffman_description/polynomial_defn.html) - - Using Sage's Homfy-PT polynomials with ``normalization='az'`` - the corresponding skein-relation is (see :meth:`homfly_polynomial` + Using Sage's HOMFLY-PT polynomial with ``normalization='az'`` + the corresponding skein-relation is (see :meth:`Link.homfly_polynomial` of :class:`Link`): - Sage: P(O) = 1, a P(L+) - ~a P(L-) = z P(L0) + .. MATH:: + + P(O) = 1,\,\,\, a P(L_+) - a^{-1} P(L_-) = z P(L_0) - Thus, the Homfly-PT polynomial of KnotInfo compares to the one of Sage - by replacing ``v`` by ``~a``. To keep them comparable this translation is - performed, as well. + Thus, the HOMFLY-PT polynomial of KnotInfo compares to the one of Sage + by replacing ``v`` by ``~a``. To keep them comparable this translation + can be performed by setting the keyword ``sage_convention`` to ``True``. EXAMPLES:: @@ -635,44 +1090,54 @@ def homfly_polynomial(self, var1='L', var2='M', original=False): sage: from sage.knots.knotinfo import KnotInfo sage: K3_1 = KnotInfo.K3_1 sage: PK3_1 = K3_1.homfly_polynomial(); PK3_1 + -v^4 + v^2*z^2 + 2*v^2 + sage: K3_1.homfly_polynomial(original=True) + '(2*v^2-v^4)+ (v^2)*z^2' + sage: PK3_1s = K3_1.homfly_polynomial(sage_convention=True); PK3_1s L^-2*M^2 + 2*L^-2 - L^-4 - sage: PK3_1 == K3_1.link().homfly_polynomial(normalization='az') + sage: PK3_1s == K3_1.link().homfly_polynomial(normalization='az') True + + for proper links:: + sage: L4a1_1 = KnotInfo.L4a1_1 sage: PL4a1_1 = L4a1_1.homfly_polynomial(var1='x', var2='y'); PL4a1_1 + -x^5*y + x^3*y^3 - x^5*y^-1 + 3*x^3*y + x^3*y^-1 + sage: PL4a1_1s = L4a1_1.homfly_polynomial(var1='x', var2='y', sage_convention=True); PL4a1_1s x^-3*y^3 + 3*x^-3*y + x^-3*y^-1 - x^-5*y - x^-5*y^-1 - sage: PL4a1_1 == L4a1_1.link().homfly_polynomial(var1='x', var2='y', normalization='az') + sage: PL4a1_1s == L4a1_1.link().homfly_polynomial(var1='x', var2='y', normalization='az') True - check the skein-relation given in the doc string of :meth:`homfly_polynomial` of - :class:`Link` (applied to one of the positive crossings of the right-handed trefoil):: + check the skein-relation from the KnotInfo description page (applied to one of + the positive crossings of the right-handed trefoil):: sage: R = PK3_1.parent() sage: PO = R.one() sage: L2a1_1 = KnotInfo.L2a1_1 sage: PL2a1_1 = L2a1_1.homfly_polynomial() - sage: a, z = R.gens() - sage: a*PK3_1 - ~a*PO == z*PL2a1_1 + sage: v, z = R.gens() + sage: ~v*PK3_1 -v*PO == z*PL2a1_1 True - check the skein-relation from the KnotInfo description page with the original version:: - - sage: pK3_1o = K3_1.homfly_polynomial(original=True); pK3_1o - '(2*v^2-v^4)+ (v^2)*z^2' - sage: pL2a1_1o = L2a1_1.homfly_polynomial(original=True); pL2a1_1o - 'v/z-v^3/z + v*z' + check the skein-relation given in the doc string of :meth:`Link.homfly_polynomial` of + :class:`Link` (applied to one of the positive crossings of the right-handed trefoil):: - sage: R. = LaurentPolynomialRing(ZZ) - sage: PO = R.one() - sage: PK3_1o = sage_eval(pK3_1o, locals={'v':v, 'z':z}) - sage: PL2a1_1o = sage_eval(pL2a1_1o, locals={'v':v, 'z':z}) - sage: ~v*PK3_1o - v*PO == z*PL2a1_1o + sage: Rs = PK3_1s.parent() + sage: POs = Rs.one() + sage: PL2a1_1s = L2a1_1.homfly_polynomial(sage_convention=True) + sage: a, z = Rs.gens() + sage: a*PK3_1s - ~a*POs == z*PL2a1_1s True + TESTS:: all(L.homfly_polynomial() == L.link().homfly_polynomial(normalization='az') for L in KnotInfo if L.crossing_number() > 0 and L.crossing_number() < 7) True + + REFERENCES: + + - :wikipedia:`HOMFLY_polynomial` """ if self.is_knot(): homfly_polynomial = self[self.items.homfly_polynomial] @@ -682,51 +1147,435 @@ def homfly_polynomial(self, var1='L', var2='M', original=False): if original: return homfly_polynomial + if sage_convention: + if not var1: + var1='L' + if not var2: + var2='M' + else: + if not var1: + var1='v' + if not var2: + var2='z' + R = self._homfly_pol_ring(var1, var2) if not homfly_polynomial and self.crossing_number() == 0: return R.one() L, M = R.gens() - lc = {'z': M, 'v': ~L} # + if sage_convention: + lc = {'v': ~L, 'z':M} # see note above + else: + lc = {'v': L, 'z':M} return eval_knotinfo(homfly_polynomial, locals=lc) + @cached_method + def kauffman_polynomial(self, var1='a', var2='z', original=False): + r""" + Return the Kauffman polynomial according to the value of column + ``kauffman_polynomial`` for this knot or link as an instance of + :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`. + + The Kauffman polynomial `F(L)` respectivlely its corresponding invariant under + regular isotopy `\Delta (L) = a^{w(L)} F(L)` where `w(L)` is the writhe of + the link `L` satisfies the following skein relation + (see the corresponding `KnotInfo description page `__): + + .. MATH:: + + \Delta(O) = 1,\,\,\, \Delta(L_+) - \Delta(L_-) = z (\Delta(L_0 + \Delta(L_{\infty})) + + Furthermore, removing a curl of sign `\epsilon` leads to a multiplication of `\Delta(L)` + with `a^{\epsilon}`. + + INPUT: + + - ``var1`` -- (default: ``'a'``) the first variable + - ``var2`` -- (default: ``'z'``) the second variable + - ``original`` -- boolean (optional, default ``False``) if set to + ``True`` the original table entry is returned as a string + + OUTPUT: + + A Laurent polynomial over the integers, more precisely an instance of + :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`. + If ``original`` is set to ``False`` then a string is returned. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L2a1_1 + sage: L.kauffman_polynomial() + a^-1*z - a^-1*z^-1 + a^-2 + a^-3*z - a^-3*z^-1 + sage: K = KnotInfo.K4_1 + sage: K.kauffman_polynomial() + a^2*z^2 + a*z^3 - a^2 - a*z + 2*z^2 + a^-1*z^3 - 1 - a^-1*z + a^-2*z^2 - a^-2 + + Comparison with Jones polynomial:: + + sage: k = _ + sage: a, z = k.variables() + sage: j = K.jones_polynomial(skein_normalization=True) + sage: t, = j.variables() + sage: k.subs(a=-t^3, z=~t+t) == j.subs(t=t^4) + True + + Check the skein relation:: + + sage: K3_1 = KnotInfo.K3_1 + sage: FK3_1 = K3_1.kauffman_polynomial() + sage: FL2a1_1 = L.kauffman_polynomial() + sage: z, a = FK3_1.variables() + sage: ΔK3_1 = FK3_1 * a**K3_1.link().writhe() + sage: ΔL2a1_1 = FL2a1_1 * a**L.link().writhe() + sage: ΔO1p = a # unknot with one positive curl + sage: ΔO2n = a**-2 # unknot with two negative curls + sage: ΔK3_1 + ΔO1p == z*(ΔL2a1_1 + ΔO2n) + True + + REFERENCES: + + - :wikipedia:`Kauffman_polynomial` + """ + kauffman_polynomial = self[self.items.kauffman_polynomial] + + if original: + return kauffman_polynomial + + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing + R = LaurentPolynomialRing(ZZ, (var1, var2)) + if not kauffman_polynomial and self.crossing_number() == 0: + return R.one() + + a, z = R.gens() + lc = {'a': a, 'z': z} + return R(eval_knotinfo(kauffman_polynomial, locals=lc)) + + + @cached_method + def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False, original=False, sage_convention=False): + r""" + Return the Jones polynomial according to the value of column ``jones_polynomial`` + for this knot or link as an element of the symbolic ring :class:`~sage.symbolic.ring.SR` + or an instance of :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial` + depending on the keyword ``skein_normalization``. Using the keyword ``puiseux`` instead + of an element of the symbolic ring an instance of :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` + can be returned. + + The Jones polynomial `V(L)` of a link `L` satisfies the following skein relation + (see the corresponding `KnotInfo description page `__): + + .. MATH:: + + V(O) = 1,\,\,\, t^{-1} V(L_+) - t V(L_-) = (t^{\frac{1}{2}} - t^{-\frac{1}{2}}) V(L_0) + + INPUT: + + - ``variab`` -- variable (default: ``None``) used according to :meth:`Link.jones_polynomial` + - ``skein_normalization`` -- boolean (default: ``False``) used according to + :meth:`Link.jones_polynomial` + - ``puiseux`` -- boolean (default ``True``) only used in case ``skein_normalization=False``. + If set to ``True`` instead of an element of the symbolic ring an instance of + :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` is returned + - ``original`` -- boolean (default ``False``) if set to + ``True`` the original table entry is returned as a string + - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` the conversion + to Sage's conventions (see the note below) is performed + + + OUTPUT: + + Depends on the keywords (in excluding order): + + - ``original=True`` a string according to the original value from the database + - ``skein_normalization=True`` a Laurent polynomial over the integers, more precisely + an instance of :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial` + - ``puiseux=True`` a puiseux series over the integers, more precisely an instance of + :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` + + In all other cases an element of the symbolic ring :class:`~sage.symbolic.ring.SR`. + + .. NOTE:: + + The only difference of conventions concerning the Jones polynomial is its representation + in the case of proper links. KnotInfo does not display these polynomials in the indeterminate + `t` used in the skein relation. Instead a variable `x` is used defined by `x^2 = t`. + Sage uses `t` in both cases, knots and proper links. Thus, to obtain the Jones polynomial + for a proper link in `t` you have to set the keyword ``sage_convention`` to ``True``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K4_1 + sage: Kj = K.jones_polynomial(); Kj + t^2 - t - 1/t + 1/t^2 + 1 + sage: Kjs = K.jones_polynomial(skein_normalization=True); Kjs + A^-8 - A^-4 + 1 - A^4 + A^8 + sage: Kjp = K.jones_polynomial(puiseux=True); Kjp + t^-2 - t^-1 + 1 - t + t^2 + + for proper links:: + + sage: L = KnotInfo.L2a1_1 + sage: Lj = L.jones_polynomial(); Lj + -x^5 - x + sage: Ljt = L.jones_polynomial(sage_convention=True); Ljt + -t^(5/2) - sqrt(t) + sage: Ljp = L.jones_polynomial(puiseux=True, sage_convention=True); Ljp + -t^(1/2) - t^(5/2) + sage: Ljs = L.jones_polynomial(skein_normalization=True); Ljs + -A^2 - A^10 + sage: Lj.parent() + Symbolic Ring + sage: Ljt.parent() + Symbolic Ring + sage: Ljp.parent() + Puiseux Series Ring in t over Integer Ring + sage: Ljs.parent() + Univariate Laurent Polynomial Ring in A over Integer Ring + + Comparison with Sage's results:: + + sage: k = K.link() + sage: kj = k.jones_polynomial() + sage: bool(Kj == kj) + True + sage: kjs = k.jones_polynomial(skein_normalization=True) + sage: Kjs == kjs + True + sage: l = L.link() + sage: lj = l.jones_polynomial() + sage: bool(Lj == lj) + False + sage: bool(Ljt == lj) # see note above + True + sage: ljs = l.jones_polynomial(skein_normalization=True) + sage: Ljs == ljs + True + + Check the skein-relation from the KnotInfo description page (applied to one of + the positive crossings of the right-handed trefoil):: + + sage: K3_1 = KnotInfo.K3_1 + sage: K3_1j = K3_1.jones_polynomial() + sage: L2a1_1j = Ljt # see note above + sage: R = L2a1_1j.parent() + sage: Oj = R(1) + sage: t = R('t') + sage: lhs = expand(~t*K3_1j - t*Oj) + sage: rhs = expand((sqrt(t) - ~sqrt(t))*L2a1_1j) + sage: bool(lhs == rhs) + True + + The same with the Puiseux series version:: + + sage: K3_1jp = K3_1.jones_polynomial(puiseux=True) + sage: L2a1_1jp = Ljp + sage: R = L2a1_1jp.parent() + sage: Ojp = R(1) + sage: t = R('t') + sage: ~t*K3_1jp - t*Ojp == (t^(1/2)-~t^(1/2))*L2a1_1jp + True + + The same in the case of skein normalization (using `t = A^4`):: + + sage: K3_1js = K3_1.jones_polynomial(skein_normalization=True) + sage: L2a1_1js = L.jones_polynomial(skein_normalization=True) + sage: Rs = K3_1js.parent() + sage: Ojs = Rs.one() + sage: A, = Rs.gens() + sage: ~A^4*K3_1js - A^4*Ojs == (A^2-~A^2)*L2a1_1js + True + + REFERENCES: + + - :wikipedia:`Jones_polynomial` + """ + jones_polynomial = self[self.items.jones_polynomial] + + if original: + return jones_polynomial + + if skein_normalization: + if not variab: + variab='A' + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing + R = LaurentPolynomialRing(ZZ, variab) + else: + if not variab: + if sage_convention or self.is_knot(): + variab='t' + else: + variab='x' + if puiseux: + from sage.rings.puiseux_series_ring import PuiseuxSeriesRing # since PuiseuxPolynomial is not available, so far + R = PuiseuxSeriesRing(ZZ, variab) + else: + from sage.symbolic.ring import SR + R = SR + + if not jones_polynomial and self.crossing_number() == 0: + return R(1) + + t = R(variab) + if skein_normalization: + if self.is_knot(): + lc = {'t': t**4} + else: + lc = {'x': t**2} + else: + if self.is_knot(): + lc = {'t': t} + elif puiseux: + lc = {'x': t**(1/2)} + elif sage_convention: + from sage.functions.other import sqrt + lc = {'x': sqrt(t)} + else: + lc = {'x': t} + + + return R(eval_knotinfo(jones_polynomial, locals=lc)) + @cached_method - def link(self, use_item=db.columns().pd_notation): + def alexander_polynomial(self, var='t', original=False, sage_convention=False): r""" - Return ``self`` as in instance of :class:`Link`. + Return the Alexander polynomial according to the value of column + ``alexander_polynomial`` for this knot as an instance of + :class:`~sage.rings.polynomial.polynomial_element.Polynomial`. + + It is obtained from the Seifert matrix `V` of ``self`` by the following + formula (see the KnotInfo description web-page; to launch it see the + example below): + + .. MATH:: + + A(L) = \det(V -t V^t) + + Here `V^t` stands for the transpose of `V`. + INPUT: - - ``use_item`` -- (optional default ``self.items.pd_notation``) + - ``var`` -- (default: ``'t'``) the variable + - ``original`` -- boolean (optional, default ``False``) if set to + ``True`` the original table entry is returned as a string + - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` the + conversion to Sage's conventions (see the note below) is performed + + OUTPUT: + + A polynomial over the integers, more precisely an instance of + :class:`~sage.rings.polynomial.polynomial_element.Polynomial`. + If ``sage_convention`` is set to ``True`` a Laurent polynomial + over the integers, more precisely an instance of + :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial` + is returned. If ``original`` is set to ``True`` then a string + is returned. + + .. NOTE:: + + As an invariant the Alexander polynomial is only unique up to + a unit factor in the Laurent polynomial ring over the integers + in the indeterminate `t`. While the normalization of the exponents + in KnotInfo guarantees it to be a proper polynomial, this is + not the case for the implementation in Sage. The transition + can be made using the keyword ``sage_convention``. But, still + there may be a difference in sign (see the example below). + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K4_1 + sage: Ka = K.alexander_polynomial(); Ka + t^2 - 3*t + 1 + + Comparison with Sage's results:: + + sage: k = K.link() + sage: ka = k.alexander_polynomial(); ka + -t^-1 + 3 - t + sage: K.alexander_polynomial(sage_convention=True) + t^-1 - 3 + t + sage: _ == -ka + True + + Launch the KnotInfo description web-page:: + + sage: K.items.alexander_polynomial.description_webpage() # not tested + True + """ + alexander_polynomial = self[self.items.alexander_polynomial] + + if original: + return alexander_polynomial + + if sage_convention: + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing + R = LaurentPolynomialRing(ZZ, var) + else: + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + R = PolynomialRing(ZZ, var) + + if not alexander_polynomial and self.crossing_number() == 0: + return R.one() + + t, = R.gens() + lc = {'t': t} + ap = R(eval_knotinfo(alexander_polynomial, locals=lc)) + if not sage_convention or ap.is_constant(): + return ap + + exp = ap.exponents() + return t ** ((-max(exp) - min(exp)) // 2) * ap + + + @cached_method + def link(self, use_item=db.columns().pd_notation, snappy=False): + r""" + Return ``self`` as an instance of :class:`Link` or optional + ``spherogram.links.invariants.Link`` (SnapPy). + + INPUT: + + - ``use_item`` -- (optional, default ``self.items.pd_notation``) instance of :class:`KnotInfoColumns` to choose the column that should be used to construct the link. Allowed values are: - -- self.items.pd_notation - -- self.items.dt_notation (only for knots) - -- self.items.gauss_notation (only for knots) - -- self.items.braid_notation + + - ``self.items.pd_notation`` + - ``self.items.braid_notation`` + - ``self.items.name`` (only for ``snappy=True``) + - ``self.items.dt_notation`` (only for knots and ``snappy=False``) + - ``self.items.gauss_notation`` (only for knots and ``snappy=False``) + + - ``snappy`` boolean (default ``False``) if set to ``True`` + the target of the conversion is the ``pip`` installable + package `SnapPy `__ + (explicitely, ``spherogram.links.invariants.Link``). + If SnapPy is not installed an ``ImportError`` is raised. To + install SnapPy use ``sage -pip install snappy``. .. NOTE:: We use the PD-notation to construct ``self`` as default. This ensures that the number of crossings - displayed in representation string of the link + displayed in the representation string of the link coincides with the crossing number as a topological invariant. But attention: The convention on how the edges are listed are opposite to each other - KnotInfo: counter clockwise - Sage: clockwise + - KnotInfo: counter clockwise + - Sage: clockwise - Therefore, we have to take the mirror_image of the - link! + Therefore, we take the mirror version of the ``pd_notation``! Furthermore, note that the mirror version may depend - on the used KnotInfo-notation. For example for the - knot `5_1` the Gauss- and the DT-notation refer to + on the used KnotInfo-notation. For instance, regarding to + the knot ``5_1`` the Gauss- and the DT-notation refer to the mirror image (see example below). EXAMPLES:: @@ -740,6 +1589,8 @@ def link(self, use_item=db.columns().pd_notation): sage: _ == K.braid() True + using ``dt_notation``:: + sage: K.link(use_item=K.items.dt_notation) Knot represented by 3 crossings sage: _.braid() @@ -752,10 +1603,38 @@ def link(self, use_item=db.columns().pd_notation): sage: L.link(use_item=L.items.dt_notation) Traceback (most recent call last): ... - NotImplementedError: Columns.dt_notation only implemented for knots + ValueError: Link construction using Columns.dt_notation not possible + + using ``snappy``:: + + sage: K7 = KnotInfo.K7_2 + sage: k7s = K7.link(snappy=True); k7s # optional - snappy + + sage: K7.link(use_item=K7.items.name, snappy=True) # optional - snappy + + sage: k7sn = _ # optional - snappy + sage: k7s == k7sn # optional - snappy + False + sage: k7s.sage_link().is_isotopic(k7sn.sage_link()) # optional - snappy + True but observe:: + sage: L2 = KnotInfo.L2a1_1 + sage: l2 = L2.link() + sage: l2s = L2.link(snappy=True).sage_link() # optional - snappy + sage: l2 == l2s # optional - snappy + False + sage: l2 == l2s.mirror_image() # optional - snappy + True + + using ``braid_notation``:: + + sage: L2.link(use_item=L.items.braid_notation) == l2 + True + + observe:: + sage: L.link(use_item=L.items.braid_notation) Link with 2 components represented by 8 crossings @@ -763,6 +1642,8 @@ def link(self, use_item=db.columns().pd_notation): sage: K6_1.link().braid() == K6_1.braid() False + also observe:: + sage: K4_1 = KnotInfo.K4_1 sage: K4_1.link().pd_code() [[4, 1, 5, 2], [8, 5, 1, 6], [6, 4, 7, 3], [2, 8, 3, 7]] @@ -780,29 +1661,410 @@ def link(self, use_item=db.columns().pd_notation): if not isinstance(use_item, KnotInfoColumns): raise TypeError('%s must be an instance of %s' %(use_item, KnotInfoColumns)) - if self.is_knot(): + if snappy: + try: + from snappy import Link + except ImportError: + raise ImportError('This option demands snappy to be installed') + elif self.is_knot(): from sage.knots.knot import Knot as Link else: from sage.knots.link import Link if use_item == self.items.pd_notation: - return Link(self.pd_notation()).mirror_image() # for mirror_image see note above + pd_code = [[a[0], a[3], a[2], a[1]] for a in self.pd_notation()] # take mirror version, see note above + return Link(pd_code) elif use_item == self.items.braid_notation: return Link(self.braid()) - elif use_item == self.items.dt_notation: - if not self.is_knot(): - raise NotImplementedError('%s only implemented for knots' %use_item) - from sage.knots.knot import Knots - return Knots().from_dowker_code(self.dt_notation()) - elif use_item == self.items.gauss_notation: + elif use_item == self.items.name and snappy: if not self.is_knot(): - raise NotImplementedError('%s only implemented for knots' %use_item) + use_item = self.items.name_unoriented + return Link(self[use_item]) + elif self.is_knot() and not snappy: + # Construction via Gauss and DT-Code only possible for knots from sage.knots.knot import Knots - return Knots().from_gauss_code(self.gauss_notation()) + if use_item == self.items.dt_notation: + return Knots().from_dowker_code(self.dt_notation()) + elif use_item == self.items.gauss_notation: + return Knots().from_gauss_code(self.gauss_notation()) + + raise ValueError('Link construction using %s not possible' %use_item) + + + def inject(self, verbose=True): + """ + Inject ``self`` with its name into the namespace of the + Python code from which this function is called. + + INPUT: + + - ``verbose`` -- boolean (optional, default ``True``) to suppress + the message printed on the invocation + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.inject() + Defining K5_2 + sage: K5_2.is_alternating() + True + """ + name = self.name + if verbose: + print("Defining %s" % (name)) + from sage.repl.user_globals import set_global + set_global(name, self) + + def series(self, overview=True): + r""" + Return the series of links ``self`` belongs to. + + INPUT: + + - ``overview`` -- boolean (optional, default ``True``) if set to ``False`` + the series will be reduced to the unoriented type of ``self`` + in the case of proper links. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K5 = KnotInfo.K5_2.series() + sage: K5(1) + + sage: KnotInfo.L4a1_1.series().inject() + Defining L4a + sage: L4a(1) + Series of links L4a1 + sage: KnotInfo.L4a1_1.series(overview=False).inject() + Defining L4a1 + sage: L4a1(1) + + """ + if overview: + S = KnotInfoSeries(self.crossing_number(), self.is_knot(), self.is_alternating()) + else: + S = KnotInfoSeries(self.crossing_number(), self.is_knot(), self.is_alternating(), self.name_unoriented()) + return S + + def diagram(self, single=False, new=0, autoraise=True): + r""" + Launch the diagram of ``self`` given on the KnotInfo web-page. + + INPUT: + + - ``single`` -- boolean (default ``False``) if set to ``True`` only one + diagram is shown. + - ``new`` -- ``int`` according to :func:`open` of :mod:`webbrowser` + (``0`` default, ``1`` new window, ``2`` new tab) + - ``autoraise`` -- boolean (default ``True``) + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K3_1 + sage: K.diagram() # not tested + True + sage: K.diagram(single=True) # not tested + True + """ + import webbrowser + if self.is_knot(): + filename = db.filename.knots else: - raise ValueError('Construction using %s not possible' %use_item) + filename = db.filename.links + + if single: + return webbrowser.open(filename.diagram_url(self[self.items.diagram], single=single), new=new, autoraise=autoraise) + else: + return webbrowser.open(filename.diagram_url(self[self.items.name]), new=new, autoraise=autoraise) + + + def knot_atlas_webpage(self, new=0, autoraise=True): + r""" + Launch the Knot Atlas web-page for ``self``. + + INPUT: + + - ``new`` -- ``int`` according to :func:`open` of :mod:`webbrowser` + (``0`` default, ``1`` new window, ``2`` new tab) + - ``autoraise`` -- boolean (default ``True``) + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K3_1 + sage: K.knot_atlas_webpage() # not tested + True + """ + import webbrowser + return webbrowser.open(self[self.items.knot_atlas_anon], new=new, autoraise=autoraise) + + def knotilus_webpage(self, new=0, autoraise=True): + r""" + Launch the Knotilus web-page for ``self``. + + INPUT: + + - ``new`` -- ``int`` according to :func:`open` of :mod:`webbrowser` + (``0`` default, ``1`` new window, ``2`` new tab) + - ``autoraise`` -- boolean (default ``True``) + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K3_1 + sage: K.knotilus_webpage(new=1) # not tested + True + """ + import webbrowser + return webbrowser.open(self[self.items.knotilus_page_anon], new=new, autoraise=autoraise) + + + +# -------------------------------------------------------------------------------------------- +# KnotInfoSeries +# -------------------------------------------------------------------------------------------- +class KnotInfoSeries(UniqueRepresentation): + r""" + This class can be used to access knots and links via their index + according to the series they belong to. + + INPUT: + + - ``crossing_number`` -- integer giving the crossing numer of this series of links + - ``is_knot`` -- boolean wether this series is a series of knots or proper links + - ``is_alternating`` -- boolean wether this series is restriced to alternating links or not. + This is not relevant for knots with less than 11 crossings + - ``name_unoriented`` -- string restricting the series to all links with that ``name_unoriented`` + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: K6 = KnotInfoSeries(6, True, True); K6 + Series of knots K6 + sage: K6(3) + + sage: list(K6) + [, , ] + sage: L6a = KnotInfoSeries(6, False, True); L6a + Series of links L6a + sage: L6a(2) + Series of links L6a2 + sage: _.inject() + Defining L6a2 + sage: list(L6a2) + [, ] + sage: L6a2(0).series() == L6a + True + sage: L6a2(0) == L6a2('0') + True + """ + + + def __init__(self, crossing_number, is_knot, is_alternating, name_unoriented=None): + r""" + Python constructor. + + EXAMPLES:: + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: L6a = KnotInfoSeries(6, False, True); L6a + Series of links L6a + sage: TestSuite(L6a).run() + """ + self._crossing_number = crossing_number + self._is_knot = is_knot + self._is_alternating = is_alternating + self._name_unoriented = name_unoriented + self._list = None + + def list(self): + r""" + Return this series as a Python list. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: K6 = KnotInfoSeries(6, True, True); K6 + Series of knots K6 + sage: K6(3) + + """ + if self._list: + return self._list + + is_knot = self._is_knot + cross_nr = self._crossing_number + is_alt = self._is_alternating + n_unori = self._name_unoriented + + self._list = [] + curr_n_unori = None + for K in KnotInfo: + if K.is_knot() != is_knot: + continue + if K.crossing_number() != cross_nr: + continue + if not is_knot or cross_nr > 10: + if K.is_alternating() != is_alt: + continue + if is_knot: + self._list.append(K) + else: + this_n_unori = K.name_unoriented() + if n_unori: + if this_n_unori != n_unori: + continue + self._list.append(K) + elif this_n_unori != curr_n_unori: + if curr_n_unori: + self._list.append(KnotInfoSeries(cross_nr, is_knot, is_alt, curr_n_unori)) + curr_n_unori = this_n_unori + else: + continue + + if curr_n_unori: + self._list.append(KnotInfoSeries(cross_nr, is_knot, is_alt, curr_n_unori)) + return self._list + + + def __repr__(self): + r""" + Return the representation string of ``self``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(6, True, True) + Series of knots K6 + sage: _.__repr__() + 'Series of knots K6' + """ + if self._is_knot: + return 'Series of knots %s' %(self._name()) + else: + return 'Series of links %s' %(self._name()) + + + def __getitem__(self, item): + r""" + Return the given ``item`` from the list of ``self`` + (making the Python build-in ``list`` work). + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(6, True, True).inject() + Defining K6 + sage: list(K6) # indirect doctest + [, , ] + """ + from sage.rings.integer import Integer + if not type(item) in (int, Integer): + raise ValueError('Item must be an integer') + l =self.list() + max_item = len(l) + if item < 0 or item > max_item: + raise ValueError('Item must be non negative and smaller than %s' %(max_item)) + + return l[item] + + def __call__(self, item): + r""" + Return the given ``item`` from the list of ``self`` + (making the function call for ``self`` work). + In contrast to ``__getitem__`` the first ``item`` + has to be ``1`` (not ``0``). + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(6, True, True).inject() + Defining K6 + sage: K6(2) # indirect doctest + + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L8a21_0_1_0.inject() # optional - database_knotinfo + Defining L8a21_0_1_0 + sage: L8a21_0_1_0.series()(1) # optional - database_knotinfo + Series of links L8a1 + sage: L8a21_0_1_0.series()(21)(2) == L8a21_0_1_0 # optional - database_knotinfo + True + sage: L8a21_0_1_0.series()(21)('010') == L8a21_0_1_0 # optional database_knotinfo + True + """ + if self._name_unoriented: + if type(item) == str: + # allow input as dual number according to naming + item = int(item, 2) + return self[item] + + from sage.rings.integer import Integer + if not type(item) in (int, Integer): + raise ValueError('Item must be an integer') + l =self.list() + max_item = len(l)+1 + if item < 1 or item > max_item: + raise ValueError('Item must be positive and smaller than %s' %(max_item)) + + return l[item-1] + + def _name(self): + r""" + Return the name of the series. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(6, True, True)._name() + 'K6' + """ + is_knot = self._is_knot + cross_nr = self._crossing_number + is_alt = self._is_alternating + n_unori = self._name_unoriented + + alt = 'a' + if not is_alt: + alt = 'n' + + if is_knot: + if cross_nr > 10: + res = 'K%s%s' %(cross_nr, alt) + else: + res = 'K%s' %(cross_nr) + elif n_unori: + res = '%s' %(n_unori) + else: + res = 'L%s%s' %(cross_nr, alt) + return res + + + def inject(self, verbose=True): + """ + Inject ``self`` with its name into the namespace of the + Python code from which this function is called. + + INPUT: + + - ``verbose`` -- boolean (optional, default ``True``) to suppress + the message printed on the invocation + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(6, True, True).inject() + Defining K6 + sage: K6(2) + + """ + name = self._name() + if verbose: + print("Defining %s" % (name)) + from sage.repl.user_globals import set_global + set_global(name, self) -KnotInfo = KnotInfoBase('KnotInfo', db.read_row_dict()) +KnotInfo = KnotInfoBase('KnotInfo', db.row_names()) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 9ab236bd7c3..e72abe28166 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -26,14 +26,18 @@ .. SEEALSO:: - There are also tables of link and knot invariants at - https://www.indiana.edu/~knotinfo/ - and https://www.indiana.edu/~linkinfo/. + There are also tables of link and knot invariants at web-pages + `KnotInfo `__ and + `LinkInfo `__. These can be + used inside Sage after installing the optional package + ``database_knotinfo`` (type ``sage -i database_knotinfo`` in a command shell, + see :mod:`~sage.knots.knotinfo`). AUTHORS: - Miguel Angel Marco Buzunariz - Amit Jamadagni +- Sebastian Oehms (October 2020, add :meth:`identify_knotinfo` and meth:`is_isotopic`) """ # **************************************************************************** @@ -2515,7 +2519,7 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): .. NOTE:: Use the ``'az'`` normalization to agree with the data - in [KnotAtlas]_ and http://www.indiana.edu/~knotinfo/. + in [KnotAtlas]_ and `KnotInfo `__. EXAMPLES: @@ -3245,3 +3249,242 @@ def delta(u, v): image += l ims += sum(line(a[0], **kwargs) for a in im) return image + + + def identify_knotinfo(self, oriented=True, mirror_version=True, unique=True): + """ + Identify this link as an item of the KontInfo database (if possible). + + INPUT: + + - ``oriented`` -- boolean (default is ``True``). If set to ``False`` the orientation + of the link will be ignored and instead of an instance of :class:`~sage.knots.knotinfo.KnotInfoBase` + a series of links (instance of :class:`~sage.knots.knotinfo.KnotInfoSeries`) will be + returned collecting all links having the same ``name_unoriented`` (if this is unique + for ``self``) + + - ``mirror_version`` -- boolean (default is ``True``). If set to ``False`` the result + of the method will be just the instance of :class:`~sage.knots.knotinfo.KnotInfoBase` (by default the result + is a tuple of the instance and a boolean, see explanation of the output below) + + - ``unique`` -- boolean (default is ``True``). This only affects the case where a unique + identification is not possible. If set to ``False`` you can obtain a matching list + (see explanation of the output below) + + OUTPUT: + + A tuple ``(K, m)`` where ``K`` is an instance of :class:`~sage.knots.knotinfo.KnotInfoBase` and ``m`` a + boolean telling if ``self`` corresponse to the mirrored version of ``K`` or not. + + If ``oriented`` is set to ``False`` then the result is a series of links (instance of + :class:`~sage.knots.knotinfo.KnotInfoSeries`, see explanation above). + + If ``mirror_version`` is set to ``False`` then the result is just ``K`` (that is: ``m`` + is suppressed). + + If it is not possible to determine a unique result a ``NotImplementedError`` will be + raised. To avoid this you can set ``unique`` to ``False``. You will get a list of matching + candidates instead. + + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = Link([[4,2,5,1], [10,3,11,4], [5,16,6,17], [7,12,8,13], + ....: [18,9,19,10], [2,11,3,12], [13,20,14,21], [15,6,16,7], + ....: [22,18,1,17], [8,19,9,20], [21,14,22,15]]) + sage: L.identify_knotinfo() # optional - database_knotinfo + (, True) + + sage: K = KnotInfo.K10_25 # optional - database_knotinfo + sage: l = K.link() # optional - database_knotinfo + sage: l.identify_knotinfo() # optional - database_knotinfo + (, False) + + Lets identify the monster unknot (works without the database, as well):: + + sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5], + ....: [17,19,8,18], [9,10,11,14], [10,12,13,11], + ....: [12,19,15,13], [20,16,14,15], [16,20,17,2]]) + sage: L.identify_knotinfo() + (, False) + + Usage of option ``mirror_version``:: + + sage: L.identify_knotinfo(mirror_version=False) == KnotInfo.K0_1 + True + + Usage of option ``oriented``:: + + sage: KnotInfo.L10a1_0.inject() # optional - database_knotinfo + Defining L10a1_0 + sage: b = L10a1_0.link().braid() # optional - database_knotinfo + sage: l10 = Link(b) # optional - database_knotinfo + sage: l10.identify_knotinfo() # optional - database_knotinfo + Traceback (most recent call last): + ... + NotImplementedError: Sorry, this link cannot be uniquely determined + sage: l10.identify_knotinfo(oriented=False) # optional - database_knotinfo + (Series of links L10a1, False) + sage: _[0].inject() # optional - database_knotinfo + Defining L10a1 + sage: list(L10a1) # optional - database_knotinfo + [, ] + + Usage of option ``unique``:: + + sage: l = K.link(use_item=K.items.gauss_notation) # optional - database_knotinfo + sage: l.identify_knotinfo() # optional - database_knotinfo + Traceback (most recent call last): + ... + NotImplementedError: Sorry, this link cannot be uniquely determined + + sage: l.identify_knotinfo(unique=False) # optional - database_knotinfo + [, ] + + Clarifying the series around the Perko pair (:wikipedia:`Perko_pair`):: + + sage: for i in range(160, 166): # optional - database_knotinfo + ....: K = Knots().from_table(10, i) + ....: print('%s_%s' %(10, i), '--->', K.identify_knotinfo()) + 10_160 ---> (, False) + 10_161 ---> (, True) + 10_162 ---> (, False) + 10_163 ---> (, False) + 10_164 ---> (, False) + 10_165 ---> (, True) + + Clarifying ther Perko series against `SnapPy `__:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(10, True, True).inject() # optional - database_knotinfo + Defining K10 + sage: for i in range(160, 166): # optional - database_knotinfo snappy + ....: K = K10(i) + ....: k = K.link(use_item=K.items.name, snappy=True) + ....: print(k, '--->', k.sage_link().identify_knotinfo()) + Plink failed to import tkinter. + ---> (, True) + ---> (, False) + ---> (, True) + ---> (, True) + ---> (, True) + ---> (, True) + + sage: import snappy # optional - snappy + sage: k10_166 = snappy.Link('10_166') # optional - snappy + sage: k10_166.sage_link().identify_knotinfo() # optional - database_knotinfo snappy + (, False) + """ + from sage.knots.knotinfo import knotinfo_matching_list, is_knotinfo_available, KnotInfoSeries + cr = len(self.pd_code()) + co = self.number_of_components() + if co == 1 and cr > 12: + # we cannot not sure if this link is recorded in the KnotInfo database + + raise NotImplementedError('Sorry, this link cannot be uniquely determined') + if co > 1 and cr > 11: + # we cannot not sure if this link is recorded in the KnotInfo database + raise NotImplementedError('Sorry, this link cannot be uniquely determined') + + H = self.homfly_polynomial(normalization='az') + + if len(H.factor()) > 1: + # we cannot be sure if this is a prime link + raise NotImplementedError('Sorry, this link cannot be uniquely determined') + + Hm = None + l = knotinfo_matching_list(cr, co, homfly_polynomial=H) + if not l: + # try with the mirrored HOMFLY-PT polynomial + M, L = H.variables() + Hm = H.subs(L=~L, M=-M) + if H != Hm: + l = knotinfo_matching_list(cr, co, homfly_polynomial=Hm) + + if not l: + is_knotinfo_available(raise_error=True) + return None + + def answer(res, mirror): + if not oriented: + res = KnotInfoSeries(res.crossing_number(), res.is_knot(), res.is_alternating(), res.name_unoriented()) + + if mirror_version: + return res, mirror + else: + return res + + + if len(l) == 1: + return answer(l[0], Hm is not None) + + self_m = self.mirror_image() + + for L in l: + if L.braid() == self.braid(): + return answer(L, False) + if L.braid() == self_m.braid(): + return answer(L, True) + + # note that KnotInfo pd_notation works counter clockwise, see docstring + # of :meth:`link` of :class:`~sage.knots.knotinfo.KnotInfoBase`. + if L.pd_notation() == self.pd_code(): + return answer(L, True) + if L.pd_notation() == self_m.pd_code(): + return answer(L, False) + + if not oriented: + from sage.sets.set import Set + lu = list(Set([L.name_unoriented() for L in l])) + if len(lu) == 1: + return answer(l[0], Hm is not None) + elif unique: + raise NotImplementedError('Sorry, this link cannot be uniquely determined') + return lu + + if unique: + raise NotImplementedError('Sorry, this link cannot be uniquely determined') + return l + + def is_isotopic(self, other): + r""" + Check wether ``self`` is isotopic to ``other``. + + INPUT: + + - ``other`` -- another instance of :class:`Link` + + EXAMPLES:: + + sage: l1 = Link([[2, 9, 3, 10], [4, 13, 5, 14], [6, 11, 7, 12], + ....: [8, 1, 9, 2], [10, 7, 11, 8], [12, 5, 13, 6], + ....: [14, 3, 1, 4]]) + sage: l2 = Link([[1, 8, 2, 9], [9, 2, 10, 3], [3, 14, 4, 1], + ....: [13, 4, 14, 5], [5, 12, 6, 13], [11, 6, 12, 7], + ....: [7, 10, 8, 11]]) + sage: l1.is_isotopic(l2) + True + + sage: l3 = l2.mirror_image() + sage: l1.is_isotopic(l3) + False + """ + if not isinstance(other, Link): + return False + + if self == other: + # surely isotopic + return True + + if self.homfly_polynomial() != other.homfly_polynomial(): + # surely non isotopic + return False + + ki = self.identify_knotinfo() + if ki and type(ki) == tuple: + kio = other.identify_knotinfo() + if kio and type(kio) == tuple: + return ki == kio + + raise NotImplementedError('Comparison not possible!') From 654a20f4dbdaa9bdb7ac10bf7ac1e054ac2ae685 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Sat, 7 Nov 2020 21:59:39 +0100 Subject: [PATCH 061/232] 30352: corrections according to review --- src/sage/databases/knotinfo_db.py | 193 +++++++++-------- src/sage/features/databases.py | 30 +++ src/sage/knots/knotinfo.py | 330 ++++++++++++++++-------------- src/sage/knots/link.py | 140 +++++++------ 4 files changed, 395 insertions(+), 298 deletions(-) diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py index 25b472cfff6..f28a9bb6a98 100644 --- a/src/sage/databases/knotinfo_db.py +++ b/src/sage/databases/knotinfo_db.py @@ -2,9 +2,10 @@ r""" KontInfo Database -This module contains the class :class:`KnotInfoDataBase` and auxilary classes for it -which serves as an interface to the lists of named knots and links provided at the web-pages -`KnotInfo `__ and `LinkInfo `__. +This module contains the class :class:`KnotInfoDataBase` and auxilary classes +for it which serves as an interface to the lists of named knots and links provided +at the web-pages `KnotInfo `__ and +`LinkInfo `__. AUTHORS: @@ -37,9 +38,10 @@ class KnotInfoColumnTypes(Enum): r""" - Enum class to specify if a column from the table of knots and links provided at the web-pages - `KnotInfo `__ and `LinkInfo `__. - is used for knots only, links only or both. + Enum class to specify if a column from the table of knots and links provided + at the web-pages `KnotInfo `__ and + `LinkInfo `__. is used for knots only, + links only or both. EXAMPLES:: @@ -57,16 +59,23 @@ class KnotInfoColumnTypes(Enum): class KnotInfoColumns(Enum): r""" - Enum class to select a column from the table of knots and links provided at the web-pages - `KnotInfo `__ and `LinkInfo `__. + Enum class to select a column from the table of knots and links provided + at the web-pages `KnotInfo `__ and + `LinkInfo `__. EXAMPLES:: - sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns, KnotInfoColumnTypes + sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: KnotInfoColumns('Columns', ki_db.read_column_dict()) + sage: cols = ki_db.columns(); cols - sage: [col.column_name() for col in _ if col.column_type() == col.types.OnlyLinks] # optional - database_knotinfo + sage: from sage.databases.knotinfo_db import KnotInfoColumns + sage: isinstance(cols.name, KnotInfoColumns) + True + + sage: def only_links(c): + ....: return c.column_type() == c.types.OnlyLinks + sage: [c.column_name() for c in cols if only_links(c)] # optional - database_knotinfo ['Name - Unoriented', 'Orientation', 'Unoriented Rank', @@ -92,10 +101,10 @@ def types(self): EXAMPLES:: - sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns + sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: KIcols = KnotInfoColumns('Columns', ki_db.read_column_dict()) - sage: KIcols.dt_code.column_type() == KIcols.dt_code.types.OnlyLinks + sage: cols = ki_db.columns() + sage: cols.dt_code.column_type() == cols.dt_code.types.OnlyLinks True """ return KnotInfoColumnTypes @@ -106,10 +115,10 @@ def column_name(self): EXAMPLES:: - sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns + sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: KIcols = KnotInfoColumns('Columns', ki_db.read_column_dict()) - sage: KIcols.dt_code.column_name() + sage: cols = ki_db.columns() + sage: cols.dt_code.column_name() 'DT code' """ return self.value[0] @@ -121,14 +130,14 @@ def column_type(self): EXAMPLES:: - sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns, KnotInfoColumnTypes + sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: KIcols = KnotInfoColumns('Columns', ki_db.read_column_dict()) - sage: KIcols.homfly_polynomial.column_type() + sage: cols = ki_db.columns() + sage: cols.homfly_polynomial.column_type() - sage: KIcols.homflypt_polynomial.column_type() + sage: cols.homflypt_polynomial.column_type() - sage: KIcols.name.column_type() + sage: cols.name.column_type() """ return self.value[1] @@ -139,12 +148,12 @@ def description_webpage(self, new=0, autoraise=True): EXAMPLES:: - sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns + sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: KIcols = KnotInfoColumns('Columns', ki_db.read_column_dict()) - sage: KIcols.pd_notation.description_webpage() # not tested + sage: cols = ki_db.columns() + sage: cols.pd_notation.description_webpage() # not tested True - sage: KIcols.homflypt_polynomial.description_webpage() # not tested + sage: cols.homflypt_polynomial.description_webpage() # not tested True """ import webbrowser @@ -218,6 +227,21 @@ def csv(self): """ return '%s.csv' %(self.value[1]) + def sobj_path(self): + r""" + Return the path name under which the data is stored internally + in ``sobj`` files. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.sobj_path().endswith('knotinfo') + True + """ + return os.path.join(SAGE_SHARE, 'knotinfo') + + def sobj_num_knots(self): r""" Return the file name under which the number of knots @@ -323,16 +347,18 @@ class KnotInfoDataBase(SageObject): r""" Database interface to KnotInfo - The original data are obtained from KnotInfo web-page (URL see the example below). In order - to have these data installed during the build process as a sage-package they are converted - as csv files into a tarball. This tarball has been created using the method :meth:`create_spkg_tarball`. + The original data are obtained from KnotInfo web-page (URL see the example + below). In order to have these data installed during the build process as + a sage-package they are converted as csv files into a tarball. This tarball + has been created using the method :meth:`create_spkg_tarball`. EXAMPLES:: sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() sage: ki_db.filename.knots - + """ filename = KnotInfoFilename @@ -344,13 +370,15 @@ def __init__(self): EXAMPLES:: sage: from sage.databases.knotinfo_db import KnotInfoDataBase - sage: from sage.env import SAGE_SHARE sage: ki_db = KnotInfoDataBase() sage: ki_db.filename.links - + """ - self._package = 'database_knotinfo' - version_file = os.path.join(SAGE_ROOT, 'build/pkgs/%s/package-version.txt' %self._package) + from sage.features.databases import DatabaseKnotInfo + self._feature = DatabaseKnotInfo() + self._sobj_path = KnotInfoFilename.knots.sobj_path() + version_file = os.path.join(SAGE_ROOT, 'build/pkgs/%s/package-version.txt' %self._feature.spkg) f = open(version_file) self._version = f.read().splitlines()[0] f.close() @@ -360,61 +388,61 @@ def __init__(self): self._names_column = 'name' self._components_column = 'components' self._knot_prefix = 'K' - self._import_path = os.path.join(SAGE_SHARE, 'knotinfo') self._knot_list = None self._link_list = None - self._available = None + self._demo = None self._num_knots = None - def is_available(self): + def demo_version(self): r""" - Return wether the KnotInfo databases are installed or not. + Return wether the KnotInfo databases are installed completely or + just the demo version is used. EXAMPLES:: sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: ki_db.is_available() # optional - database_knotinfo - True + sage: ki_db.demo_version() # optional - database_knotinfo + False """ - if not self._available: - try: - lib_path = self._import_path - filename = self.filename.knots.sobj_num_knots() - self._num_knots = load('%s/%s' %(lib_path, filename)) - self._available = True - except FileNotFoundError: - self._available = False + if not self._demo: + if self._feature.is_present(): + self._num_knots = load(self._feature.absolute_path()) + self._demo = False + else: + self._demo = True self._num_knots = len([v for v in row_demo_sample.values() if v[1]==1]) - return self._available + return self._demo def create_spkg_tarball(self, path_for_src=None): r""" - Create a tarball for the sage-package ``database_knotinfo`` in the ``upstream`` directory. This - utility should only be used by users who know what they do in case of a switch to a new - version of the data files (that is if the original files on KnotInfo web-page have changed). - In that case an invocation of ``sage -package update database_knotinfo `` and - ``sage -package fix-checksum database_knotinfo`` will be necessary. + Create a tarball for the sage-package ``database_knotinfo`` in the + ``upstream`` directory. This utility should only be used by users who + know what they do in case of a switch to a new version of the data files + (that is if the original files on KnotInfo web-page have changed). In that + case an invocation of ``sage -package update database_knotinfo `` + and ``sage -package fix-checksum database_knotinfo`` will be necessary. INPUT: - -- ``path_for_src`` - string of the path under which the source are stored in a - subdirectory called ``src``. In that directory there should be the data files in - csv format (for example ``KnotInfoDataBase.filename.knots.csv()``) + - ``path_for_src`` -- string of the path under which the source are + stored in a subdirectory called ``src``. In that directory there should + be the data files in csv format (for example + ``KnotInfoDataBase.filename.knots.csv()``) EXAMPLES:: sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: ki_db.create_spkg_tarball() # not tested (because of internet access) + sage: ki_db.create_spkg_tarball() # not tested (internet access) """ if not path_for_src: path_for_src = os.environ['PWD'] - os.system('cd %s; tar -cvjSf %s/upstream/%s-%s.tar.bz2 src' %(path_for_src, SAGE_ROOT, self._package, self._version) ) + os.system('cd %s; tar -cvjSf %s/upstream/%s-%s.tar.bz2 src' %(path_for_src, SAGE_ROOT, self.feature.spkg, self._version) ) def version(self): @@ -438,7 +466,7 @@ def knot_list(self): sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: len(ki_db.knot_list()) # not tested because its only used on installation + sage: len(ki_db.knot_list()) # not tested (just used on installation) """ if self._knot_list: return self._knot_list @@ -460,7 +488,7 @@ def link_list(self): sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: len(ki_db.link_list()) # not tested because its only used on installation + sage: len(ki_db.link_list()) # not tested (just used on installation) """ if self._link_list: return self._link_list @@ -481,7 +509,7 @@ def create_col_dict_sobj(self): sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: ki_db.create_col_dict_sobj() # not tested because its only used on installation + sage: ki_db.create_col_dict_sobj() # not tested (used on installation) """ knot_list = self.knot_list() knot_column_names = knot_list[0] @@ -491,10 +519,10 @@ def create_col_dict_sobj(self): link_column_names = link_list[0] from sage.misc.misc import sage_makedirs - sage_makedirs(self._import_path) + sage_makedirs(self._sobj_path) num_knots = len_knots - 1 - save(num_knots, '%s/%s' %(self._import_path, self.filename.knots.sobj_num_knots())) + save(num_knots, '%s/%s' %(self._sobj_path, self.filename.knots.sobj_num_knots())) column_dict = {} @@ -530,7 +558,7 @@ def create_col_dict_sobj(self): col_type = KnotInfoColumnTypes.OnlyLinks column_dict[col] = [name, col_type] - save(column_dict, '%s/%s' %(self._import_path, self.filename.knots.sobj_column())) + save(column_dict, '%s/%s' %(self._sobj_path, self.filename.knots.sobj_column())) @@ -541,16 +569,17 @@ def create_data_sobj(self): strings giving the entries of the database table. The length of these lists depends on the type of the corresponding - column. If a column is used in both tables (``KnotInfoColumnTypes.KnotsAndLinks``) - the list of proper links is appended to the list of knots. - In both other cases the lenght of the list corresponds to - the number of listed knots and proper links respectively. + column. If a column is used in both tables + (``KnotInfoColumnTypes.KnotsAndLinks``) the list of proper links + is appended to the list of knots. In both other cases the lenght + of the list corresponds to the number of listed knots and proper + links respectively. EXAMPLES:: sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: ki_db.create_data_sobj() # not tested because its only used on installation + sage: ki_db.create_data_sobj() # not tested (just used on installation) """ knot_list = self.knot_list() link_list = self.link_list() @@ -586,9 +615,9 @@ def create_data_sobj(self): val_list.append(link_list[i][col.name]) if val_list: - save(val_list, '%s/%s' %(self._import_path, self.filename.knots.sobj_data(col))) + save(val_list, '%s/%s' %(self._sobj_path, self.filename.knots.sobj_data(col))) - save(row_dict, '%s/%s' %(self._import_path, self.filename.knots.sobj_row())) + save(row_dict, '%s/%s' %(self._sobj_path, self.filename.knots.sobj_row())) @cached_method @@ -628,11 +657,11 @@ def read_column_dict(self): sage: len(ki_db.read_column_dict()) # optional - database_knotinfo 122 """ - if not self.is_available(): + if self.demo_version(): return column_demo_sample - lib_path = self._import_path + sobj_path = self._sobj_path filename = self.filename.knots.sobj_column() - return load('%s/%s' %(lib_path, filename)) + return load('%s/%s' %(sobj_path, filename)) # ------------------------------------------------------------------------------------------------------------- # read the dictionary for the row names that is the knot and link names from sobj-file @@ -656,11 +685,11 @@ def read_row_dict(self): sage: ki_db.read_row_dict()['K7_1'] [8, 1] """ - if not self.is_available(): + if self.demo_version(): return row_demo_sample - lib_path = self._import_path + sobj_path = self._sobj_path filename = self.filename.knots.sobj_row() - return load('%s/%s' %(lib_path, filename)) + return load('%s/%s' %(sobj_path, filename)) # ------------------------------------------------------------------------------------------------------------- # return a dictionary to obtain the original name to a row_dict key @@ -707,7 +736,7 @@ def read_num_knots(self): 2978 """ if not self._num_knots: - self.is_available() + self.demo_version() return self._num_knots @@ -736,17 +765,17 @@ def read(self, column): if not isinstance(column, KnotInfoColumns): raise TypeError('column must be an instance of enum %s' (KnotInfoColumns)) - if not self.is_available(): + if self.demo_version(): return data_demo_sample[column] - lib_path = self._import_path + sobj_path = self._sobj_path if column.column_type() == column.types.OnlyLinks: filename = self.filename.links.sobj_data(column) else: filename = self.filename.knots.sobj_data(column) verbose('loading data library %s ...' %(filename)) - res = load('%s/%s' %(lib_path, filename)) + res = load('%s/%s' %(sobj_path, filename)) verbose('... finished!') return res diff --git a/src/sage/features/databases.py b/src/sage/features/databases.py index ed0fb65f07c..37c2bc0cbe9 100644 --- a/src/sage/features/databases.py +++ b/src/sage/features/databases.py @@ -64,3 +64,33 @@ def __init__(self): StaticFile.__init__(self, "John Jones's tables of number fields", filename='jones/jones.sobj', spkg="database_jones_numfield") + + +class DatabaseKnotInfo(StaticFile): + r""" + A :class:`Feature` which describes the presence of the databases at the + web-pages `KnotInfo `__ and + `LinkInfo `__. + + + + EXAMPLES:: + + sage: from sage.features.databases import DatabaseKnotInfo + sage: DatabaseKnotInfo().is_present() # optional: database_knotinfo + FeatureTestResult('KnotInfo and LinkInfo databases', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.databases import DatabaseKnotInfo + sage: isinstance(DatabaseKnotInfo(), DatabaseKnotInfo) + True + """ + from sage.databases.knotinfo_db import KnotInfoFilename + StaticFile.__init__(self, "KnotInfo and LinkInfo databases", + filename=KnotInfoFilename.knots.sobj_num_knots(), + spkg='database_knotinfo', + search_path = [KnotInfoFilename.knots.sobj_path()], + url=KnotInfoFilename.knots.url()) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index b91498cfa41..21883be92fd 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -2,37 +2,44 @@ r""" Access to the KnotInfo database -This module contains the class :class:`KnotInfoBase` which is derived from :class:`Enum` -and provides knots and links listed in the databases at the web-pages `KnotInfo `__ +This module contains the class :class:`KnotInfoBase` which is derived from +:class:`Enum` and provides knots and links listed in the databases at the +web-pages `KnotInfo `__ and `LinkInfo `__ as its items. -This interface contains a set of about twenty knots and links statically as demonstration cases. The complete -database can be installed as an optional Sage package using +This interface contains a set of about twenty knots and links statically as +demonstration cases. The complete database can be installed as an optional Sage +package using -- ``sage -i database_knotinfo`` (does not install if the current version is already present) -- ``sage -f database_knotinfo`` (installs even if the current version is already present) +- ``sage -i database_knotinfo`` (does not install if the current version is present) +- ``sage -f database_knotinfo`` (installs even if the current version is present) -To perform all the doctests concerning the usage of the database on the installation add the option ``-c``. -In this case (for instance ``sage -f -c database_knotinfo``) the installation breaks on failing tests. +To perform all the doctests concerning the usage of the database on the installation +add the option ``-c``. In this case (for instance ``sage -f -c database_knotinfo``) +the installation breaks on failing tests. -The installation of the complete database will be necessary in order to have access to all the properties -recorded in the databases, as well. +The installation of the complete database will be necessary in order to have +access to all the properties recorded in the databases, as well. -Be aware that there are a couple of conventions used differently on KnotInfo as in Sage, especially -concerning the selection of the symmetry version of the link. In our transitions to Sage objects -these are translated (by default) in order to avoid confusion about exchanged mirror versions. +Be aware that there are a couple of conventions used differently on KnotInfo as +in Sage, especially concerning the selection of the symmetry version of the link. +In our transitions to Sage objects these are translated (by default) in order to +avoid confusion about exchanged mirror versions. Briefly, these differences are: - - ``pd_notation`` -- KnotInfo: counter clockwise Sage: clockwise, see note in - :meth:`KnotInfoBase.link` +- ``pd_notation`` -- KnotInfo: counter clockwise Sage: clockwise, see note + in :meth:`KnotInfoBase.link` - - ``homfly_polynomial`` -- KnotInfo: ``v`` Sage: `1/a`, see note in :meth:`KnotInfoBase.homfly_polynomial`. +- ``homfly_polynomial`` -- KnotInfo: ``v`` Sage: `1/a`, see note in + :meth:`KnotInfoBase.homfly_polynomial`. - - ``braid_notation`` -- This is used accordingly: The crossing of the braid generators are positive - in both systems. Here it is listed because there could arise confusion from the source where they are - taken from. There, the braid generators are assumed to have a negative crossing - (see definition 3 of `Gittings, T., "Minimum Braids: A Complete Invariant of Knots and Links `__). +- ``braid_notation`` -- This is used accordingly: The crossing of the braid + generators are positive in both systems. Here it is listed because there could + arise confusion from the source where they are taken from. There, the braid + generators are assumed to have a negative crossing (see definition 3 of + `Gittings, T., "Minimum Braids: A Complete Invariant of Knots and Links + `__). EXAMPLES:: @@ -82,30 +89,34 @@ sage: type(l) -If you have `SnapPy `__ installed inside Sage -you can obtain an instance of :class:`~spherogram.links.links_base.Link`, too:: +If you have `SnapPy `__ installed inside +Sage you can obtain an instance of :class:`~spherogram.links.links_base.Link`, +too:: sage: L6 = KnotInfo.L6a2_0 - sage: l6s = L6.link(snappy=True); l6s # optional - snappy + sage: l6s = L6.link(snappy=True); l6s # optional - snappy Plink failed to import tkinter. - sage: type(l6s) # optional - snappy + sage: type(l6s) # optional - snappy sage: l6 = L6.link() - sage: l6 == l6s.sage_link() # optional - snappy + sage: l6 == l6s.sage_link() # optional - snappy True - sage: l6sn = L6.link(use_item=L6.items.name, snappy=True); l6sn # optional - snappy + sage: L6.link(L6.items.name, snappy=True) # optional - snappy - sage: l6s == l6sn # optional - snappy + sage: l6sn = _ # optional - snappy + sage: l6s == l6sn # optional - snappy False - sage: l6sn.sage_link().is_isotopic(l6) # optional - snappy + sage: l6sn.sage_link().is_isotopic(l6) # optional - snappy True -But observe that the name conversion to SnapPy does not distingiush orientation types:: +But observe that the name conversion to SnapPy does not distingiush orientation +types:: sage: L6b = KnotInfo.L6a2_1 - sage: l6bsn = L6b.link(use_item=L6b.items.name, snappy=True) # optional - snappy - sage: l6bsn.PD_code() == l6sn.PD_code() # optional - snappy + sage: L6b.link(L6b.items.name, snappy=True) # optional - snappy + + sage: _.PD_code() == l6sn.PD_code() # optional - snappy True Obtaining the HOMFLY-PT polynomial:: @@ -120,7 +131,7 @@ Obtaining the original string from the database for an arbitrary property:: - sage: K[K.items.classical_conway_name] # optional - database_knotinfo + sage: K[K.items.classical_conway_name] # optional - database_knotinfo '4_1' Further methods:: @@ -150,7 +161,9 @@ Using the ``column_type`` of a property:: - sage: [i.column_name() for i in K.items if i.column_type() != i.types.OnlyLinks and K[i] == 'Y'] # optional - database_knotinfo + sage: def select_column(i): + ....: return i.column_type() != i.types.OnlyLinks and K[i] == 'Y' + sage: [i.column_name() for i in K.items if select_column(i)] # optional - database_knotinfo ['Alternating', 'Fibered', 'Quasialternating', 'Adequate'] You can launch web-pages attached to the links:: @@ -178,13 +191,13 @@ or ``KnotInfo.L7`` before stroking the "tab-key". In the latter case the selection list will be reduced to proper links with 7 crossings. -Finally there is a method :meth:`Link.identify_knotinfo` of class :class:`Link` to find an instance +Finally there is a method :meth:`Link.get_knotinfo` of class :class:`Link` to find an instance in the KnotInfo database:: sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5], ....: [17,19,8,18], [9,10,11,14], [10,12,13,11], ....: [12,19,15,13], [20,16,14,15], [16,20,17,2]]) - sage: L.identify_knotinfo() + sage: L.get_knotinfo() (, False) @@ -226,26 +239,6 @@ -def is_knotinfo_available(raise_error=False): - r""" - Return whether the KnotInfo databases are installed or not. - - INPUT: - - - ``raise_error`` -- boolean (default ``False``) if set to ``True`` - an import error is raised in case KnotInfo is not installed - - EXAMPLES:: - - sage: from sage.knots.knotinfo import is_knotinfo_available - sage: is_knotinfo_available() # optional - database_knotinfo - True - """ - res = db.is_available() - if not res and raise_error: - raise ImportError('This functionality needs KnotInfo to be installed! Type `sage -i database_knotinfo` to have this done') - return res - @cached_function def knotinfo_matching_list(number_of_crossings, num_components, homfly_polynomial=None): r""" @@ -267,19 +260,29 @@ def knotinfo_matching_list(number_of_crossings, num_components, homfly_polynomia [, ] sage: [l.name for l in knotinfo_matching_list(4,2)] ['L2a1_0', 'L2a1_1', 'L4a1_0', 'L4a1_1'] - sage: L = KnotInfo.L6a3_0 # optional - database_knotinfo - sage: h = L.homfly_polynomial(sage_convention=True) # optional - database_knotinfo - sage: l = knotinfo_matching_list(L.crossing_number(), L.num_components(), h) # optional - database_knotinfo - sage: len(l) == 1 and l[0] == L # optional - database_knotinfo - True + + sage: KnotInfo.L6a2_0.inject() + Defining L6a2_0 + sage: L6a2_0.num_components() + 2 + sage: h = L6a2_0.homfly_polynomial(sage_convention=True) + sage: knotinfo_matching_list(6, 2, h) + [] + + Care is needed for non irreducible HOMFLY-PT polynomials:: + + sage: k4_1 = KnotInfo.K4_1.link() + sage: k5_2 = KnotInfo.K5_2.link() + sage: H = k4_1.connected_sum(k5_2).homfly_polynomial(normalization='az') + sage: knotinfo_matching_list(9, 1, H) # optional - database_knotinfo + [] """ res = [] if homfly_polynomial: l = knotinfo_matching_list(number_of_crossings, num_components) for L in l: - if homfly_polynomial: - if L.homfly_polynomial(sage_convention=True) != homfly_polynomial: - continue + if L.homfly_polynomial(sage_convention=True) != homfly_polynomial: + continue res.append(L) return res @@ -356,7 +359,8 @@ class KnotInfoBase(Enum): sage: [knot.name for knot in KnotInfo if knot.crossing_number() < 5] ['K0_1', 'K3_1', 'K4_1', 'L2a1_0', 'L2a1_1', 'L4a1_0', 'L4a1_1'] - More examples and information can be seen in the module header :mod:`~sage.knots.knotinfo` (by typing):: + More examples and information can be seen in the module header + :mod:`~sage.knots.knotinfo` (by typing):: sage: import sage.knots.knotinfo # not tested sage: sage.knots.knotinfo? # not tested @@ -377,19 +381,8 @@ def items(self): sage: from sage.knots.knotinfo import KnotInfo sage: L = KnotInfo.L4a1_0 sage: it = L.items - sage: [i.name for i in it if i.name.endswith('notation')] # optional - database_knotinfo - ['dt_notation', - 'conway_notation', - 'two_bridge_notation', - 'gauss_notation', - 'enhanced_gauss_notation', - 'pd_notation', - 'braid_notation', - 'positive_braid_notation', - 'positive_pd_notation', - 'strongly_quasipositive_braid_notation', - 'quasipositive_braid_notation', - 'arc_notation'] + sage: [i.name for i in it if i.name.startswith('braid')] + ['braid_index', 'braid_length', 'braid_notation'] sage: L.items.dt_notation.column_name() 'DT Notation' @@ -660,7 +653,8 @@ def braid_notation(self, original=False): break if not self.is_knot(): - # in linkinfo the braid_notation includes the braid_index as first item of a pair + # in linkinfo the braid_notation includes the braid_index as + # first item of a pair braid_notation = braid_notation[1] return braid_notation @@ -797,7 +791,8 @@ def is_knot(self): @cached_method def name_unoriented(self): r""" - Return the the part of the name of ``self`` which is independent on the orientation. + Return the the part of the name of ``self`` which is independent on the + orientation. EXAMPLES:: @@ -837,12 +832,12 @@ class of an oriented pair, `K = (S_3, S_1)`, with `S_i` EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfo - sage: [(L.name, L.symmetry_type()) for L in KnotInfo if L.is_knot() and L.crossing_number() < 6] - [('K0_1', 'fully amphicheiral'), - ('K3_1', 'reversible'), - ('K4_1', 'fully amphicheiral'), - ('K5_1', 'reversible'), - ('K5_2', 'reversible')] + sage: KnotInfo.K6_1.series().inject() + Defining K6 + sage: [(K.name, K.symmetry_type()) for K in K6] + [('K6_1', 'reversible'), + ('K6_2', 'reversible'), + ('K6_3', 'fully amphicheiral')] """ if not self.is_knot(): raise NotImplementedError('This is only available for knots') @@ -881,10 +876,10 @@ def is_amphicheiral(self, positive=False): EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfo - sage: K = KnotInfo.K12a_427 # optional - database_knotinfo - sage: K.is_amphicheiral() # optional - database_knotinfo + sage: K = KnotInfo.K12a_427 # optional - database_knotinfo + sage: K.is_amphicheiral() # optional - database_knotinfo False - sage: K.is_amphicheiral(positive=True) # optional - database_knotinfo + sage: K.is_amphicheiral(positive=True) # optional - database_knotinfo True """ if positive: @@ -921,7 +916,7 @@ def is_almost_alternating(self): sage: KnotInfo.K5_2.is_almost_alternating() # optional - database_knotinfo False """ - is_knotinfo_available(raise_error=True) # column not available in demo-version + db._feature.require() # column not available in demo-version return knotinfo_bool(self[self.items.almost_alternating]) @cached_method @@ -935,7 +930,7 @@ def is_quasi_alternating(self): sage: KnotInfo.K5_2.is_quasi_alternating() # optional - database_knotinfo True """ - is_knotinfo_available(raise_error=True) # column not available in demo-version + db._feature.require() # column not available in demo-version return knotinfo_bool(self[self.items.quasi_alternating]) @cached_method @@ -949,7 +944,7 @@ def is_adequate(self): sage: KnotInfo.K5_2.is_adequate() # optional - database_knotinfo True """ - is_knotinfo_available(raise_error=True) # column not available in demo-version + db._feature.require() # column not available in demo-version return knotinfo_bool(self[self.items.adequate]) @cached_method @@ -976,7 +971,7 @@ def is_quasipositive(self): sage: KnotInfo.K5_2.is_quasipositive() # optional - database_knotinfo True """ - is_knotinfo_available(raise_error=True) # column not available in demo-version + db._feature.require() # column not available in demo-version return knotinfo_bool(self[self.items.quasipositive]) @cached_method @@ -990,7 +985,7 @@ def is_strongly_quasipositive(self): sage: KnotInfo.K5_2.is_strongly_quasipositive() # optional - database_knotinfo True """ - is_knotinfo_available(raise_error=True) # column not available in demo-version + db._feature.require() # column not available in demo-version return knotinfo_bool(self[self.items.strongly_quasipositive]) @cached_method @@ -1004,7 +999,7 @@ def is_positive_braid(self): sage: KnotInfo.K5_2.is_positive_braid() # optional - database_knotinfo False """ - is_knotinfo_available(raise_error=True) # column not available in demo-version + db._feature.require() # column not available in demo-version return knotinfo_bool(self[self.items.positive_braid]) @cached_method @@ -1043,8 +1038,9 @@ def homfly_polynomial(self, var1=None, var2=None, original=False, sage_conventio element class according to the output of :meth:`Link.homfly_polynomial` of :class:`Link`. - The HOMFLY-PT polynomial `P(L)` of a link `L` satisfies the following skein relation - (see the corresponding `KnotInfo description page `__): + The HOMFLY-PT polynomial `P(L)` of a link `L` satisfies the following skein + relation (see the corresponding `KnotInfo description page + `__): .. MATH:: @@ -1053,13 +1049,15 @@ def homfly_polynomial(self, var1=None, var2=None, original=False, sage_conventio INPUT: - ``var1`` -- string for the name of the first variable (default depending - on keyword ``sage_convention``: ``'v'`` or ``'L'`` if ``sage_convention == True``) + on keyword ``sage_convention``: ``'v'`` or ``'L'`` if + ``sage_convention == True``) - ``var2`` -- string for the name of the second variable (default depending - on keyword ``sage_convention``: ``'z'`` or ``'M'`` if ``sage_convention == True``) + on keyword ``sage_convention``: ``'z'`` or ``'M'`` if + ``sage_convention == True``) - ``original`` -- boolean (default ``False``) if set to ``True`` the original table entry is returned as a string - - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` the conversion - to Sage's conventions (see the note below) is performed + - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` + the conversion to Sage's conventions (see the note below) is performed OUTPUT: @@ -1103,13 +1101,13 @@ def homfly_polynomial(self, var1=None, var2=None, original=False, sage_conventio sage: L4a1_1 = KnotInfo.L4a1_1 sage: PL4a1_1 = L4a1_1.homfly_polynomial(var1='x', var2='y'); PL4a1_1 -x^5*y + x^3*y^3 - x^5*y^-1 + 3*x^3*y + x^3*y^-1 - sage: PL4a1_1s = L4a1_1.homfly_polynomial(var1='x', var2='y', sage_convention=True); PL4a1_1s + sage: L4a1_1.homfly_polynomial(var1='x', var2='y', sage_convention=True) x^-3*y^3 + 3*x^-3*y + x^-3*y^-1 - x^-5*y - x^-5*y^-1 - sage: PL4a1_1s == L4a1_1.link().homfly_polynomial(var1='x', var2='y', normalization='az') + sage: _ == L4a1_1.link().homfly_polynomial(var1='x', var2='y', normalization='az') True - check the skein-relation from the KnotInfo description page (applied to one of - the positive crossings of the right-handed trefoil):: + check the skein-relation from the KnotInfo description page (applied to one + of the positive crossings of the right-handed trefoil):: sage: R = PK3_1.parent() sage: PO = R.one() @@ -1119,8 +1117,9 @@ def homfly_polynomial(self, var1=None, var2=None, original=False, sage_conventio sage: ~v*PK3_1 -v*PO == z*PL2a1_1 True - check the skein-relation given in the doc string of :meth:`Link.homfly_polynomial` of - :class:`Link` (applied to one of the positive crossings of the right-handed trefoil):: + check the skein-relation given in the doc string of :meth:`Link.homfly_polynomial` + of :class:`Link` (applied to one of the positive crossings of the + right-handed trefoil):: sage: Rs = PK3_1s.parent() sage: POs = Rs.one() @@ -1132,7 +1131,8 @@ def homfly_polynomial(self, var1=None, var2=None, original=False, sage_conventio TESTS:: - all(L.homfly_polynomial() == L.link().homfly_polynomial(normalization='az') for L in KnotInfo if L.crossing_number() > 0 and L.crossing_number() < 7) + sage: all(L.homfly_polynomial(sage_convention=True) == L.link().homfly_polynomial(normalization='az')\ + for L in KnotInfo if L.crossing_number() > 0 and L.crossing_number() < 7) True REFERENCES: @@ -1176,17 +1176,18 @@ def kauffman_polynomial(self, var1='a', var2='z', original=False): ``kauffman_polynomial`` for this knot or link as an instance of :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`. - The Kauffman polynomial `F(L)` respectivlely its corresponding invariant under - regular isotopy `\Delta (L) = a^{w(L)} F(L)` where `w(L)` is the writhe of - the link `L` satisfies the following skein relation - (see the corresponding `KnotInfo description page `__): + The Kauffman polynomial `F(L)` respectivlely its corresponding invariant + under regular isotopy `\Delta (L) = a^{w(L)} F(L)` where `w(L)` is the + writhe of the link `L` satisfies the following skein relation + (see the corresponding `KnotInfo description page + `__): .. MATH:: \Delta(O) = 1,\,\,\, \Delta(L_+) - \Delta(L_-) = z (\Delta(L_0 + \Delta(L_{\infty})) - Furthermore, removing a curl of sign `\epsilon` leads to a multiplication of `\Delta(L)` - with `a^{\epsilon}`. + Furthermore, removing a curl of sign `\epsilon` leads to a multiplication + of `\Delta(L)` with `a^{\epsilon}`. INPUT: @@ -1255,15 +1256,18 @@ def kauffman_polynomial(self, var1='a', var2='z', original=False): @cached_method def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False, original=False, sage_convention=False): r""" - Return the Jones polynomial according to the value of column ``jones_polynomial`` - for this knot or link as an element of the symbolic ring :class:`~sage.symbolic.ring.SR` - or an instance of :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial` - depending on the keyword ``skein_normalization``. Using the keyword ``puiseux`` instead - of an element of the symbolic ring an instance of :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` - can be returned. + Return the Jones polynomial according to the value of column + ``jones_polynomial`` for this knot or link as an element of the symbolic + ring :class:`~sage.symbolic.ring.SR` or an instance of + :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial` + depending on the keyword ``skein_normalization``. Using the keyword + ``puiseux`` instead of an element of the symbolic ring an instance of + :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` can be + returned. - The Jones polynomial `V(L)` of a link `L` satisfies the following skein relation - (see the corresponding `KnotInfo description page `__): + The Jones polynomial `V(L)` of a link `L` satisfies the following skein + relation (see the corresponding `KnotInfo description page + `__): .. MATH:: @@ -1272,36 +1276,40 @@ def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False INPUT: - ``variab`` -- variable (default: ``None``) used according to :meth:`Link.jones_polynomial` - - ``skein_normalization`` -- boolean (default: ``False``) used according to - :meth:`Link.jones_polynomial` - - ``puiseux`` -- boolean (default ``True``) only used in case ``skein_normalization=False``. - If set to ``True`` instead of an element of the symbolic ring an instance of - :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` is returned + - ``skein_normalization`` -- boolean (default: ``False``) used according + to :meth:`Link.jones_polynomial` + - ``puiseux`` -- boolean (default ``True``) only used in case + ``skein_normalization=False``. If set to ``True`` instead of an element + of the symbolic ring an instance of :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` + is returned - ``original`` -- boolean (default ``False``) if set to ``True`` the original table entry is returned as a string - - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` the conversion - to Sage's conventions (see the note below) is performed + - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` the + conversion to Sage's conventions (see the note below) is performed OUTPUT: Depends on the keywords (in excluding order): - - ``original=True`` a string according to the original value from the database - - ``skein_normalization=True`` a Laurent polynomial over the integers, more precisely - an instance of :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial` - - ``puiseux=True`` a puiseux series over the integers, more precisely an instance of - :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` + - ``original=True`` a string according to the original value from the + database + - ``skein_normalization=True`` a Laurent polynomial over the integers, + more precisely an instance of :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial` + - ``puiseux=True`` a puiseux series over the integers, more precisely an + instance of :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` In all other cases an element of the symbolic ring :class:`~sage.symbolic.ring.SR`. .. NOTE:: - The only difference of conventions concerning the Jones polynomial is its representation - in the case of proper links. KnotInfo does not display these polynomials in the indeterminate - `t` used in the skein relation. Instead a variable `x` is used defined by `x^2 = t`. - Sage uses `t` in both cases, knots and proper links. Thus, to obtain the Jones polynomial - for a proper link in `t` you have to set the keyword ``sage_convention`` to ``True``. + The only difference of conventions concerning the Jones polynomial is + its representation in the case of proper links. KnotInfo does not + display these polynomials in the indeterminate `t` used in the skein + relation. Instead a variable `x` is used defined by `x^2 = t`. Sage + uses `t` in both cases, knots and proper links. Thus, to obtain the + Jones polynomial for a proper link in `t` you have to set the keyword + ``sage_convention`` to ``True``. EXAMPLES:: @@ -1353,8 +1361,8 @@ def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False sage: Ljs == ljs True - Check the skein-relation from the KnotInfo description page (applied to one of - the positive crossings of the right-handed trefoil):: + Check the skein-relation from the KnotInfo description page (applied to one + of the positive crossings of the right-handed trefoil):: sage: K3_1 = KnotInfo.K3_1 sage: K3_1j = K3_1.jones_polynomial() @@ -1461,8 +1469,8 @@ def alexander_polynomial(self, var='t', original=False, sage_convention=False): - ``var`` -- (default: ``'t'``) the variable - ``original`` -- boolean (optional, default ``False``) if set to ``True`` the original table entry is returned as a string - - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` the - conversion to Sage's conventions (see the note below) is performed + - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` + the conversion to Sage's conventions (see the note below) is performed OUTPUT: @@ -1503,7 +1511,7 @@ def alexander_polynomial(self, var='t', original=False, sage_convention=False): Launch the KnotInfo description web-page:: - sage: K.items.alexander_polynomial.description_webpage() # not tested + sage: K.items.alexander_polynomial.description_webpage() # not tested True """ alexander_polynomial = self[self.items.alexander_polynomial] @@ -1608,12 +1616,12 @@ def link(self, use_item=db.columns().pd_notation, snappy=False): using ``snappy``:: sage: K7 = KnotInfo.K7_2 - sage: k7s = K7.link(snappy=True); k7s # optional - snappy + sage: k7s = K7.link(snappy=True); k7s # optional - snappy - sage: K7.link(use_item=K7.items.name, snappy=True) # optional - snappy + sage: K7.link(K7.items.name, snappy=True) # optional - snappy - sage: k7sn = _ # optional - snappy - sage: k7s == k7sn # optional - snappy + sage: k7sn = _ # optional - snappy + sage: k7s == k7sn # optional - snappy False sage: k7s.sage_link().is_isotopic(k7sn.sage_link()) # optional - snappy True @@ -1721,9 +1729,9 @@ def series(self, overview=True): INPUT: - - ``overview`` -- boolean (optional, default ``True``) if set to ``False`` - the series will be reduced to the unoriented type of ``self`` - in the case of proper links. + - ``overview`` -- boolean (optional, default ``True``) if set to + ``False`` the series will be reduced to the unoriented type of + ``self`` in the case of proper links. EXAMPLES:: @@ -1831,11 +1839,15 @@ class KnotInfoSeries(UniqueRepresentation): INPUT: - - ``crossing_number`` -- integer giving the crossing numer of this series of links - - ``is_knot`` -- boolean wether this series is a series of knots or proper links - - ``is_alternating`` -- boolean wether this series is restriced to alternating links or not. + - ``crossing_number`` -- integer giving the crossing numer of this series + of links + - ``is_knot`` -- boolean wether this series is a series of knots + or proper links + - ``is_alternating`` -- boolean wether this series is restriced to + alternating links or not This is not relevant for knots with less than 11 crossings - - ``name_unoriented`` -- string restricting the series to all links with that ``name_unoriented`` + - ``name_unoriented`` -- string restricting the series to all links with + that ``name_unoriented`` EXAMPLES:: @@ -1984,13 +1996,15 @@ def __call__(self, item): sage: from sage.knots.knotinfo import KnotInfo - sage: KnotInfo.L8a21_0_1_0.inject() # optional - database_knotinfo + sage: KnotInfo.L8a21_0_1_0.inject() # optional - database_knotinfo Defining L8a21_0_1_0 - sage: L8a21_0_1_0.series()(1) # optional - database_knotinfo + sage: L8a21_0_1_0.series().inject() # optional - database_knotinfo + Defining L8a + sage: L8a(1) # optional - database_knotinfo Series of links L8a1 - sage: L8a21_0_1_0.series()(21)(2) == L8a21_0_1_0 # optional - database_knotinfo + sage: L8a(21)(2) == L8a21_0_1_0 # optional - database_knotinfo True - sage: L8a21_0_1_0.series()(21)('010') == L8a21_0_1_0 # optional database_knotinfo + sage: L8a(21)('010') == L8a21_0_1_0 # optional - database_knotinfo True """ if self._name_unoriented: diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index e72abe28166..5b0c263cc80 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -37,7 +37,7 @@ - Miguel Angel Marco Buzunariz - Amit Jamadagni -- Sebastian Oehms (October 2020, add :meth:`identify_knotinfo` and meth:`is_isotopic`) +- Sebastian Oehms (October 2020, add :meth:`get_knotinfo` and meth:`is_isotopic`) """ # **************************************************************************** @@ -3251,40 +3251,43 @@ def delta(u, v): return image - def identify_knotinfo(self, oriented=True, mirror_version=True, unique=True): + def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): """ Identify this link as an item of the KontInfo database (if possible). INPUT: - - ``oriented`` -- boolean (default is ``True``). If set to ``False`` the orientation - of the link will be ignored and instead of an instance of :class:`~sage.knots.knotinfo.KnotInfoBase` - a series of links (instance of :class:`~sage.knots.knotinfo.KnotInfoSeries`) will be - returned collecting all links having the same ``name_unoriented`` (if this is unique - for ``self``) + - ``oriented`` -- boolean (default is ``True``). If set to ``False`` the + orientation of the link will be ignored and instead of an instance of + :class:`~sage.knots.knotinfo.KnotInfoBase` a series of links (instance + of :class:`~sage.knots.knotinfo.KnotInfoSeries`) will be returned collecting + all links having the same ``name_unoriented`` (if this is unique for + ``self``) - - ``mirror_version`` -- boolean (default is ``True``). If set to ``False`` the result - of the method will be just the instance of :class:`~sage.knots.knotinfo.KnotInfoBase` (by default the result - is a tuple of the instance and a boolean, see explanation of the output below) + - ``mirror_version`` -- boolean (default is ``True``). If set to ``False`` + the result of the method will be just the instance of :class:`~sage.knots.knotinfo.KnotInfoBase` + (by default the result is a tuple of the instance and a boolean, see + explanation of the output below) - - ``unique`` -- boolean (default is ``True``). This only affects the case where a unique - identification is not possible. If set to ``False`` you can obtain a matching list - (see explanation of the output below) + - ``unique`` -- boolean (default is ``True``). This only affects the case + where a unique identification is not possible. If set to ``False`` you + can obtain a matching list (see explanation of the output below) OUTPUT: - A tuple ``(K, m)`` where ``K`` is an instance of :class:`~sage.knots.knotinfo.KnotInfoBase` and ``m`` a - boolean telling if ``self`` corresponse to the mirrored version of ``K`` or not. + A tuple ``(K, m)`` where ``K`` is an instance of :class:`~sage.knots.knotinfo.KnotInfoBase` + and ``m`` a boolean telling if ``self`` corresponse to the mirrored version + of ``K`` or not. - If ``oriented`` is set to ``False`` then the result is a series of links (instance of - :class:`~sage.knots.knotinfo.KnotInfoSeries`, see explanation above). + If ``oriented`` is set to ``False`` then the result is a series of links + (instance of :class:`~sage.knots.knotinfo.KnotInfoSeries`, see explanation above). - If ``mirror_version`` is set to ``False`` then the result is just ``K`` (that is: ``m`` - is suppressed). + If ``mirror_version`` is set to ``False`` then the result is just ``K`` + (that is: ``m`` is suppressed). - If it is not possible to determine a unique result a ``NotImplementedError`` will be - raised. To avoid this you can set ``unique`` to ``False``. You will get a list of matching - candidates instead. + If it is not possible to determine a unique result a ``NotImplementedError`` + will be raised. To avoid this you can set ``unique`` to ``False``. You + will get a list of matching candidates instead. EXAMPLES:: @@ -3293,25 +3296,40 @@ def identify_knotinfo(self, oriented=True, mirror_version=True, unique=True): sage: L = Link([[4,2,5,1], [10,3,11,4], [5,16,6,17], [7,12,8,13], ....: [18,9,19,10], [2,11,3,12], [13,20,14,21], [15,6,16,7], ....: [22,18,1,17], [8,19,9,20], [21,14,22,15]]) - sage: L.identify_knotinfo() # optional - database_knotinfo + sage: L.get_knotinfo() # optional - database_knotinfo (, True) - sage: K = KnotInfo.K10_25 # optional - database_knotinfo - sage: l = K.link() # optional - database_knotinfo - sage: l.identify_knotinfo() # optional - database_knotinfo + sage: K = KnotInfo.K10_25 # optional - database_knotinfo + sage: l = K.link() # optional - database_knotinfo + sage: l.get_knotinfo() # optional - database_knotinfo (, False) + Knots with more than 12 and proper links having more than 11 crossings + cannot be identified. In addition non prime links or even links whose + HOMFLY-PT polynomial is not irreducible cannot be identified:: + + sage: b, = BraidGroup(2).gens() + sage: Link(b**13).get_knotinfo() + Traceback (most recent call last): + ... + NotImplementedError: this knot having more than 12 crossings cannot be determined + + sage: KnotInfo.K9_12.link().get_knotinfo() # optional - database_knotinfo + Traceback (most recent call last): + ... + NotImplementedError: this (possibly non prime) link cannot be determined + Lets identify the monster unknot (works without the database, as well):: sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5], ....: [17,19,8,18], [9,10,11,14], [10,12,13,11], ....: [12,19,15,13], [20,16,14,15], [16,20,17,2]]) - sage: L.identify_knotinfo() + sage: L.get_knotinfo() (, False) Usage of option ``mirror_version``:: - sage: L.identify_knotinfo(mirror_version=False) == KnotInfo.K0_1 + sage: L.get_knotinfo(mirror_version=False) == KnotInfo.K0_1 True Usage of option ``oriented``:: @@ -3320,11 +3338,12 @@ def identify_knotinfo(self, oriented=True, mirror_version=True, unique=True): Defining L10a1_0 sage: b = L10a1_0.link().braid() # optional - database_knotinfo sage: l10 = Link(b) # optional - database_knotinfo - sage: l10.identify_knotinfo() # optional - database_knotinfo + sage: l10.get_knotinfo() # optional - database_knotinfo Traceback (most recent call last): ... - NotImplementedError: Sorry, this link cannot be uniquely determined - sage: l10.identify_knotinfo(oriented=False) # optional - database_knotinfo + NotImplementedError: this link cannot be uniquely determined + + sage: l10.get_knotinfo(oriented=False) # optional - database_knotinfo (Series of links L10a1, False) sage: _[0].inject() # optional - database_knotinfo Defining L10a1 @@ -3333,20 +3352,20 @@ def identify_knotinfo(self, oriented=True, mirror_version=True, unique=True): Usage of option ``unique``:: - sage: l = K.link(use_item=K.items.gauss_notation) # optional - database_knotinfo - sage: l.identify_knotinfo() # optional - database_knotinfo + sage: l = K.link(K.items.gauss_notation) # optional - database_knotinfo + sage: l.get_knotinfo() # optional - database_knotinfo Traceback (most recent call last): ... - NotImplementedError: Sorry, this link cannot be uniquely determined + NotImplementedError: this link cannot be uniquely determined - sage: l.identify_knotinfo(unique=False) # optional - database_knotinfo + sage: l.get_knotinfo(unique=False) # optional - database_knotinfo [, ] Clarifying the series around the Perko pair (:wikipedia:`Perko_pair`):: - sage: for i in range(160, 166): # optional - database_knotinfo + sage: for i in range(160, 166): # optional - database_knotinfo ....: K = Knots().from_table(10, i) - ....: print('%s_%s' %(10, i), '--->', K.identify_knotinfo()) + ....: print('%s_%s' %(10, i), '--->', K.get_knotinfo()) 10_160 ---> (, False) 10_161 ---> (, True) 10_162 ---> (, False) @@ -3356,14 +3375,17 @@ def identify_knotinfo(self, oriented=True, mirror_version=True, unique=True): Clarifying ther Perko series against `SnapPy `__:: + sage: import snappy # optional - snappy + Plink failed to import tkinter. sage: from sage.knots.knotinfo import KnotInfoSeries - sage: KnotInfoSeries(10, True, True).inject() # optional - database_knotinfo + sage: KnotInfoSeries(10, True, True) # optional - database_knotinfo + Series of knots K10 + sage: _.inject() # optional - database_knotinfo Defining K10 - sage: for i in range(160, 166): # optional - database_knotinfo snappy + sage: for i in range(160, 166): # optional - database_knotinfo snappy ....: K = K10(i) - ....: k = K.link(use_item=K.items.name, snappy=True) - ....: print(k, '--->', k.sage_link().identify_knotinfo()) - Plink failed to import tkinter. + ....: k = K.link(K.items.name, snappy=True) + ....: print(k, '--->', k.sage_link().get_knotinfo()) ---> (, True) ---> (, False) ---> (, True) @@ -3371,27 +3393,28 @@ def identify_knotinfo(self, oriented=True, mirror_version=True, unique=True): ---> (, True) ---> (, True) - sage: import snappy # optional - snappy - sage: k10_166 = snappy.Link('10_166') # optional - snappy - sage: k10_166.sage_link().identify_knotinfo() # optional - database_knotinfo snappy + sage: snappy.Link('10_166') # optional - snappy + + sage: _.sage_link().get_knotinfo() # optional - database_knotinfo snappy (, False) """ - from sage.knots.knotinfo import knotinfo_matching_list, is_knotinfo_available, KnotInfoSeries + from sage.knots.knotinfo import knotinfo_matching_list, KnotInfoSeries cr = len(self.pd_code()) co = self.number_of_components() if co == 1 and cr > 12: - # we cannot not sure if this link is recorded in the KnotInfo database + # we cannot not be sure if this link is recorded in the KnotInfo database - raise NotImplementedError('Sorry, this link cannot be uniquely determined') + raise NotImplementedError('this knot having more than 12 crossings cannot be determined') if co > 1 and cr > 11: - # we cannot not sure if this link is recorded in the KnotInfo database - raise NotImplementedError('Sorry, this link cannot be uniquely determined') + # we cannot not be sure if this link is recorded in the KnotInfo database + raise NotImplementedError('this link having more than 11 crossings cannot be determined') H = self.homfly_polynomial(normalization='az') if len(H.factor()) > 1: - # we cannot be sure if this is a prime link - raise NotImplementedError('Sorry, this link cannot be uniquely determined') + # we cannot be sure if this is a prime link (see the example for the connected + # sum of K4_1 and K5_2 in the doctest of :func:`knotinfo_matching_list`) + raise NotImplementedError('this (possibly non prime) link cannot be determined') Hm = None l = knotinfo_matching_list(cr, co, homfly_polynomial=H) @@ -3403,7 +3426,8 @@ def identify_knotinfo(self, oriented=True, mirror_version=True, unique=True): l = knotinfo_matching_list(cr, co, homfly_polynomial=Hm) if not l: - is_knotinfo_available(raise_error=True) + from sage.features.databases import DatabaseKnotInfo + DatabaseKnotInfo().require() return None def answer(res, mirror): @@ -3440,11 +3464,11 @@ def answer(res, mirror): if len(lu) == 1: return answer(l[0], Hm is not None) elif unique: - raise NotImplementedError('Sorry, this link cannot be uniquely determined') + raise NotImplementedError('this link cannot be uniquely determined') return lu if unique: - raise NotImplementedError('Sorry, this link cannot be uniquely determined') + raise NotImplementedError('this link cannot be uniquely determined') return l def is_isotopic(self, other): @@ -3481,10 +3505,10 @@ def is_isotopic(self, other): # surely non isotopic return False - ki = self.identify_knotinfo() + ki = self.get_knotinfo() if ki and type(ki) == tuple: - kio = other.identify_knotinfo() + kio = other.get_knotinfo() if kio and type(kio) == tuple: return ki == kio - raise NotImplementedError('Comparison not possible!') + raise NotImplementedError('comparison not possible!') From 255a768c8b881d9827d3b9db2c01775840842ff0 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Sat, 7 Nov 2020 22:18:24 +0100 Subject: [PATCH 062/232] 30352: once again --- src/sage/databases/knotinfo_db.py | 2 +- src/sage/knots/knotinfo.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py index f28a9bb6a98..9203a8d24e7 100644 --- a/src/sage/databases/knotinfo_db.py +++ b/src/sage/databases/knotinfo_db.py @@ -442,7 +442,7 @@ def create_spkg_tarball(self, path_for_src=None): if not path_for_src: path_for_src = os.environ['PWD'] - os.system('cd %s; tar -cvjSf %s/upstream/%s-%s.tar.bz2 src' %(path_for_src, SAGE_ROOT, self.feature.spkg, self._version) ) + os.system('cd %s; tar -cvjSf %s/upstream/%s-%s.tar.bz2 src' %(path_for_src, SAGE_ROOT, self._feature.spkg, self._version) ) def version(self): diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 21883be92fd..27b56dd68e5 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -1878,6 +1878,7 @@ def __init__(self, crossing_number, is_knot, is_alternating, name_unoriented=Non Python constructor. EXAMPLES:: + sage: from sage.knots.knotinfo import KnotInfoSeries sage: L6a = KnotInfoSeries(6, False, True); L6a Series of links L6a From 7173489d9f621191dc80c22197be4d9eaf1ee1cd Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Mon, 9 Nov 2020 16:51:47 +0100 Subject: [PATCH 063/232] 30352: rebase to 9.3.beta1 --- .../test/Adding_Pictures_and_screenshots.sws | Bin 281795 -> 281797 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/build/pkgs/sage_sws2rst/src/test/Adding_Pictures_and_screenshots.sws b/build/pkgs/sage_sws2rst/src/test/Adding_Pictures_and_screenshots.sws index d742ce4437f14561448399444c09589b9cd026fd..3dff6b3aada1900a931ac92a13222adcf874d512 100644 GIT binary patch delta 35 ocmX>+Q}F0a!GaO)Q}FOi!G Date: Wed, 18 Nov 2020 14:12:42 +0100 Subject: [PATCH 064/232] 30352: use braid.is_conjugated and restructure get_knotinfo --- src/sage/knots/knotinfo.py | 195 ++++++++++++++++------------- src/sage/knots/link.py | 250 +++++++++++++++++++++++++++++-------- 2 files changed, 304 insertions(+), 141 deletions(-) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 27b56dd68e5..94569598372 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -93,17 +93,26 @@ Sage you can obtain an instance of :class:`~spherogram.links.links_base.Link`, too:: - sage: L6 = KnotInfo.L6a2_0 + sage: L6 = KnotInfo.L6a1_0 sage: l6s = L6.link(snappy=True); l6s # optional - snappy Plink failed to import tkinter. + doctest:warning + ... + DeprecationWarning: the complex_field module is deprecated, please use sage.rings.complex_mpfr + See http://trac.sagemath.org/24483 for details. + doctest:warning + ... + DeprecationWarning: the complex_number module is deprecated, please use sage.rings.complex_mpfr + See http://trac.sagemath.org/24483 for details. + sage: type(l6s) # optional - snappy sage: l6 = L6.link() sage: l6 == l6s.sage_link() # optional - snappy True sage: L6.link(L6.items.name, snappy=True) # optional - snappy - + sage: l6sn = _ # optional - snappy sage: l6s == l6sn # optional - snappy False @@ -113,9 +122,9 @@ But observe that the name conversion to SnapPy does not distingiush orientation types:: - sage: L6b = KnotInfo.L6a2_1 + sage: L6b = KnotInfo.L6a1_1 sage: L6b.link(L6b.items.name, snappy=True) # optional - snappy - + sage: _.PD_code() == l6sn.PD_code() # optional - snappy True @@ -239,63 +248,6 @@ -@cached_function -def knotinfo_matching_list(number_of_crossings, num_components, homfly_polynomial=None): - r""" - Return a list of links from the KontInfo and LinkInfo tables with given properties. - - INPUT: - - - ``number_of_crossings`` -- Python ``int`` giving the (not necessarily minimal) - number of crossings to be matched - - ``num_components`` -- Python ``int`` giving the number of components - to be matched - - ``homfly_polynomial`` -- instance of :class:`~sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomial_mpair` - giving the HOMFLY-PT polynomial to be matched - - EXAMPLES:: - - sage: from sage.knots.knotinfo import KnotInfo, knotinfo_matching_list - sage: knotinfo_matching_list(3,1) - [, ] - sage: [l.name for l in knotinfo_matching_list(4,2)] - ['L2a1_0', 'L2a1_1', 'L4a1_0', 'L4a1_1'] - - sage: KnotInfo.L6a2_0.inject() - Defining L6a2_0 - sage: L6a2_0.num_components() - 2 - sage: h = L6a2_0.homfly_polynomial(sage_convention=True) - sage: knotinfo_matching_list(6, 2, h) - [] - - Care is needed for non irreducible HOMFLY-PT polynomials:: - - sage: k4_1 = KnotInfo.K4_1.link() - sage: k5_2 = KnotInfo.K5_2.link() - sage: H = k4_1.connected_sum(k5_2).homfly_polynomial(normalization='az') - sage: knotinfo_matching_list(9, 1, H) # optional - database_knotinfo - [] - """ - res = [] - if homfly_polynomial: - l = knotinfo_matching_list(number_of_crossings, num_components) - for L in l: - if L.homfly_polynomial(sage_convention=True) != homfly_polynomial: - continue - res.append(L) - return res - - for L in KnotInfo: - if L.crossing_number() > number_of_crossings: - continue - if L.num_components() != num_components: - continue - res.append(L) - - return res - - def eval_knotinfo(string, locals={}, to_tuple=True): r""" Preparse a string from the KnotInfo database and evaluate it by ``sage_eval``. @@ -371,6 +323,27 @@ class KnotInfoBase(Enum): Defining K7_1 sage: TestSuite(K7_1).run() """ + + def __gt__(self, other): + r""" + Implement comparision of different items in order to have ``sorted`` work. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L4a1_0 < KnotInfo.L4a1_1 # indirect doctest + True + sage: KnotInfo.L2a1_0 < KnotInfo.K3_1 # indirect doctest + False + sage: KnotInfo.K10_3 > KnotInfo.K3_1 # optional - database_knotinfo + True + """ + if self.__class__ is other.__class__: + tups = (not self.is_knot(), self.crossing_number(), self.value) + tupo = (not other.is_knot(), other.crossing_number(), other.value) + return tups > tupo + return NotImplemented + @property def items(self): r""" @@ -1723,15 +1696,17 @@ def inject(self, verbose=True): from sage.repl.user_globals import set_global set_global(name, self) - def series(self, overview=True): + def series(self, oriented=False): r""" Return the series of links ``self`` belongs to. INPUT: - - ``overview`` -- boolean (optional, default ``True``) if set to - ``False`` the series will be reduced to the unoriented type of - ``self`` in the case of proper links. + - ``oriented`` -- boolean (default False) it only affects proper links. + By default the items of the series will be again series of links + collecting all orientation mutants of an unoriented name. To obtain + the series of the individual links this keyword has to be set to + ``True``. EXAMPLES:: @@ -1743,15 +1718,17 @@ def series(self, overview=True): Defining L4a sage: L4a(1) Series of links L4a1 - sage: KnotInfo.L4a1_1.series(overview=False).inject() + sage: KnotInfo.L4a1_1.series(oriented=True).inject() Defining L4a1 + sage: L4a(1) == L4a1 + True sage: L4a1(1) """ - if overview: - S = KnotInfoSeries(self.crossing_number(), self.is_knot(), self.is_alternating()) - else: + if oriented: S = KnotInfoSeries(self.crossing_number(), self.is_knot(), self.is_alternating(), self.name_unoriented()) + else: + S = KnotInfoSeries(self.crossing_number(), self.is_knot(), self.is_alternating()) return S def diagram(self, single=False, new=0, autoraise=True): @@ -1888,29 +1865,40 @@ def __init__(self, crossing_number, is_knot, is_alternating, name_unoriented=Non self._is_knot = is_knot self._is_alternating = is_alternating self._name_unoriented = name_unoriented - self._list = None - def list(self): + @cached_method + def list(self, oriented=False): r""" Return this series as a Python list. + INPUT: + + - ``oriented`` -- boolean (default False) it only affects series of + proper links. By default the list items of a series of proper links + are again series of links collecting all orientation types of an + unoriented name. To obtain the list of the individual links this + keyword has to be set to ``True``. + EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfoSeries sage: K6 = KnotInfoSeries(6, True, True); K6 Series of knots K6 - sage: K6(3) - + sage: K6.list() + [, , ] + sage: KnotInfoSeries(2, False, True).inject() + Defining L2a + sage: L2a.list() + [Series of links L2a1] + sage: L2a.list(oriented=True) + [, ] """ - if self._list: - return self._list - is_knot = self._is_knot cross_nr = self._crossing_number is_alt = self._is_alternating n_unori = self._name_unoriented - self._list = [] + res = [] curr_n_unori = None for K in KnotInfo: if K.is_knot() != is_knot: @@ -1920,24 +1908,59 @@ def list(self): if not is_knot or cross_nr > 10: if K.is_alternating() != is_alt: continue - if is_knot: - self._list.append(K) + if is_knot or oriented: + res.append(K) else: this_n_unori = K.name_unoriented() if n_unori: if this_n_unori != n_unori: continue - self._list.append(K) + res.append(K) elif this_n_unori != curr_n_unori: if curr_n_unori: - self._list.append(KnotInfoSeries(cross_nr, is_knot, is_alt, curr_n_unori)) + res.append(KnotInfoSeries(cross_nr, is_knot, is_alt, curr_n_unori)) curr_n_unori = this_n_unori else: continue if curr_n_unori: - self._list.append(KnotInfoSeries(cross_nr, is_knot, is_alt, curr_n_unori)) - return self._list + res.append(KnotInfoSeries(cross_nr, is_knot, is_alt, curr_n_unori)) + return res + + + @cached_method + def lower_list(self, oriented=False): + r""" + Return this series together with all series with smaller crossing number + as a Python list. + + INPUT: + + - ``oriented`` -- boolean (default False) see the description + for :meth:`list`. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(5, True, True).lower_list() + [, + , + , + , + ] + sage: KnotInfoSeries(4, False, True).lower_list() + [Series of links L2a1, Series of links L4a1] + sage: KnotInfoSeries(4, False, True).lower_list(oriented=True) + [, + , + , + ] + """ + l = [] + for i in range(self._crossing_number): + LS = type(self)(i, self._is_knot, self._is_alternating, self._name_unoriented ) + l += LS.list(oriented=oriented) + return l + self.list(oriented=oriented) def __repr__(self): @@ -1974,7 +1997,7 @@ def __getitem__(self, item): from sage.rings.integer import Integer if not type(item) in (int, Integer): raise ValueError('Item must be an integer') - l =self.list() + l = self.list() max_item = len(l) if item < 0 or item > max_item: raise ValueError('Item must be non negative and smaller than %s' %(max_item)) @@ -2056,7 +2079,7 @@ def _name(self): def inject(self, verbose=True): - """ + r""" Inject ``self`` with its name into the namespace of the Python code from which this function is called. @@ -2080,6 +2103,4 @@ def inject(self, verbose=True): set_global(name, self) - - KnotInfo = KnotInfoBase('KnotInfo', db.row_names()) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 3aaaafd890b..c7eb60aad49 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -3251,8 +3251,146 @@ def delta(u, v): return image - def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): + def _markov_move_cmp(self, braid): + r""" + Return whether ``self`` can be transformed to the closure of ``braid`` + by a sequence of Markov moves. + + More precisely it is checked whether the braid of ``self`` is conjugated + to the given braid in the following sense. If both braids have the same + number of strands it is checked if they are conjugated to each other in + their common braid group (Markov move I). If the number of strands differs, + the braid having less strands is extended by Markov moves II (appendening + the largest generator or its inverse recursively) until a common braid + group can be achieved, where conjugation is tested. + + Be aware, that a negative result does not ensure that ``self`` is not + isotopic to the closure of ``braid``. + + EXAMPLES:: + + sage: b = BraidGroup(4)((1, 2, -3, 2, 2, 2, 2, 2, 2, -1, 2, 3, 2)) + sage: L = Link([[2, 1, 4, 5], [5, 4, 6, 7], [7, 6, 8, 9], [9, 8, 10, 11], + ....: [11, 10, 12, 13], [13, 12, 14, 15], [15, 14, 16, 17], + ....: [3, 17, 18, 19], [16, 1, 21, 18], [19, 21, 2, 3]]) + sage: L._markov_move_cmp(b) # both are isotopic to ``9_3`` + True + sage: bL = L.braid(); bL + s0^7*s1*s0^-1*s1 + sage: Lb = Link(b); Lb + Link with 1 component represented by 13 crossings + sage: Lb._markov_move_cmp(bL) + True + sage: L == Lb + False + sage: b.strands() > bL.strands() + True + + REFERENCES: + + - :wikipedia:`Markov_theorem` + """ + sb = self.braid() + sb_ind = sb.strands() + + ob = braid + ob_ind = ob.strands() + + if sb_ind == ob_ind: + return sb.is_conjugated(ob) + + if sb_ind > ob_ind: + # if the braid of self has more strands we have to perfom + # Markov II moves + B = sb.parent() + g = B.gen(ob_ind-1) + ob = B(ob) + if sb_ind > ob_ind+1: + # proceed by recursion + res = self._markov_move_cmp(ob*g) + if not res: + res = self._markov_move_cmp(ob*~g) + else: + res = sb.is_conjugated(ob*g) + if not res: + res = sb.is_conjugated(ob*~g) + return res + else: + L = Link(ob) + return L._markov_move_cmp(sb) + + + def _knotinfo_matching_list(self): + r""" + Return a list of links from the KontInfo and LinkInfo databases which match + the properties of ``self`` as much as possible. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L5a1_0.inject() + Defining L5a1_0 + sage: L5a1_0.link()._knotinfo_matching_list() + [] + sage: Link(L5a1_0.braid())._knotinfo_matching_list() + [, ] + + Care is needed for links having non irreducible HOMFLY-PT polynomials:: + + sage: k4_1 = KnotInfo.K4_1.link() + sage: k5_2 = KnotInfo.K5_2.link() + sage: k = k4_1.connected_sum(k5_2) + sage: k._knotinfo_matching_list() # optional - database_knotinfo + [] + """ + from sage.knots.knotinfo import KnotInfoSeries + cr = len(self.pd_code()) + co = self.number_of_components() + + if self.is_knot(): + l = KnotInfoSeries(cr, True, self.is_alternating()).lower_list(oriented=True) + else: + # as long as the method `is_alternating` is not implemented for + # proper links we have to sum the lists from both series + l = KnotInfoSeries(cr, False, True).lower_list(oriented=True) + l += KnotInfoSeries(cr, False, False).lower_list(oriented=True) + + Hp = self.homfly_polynomial(normalization='az') + pdm = self.mirror_image().pd_code() # for mirror_image see note below + br = self.braid() + br_ind = br.strands() + + res = [] + for L in l: + if L.num_components() != co: + continue + if L.homfly_polynomial(sage_convention=True) != Hp: + continue + res.append(L) + + if len(res) <= 1: + return res + + res_red = [] + for L in res: + if L.pd_notation() == pdm: + # note that KnotInfo pd_notation works counter clockwise. Therefore, + # to compensate this we compare with the mirrored pd_code. See also, + # docstring of :meth:`link` of :class:`~sage.knots.knotinfo.KnotInfoBase`. + return[L] # pd_notation is unique in the KnotInfo database + + if L.braid_index() <= br_ind: + if self._markov_move_cmp(L.braid()): + res_red.append(L) + + if res_red: + return res_red + return res + + + def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): + r""" Identify this link as an item of the KontInfo database (if possible). INPUT: @@ -3289,6 +3427,19 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): will be raised. To avoid this you can set ``unique`` to ``False``. You will get a list of matching candidates instead. + .. NOTE:: + + The identification of proper links may fail due to the following + fact: In opposite to the database for knots, there are pairs of + oriented mutants of an unoriented link which are isotopic to each + other. For example ``L5a1_0`` and ``L5a1_1`` is such a pair. + + This is because all combinatorial possible oriented mutants are + listed with individual names regardless whether they are pairwise + non isotopic or not. In such a case the identification is not + unique and therefore a ``NotImplemendedError`` is raised. Here, + the usage of the keywords ``oriented`` and ``unique`` may help to + find the matching items (see example for ``L5a1_0`` below). EXAMPLES:: @@ -3319,7 +3470,7 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): ... NotImplementedError: this (possibly non prime) link cannot be determined - Lets identify the monster unknot (works without the database, as well):: + Lets identify the monster unknot:: sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5], ....: [17,19,8,18], [9,10,11,14], [10,12,13,11], @@ -3334,21 +3485,20 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): Usage of option ``oriented``:: - sage: KnotInfo.L10a1_0.inject() # optional - database_knotinfo - Defining L10a1_0 - sage: b = L10a1_0.link().braid() # optional - database_knotinfo - sage: l10 = Link(b) # optional - database_knotinfo - sage: l10.get_knotinfo() # optional - database_knotinfo + sage: KnotInfo.L5a1_0.inject() + Defining L5a1_0 + sage: l5 = L5a1_0.link() + sage: l5.get_knotinfo() Traceback (most recent call last): ... NotImplementedError: this link cannot be uniquely determined - sage: l10.get_knotinfo(oriented=False) # optional - database_knotinfo - (Series of links L10a1, False) - sage: _[0].inject() # optional - database_knotinfo - Defining L10a1 - sage: list(L10a1) # optional - database_knotinfo - [, ] + sage: l5.get_knotinfo(oriented=False) + (Series of links L5a1, False) + sage: _[0].inject() + Defining L5a1 + sage: list(L5a1) + [, ] Usage of option ``unique``:: @@ -3359,7 +3509,7 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): NotImplementedError: this link cannot be uniquely determined sage: l.get_knotinfo(unique=False) # optional - database_knotinfo - [, ] + [(, False), (, False)] Clarifying the series around the Perko pair (:wikipedia:`Perko_pair`):: @@ -3377,6 +3527,15 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): sage: import snappy # optional - snappy Plink failed to import tkinter. + doctest:warning + ... + DeprecationWarning: the complex_field module is deprecated, please use sage.rings.complex_mpfr + See http://trac.sagemath.org/24483 for details. + doctest:warning + ... + DeprecationWarning: the complex_number module is deprecated, please use sage.rings.complex_mpfr + See http://trac.sagemath.org/24483 for details. + sage: from sage.knots.knotinfo import KnotInfoSeries sage: KnotInfoSeries(10, True, True) # optional - database_knotinfo Series of knots K10 @@ -3398,14 +3557,12 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): sage: _.sage_link().get_knotinfo() # optional - database_knotinfo snappy (, False) """ - from sage.knots.knotinfo import knotinfo_matching_list, KnotInfoSeries cr = len(self.pd_code()) - co = self.number_of_components() - if co == 1 and cr > 12: + if self.is_knot() and cr > 12: # we cannot not be sure if this link is recorded in the KnotInfo database raise NotImplementedError('this knot having more than 12 crossings cannot be determined') - if co > 1 and cr > 11: + if not self.is_knot() and cr > 11: # we cannot not be sure if this link is recorded in the KnotInfo database raise NotImplementedError('this link having more than 11 crossings cannot be determined') @@ -3416,60 +3573,41 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): # sum of K4_1 and K5_2 in the doctest of :func:`knotinfo_matching_list`) raise NotImplementedError('this (possibly non prime) link cannot be determined') - Hm = None - l = knotinfo_matching_list(cr, co, homfly_polynomial=H) - if not l: - # try with the mirrored HOMFLY-PT polynomial - M, L = H.variables() - Hm = H.subs(L=~L, M=-M) - if H != Hm: - l = knotinfo_matching_list(cr, co, homfly_polynomial=Hm) + ls = self._knotinfo_matching_list() + lm = self.mirror_image()._knotinfo_matching_list() + + from sage.sets.set import Set + l = list(Set(ls + lm)) if not l: from sage.features.databases import DatabaseKnotInfo DatabaseKnotInfo().require() return None - def answer(res, mirror): + def answer(L): + mirrored = not L in ls if not oriented: - res = KnotInfoSeries(res.crossing_number(), res.is_knot(), res.is_alternating(), res.name_unoriented()) + from sage.knots.knotinfo import KnotInfoSeries + L = KnotInfoSeries(L.crossing_number(), L.is_knot(), L.is_alternating(), L.name_unoriented()) if mirror_version: - return res, mirror + return L, mirrored else: - return res + return L if len(l) == 1: - return answer(l[0], Hm is not None) - - self_m = self.mirror_image() - - for L in l: - if L.braid() == self.braid(): - return answer(L, False) - if L.braid() == self_m.braid(): - return answer(L, True) - - # note that KnotInfo pd_notation works counter clockwise, see docstring - # of :meth:`link` of :class:`~sage.knots.knotinfo.KnotInfoBase`. - if L.pd_notation() == self.pd_code(): - return answer(L, True) - if L.pd_notation() == self_m.pd_code(): - return answer(L, False) + return answer(l[0]) if not oriented: - from sage.sets.set import Set - lu = list(Set([L.name_unoriented() for L in l])) - if len(lu) == 1: - return answer(l[0], Hm is not None) - elif unique: - raise NotImplementedError('this link cannot be uniquely determined') - return lu + lu = list(Set([L.name_unoriented() for L in l])) + if len(lu) == 1: + return answer(l[0]) if unique: raise NotImplementedError('this link cannot be uniquely determined') - return l + return sorted([answer(L) for L in l]) + def is_isotopic(self, other): r""" @@ -3505,6 +3643,10 @@ def is_isotopic(self, other): # surely non isotopic return False + if self._markov_move_cmp(other.braid()): + # surely isotopic + return True + ki = self.get_knotinfo() if ki and type(ki) == tuple: kio = other.get_knotinfo() From c6c57dc6633410faa63dffe85cf8c47b567191bf Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Thu, 3 Dec 2020 12:08:04 +0100 Subject: [PATCH 065/232] 30352: normalization vz, _test_recover --- src/sage/knots/knotinfo.py | 307 +++++++++++++++++++++++------------- src/sage/knots/link.py | 308 +++++++++++++++++++++++++++---------- 2 files changed, 425 insertions(+), 190 deletions(-) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 94569598372..657be072536 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -132,9 +132,7 @@ sage: L.homfly_polynomial() -v^-1*z - v^-3*z - v^-3*z^-1 + v^-5*z^-1 - sage: L.homfly_polynomial(sage_convention=True) - L^5*M^-1 - L^3*M - L^3*M^-1 - L*M - sage: _ == l.homfly_polynomial(normalization='az') + sage: _ == l.homfly_polynomial(normalization='vz') True @@ -207,7 +205,7 @@ ....: [17,19,8,18], [9,10,11,14], [10,12,13,11], ....: [12,19,15,13], [20,16,14,15], [16,20,17,2]]) sage: L.get_knotinfo() - (, False) + (, None) REFERENCES: @@ -736,6 +734,13 @@ def determinant(self): r""" Return the determinant of ``self``. + .. NOTE:: + + KnotInfo's value for the unknot ``0_1`` is zero. This is not + compatible whith Sage's result (the value of the Alexander + polynomial at -1). Since this method is needed to identify + Sage links we take the according value in that case. + EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfo @@ -743,7 +748,12 @@ def determinant(self): 4 sage: KnotInfo.K3_1.determinant() 3 + sage: KnotInfo.K0_1.determinant() + 1 """ + if self.crossing_number() == 0: + # see note above + return 1 return int(self[self.items.determinant]) @cached_method @@ -814,9 +824,11 @@ class of an oriented pair, `K = (S_3, S_1)`, with `S_i` """ if not self.is_knot(): raise NotImplementedError('This is only available for knots') - if not self[self.items.symmetry_type] and self.crossing_number() == 0: + + symmetry_type = self[self.items.symmetry_type].strip() # for example K10_88 is a case with trailing whitespaces + if not symmetry_type and self.crossing_number() == 0: return 'fully amphicheiral' - return self[self.items.symmetry_type] + return symmetry_type @cached_method def is_reversible(self): @@ -829,9 +841,10 @@ def is_reversible(self): sage: KnotInfo.K6_3.is_reversible() True """ - if self.symmetry_type() == 'reversible': + symmetry_type = self.symmetry_type() + if symmetry_type == 'reversible': return True - if self.symmetry_type() == 'fully amphicheiral': + if symmetry_type == 'fully amphicheiral': return True return False @@ -846,22 +859,36 @@ def is_amphicheiral(self, positive=False): if ``self`` is positive or negative amphicheiral (see documentation of :meth:`symmetry_type`) + OUTPUT: + + ``True`` if ``self`` is fully or negative amphicheiral per default. if + ``positive`` is set to ``True`` than fully and positive amphicheiral + links give ``True``. + EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfo - sage: K = KnotInfo.K12a_427 # optional - database_knotinfo - sage: K.is_amphicheiral() # optional - database_knotinfo + sage: Kp = KnotInfo.K12a_427 # optional - database_knotinfo + sage: Kp.is_amphicheiral() # optional - database_knotinfo False - sage: K.is_amphicheiral(positive=True) # optional - database_knotinfo + sage: Kp.is_amphicheiral(positive=True) # optional - database_knotinfo True + + sage: Kn = KnotInfo.K10_88 # optional - database_knotinfo + sage: Kn.is_amphicheiral() # optional - database_knotinfo + True + sage: Kn.is_amphicheiral(positive=True) # optional - database_knotinfo + False """ + symmetry_type = self.symmetry_type() if positive: - if self.symmetry_type() == 'positive amphicheiral': + if symmetry_type == 'positive amphicheiral': return True else: - if self.symmetry_type() == 'negative amphicheiral': + if symmetry_type == 'negative amphicheiral': return True - if self.symmetry_type() == 'fully amphicheiral': + + if symmetry_type == 'fully amphicheiral': return True return False @@ -1003,7 +1030,7 @@ def is_oriented(self): @cached_method - def homfly_polynomial(self, var1=None, var2=None, original=False, sage_convention=False): + def homfly_polynomial(self, var1='v', var2='z', original=False): r""" Return the HOMFLY-PT polynomial according to the value of column ``homfly_polynomial`` for this knot or link (in the latter case the @@ -1021,16 +1048,10 @@ def homfly_polynomial(self, var1=None, var2=None, original=False, sage_conventio INPUT: - - ``var1`` -- string for the name of the first variable (default depending - on keyword ``sage_convention``: ``'v'`` or ``'L'`` if - ``sage_convention == True``) - - ``var2`` -- string for the name of the second variable (default depending - on keyword ``sage_convention``: ``'z'`` or ``'M'`` if - ``sage_convention == True``) + - ``var1`` -- string (default ``v``) for the name of the first variable + - ``var2`` -- string (default ``z``) for the name of the second variable - ``original`` -- boolean (default ``False``) if set to ``True`` the original table entry is returned as a string - - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` - the conversion to Sage's conventions (see the note below) is performed OUTPUT: @@ -1041,20 +1062,9 @@ def homfly_polynomial(self, var1=None, var2=None, original=False, sage_conventio .. NOTE:: The skein-relation for the HOMFLY-PT polynomial given on KnotInfo - differs from the ones used in Sage. - - Using Sage's HOMFLY-PT polynomial with ``normalization='az'`` - the corresponding skein-relation is (see :meth:`Link.homfly_polynomial` - of :class:`Link`): - - .. MATH:: - - P(O) = 1,\,\,\, a P(L_+) - a^{-1} P(L_-) = z P(L_0) - - Thus, the HOMFLY-PT polynomial of KnotInfo compares to the one of Sage - by replacing ``v`` by ``~a``. To keep them comparable this translation - can be performed by setting the keyword ``sage_convention`` to ``True``. - + does not match the default used in Sage. For comparison you have + to use the keyword argument ``normalization='vz'`` on the side + of Sage. EXAMPLES:: @@ -1064,9 +1074,7 @@ def homfly_polynomial(self, var1=None, var2=None, original=False, sage_conventio -v^4 + v^2*z^2 + 2*v^2 sage: K3_1.homfly_polynomial(original=True) '(2*v^2-v^4)+ (v^2)*z^2' - sage: PK3_1s = K3_1.homfly_polynomial(sage_convention=True); PK3_1s - L^-2*M^2 + 2*L^-2 - L^-4 - sage: PK3_1s == K3_1.link().homfly_polynomial(normalization='az') + sage: PK3_1 == K3_1.link().homfly_polynomial(normalization='vz') True for proper links:: @@ -1074,9 +1082,7 @@ def homfly_polynomial(self, var1=None, var2=None, original=False, sage_conventio sage: L4a1_1 = KnotInfo.L4a1_1 sage: PL4a1_1 = L4a1_1.homfly_polynomial(var1='x', var2='y'); PL4a1_1 -x^5*y + x^3*y^3 - x^5*y^-1 + 3*x^3*y + x^3*y^-1 - sage: L4a1_1.homfly_polynomial(var1='x', var2='y', sage_convention=True) - x^-3*y^3 + 3*x^-3*y + x^-3*y^-1 - x^-5*y - x^-5*y^-1 - sage: _ == L4a1_1.link().homfly_polynomial(var1='x', var2='y', normalization='az') + sage: _ == L4a1_1.link().homfly_polynomial('x', 'y', 'vz') True check the skein-relation from the KnotInfo description page (applied to one @@ -1090,21 +1096,9 @@ def homfly_polynomial(self, var1=None, var2=None, original=False, sage_conventio sage: ~v*PK3_1 -v*PO == z*PL2a1_1 True - check the skein-relation given in the doc string of :meth:`Link.homfly_polynomial` - of :class:`Link` (applied to one of the positive crossings of the - right-handed trefoil):: - - sage: Rs = PK3_1s.parent() - sage: POs = Rs.one() - sage: PL2a1_1s = L2a1_1.homfly_polynomial(sage_convention=True) - sage: a, z = Rs.gens() - sage: a*PK3_1s - ~a*POs == z*PL2a1_1s - True - - TESTS:: - sage: all(L.homfly_polynomial(sage_convention=True) == L.link().homfly_polynomial(normalization='az')\ + sage: all(L.homfly_polynomial() == L.link().homfly_polynomial(normalization='vz')\ for L in KnotInfo if L.crossing_number() > 0 and L.crossing_number() < 7) True @@ -1120,26 +1114,12 @@ def homfly_polynomial(self, var1=None, var2=None, original=False, sage_conventio if original: return homfly_polynomial - if sage_convention: - if not var1: - var1='L' - if not var2: - var2='M' - else: - if not var1: - var1='v' - if not var2: - var2='z' - R = self._homfly_pol_ring(var1, var2) if not homfly_polynomial and self.crossing_number() == 0: return R.one() L, M = R.gens() - if sage_convention: - lc = {'v': ~L, 'z':M} # see note above - else: - lc = {'v': L, 'z':M} + lc = {'v': L, 'z':M} return eval_knotinfo(homfly_polynomial, locals=lc) @cached_method @@ -1227,7 +1207,7 @@ def kauffman_polynomial(self, var1='a', var2='z', original=False): @cached_method - def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False, original=False, sage_convention=False): + def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False, original=False, use_sqrt=False): r""" Return the Jones polynomial according to the value of column ``jones_polynomial`` for this knot or link as an element of the symbolic @@ -1257,8 +1237,7 @@ def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False is returned - ``original`` -- boolean (default ``False``) if set to ``True`` the original table entry is returned as a string - - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` the - conversion to Sage's conventions (see the note below) is performed + - ``use_sqrt`` -- boolean (default ``False``) see the note below OUTPUT: @@ -1276,13 +1255,13 @@ def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False .. NOTE:: - The only difference of conventions concerning the Jones polynomial is - its representation in the case of proper links. KnotInfo does not - display these polynomials in the indeterminate `t` used in the skein - relation. Instead a variable `x` is used defined by `x^2 = t`. Sage - uses `t` in both cases, knots and proper links. Thus, to obtain the - Jones polynomial for a proper link in `t` you have to set the keyword - ``sage_convention`` to ``True``. + There is a difference to Sage's conventions concerning the Jones + polynomial in the case of proper links. KnotInfo does not display + these polynomials in the indeterminate `t` used in the skein relation. + Instead a variable `x` is used defined by `x^2 = t`. Sage uses `t` in + both cases, knots and proper links. Thus, to obtain the Jones polynomial + for a proper link in `t` you have to set the keyword ``use_sqrt`` + to ``True``. EXAMPLES:: @@ -1300,9 +1279,9 @@ def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False sage: L = KnotInfo.L2a1_1 sage: Lj = L.jones_polynomial(); Lj -x^5 - x - sage: Ljt = L.jones_polynomial(sage_convention=True); Ljt + sage: Ljt = L.jones_polynomial(use_sqrt=True); Ljt -t^(5/2) - sqrt(t) - sage: Ljp = L.jones_polynomial(puiseux=True, sage_convention=True); Ljp + sage: Ljp = L.jones_polynomial(puiseux=True); Ljp -t^(1/2) - t^(5/2) sage: Ljs = L.jones_polynomial(skein_normalization=True); Ljs -A^2 - A^10 @@ -1384,7 +1363,7 @@ def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False R = LaurentPolynomialRing(ZZ, variab) else: if not variab: - if sage_convention or self.is_knot(): + if use_sqrt or self.is_knot() or puiseux: variab='t' else: variab='x' @@ -1409,7 +1388,7 @@ def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False lc = {'t': t} elif puiseux: lc = {'x': t**(1/2)} - elif sage_convention: + elif use_sqrt: from sage.functions.other import sqrt lc = {'x': sqrt(t)} else: @@ -1420,7 +1399,7 @@ def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False @cached_method - def alexander_polynomial(self, var='t', original=False, sage_convention=False): + def alexander_polynomial(self, var='t', original=False, laurent_poly=False): r""" Return the Alexander polynomial according to the value of column ``alexander_polynomial`` for this knot as an instance of @@ -1442,14 +1421,13 @@ def alexander_polynomial(self, var='t', original=False, sage_convention=False): - ``var`` -- (default: ``'t'``) the variable - ``original`` -- boolean (optional, default ``False``) if set to ``True`` the original table entry is returned as a string - - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` - the conversion to Sage's conventions (see the note below) is performed + - ``laurent_poly`` -- boolean (default ``False``) see the note below OUTPUT: A polynomial over the integers, more precisely an instance of :class:`~sage.rings.polynomial.polynomial_element.Polynomial`. - If ``sage_convention`` is set to ``True`` a Laurent polynomial + If ``laurent_poly`` is set to ``True`` a Laurent polynomial over the integers, more precisely an instance of :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial` is returned. If ``original`` is set to ``True`` then a string @@ -1461,9 +1439,9 @@ def alexander_polynomial(self, var='t', original=False, sage_convention=False): a unit factor in the Laurent polynomial ring over the integers in the indeterminate `t`. While the normalization of the exponents in KnotInfo guarantees it to be a proper polynomial, this is - not the case for the implementation in Sage. The transition - can be made using the keyword ``sage_convention``. But, still - there may be a difference in sign (see the example below). + not the case for the implementation in Sage. Use the keyword + ``laurent_poly`` to achiev a normalization according to Sage. + But, still there may be a difference in sign (see the example below). EXAMPLES:: @@ -1477,7 +1455,7 @@ def alexander_polynomial(self, var='t', original=False, sage_convention=False): sage: k = K.link() sage: ka = k.alexander_polynomial(); ka -t^-1 + 3 - t - sage: K.alexander_polynomial(sage_convention=True) + sage: K.alexander_polynomial(laurent_poly=True) t^-1 - 3 + t sage: _ == -ka True @@ -1492,7 +1470,7 @@ def alexander_polynomial(self, var='t', original=False, sage_convention=False): if original: return alexander_polynomial - if sage_convention: + if laurent_poly: from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing R = LaurentPolynomialRing(ZZ, var) else: @@ -1505,7 +1483,7 @@ def alexander_polynomial(self, var='t', original=False, sage_convention=False): t, = R.gens() lc = {'t': t} ap = R(eval_knotinfo(alexander_polynomial, locals=lc)) - if not sage_convention or ap.is_constant(): + if not laurent_poly or ap.is_constant(): return ap exp = ap.exponents() @@ -1671,6 +1649,58 @@ def link(self, use_item=db.columns().pd_notation, snappy=False): raise ValueError('Link construction using %s not possible' %use_item) + def _test_recover(self, **options): + r""" + Check if ``self`` can be recovered from its conversion to Sage links + using the ``pd_notation`` and the ``braid_notation`` and their + mirror images. + + The method is used by the ``TestSuite`` of the series of ``self``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: from sage.misc.sage_unittest import instance_tester + sage: K = KnotInfo.K6_1 + sage: K._test_recover(tester=instance_tester(K)) + """ + def recover(mirror, braid): + r""" + Check if ``self`` can be recovered form its associated + Sage link. + """ + if braid: + l = self.link(self.items.braid_notation) + else: + l = self.link() + if mirror: + l = l.mirror_image() + + def check_result(L): + r""" + Check a single result from ``get_knotinfo`` + """ + if L == (self, None) or L == (self, '?'): + return True + if mirror: + return L == (self, True) + else: + return L == (self, False) + + try: + L = l.get_knotinfo() + return check_result(L) + except NotImplementedError: + Llist = l.get_knotinfo(unique=False) + return any(check_result(L) for L in Llist) + + tester = options['tester'] + tester.assertTrue(recover(False, False)) + tester.assertTrue(recover(True, False)) + tester.assertTrue(recover(False, True)) + tester.assertTrue(recover(True, True)) + + def inject(self, verbose=True): """ @@ -1696,6 +1726,7 @@ def inject(self, verbose=True): from sage.repl.user_globals import set_global set_global(name, self) + @cached_method def series(self, oriented=False): r""" Return the series of links ``self`` belongs to. @@ -1809,7 +1840,7 @@ def knotilus_webpage(self, new=0, autoraise=True): # -------------------------------------------------------------------------------------------- # KnotInfoSeries # -------------------------------------------------------------------------------------------- -class KnotInfoSeries(UniqueRepresentation): +class KnotInfoSeries(UniqueRepresentation, SageObject): r""" This class can be used to access knots and links via their index according to the series they belong to. @@ -1859,7 +1890,6 @@ def __init__(self, crossing_number, is_knot, is_alternating, name_unoriented=Non sage: from sage.knots.knotinfo import KnotInfoSeries sage: L6a = KnotInfoSeries(6, False, True); L6a Series of links L6a - sage: TestSuite(L6a).run() """ self._crossing_number = crossing_number self._is_knot = is_knot @@ -1867,17 +1897,30 @@ def __init__(self, crossing_number, is_knot, is_alternating, name_unoriented=Non self._name_unoriented = name_unoriented @cached_method - def list(self, oriented=False): + def list(self, oriented=False, comp=None, det=None, homfly=None): r""" Return this series as a Python list. INPUT: - - ``oriented`` -- boolean (default False) it only affects series of - proper links. By default the list items of a series of proper links - are again series of links collecting all orientation types of an + - ``oriented`` -- boolean (optional, default ``False``) it only affects + series of proper links. By default the list items of a series of proper + links are again series of links collecting all orientation types of an unoriented name. To obtain the list of the individual links this - keyword has to be set to ``True``. + keyword has to be set to ``True`` + + - ``comp`` (optional, default ``None``) if given an integer for this + keyword the list is restriced to links having the according number + of components. This keyword implies ``oriented=True`` + + - ``det`` (optional, default ``None``) if given an integer for this + keyword the list is restriced to links having the according value + for its determinant. This keyword implies ``oriented=True`` + + - ``homfly`` (optional, default ``None``) if given a HOMFLY-PT polynomial + having ``normalization='vz'`` for this keyword the list is restriced to + links having the according value for its HOMFLY-PT polynomial. This + keyword implies ``oriented=True`` EXAMPLES:: @@ -1893,6 +1936,23 @@ def list(self, oriented=False): sage: L2a.list(oriented=True) [, ] """ + if homfly is not None: + # additional restriction to number of components, determinant and + # HOMFLY-PT polynomial + l = self.list(oriented=True, comp=comp, det=det) + return [L for L in l if L.homfly_polynomial() == homfly] + + if det is not None: + # additional restriction to number of components and determinant + l = self.list(oriented=True, comp=comp) + return [L for L in l if L.determinant() == det] + + if comp is not None: + # additional restriction to number of components + l = self.list(oriented=True) + return [L for L in l if L.num_components() == comp] + + # default case is_knot = self._is_knot cross_nr = self._crossing_number is_alt = self._is_alternating @@ -1929,15 +1989,24 @@ def list(self, oriented=False): @cached_method - def lower_list(self, oriented=False): + def lower_list(self, oriented=False, comp=None, det=None, homfly=None): r""" Return this series together with all series with smaller crossing number as a Python list. INPUT: - - ``oriented`` -- boolean (default False) see the description - for :meth:`list`. + - ``oriented`` -- boolean (optional, default ``False``) see the + description for :meth:`list` + + - ``comp`` (optional, default ``None``) see the description for + :meth:`list` + + - ``det`` (optional, default ``None``) see the description for + :meth:`list` + + - ``homfly`` (optional, default ``None``) see the description for + :meth:`list` EXAMPLES:: @@ -1957,10 +2026,11 @@ def lower_list(self, oriented=False): ] """ l = [] - for i in range(self._crossing_number): - LS = type(self)(i, self._is_knot, self._is_alternating, self._name_unoriented ) - l += LS.list(oriented=oriented) - return l + self.list(oriented=oriented) + cr = self._crossing_number + if cr > 0: + LS = type(self)(cr - 1, self._is_knot, self._is_alternating, self._name_unoriented ) + l = LS.lower_list(oriented=oriented, comp=comp, det=det, homfly=homfly) + return l + self.list(oriented=oriented, comp=comp, det=det, homfly=homfly) def __repr__(self): @@ -2077,6 +2147,25 @@ def _name(self): res = 'L%s%s' %(cross_nr, alt) return res + def _test_recover(self, **options): + r""" + Method used by ``TestSuite``. Tests if all links of the series can be + recovered from their conversion to Sage links. It uses :meth:`_test_recover` + of :class:`KnotInfoBase`. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: TestSuite(KnotInfo.L5a1_0.series()).run(verbose=True) # indirec doctest + running ._test_category() . . . pass + running ._test_new() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + running ._test_recover() . . . pass + """ + for L in self: + L._test_recover(**options) + def inject(self, verbose=True): r""" diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index c7eb60aad49..4883f2a2beb 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -587,6 +587,7 @@ def __ne__(self, other): """ return not self.__eq__(other) + def braid(self): """ Return a braid representation of ``self``. @@ -2116,6 +2117,7 @@ def regions(self): regions.append(region) return regions + @cached_method def mirror_image(self): r""" Return the mirror image of ``self``. @@ -2462,6 +2464,7 @@ def _bracket(self): cross[cross.index(b)] = a return t * Link(rest)._bracket() + ~t * Link(rest_2)._bracket() + @cached_method def _isolated_components(self): r""" Return the PD codes of the isolated components of ``self``. @@ -2487,7 +2490,8 @@ def _isolated_components(self): return [[list(i) for i in j] for j in G.connected_components(sort=False)] - def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): + @cached_method + def homfly_polynomial(self, var1=None, var2=None, normalization='lm'): r""" Return the HOMFLY polynomial of ``self``. @@ -2497,8 +2501,10 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): INPUT: - - ``var1`` -- (default: ``'L'``) the first variable - - ``var2`` -- (default: ``'M'``) the second variable + - ``var1`` -- (default: ``'L'``) the first variable. If ``normalization`` + is set to ``az`` resp. ``vz`` the default is ``a`` resp. ``v`` + - ``var2`` -- (default: ``'M'``) the second variable. If ``normalization`` + is set to ``az`` resp. ``vz`` the default is ``z`` - ``normalization`` -- (default: ``lm``) the system of coordinates and can be one of the following: @@ -2508,6 +2514,9 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): * ``'az'`` -- corresponding to the Skein relation `a\cdot P(K _+) - a^{-1}\cdot P(K _-) = z \cdot P(K _0)` + * ``'vz'`` -- corresponding to the Skein relation + `v^{-1}\cdot P(K _+) - v\cdot P(K _-) = z \cdot P(K _0)` + where `P(K _+)`, `P(K _-)` and `P(K _0)` represent the HOMFLY polynomials of three links that vary only in one crossing; that is the positive, negative, or smoothed links respectively @@ -2519,7 +2528,10 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): .. NOTE:: Use the ``'az'`` normalization to agree with the data - in [KnotAtlas]_ and `KnotInfo `__. + in [KnotAtlas]_ + + Use the ``'vz'`` normalization to agree with the data + `KnotInfo `__. EXAMPLES: @@ -2544,7 +2556,7 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): sage: L.homfly_polynomial() L^3*M^-1 - L*M + L*M^-1 sage: L = Link([[1,4,2,3], [4,1,3,2]]) - sage: L.homfly_polynomial('a', 'z', 'az') + sage: L.homfly_polynomial(normalization='az') a^3*z^-1 - a*z - a*z^-1 The figure-eight knot:: @@ -2563,6 +2575,13 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): sage: L.homfly_polynomial() 1 + Comparison with KnotInfo:: + + sage: KI, m = K.get_knotinfo(); KI, m + (, False) + sage: K.homfly_polynomial(normalization='vz') == KI.homfly_polynomial() + True + The knot `9_6`:: sage: B = BraidGroup(3) @@ -2584,24 +2603,43 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): -L*M^-1 - L^-1*M^-1 sage: L.homfly_polynomial() -L*M^-1 - L^-1*M^-1 - sage: L.homfly_polynomial('a', 'z', 'az') - a*z^-1 - a^-1*z^-1 - sage: L2.homfly_polynomial('a', 'z', 'az') + sage: L.homfly_polynomial(normalization='az') a*z^-1 - a^-1*z^-1 + sage: L2.homfly_polynomial('α', 'ζ', 'az') + α*ζ^-1 - α^-1*ζ^-1 + sage: L.homfly_polynomial(normalization='vz') + -v*z^-1 + v^-1*z^-1 + sage: L2.homfly_polynomial('ν', 'ζ', 'vz') + -ν*ζ^-1 + ν^-1*ζ^-1 REFERENCES: - :wikipedia:`HOMFLY_polynomial` - http://mathworld.wolfram.com/HOMFLYPolynomial.html """ + if not var1: + if normalization == 'az': + var1 = 'a' + elif normalization == 'vz': + var1 = 'v' + else: + var1 = 'L' + if not var2: + if normalization == 'lm': + var2 = 'M' + else: + var2 = 'z' + L = LaurentPolynomialRing(ZZ, [var1, var2]) if len(self._isolated_components()) > 1: if normalization == 'lm': fact = L({(1, -1):-1, (-1, -1):-1}) elif normalization == 'az': fact = L({(1, -1):1, (-1, -1):-1}) + elif normalization == 'vz': + fact = L({(1, -1):-1, (-1, -1):1}) else: - raise ValueError('normalization must be either `lm` or `az`') + raise ValueError('normalization must be either `lm`, `az` or `vz`') fact = fact ** (len(self._isolated_components())-1) for i in self._isolated_components(): fact = fact * Link(i).homfly_polynomial(var1, var2, normalization) @@ -2629,8 +2667,13 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): return L(auxdic) else: return -L(auxdic) + elif normalization == 'vz': + h_az = self.homfly_polynomial(var1=var1, var2=var2, normalization='az') + a, z = h_az.parent().gens() + v = ~a + return h_az.subs({a:v}) else: - raise ValueError('normalization must be either `lm` or `az`') + raise ValueError('normalization must be either `lm`, `az` or `vz`') def _coloring_matrix(self, n): r""" @@ -3319,21 +3362,26 @@ def _markov_move_cmp(self, braid): L = Link(ob) return L._markov_move_cmp(sb) - + @cached_method def _knotinfo_matching_list(self): r""" Return a list of links from the KontInfo and LinkInfo databases which match the properties of ``self`` as much as possible. + OUTPUT: + + A tuple ``(l, proved)`` where ``l`` is the matching list and ``proved`` a boolean + telling if the entries of ``l`` are checked to be isotopic to self or not. + EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfo sage: KnotInfo.L5a1_0.inject() Defining L5a1_0 sage: L5a1_0.link()._knotinfo_matching_list() - [] + ([], True) sage: Link(L5a1_0.braid())._knotinfo_matching_list() - [, ] + ([, ], True) Care is needed for links having non irreducible HOMFLY-PT polynomials:: @@ -3341,53 +3389,67 @@ def _knotinfo_matching_list(self): sage: k5_2 = KnotInfo.K5_2.link() sage: k = k4_1.connected_sum(k5_2) sage: k._knotinfo_matching_list() # optional - database_knotinfo - [] + ([], False) """ from sage.knots.knotinfo import KnotInfoSeries - cr = len(self.pd_code()) - co = self.number_of_components() - - if self.is_knot(): - l = KnotInfoSeries(cr, True, self.is_alternating()).lower_list(oriented=True) + cr = len(self.pd_code()) + co = self.number_of_components() + + # set the limits for the KnotInfoSeries + if cr > 11 and co > 1: + cr = 11 + if cr > 12: + cr = 12 + + Hp = self.homfly_polynomial(normalization='vz') + + det = None + if cr > 6: + # for larger crossing numbers the KnotInfoSeries become very + # large, as well. For better performance we restrict the cached + # lists by the determinant and number of components. + # + # Since :meth:`determinant` is not implemented for proper links + # we have to go back to the roots. + ap = self.alexander_polynomial() + det = Integer(abs(ap(-1))) + + is_knot = self.is_knot() + if is_knot and cr < 11: + S = KnotInfoSeries(cr, True, None) + l = S.lower_list(oriented=True, comp=co, det=det, homfly=Hp) else: - # as long as the method `is_alternating` is not implemented for - # proper links we have to sum the lists from both series - l = KnotInfoSeries(cr, False, True).lower_list(oriented=True) - l += KnotInfoSeries(cr, False, False).lower_list(oriented=True) - - Hp = self.homfly_polynomial(normalization='az') - pdm = self.mirror_image().pd_code() # for mirror_image see note below + # the result of :meth:`is_alternating` depends on the specific + # diagram of the link. For example ``K11a_2`` is an alternating + # knot but ``Link(KnotInfo.K11a_2.braid()).is_alternating()`` + # gives ``False``. Therefore, we have to take both series + # into consideration. + Sa = KnotInfoSeries(cr, is_knot, True) + Sn = KnotInfoSeries(cr, is_knot, False) + la = Sa.lower_list(oriented=True, comp=co, det=det, homfly=Hp) + ln = Sn.lower_list(oriented=True, comp=co, det=det, homfly=Hp) + l = sorted(list(set(la + ln))) + + pdm = [[a[0], a[3], a[2], a[1]] for a in self.pd_code() ] br = self.braid() br_ind = br.strands() res = [] for L in l: - if L.num_components() != co: - continue - if L.homfly_polynomial(sage_convention=True) != Hp: - continue - res.append(L) - - if len(res) <= 1: - return res - - res_red = [] - for L in res: if L.pd_notation() == pdm: # note that KnotInfo pd_notation works counter clockwise. Therefore, # to compensate this we compare with the mirrored pd_code. See also, # docstring of :meth:`link` of :class:`~sage.knots.knotinfo.KnotInfoBase`. - return[L] # pd_notation is unique in the KnotInfo database + return[L], True # pd_notation is unique in the KnotInfo database if L.braid_index() <= br_ind: if self._markov_move_cmp(L.braid()): - res_red.append(L) - - if res_red: - return res_red - return res + res.append(L) + if res: + return res, True + return l, False def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): r""" @@ -3414,8 +3476,10 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): OUTPUT: A tuple ``(K, m)`` where ``K`` is an instance of :class:`~sage.knots.knotinfo.KnotInfoBase` - and ``m`` a boolean telling if ``self`` corresponse to the mirrored version - of ``K`` or not. + and ``m`` a boolean (for chiral links) telling if ``self`` corresponse + to the mirrored version of ``K`` or not. The value of ``m`` is ``None`` + for amphicheiral links and ``?`` if it cannot be determined uniquely + and the keyword option ``unique=False`` is given. If ``oriented`` is set to ``False`` then the result is a series of links (instance of :class:`~sage.knots.knotinfo.KnotInfoSeries`, see explanation above). @@ -3465,7 +3529,9 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): ... NotImplementedError: this knot having more than 12 crossings cannot be determined - sage: KnotInfo.K9_12.link().get_knotinfo() # optional - database_knotinfo + sage: Link([[1, 5, 2, 4], [3, 1, 4, 8], [5, 3, 6, 2], [6, 9, 7, 10], [10, 7, 9, 8]]) + Link with 2 components represented by 5 crossings + sage: _.get_knotinfo() Traceback (most recent call last): ... NotImplementedError: this (possibly non prime) link cannot be determined @@ -3476,7 +3542,7 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): ....: [17,19,8,18], [9,10,11,14], [10,12,13,11], ....: [12,19,15,13], [20,16,14,15], [16,20,17,2]]) sage: L.get_knotinfo() - (, False) + (, None) Usage of option ``mirror_version``:: @@ -3487,7 +3553,7 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): sage: KnotInfo.L5a1_0.inject() Defining L5a1_0 - sage: l5 = L5a1_0.link() + sage: l5 = Link(L5a1_0.braid()) sage: l5.get_knotinfo() Traceback (most recent call last): ... @@ -3502,6 +3568,14 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): Usage of option ``unique``:: + sage: L2a1 = Link(b**2) + sage: L2a1.get_knotinfo() + Traceback (most recent call last): + ... + NotImplementedError: this link cannot be uniquely determined + sage: L2a1.get_knotinfo(unique=False) + [(, True), (, False)] + sage: l = K.link(K.items.gauss_notation) # optional - database_knotinfo sage: l.get_knotinfo() # optional - database_knotinfo Traceback (most recent call last): @@ -3511,6 +3585,18 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): sage: l.get_knotinfo(unique=False) # optional - database_knotinfo [(, False), (, False)] + sage: k11 = KnotInfo.K11n_82.link() # optional - database_knotinfo + sage: k11m = k11.mirror_image() # optional - database_knotinfo + sage: k11m.braid() # optional - database_knotinfo + s0*s1^-1*s0*s2^-1*s1*(s1*s2^-1)^2*s2^-2 + sage: k11m.get_knotinfo() # optional - database_knotinfo + Traceback (most recent call last): + ... + NotImplementedError: mirror type of this link cannot be uniquely determined + + sage: k11m.get_knotinfo(unique=False) # optional - database_knotinfo + [(, '?')] + Clarifying the series around the Perko pair (:wikipedia:`Perko_pair`):: sage: for i in range(160, 166): # optional - database_knotinfo @@ -3557,56 +3643,116 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): sage: _.sage_link().get_knotinfo() # optional - database_knotinfo snappy (, False) """ + def answer(L): + r""" + Return a single item of the KnotInfo database according to the keyword + arguments ``oriented`` and ``mirror_version``. + """ + is_knot = L.is_knot() + if not oriented and not is_knot: + L = L.series(oriented=True) + + if mirror_version: + chiral = True + if is_knot: + if L.is_amphicheiral() or L.is_amphicheiral(positive=True): + chiral = False + elif L in ls and L in lm: + if proved_s and proved_m: + chiral = False + elif self._markov_move_cmp(self_m.braid()): + chiral = False + elif unique: + raise NotImplementedError('this link cannot be uniquely determined (unknown chirality)') + + if not chiral: + mirrored = None + elif proved_m and not proved_s: + mirrored = True + elif proved_s and not proved_m: + mirrored = False + else: + # nothing proved + if L in ls and L in lm: + # In case of a chiral link this means that the HOMFLY-PT + # polynomial does not distinguish mirror images (see the above + # example ``k11m``). + if unique: + raise NotImplementedError('mirror type of this link cannot be uniquely determined') + mirrored = '?' + elif L in lm: + mirrored = True + else: + mirrored = False + + return L, mirrored + else: + return L + + def answer_list(l): + r""" + Return a list of items of the KnotInfo database according to the keyword + arguments ``oriented`` and ``unique``. + """ + if not unique: + return sorted([answer(L) for L in l]) + + if len(l) == 1: + return answer(l[0]) + + if not oriented: + lu = list(set([L.name_unoriented() for L in l])) + if len(lu) == 1: + return answer(l[0]) + + raise NotImplementedError('this link cannot be uniquely determined') + + + self_m = self.mirror_image() + ls, proved_s = self._knotinfo_matching_list() + lm, proved_m = self_m._knotinfo_matching_list() + l = list(set(ls + lm)) + + if proved_s and proved_m: + return answer_list(l) + + if proved_s: + return answer_list(ls) + + if proved_m: + return answer_list(lm) + + # here we come if we cannot be sure about the found result + + if l and not unique: + return answer_list(l) + + uniq_txt = '' + if l: + uniq_txt = ' uniquely' + cr = len(self.pd_code()) if self.is_knot() and cr > 12: # we cannot not be sure if this link is recorded in the KnotInfo database + raise NotImplementedError('this knot having more than 12 crossings cannot be%s determined' %uniq_txt) - raise NotImplementedError('this knot having more than 12 crossings cannot be determined') if not self.is_knot() and cr > 11: # we cannot not be sure if this link is recorded in the KnotInfo database - raise NotImplementedError('this link having more than 11 crossings cannot be determined') + raise NotImplementedError('this link having more than 11 crossings cannot be%s determined' %uniq_txt) H = self.homfly_polynomial(normalization='az') if len(H.factor()) > 1: # we cannot be sure if this is a prime link (see the example for the connected # sum of K4_1 and K5_2 in the doctest of :func:`knotinfo_matching_list`) - raise NotImplementedError('this (possibly non prime) link cannot be determined') - - ls = self._knotinfo_matching_list() - lm = self.mirror_image()._knotinfo_matching_list() - - from sage.sets.set import Set - l = list(Set(ls + lm)) + raise NotImplementedError('this (possibly non prime) link cannot be%s determined' %uniq_txt) if not l: from sage.features.databases import DatabaseKnotInfo DatabaseKnotInfo().require() - return None + return l - def answer(L): - mirrored = not L in ls - if not oriented: - from sage.knots.knotinfo import KnotInfoSeries - L = KnotInfoSeries(L.crossing_number(), L.is_knot(), L.is_alternating(), L.name_unoriented()) - - if mirror_version: - return L, mirrored - else: - return L - - - if len(l) == 1: - return answer(l[0]) - - if not oriented: - lu = list(Set([L.name_unoriented() for L in l])) - if len(lu) == 1: - return answer(l[0]) - - if unique: - raise NotImplementedError('this link cannot be uniquely determined') - return sorted([answer(L) for L in l]) + return answer_list(l) def is_isotopic(self, other): From febf374654b8760600e47cf852aed5551bb14281 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Fri, 4 Dec 2020 09:12:45 +0100 Subject: [PATCH 066/232] 39352: some pyflake fixes which got lost --- src/sage/features/databases.py | 1 - src/sage/knots/knotinfo.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sage/features/databases.py b/src/sage/features/databases.py index 37c2bc0cbe9..fb1595ac672 100644 --- a/src/sage/features/databases.py +++ b/src/sage/features/databases.py @@ -3,7 +3,6 @@ Testing for databases at runtime """ -import os from . import StaticFile from sage.env import CREMONA_MINI_DATA_DIR, CREMONA_LARGE_DATA_DIR diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 657be072536..997750feabc 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -234,14 +234,14 @@ from enum import Enum -from sage.misc.cachefunc import cached_method, cached_function +from sage.misc.cachefunc import cached_method from sage.misc.sage_eval import sage_eval from sage.structure.sage_object import SageObject from sage.structure.unique_representation import UniqueRepresentation from sage.rings.integer_ring import ZZ from sage.groups.braid import BraidGroup from sage.knots.knot import Knots -from sage.databases.knotinfo_db import KnotInfoColumnTypes, KnotInfoColumns, db +from sage.databases.knotinfo_db import KnotInfoColumns, db From e8ec73e5ec6634a136006da0b281b751dfebec15 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Fri, 18 Dec 2020 18:47:01 +0100 Subject: [PATCH 067/232] 30352: changes according to review and feedback Chuck Livingston --- src/sage/knots/knot.py | 28 +++- src/sage/knots/knotinfo.py | 256 +++++++++++++++++++++++++++------- src/sage/knots/link.py | 276 +++++++++++++++++++++++-------------- 3 files changed, 405 insertions(+), 155 deletions(-) diff --git a/src/sage/knots/knot.py b/src/sage/knots/knot.py index f9b7ec532d7..d9536d8785b 100644 --- a/src/sage/knots/knot.py +++ b/src/sage/knots/knot.py @@ -386,9 +386,9 @@ def connected_sum(self, other): :: sage: rev_trefoil = Knot(B([-1,-1,-1])) - sage: K = trefoil.connected_sum(rev_trefoil); K + sage: K2 = trefoil.connected_sum(rev_trefoil); K2 Knot represented by 6 crossings - sage: K.braid() + sage: K2.braid() s0^3*s1^-1*s0^-3*s1 .. PLOT:: @@ -397,8 +397,19 @@ def connected_sum(self, other): B = BraidGroup(2) t = Knot(B([1,1,1])) tr = Knot(B([-1,-1,-1])) - K = t.connected_sum(tr) - sphinx_plot(K.plot()) + K2 = t.connected_sum(tr) + sphinx_plot(K2.plot()) + + Observe that both knots have according ``dowker_notation`` (showing that + the constructing from DT-code may not be unique for non prime knots, see + :meth:`from_dowker_code`):: + + sage: K.dowker_notation() + [(4, 1), (2, 5), (6, 3), (10, 7), (8, 11), (12, 9)] + sage: K2.dowker_notation() + [(4, 1), (2, 5), (6, 3), (7, 10), (11, 8), (9, 12)] + sage: K.homfly_polynomial() == K2.homfly_polynomial() + False TESTS:: @@ -534,6 +545,15 @@ def from_dowker_code(self, code): a knot + .. WARNING:: + + In general the Dowker-Thistlethwaite code does not describe a knot + uniquely. It is not only insensitive on mirror images, but may also + mix up non prime knots. For example ``[4, 6, 2, 10, 12, 8]`` describes + the connected sum of two trefoil knots, as well as the connected sum + of a trefoil with its mirror (see the corresponding example in the + documentation of :meth:`connected_sum`). + EXAMPLES:: sage: W = Knots() diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 997750feabc..582041efa0b 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -23,24 +23,21 @@ Be aware that there are a couple of conventions used differently on KnotInfo as in Sage, especially concerning the selection of the symmetry version of the link. -In our transitions to Sage objects these are translated (by default) in order to -avoid confusion about exchanged mirror versions. -Briefly, these differences are: +In this context you should note that the PD notation is recorded counter +clockwise in KnotInfo (see note in :meth:`KnotInfoBase.link`). In our transition +to Sage objects this is translated (by default) in order to avoid confusion about +exchanged mirror versions. -- ``pd_notation`` -- KnotInfo: counter clockwise Sage: clockwise, see note - in :meth:`KnotInfoBase.link` - -- ``homfly_polynomial`` -- KnotInfo: ``v`` Sage: `1/a`, see note in - :meth:`KnotInfoBase.homfly_polynomial`. - -- ``braid_notation`` -- This is used accordingly: The crossing of the braid - generators are positive in both systems. Here it is listed because there could - arise confusion from the source where they are taken from. There, the braid - generators are assumed to have a negative crossing (see definition 3 of - `Gittings, T., "Minimum Braids: A Complete Invariant of Knots and Links - `__). +Also, note that the braid notation is used according to Sage, even thought in +the source where it is taken from, the braid generators are assumed to have a +negative crossing which would be opposite to the convention in Sage (see definition +3 of `Gittings, T., "Minimum Braids: A Complete Invariant of Knots and Links +`__). +For different conventions regarding normalization of the polynomial invariants see +the according documentation of :meth:`KnotInfoBase.homfly_polynomial`, +:meth:`KnotInfoBase.jones_polynomial` and :meth:`KnotInfoBase.alexander_polynomial`. EXAMPLES:: @@ -218,6 +215,8 @@ AUTHORS: - Sebastian Oehms August 2020: initial version + +Thanks to Chuck Livingston and Allison Moore for their support. """ @@ -861,10 +860,19 @@ def is_amphicheiral(self, positive=False): OUTPUT: - ``True`` if ``self`` is fully or negative amphicheiral per default. if + Boolean or ``None`` if this cannot be determined. + + ``True`` if ``self`` is fully or negative amphicheiral per default. If ``positive`` is set to ``True`` than fully and positive amphicheiral links give ``True``. + .. NOTE:: + + For proper links this property is not provided in the database. + Anyway, we support it here in this case, as well, except for a few + items where it cannot be determined easily and where ``None`` + is returned as answer. + EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfo @@ -879,18 +887,57 @@ def is_amphicheiral(self, positive=False): True sage: Kn.is_amphicheiral(positive=True) # optional - database_knotinfo False + + sage: KnotInfo.L4a1_0.is_amphicheiral() + False + sage: KnotInfo.L10n59_1.is_amphicheiral() # optional - database_knotinfo + True + sage: KnotInfo.L10n59_0.inject() # optional - database_knotinfo + Defining L10n59_0 + sage: L10n59_0.is_amphicheiral() is None # optional - database_knotinfo + True """ - symmetry_type = self.symmetry_type() - if positive: - if symmetry_type == 'positive amphicheiral': + if self.is_knot(): + symmetry_type = self.symmetry_type() + if positive: + if symmetry_type == 'positive amphicheiral': + return True + else: + if symmetry_type == 'negative amphicheiral': + return True + + if symmetry_type == 'fully amphicheiral': return True - else: - if symmetry_type == 'negative amphicheiral': + return False + + h = self.homfly_polynomial() + v, z = h.parent().gens() + hm = h.subs(v=~v, z=-z) + if h != hm: + return False + + k = self.kauffman_polynomial() + a, z = k.parent().gens() + km = k.subs(a=~a) + if k != km: + return False + + b = self.braid() + bi = ~b + if b.is_conjugated(bi): + # at least negative amphicheiral + if not positive: return True - if symmetry_type == 'fully amphicheiral': - return True - return False + # revert orientation (back) + bit = list(bi.Tietze()) + bit.reverse() + bm = b.parent()(tuple(bit)) + if b.is_conjugated(bm): + if positive: + return True + + return None @cached_method def is_alternating(self): @@ -1099,7 +1146,7 @@ def homfly_polynomial(self, var1='v', var2='z', original=False): TESTS:: sage: all(L.homfly_polynomial() == L.link().homfly_polynomial(normalization='vz')\ - for L in KnotInfo if L.crossing_number() > 0 and L.crossing_number() < 7) + for L in KnotInfo if L.crossing_number() < 7) True REFERENCES: @@ -1649,20 +1696,85 @@ def link(self, use_item=db.columns().pd_notation, snappy=False): raise ValueError('Link construction using %s not possible' %use_item) - def _test_recover(self, **options): + + @cached_method + def is_unique(self): + r""" + Return whether there is no other isotopic link in the database or not. + + OUTPUT: + + Boolean or ``None`` if this cannot be determined. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L4a1_0.is_unique() + True + sage: KnotInfo.L5a1_0.is_unique() + False + sage: L = KnotInfo.L7a7_0_0 # optional - database_knotinfo + sage: L.series(oriented=True).inject() # optional - database_knotinfo + Defining L7a7 + sage: [(L,L.is_unique()) for L in L7a7] # optional - database_knotinfo + [(, False), + (, None), + (, False), + (, True)] + """ + # an isotopic pair must have the same unoriented name. So, we can focus + # on such series + if self.is_knot(): + return True + S = self.series(oriented=True) + hp = self.homfly_polynomial() + Sl = S.list(homfly=hp) + if len(Sl) == 1: + return True + kp = self.kauffman_polynomial() + Sl = [L for L in Sl if L != self and L.kauffman_polynomial() == kp] + if not Sl: + return True + + b = self.braid() + for L in Sl: + Lb = L.braid() + if L.braid() == b: + return False + if Lb.is_conjugated(b): + return False + + return None + + @cached_method + def is_recoverable(self, unique=True): r""" - Check if ``self`` can be recovered from its conversion to Sage links + Return if ``self`` can be recovered from its conversion to Sage links using the ``pd_notation`` and the ``braid_notation`` and their mirror images. - The method is used by the ``TestSuite`` of the series of ``self``. + The method is indirectly used by the ``TestSuite`` of the series of ``self``. + + INPUT: + + - ``unique`` -- boolean (optional, default=``True``) if set to ``False`` + it is only checked if ``self``is among the recovered items EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfo - sage: from sage.misc.sage_unittest import instance_tester - sage: K = KnotInfo.K6_1 - sage: K._test_recover(tester=instance_tester(K)) + sage: KnotInfo.L4a1_0.inject() + Defining L4a1_0 + sage: L4a1_0.is_recoverable() + True + sage: L4a1_0.is_recoverable(unique=False) + True + sage: KnotInfo.L5a1_0.inject() + Defining L5a1_0 + sage: L5a1_0.is_recoverable() + False + sage: L5a1_0.is_recoverable(unique=False) + True """ def recover(mirror, braid): r""" @@ -1676,31 +1788,33 @@ def recover(mirror, braid): if mirror: l = l.mirror_image() - def check_result(L): + def check_result(L, m): r""" Check a single result from ``get_knotinfo`` """ - if L == (self, None) or L == (self, '?'): + if L != self: + return False + if m is None or m == '?': return True if mirror: - return L == (self, True) + return m else: - return L == (self, False) + return not m try: - L = l.get_knotinfo() - return check_result(L) + L, m = l.get_knotinfo() + if isinstance(L, KnotInfoBase): + return check_result(L,m) + elif unique: + return False except NotImplementedError: - Llist = l.get_knotinfo(unique=False) - return any(check_result(L) for L in Llist) - - tester = options['tester'] - tester.assertTrue(recover(False, False)) - tester.assertTrue(recover(True, False)) - tester.assertTrue(recover(False, True)) - tester.assertTrue(recover(True, True)) - + if unique: + return False + Llist = l.get_knotinfo(unique=False) + return any(check_result(L, m) for (L, m) in Llist) + from sage.misc.misc import some_tuples + return all(recover(mirror, braid) for mirror, braid in some_tuples([True, False], 2, 4)) def inject(self, verbose=True): """ @@ -2147,11 +2261,48 @@ def _name(self): res = 'L%s%s' %(cross_nr, alt) return res + def is_recoverable(self, unique=True, max_samples=8): + r""" + Return if all items of ``self`` can be recovered from its conversion to + Sage links using the ``pd_notation`` and the ``braid_notation`` and their + mirror images. + + The method is indirectly used by the ``TestSuite``. + + INPUT: + + - ``unique`` -- boolean (optional, default=``True``) see :meth:`KnotInfoBase.is_recoverable` + - ``max_samples`` non negative integer or ``infinity`` (optional, default `8`) limits the number of + items to check (random sample). If set to ``infinity`` then no limit is set. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L4a1_0.series().inject() + Defining L4a + sage: L4a.is_recoverable() + True + sage: L4a.is_recoverable(unique=False) + True + sage: KnotInfo.L5a1_0.series().inject() + Defining L5a + sage: L5a.is_recoverable() + False + sage: L5a.is_recoverable(unique=False) + True + """ + from sage.misc.misc import some_tuples + l = self.list(oriented=True) + bound = len(l) + return all(L.is_recoverable(unique=unique) for L, in some_tuples(l, 1, bound, max_samples=max_samples)) + def _test_recover(self, **options): r""" Method used by ``TestSuite``. Tests if all links of the series can be - recovered from their conversion to Sage links. It uses :meth:`_test_recover` - of :class:`KnotInfoBase`. + recovered from their conversion to Sage links. It uses :meth:`is_recoverable`. + Thus, per default maximal `8` items (random sample) are tested. Use the + option ``max_samples`` to choose another limit or test all + (``max_samples=infinity``) EXAMPLES:: @@ -2162,9 +2313,14 @@ def _test_recover(self, **options): running ._test_not_implemented_methods() . . . pass running ._test_pickling() . . . pass running ._test_recover() . . . pass + sage: TestSuite(KnotInfo.K6_1.series()).run(max_samples=infinity) # indirec doctest """ - for L in self: - L._test_recover(**options) + tester = options['tester'] + max_samples = tester._max_samples + if max_samples: + tester.assertTrue(self.is_recoverable(unique=False, max_samples=max_samples)) + else: + tester.assertTrue(self.is_recoverable(unique=False)) def inject(self, verbose=True): diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index cdd93c56c8b..fcb0f536495 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -3455,22 +3455,16 @@ def _knotinfo_matching_list(self): res.append(L) if res: - return res, True + if len(res) > 1 or res[0].is_unique(): + return res, True return l, False - def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): + def get_knotinfo(self, mirror_version=True, unique=True): r""" Identify this link as an item of the KontInfo database (if possible). INPUT: - - ``oriented`` -- boolean (default is ``True``). If set to ``False`` the - orientation of the link will be ignored and instead of an instance of - :class:`~sage.knots.knotinfo.KnotInfoBase` a series of links (instance - of :class:`~sage.knots.knotinfo.KnotInfoSeries`) will be returned collecting - all links having the same ``name_unoriented`` (if this is unique for - ``self``) - - ``mirror_version`` -- boolean (default is ``True``). If set to ``False`` the result of the method will be just the instance of :class:`~sage.knots.knotinfo.KnotInfoBase` (by default the result is a tuple of the instance and a boolean, see @@ -3483,13 +3477,14 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): OUTPUT: A tuple ``(K, m)`` where ``K`` is an instance of :class:`~sage.knots.knotinfo.KnotInfoBase` - and ``m`` a boolean (for chiral links) telling if ``self`` corresponse + and ``m`` a boolean (for chiral links) telling if ``self`` corresponds to the mirrored version of ``K`` or not. The value of ``m`` is ``None`` for amphicheiral links and ``?`` if it cannot be determined uniquely and the keyword option ``unique=False`` is given. - If ``oriented`` is set to ``False`` then the result is a series of links - (instance of :class:`~sage.knots.knotinfo.KnotInfoSeries`, see explanation above). + For proper links, if the orientation mutant cannot be uniquely determined, + K will be a series of links gathering all links having the same unoriented + name, that is an instance of :class:`~sage.knots.knotinfo.KnotInfoSeries`. If ``mirror_version`` is set to ``False`` then the result is just ``K`` (that is: ``m`` is suppressed). @@ -3500,17 +3495,20 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): .. NOTE:: - The identification of proper links may fail due to the following - fact: In opposite to the database for knots, there are pairs of - oriented mutants of an unoriented link which are isotopic to each + The identification of proper links may fail to be unique due to the + following fact: In opposite to the database for knots, there are pairs + of oriented mutants of an unoriented link which are isotopic to each other. For example ``L5a1_0`` and ``L5a1_1`` is such a pair. This is because all combinatorial possible oriented mutants are listed with individual names regardless whether they are pairwise non isotopic or not. In such a case the identification is not - unique and therefore a ``NotImplemendedError`` is raised. Here, - the usage of the keywords ``oriented`` and ``unique`` may help to - find the matching items (see example for ``L5a1_0`` below). + unique and therefore a series of the links will be returned which + gathers all having the same unoriented name. + + To obtain the individual oriented links being isotopic to ``self`` + use the keyword ``unique`` (see the examples for ``L2a1_1`` and + ``L5a1_0`` below). EXAMPLES:: @@ -3556,38 +3554,14 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): sage: L.get_knotinfo(mirror_version=False) == KnotInfo.K0_1 True - Usage of option ``oriented``:: - - sage: KnotInfo.L5a1_0.inject() - Defining L5a1_0 - sage: l5 = Link(L5a1_0.braid()) - sage: l5.get_knotinfo() - Traceback (most recent call last): - ... - NotImplementedError: this link cannot be uniquely determined - - sage: l5.get_knotinfo(oriented=False) - (Series of links L5a1, False) - sage: _[0].inject() - Defining L5a1 - sage: list(L5a1) - [, ] - Usage of option ``unique``:: - sage: L2a1 = Link(b**2) - sage: L2a1.get_knotinfo() - Traceback (most recent call last): - ... - NotImplementedError: this link cannot be uniquely determined - sage: L2a1.get_knotinfo(unique=False) - [(, True), (, False)] - sage: l = K.link(K.items.gauss_notation) # optional - database_knotinfo sage: l.get_knotinfo() # optional - database_knotinfo Traceback (most recent call last): ... NotImplementedError: this link cannot be uniquely determined + use keyword argument `unique` to obtain more details sage: l.get_knotinfo(unique=False) # optional - database_knotinfo [(, False), (, False)] @@ -3600,10 +3574,57 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): Traceback (most recent call last): ... NotImplementedError: mirror type of this link cannot be uniquely determined + use keyword argument `unique` to obtain more details sage: k11m.get_knotinfo(unique=False) # optional - database_knotinfo [(, '?')] + sage: t = (-1, 2, -1, 2, -1, 3, -2, 3, -2) + sage: l9 = Link(BraidGroup(4)(t)) + sage: l9.get_knotinfo() # optional - database_knotinfo + Traceback (most recent call last): + ... + NotImplementedError: this link cannot be uniquely determined + use keyword argument `unique` to obtain more details + + sage: l9.get_knotinfo(unique=False) # optional - database_knotinfo + [(, False), + (, False)] + + sage: t = (1, 2, 3, -4, 3, -2, -1, 3, -2, 3, -2, 3, -4, 3, -2) + sage: l15 = Link(BraidGroup(5)(t)) + sage: l15.get_knotinfo() # optional - database_knotinfo + Traceback (most recent call last): + ... + NotImplementedError: this link having more than 11 crossings cannot be uniquely determined + use keyword argument `unique` to obtain more details + + sage:l15.get_knotinfo(unique=False) # optional - database_knotinfo + [(, False), + (, False)] + + Furthermore, if the result is a complete series of oriented links having + the same unoriented name (according to the note above) the option can be + used to achieve more detailed information:: + + sage: L2a1 = Link(b**2) + sage: L2a1.get_knotinfo() + (Series of links L2a1, None) + sage: L2a1.get_knotinfo(unique=False) + [(, True), (, False)] + + sage: KnotInfo.L5a1_0.inject() + Defining L5a1_0 + sage: l5 = Link(L5a1_0.braid()) + sage: l5.get_knotinfo() + (Series of links L5a1, False) + sage: _[0].inject() + Defining L5a1 + sage: list(L5a1) + [, ] + sage: l5.get_knotinfo(unique=False) + [(, False), (, False)] + Clarifying the series around the Perko pair (:wikipedia:`Perko_pair`):: sage: for i in range(160, 166): # optional - database_knotinfo @@ -3616,7 +3637,8 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): 10_164 ---> (, False) 10_165 ---> (, True) - Clarifying ther Perko series against `SnapPy `__:: + Clarifying ther Perko series against `SnapPy + `__:: sage: import snappy # optional - snappy Plink failed to import tkinter. @@ -3649,57 +3671,84 @@ def get_knotinfo(self, oriented=True, mirror_version=True, unique=True): sage: _.sage_link().get_knotinfo() # optional - database_knotinfo snappy (, False) + + Another pair of confusion (see the corresponding `Warning + `__):: + + sage: Ks10_86 = snappy.Link('10_86') # optional - snappy + sage: Ks10_83 = snappy.Link('10_83') # optional - snappy + sage: Ks10_86.sage_link().get_knotinfo() # optional - snappy + (, False) + sage: Ks10_83.sage_link().get_knotinfo() # optional - snappy + (, True) """ + non_unique_hint = '\nuse keyword argument `unique` to obtain more details' def answer(L): r""" Return a single item of the KnotInfo database according to the keyword - arguments ``oriented`` and ``mirror_version``. + arguments ``mirror_version``. """ - is_knot = L.is_knot() - if not oriented and not is_knot: - L = L.series(oriented=True) - - if mirror_version: - chiral = True - if is_knot: - if L.is_amphicheiral() or L.is_amphicheiral(positive=True): - chiral = False - elif L in ls and L in lm: - if proved_s and proved_m: - chiral = False - elif self._markov_move_cmp(self_m.braid()): - chiral = False - elif unique: - raise NotImplementedError('this link cannot be uniquely determined (unknown chirality)') - - if not chiral: - mirrored = None - elif proved_m and not proved_s: + if not mirror_version: + return L + + chiral = True + ach = L.is_amphicheiral(); achp = L.is_amphicheiral(positive=True) + if ach is None and achp is None: + if unique: + raise NotImplementedError('this link cannot be uniquely determined (unknown chirality)%s' %non_unique_hint) + elif L.is_amphicheiral() or L.is_amphicheiral(positive=True): + chiral = False + + if not chiral: + mirrored = None + elif proved_m and not proved_s: + mirrored = True + elif proved_s and not proved_m: + mirrored = False + else: + # nothing proved + if L in ls and L in lm: + # In case of a chiral link this means that the HOMFLY-PT + # polynomial does not distinguish mirror images (see the above + # example ``k11m``). + if unique: + raise NotImplementedError('mirror type of this link cannot be uniquely determined%s' %non_unique_hint) + mirrored = '?' + elif L in lm: mirrored = True - elif proved_s and not proved_m: - mirrored = False else: - # nothing proved - if L in ls and L in lm: - # In case of a chiral link this means that the HOMFLY-PT - # polynomial does not distinguish mirror images (see the above - # example ``k11m``). - if unique: - raise NotImplementedError('mirror type of this link cannot be uniquely determined') - mirrored = '?' - elif L in lm: - mirrored = True - else: - mirrored = False + mirrored = False - return L, mirrored - else: - return L + return L, mirrored + + def answer_unori(S): + r""" + Return a series of oriented links having the same unoriented name + according to the keyword ``mirror_version``. + """ + if not mirror_version: + return S + + mirrored = [answer(L)[1] for L in S] + if all(mirrored): + # all matching links are mirrored to self + return S, True + if any(i == '?' for i in mirrored): + # unknown chirality for a matching link + return S, '?' + if any(i is None for i in mirrored): + # an amphicheiral link matches + return S, None + if not any(mirrored): + # no matching link is mirrored to self + return S, False + # finally both mirror types match + return S, None def answer_list(l): r""" Return a list of items of the KnotInfo database according to the keyword - arguments ``oriented`` and ``unique``. + argument ``unique``. """ if not unique: return sorted([answer(L) for L in l]) @@ -3707,12 +3756,12 @@ def answer_list(l): if len(l) == 1: return answer(l[0]) - if not oriented: - lu = list(set([L.name_unoriented() for L in l])) - if len(lu) == 1: - return answer(l[0]) + if not l[0].is_knot(): + S = l[0].series(oriented=True) + if set(list(S)) == set(l): + return answer_unori(S) - raise NotImplementedError('this link cannot be uniquely determined') + raise NotImplementedError('this link cannot be uniquely determined%s' %non_unique_hint) self_m = self.mirror_image() @@ -3734,25 +3783,25 @@ def answer_list(l): if l and not unique: return answer_list(l) - uniq_txt = '' + uniq_txt = ('', '') if l: - uniq_txt = ' uniquely' + uniq_txt = (' uniquely', non_unique_hint) cr = len(self.pd_code()) if self.is_knot() and cr > 12: # we cannot not be sure if this link is recorded in the KnotInfo database - raise NotImplementedError('this knot having more than 12 crossings cannot be%s determined' %uniq_txt) + raise NotImplementedError('this knot having more than 12 crossings cannot be%s determined%s' %uniq_txt) if not self.is_knot() and cr > 11: # we cannot not be sure if this link is recorded in the KnotInfo database - raise NotImplementedError('this link having more than 11 crossings cannot be%s determined' %uniq_txt) + raise NotImplementedError('this link having more than 11 crossings cannot be%s determined%s' %uniq_txt) - H = self.homfly_polynomial(normalization='az') + H = self.homfly_polynomial(normalization='vz') - if len(H.factor()) > 1: + if sum(exp for f, exp in H.factor()) > 1: # we cannot be sure if this is a prime link (see the example for the connected - # sum of K4_1 and K5_2 in the doctest of :func:`knotinfo_matching_list`) - raise NotImplementedError('this (possibly non prime) link cannot be%s determined' %uniq_txt) + # sum of K4_1 and K5_2 in the doctest of :meth:`_knotinfo_matching_list`) + raise NotImplementedError('this (possibly non prime) link cannot be%s determined%s' %uniq_txt) if not l: from sage.features.databases import DatabaseKnotInfo @@ -3784,6 +3833,22 @@ def is_isotopic(self, other): sage: l3 = l2.mirror_image() sage: l1.is_isotopic(l3) False + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L7a7_0_0 # optional - database_knotinfo + sage: L.series(oriented=True).inject() # optional - database_knotinfo + Defining L7a7 + sage: L == L7a7(0) # optional - database_knotinfo + True + sage: l = L.link() # optional - database_knotinfo + sage: l.is_isotopic(L7a7(1).link()) # optional - database_knotinfo + Traceback (most recent call last): + ... + NotImplementedError: comparison not possible! + sage: l.is_isotopic(L7a7(2).link()) # optional - database_knotinfo + True + sage: l.is_isotopic(L7a7(3).link()) # optional - database_knotinfo + False """ if not isinstance(other, Link): return False @@ -3800,10 +3865,19 @@ def is_isotopic(self, other): # surely isotopic return True - ki = self.get_knotinfo() - if ki and type(ki) == tuple: - kio = other.get_knotinfo() - if kio and type(kio) == tuple: - return ki == kio + try: + ki, m = self.get_knotinfo() + try: + if ki.is_unique(): + try: + kio = other.get_knotinfo() + return (ki, m) == kio + except NotImplementedError: + pass + except AttributeError: + # ki is a series + pass + except NotImplementedError: + pass raise NotImplementedError('comparison not possible!') From 440b5f39c410ebc6e574714fe939d40a18143285 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Tue, 22 Dec 2020 16:35:12 +0100 Subject: [PATCH 068/232] 30352: add _test_database and fix broken installation --- build/pkgs/database_knotinfo/spkg-check.in | 2 +- build/pkgs/database_knotinfo/spkg-install.py | 7 +-- src/sage/databases/knotinfo_db.py | 65 ++++++++++++++------ src/sage/knots/knotinfo.py | 12 ++-- 4 files changed, 57 insertions(+), 29 deletions(-) diff --git a/build/pkgs/database_knotinfo/spkg-check.in b/build/pkgs/database_knotinfo/spkg-check.in index 94d3e947e00..6a1eb79a95a 100644 --- a/build/pkgs/database_knotinfo/spkg-check.in +++ b/build/pkgs/database_knotinfo/spkg-check.in @@ -1,7 +1,7 @@ cd $SAGE_ROOT/src/sage/ echo "Testing databases/knotinfo_db.py" -sage -t --optional="sage,database_knotinfo" databases/knotinfo_db.py || sdh_die "Error testing KnotInfo databases" +sage -t --long --optional="sage,database_knotinfo" databases/knotinfo_db.py || sdh_die "Error testing KnotInfo databases" echo "Testing knots/knotinfo.py" sage -t --optional="sage,database_knotinfo" knots/knotinfo.py || sdh_die "Error testing KnotInfo funcionality" diff --git a/build/pkgs/database_knotinfo/spkg-install.py b/build/pkgs/database_knotinfo/spkg-install.py index 7e9c3fad8f3..3b122a13c3a 100644 --- a/build/pkgs/database_knotinfo/spkg-install.py +++ b/build/pkgs/database_knotinfo/spkg-install.py @@ -1,15 +1,12 @@ import os -from sage.all import save from sage.env import SAGE_SHARE from sage.misc.misc import sage_makedirs -from sage.databases.knotinfo_db import KnotInfoDataBase install_root = os.path.join(SAGE_SHARE, 'knotinfo') if __name__ == '__main__': sage_makedirs(install_root) print("Creating the KnotInfo database.") - ki_db = KnotInfoDataBase() - ki_db.create_col_dict_sobj() - ki_db.create_data_sobj() + from sage.databases.knotinfo_db import KnotInfoDataBase + KnotInfoDataBase(install=True) os.system('cp package-version.txt %s' %install_root) diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py index 9203a8d24e7..9d226af64d4 100644 --- a/src/sage/databases/knotinfo_db.py +++ b/src/sage/databases/knotinfo_db.py @@ -30,6 +30,7 @@ from enum import Enum from sage.structure.sage_object import SageObject +from sage.structure.unique_representation import UniqueRepresentation from sage.misc.persist import save, load from sage.misc.verbose import verbose from sage.misc.cachefunc import cached_method @@ -335,7 +336,7 @@ def diagram_url(self, fname, single=False): knots = ['https://knotinfo.math.indiana.edu/', 'knotinfo_data_complete'] - links = ['https://linkinfo.sitehost.iu.edu/', 'linkinfo_data_complete'] + links = ['https://linkinfo.sitehost.iu.edu/', 'linkinfo_data_complete'] @@ -343,7 +344,7 @@ def diagram_url(self, fname, single=False): #---------------------------------------------------------------------------------------------------------------------------- # Class to provide data for knots and links from the KnotInfo web-page #---------------------------------------------------------------------------------------------------------------------------- -class KnotInfoDataBase(SageObject): +class KnotInfoDataBase(SageObject, UniqueRepresentation): r""" Database interface to KnotInfo @@ -363,7 +364,7 @@ class KnotInfoDataBase(SageObject): filename = KnotInfoFilename - def __init__(self): + def __init__(self, install=False): r""" Python constructor. @@ -375,25 +376,36 @@ def __init__(self): """ - from sage.features.databases import DatabaseKnotInfo - self._feature = DatabaseKnotInfo() - self._sobj_path = KnotInfoFilename.knots.sobj_path() - version_file = os.path.join(SAGE_ROOT, 'build/pkgs/%s/package-version.txt' %self._feature.spkg) - f = open(version_file) - self._version = f.read().splitlines()[0] - f.close() - # some constants self._delimiter = '|' self._names_column = 'name' self._components_column = 'components' self._knot_prefix = 'K' + self._sobj_path = KnotInfoFilename.knots.sobj_path() self._knot_list = None self._link_list = None self._demo = None self._num_knots = None + if install: + knot_list = self.knot_list() + num_knots = len(knot_list) - 1 + print('Setting the number of Knots: %s!' %num_knots) + save(num_knots, '%s/%s' %(self._sobj_path, self.filename.knots.sobj_num_knots())) + + from sage.features.databases import DatabaseKnotInfo + self._feature = DatabaseKnotInfo() + + version_file = os.path.join(SAGE_ROOT, 'build/pkgs/%s/package-version.txt' %self._feature.spkg) + f = open(version_file) + self._version = f.read().splitlines()[0] + f.close() + + if install: + self._feature._cache_is_present = None # must be reset for package installation + self._create_col_dict_sobj() + self._create_data_sobj() def demo_version(self): r""" @@ -500,7 +512,7 @@ def link_list(self): link_csv.close() return self._link_list - def create_col_dict_sobj(self): + def _create_col_dict_sobj(self): r""" Create ``sobj`` files containing the number of knots and a dictionary for the columns of the table. @@ -509,7 +521,7 @@ def create_col_dict_sobj(self): sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: ki_db.create_col_dict_sobj() # not tested (used on installation) + sage: ki_db._create_col_dict_sobj() # not tested (used on installation) """ knot_list = self.knot_list() knot_column_names = knot_list[0] @@ -521,9 +533,6 @@ def create_col_dict_sobj(self): from sage.misc.misc import sage_makedirs sage_makedirs(self._sobj_path) - num_knots = len_knots - 1 - save(num_knots, '%s/%s' %(self._sobj_path, self.filename.knots.sobj_num_knots())) - column_dict = {} # ---------------------------------------------------------------- @@ -562,7 +571,7 @@ def create_col_dict_sobj(self): - def create_data_sobj(self): + def _create_data_sobj(self): r""" Create ``sobj`` files containing the contents of the whole table. To each column there is created one file containing a list of @@ -579,7 +588,7 @@ def create_data_sobj(self): sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() - sage: ki_db.create_data_sobj() # not tested (just used on installation) + sage: ki_db._create_data_sobj() # not tested (just used on installation) """ knot_list = self.knot_list() link_list = self.link_list() @@ -780,6 +789,26 @@ def read(self, column): return res + def _test_database(self, **options): + r""" + Method used by TestSuite. Performs :meth:`KnotInfoBase.is_recoverable`. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: TestSuite(ki_db).run() # long time + """ + from sage.knots.knotinfo import KnotInfo + from sage.misc.misc import some_tuples + tester = options['tester'] + max_samples = tester._max_samples + if not max_samples: + max_samples = 20 + l = list(KnotInfo) + sample = some_tuples(l, 1, len(l), max_samples=max_samples) + tester.assertTrue(all(L.is_recoverable(unique=False) for L, in sample)) + column_demo_sample = { diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 582041efa0b..71e0eb2a507 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -1758,7 +1758,7 @@ def is_recoverable(self, unique=True): INPUT: - ``unique`` -- boolean (optional, default=``True``) if set to ``False`` - it is only checked if ``self``is among the recovered items + it is only checked if ``self`` is among the recovered items EXAMPLES:: @@ -1790,7 +1790,7 @@ def recover(mirror, braid): def check_result(L, m): r""" - Check a single result from ``get_knotinfo`` + Check a single result from ``get_knotinfo``. """ if L != self: return False @@ -2271,9 +2271,11 @@ def is_recoverable(self, unique=True, max_samples=8): INPUT: - - ``unique`` -- boolean (optional, default=``True``) see :meth:`KnotInfoBase.is_recoverable` - - ``max_samples`` non negative integer or ``infinity`` (optional, default `8`) limits the number of - items to check (random sample). If set to ``infinity`` then no limit is set. + - ``unique`` -- boolean (optional, default=``True``) see + :meth:`KnotInfoBase.is_recoverable` + - ``max_samples`` -- non negative integer or ``infinity`` (optional, + default ``8``) limits the number of items to check (random sample). + If set to ``infinity`` then no limit is set. EXAMPLES:: From f9d42c8f799e657c31b79a8657e44061655812e5 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 17 Aug 2020 19:56:34 +0200 Subject: [PATCH 069/232] Implement theta basis in rank 2 --- src/doc/en/reference/references/index.rst | 3 + src/sage/algebras/cluster_algebra.py | 250 ++++++++++++++++++---- 2 files changed, 217 insertions(+), 36 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index ad49f9c38cb..86a33ba222a 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4680,6 +4680,9 @@ REFERENCES: .. [Rea2009] Nathan Reading, *Noncrossing partitions and the shard intersection order*, DMTCS Proceedings of FPSAC 2009, 745--756 +.. [ReSt2020] Nathan Reading and Salvatore Stella, *An affine almost positive + roots model*, J. Comb. Algebra Volume 4, Issue 1, 2020, pp. 1--59 + .. [Red2001] Maria Julia Redondo. *Hochschild cohomology: some methods for computations*. Resenhas IME-USP 5 (2), 113-137 (2001). http://inmabb.criba.edu.ar/gente/mredondo/crasp.pdfc diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index f79dccc2eef..c403716a358 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -363,6 +363,7 @@ from sage.functions.other import binomial from sage.geometry.cone import Cone from sage.geometry.fan import Fan +from sage.graphs.digraph import DiGraph from sage.matrix.constructor import identity_matrix, matrix from sage.matrix.special import block_matrix from sage.misc.cachefunc import cached_method @@ -1476,6 +1477,48 @@ def _coerce_map_from_(self, other): # everything that is in the base can be coerced to self return self.base().has_coerce_map_from(other) + @cached_method + def coxeter_element(self): + r""" + Return the Coxeter element associated to the initial exchange matrix, if acyclic. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.coxeter_element() + [0, 1, 2] + + Raise an error if the initial exchange matrix is not acyclic:: + + sage: A = ClusterAlgebra(matrix([[0,1,-1],[-1,0,1],[1,-1,0]])) + sage: A.coxeter_element() + Traceback (most recent call last): + ... + ValueError: The initial exchange matrix is not acyclic. + """ + dg = DiGraph(self.b_matrix().apply_map(lambda x: ZZ(0) if x <= 0 else ZZ(1))) + acyclic, coxeter = dg.is_directed_acyclic(certificate=True) + if not acyclic: + raise ValueError("The initial exchange matrix is not acyclic.") + return coxeter + + @cached_method + def is_acyclic(self): + r""" + Return ``True`` if the exchange matrix in the initial seed is acyclic, ``False`` otherwise. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.is_acyclic() + True + sage: A = ClusterAlgebra(matrix([[0,1,-1],[-1,0,1],[1,-1,0]])) + sage: A.is_acyclic() + False + """ + dg = DiGraph(self.b_matrix().apply_map(lambda x: ZZ(0) if x <= 0 else ZZ(1))) + return dg.is_directed_acyclic() + def rank(self): r""" Return the rank of ``self``, i.e. the number of cluster variables @@ -1626,6 +1669,94 @@ def b_matrix(self): n = self.rank() return copy(self._B0[:n, :]) + def euler_matrix(self): + r""" + Return the Euler matrix associated to ``self``. + + ALGORITHM: + + This method returns the matrix of the biliinear form defined in Equation (2.1) of [ReSt2020]_ . + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.euler_matrix() + [ 1 0 0] + [-1 1 0] + [-1 -1 1] + + Raise an error if the initial exchange matrix is not acyclic:: + + sage: A = ClusterAlgebra(matrix([[0,1,-1],[-1,0,1],[1,-1,0]])) + sage: A.euler_matrix() + Traceback (most recent call last): + ... + ValueError: The initial exchange matrix is not acyclic. + """ + + if not self.is_acyclic(): + raise ValueError("The initial exchange matrix is not acyclic.") + return 1 + self.b_matrix().apply_map(lambda x: min(ZZ(0), x)) + + def d_vector_to_g_vector(self, d): + r""" + Return the g-vector of an element of ``self`` having d-vector ``d`` + + INPUT: + + - ``d`` -- the d-vector + + ALGORITHM: + + This method implements the piecewise-linear map `\\nu_c` introduced in Section 9.1 of [ReSt2020]_. + + .. WARNING: + + This implementation works only when the initial exchange matrix is acyclic. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.d_vector_to_g_vector((1,0,-1)) + (-1, 1, 2) + """ + dm = vector(( x if x < 0 else 0 for x in d)) + dp = vector(d) - dm + return tuple(- dm - self.euler_matrix()*dp) + + def g_vector_to_d_vector(self, g): + r""" + Return the d-vector of an element of ``self`` having g-vector ``g`` + + INPUT: + + - ``g`` -- the g-vector + + ALGORITHM: + + This method implements the inverse of the piecewise-linear map `\\nu_c` introduced in Section 9.1 of [ReSt2020]_. + + .. WARNING: + + This implementation works only when the initial exchange matrix is acyclic. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.g_vector_to_d_vector((-1,1,2)) + (1, 0, -1) + """ + E = -self.euler_matrix() + c = self.coxeter_element() + dp = vector(ZZ, self.rank()) + g = vector(g) + for i in c: + dp[i] = -min(g[i], 0) + g += min(g[i],0)*E.column(i) + return tuple(-g+dp) + + return d + def g_vectors(self, mutating_F=True): r""" Return an iterator producing all the g-vectors of ``self``. @@ -2392,28 +2523,89 @@ def greedy_element(self, d_vector): if self.rank() != 2: raise ValueError('greedy elements are only defined in rank 2') - if len(self.coefficients()) != 0: - raise NotImplementedError('can only compute greedy elements in the coefficient-free case') + return self.theta_basis_element(self.d_vector_to_g_vector(d_vector)) - b = abs(self.b_matrix()[0, 1]) - c = abs(self.b_matrix()[1, 0]) - a1, a2 = d_vector - # Here we use the generators of self.ambient() because cluster variables - # do not have an inverse. - x1, x2 = self.ambient().gens() - if a1 < 0: - if a2 < 0: - return self.retract(x1 ** (-a1) * x2 ** (-a2)) - else: - return self.retract(x1 ** (-a1) * ((1 + x2 ** c) / x1) ** a2) - elif a2 < 0: - return self.retract(((1 + x1 ** b) / x2) ** a1 * x2 ** (-a2)) - output = 0 - for p in range(0, a2 + 1): - for q in range(0, a1 + 1): - output += self._greedy_coefficient(d_vector, p, q) * x1 ** (b * p) * x2 ** (c * q) - return self.retract(x1 ** (-a1) * x2 ** (-a2) * output) + @cached_method(key=lambda a, b: tuple(b)) + def theta_basis_element(self, g_vector): + r""" + Return the element of the theta basis of ``self`` with g-vector ``g_vector``. + + INPUT: + + - ``g_vector`` -- tuple; the g-vector of the element to compute + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,-3],[2,0]]), principal_coefficients=True) + sage: A.theta_basis_element((-1,-1)) + (x1^8*y0^4*y1 + 4*x1^6*y0^3*y1 + 6*x1^4*y0^2*y1 + x0^3*x1^2*y0 + 4*x1^2*y0*y1 + x0^3 + y1)/(x0^4*x1) + + sage: A = ClusterAlgebra(['F', 4]) + sage: A.theta_basis_element((1, 0, 0, 0)) + Traceback (most recent call last): + ... + NotImplementedError: Currently only implemented for cluster algebras of rank 2. + + .. WARNING:: + + Currently only cluster algebras of rank 2 are supported + + .. SEEALSO:: + + :meth:`sage.algebras.cluster_algebra.theta_basis_F_polynomial` + """ + g_vector = tuple(g_vector) + F = self.theta_basis_F_polynomial(g_vector) + F_std = F.subs(self._yhat) + g_mon = prod(self.ambient().gen(i) ** g_vector[i] for i in range(self.rank())) + F_trop = self.ambient()(F.subs(self._y))._fraction_pair()[1] + return self.retract(g_mon * F_std * F_trop) + @cached_method(key=lambda a, b: tuple(b)) + def theta_basis_F_polynomial(self, g_vector): + r""" + Return the F-polynomial of the element of the theta basis of ``self`` with g-vector ``g_vector``. + + INPUT: + + - ``g_vector`` -- tuple; the g-vector of the F-polynomial to compute + + ALGORITHM: + + This method uses the fact that the greedy basis and the theta basis + coincide in rank 2 and uses the former defining recursion (Equation + (1.5) from [LLZ2014]_) to compute. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,-3],[2,0]]), principal_coefficients=True) + sage: A.theta_basis_F_polynomial((-1,-1)) + u0^4*u1 + 4*u0^3*u1 + 6*u0^2*u1 + 4*u0*u1 + u0 + u1 + 1 + + sage: A = ClusterAlgebra(['F', 4]) + sage: A.theta_basis_F_polynomial((1, 0, 0, 0)) + Traceback (most recent call last): + ... + NotImplementedError: Currently only implemented for cluster algebras of rank 2. + """ + if self.rank() != 2: + raise NotImplementedError("Currently only implemented for cluster algebras of rank 2.") + + # extract the part of g_vector not coming from the initial cluster + d = tuple( max(x, 0) for x in self.g_vector_to_d_vector(g_vector) ) + g = self.d_vector_to_g_vector(d) + + shifts = ((d[0]+g[0])/self._B0[0, 1], (d[1]+g[1])/self._B0[1, 0] ) + signs = ( sign(self._B0[0, 1]), sign(self._B0[1, 0]) ) + + u = list(self._U.gens()) + output = self._U.zero() + for p in range(0, d[1] + 1): + for q in range(0, d[0] + 1): + output += self._greedy_coefficient(d, p, q) * u[1] ** (signs[0]*p - shifts[0]) * u[0] ** (signs[1]*q - shifts[1]) + return output + + @cached_method def _greedy_coefficient(self, d_vector, p, q): r""" Return the coefficient of the monomial ``x1 ** (b * p) * x2 ** (c * q)`` @@ -2429,8 +2621,8 @@ def _greedy_coefficient(self, d_vector, p, q): sage: A._greedy_coefficient((1, 1), 1, 0) 1 """ - b = abs(self.b_matrix()[0, 1]) - c = abs(self.b_matrix()[1, 0]) + b = abs(self._B0[0, 1]) + c = abs(self._B0[1, 0]) a1, a2 = d_vector p = Integer(p) q = Integer(q) @@ -2493,17 +2685,3 @@ def lower_bound(self): NotImplementedError: not implemented yet """ raise NotImplementedError("not implemented yet") - - def theta_basis_element(self, g_vector): - r""" - Return the element of the theta basis with g-vector ``g_vector``. - - EXAMPLES:: - - sage: A = ClusterAlgebra(['F', 4]) - sage: A.theta_basis_element((1, 0, 0, 0)) - Traceback (most recent call last): - ... - NotImplementedError: not implemented yet - """ - raise NotImplementedError("not implemented yet") From 0d2909f7e833206dadbffbb1478da60d35b40a43 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 21 Jan 2021 15:10:37 +0100 Subject: [PATCH 070/232] Implement theta basis decomposition --- src/sage/algebras/cluster_algebra.py | 85 ++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index c403716a358..6dbb7723b3e 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -493,11 +493,11 @@ def g_vector(self): sage: sum(A.initial_cluster_variables()).g_vector() Traceback (most recent call last): ... - ValueError: this element is not homogeneous + ValueError: this element does not have a well defined g-vector """ components = self.homogeneous_components() if len(components) != 1: - raise ValueError("this element is not homogeneous") + raise ValueError("this element does not have a well defined g-vector") k, = components return k @@ -515,17 +515,17 @@ def F_polynomial(self): sage: sum(A.initial_cluster_variables()).F_polynomial() Traceback (most recent call last): ... - ValueError: this element is not homogeneous + ValueError: this element does not have a well defined g-vector """ if not self.is_homogeneous(): - raise ValueError("this element is not homogeneous") + raise ValueError("this element does not have a well defined g-vector") subs_dict = dict() A = self.parent() for x in A.initial_cluster_variables(): subs_dict[x.lift()] = A._U(1) for i in range(A.rank()): subs_dict[A.coefficient(i).lift()] = A._U.gen(i) - return self.lift().substitute(subs_dict) + return A._U(self.lift().substitute(subs_dict)) def is_homogeneous(self): r""" @@ -572,6 +572,69 @@ def homogeneous_components(self): components[g_vect] = self.parent().retract(x.monomial_coefficient(m) * m) return components + def theta_basis_decomposition(self): + r""" + Return the decomposition of ``self`` in the theta basis. + + OUTPUT: + + A dictionary whose keys are the g-vectors and whose values are the coefficients + in the decoposition of ``self`` in the theta basis. + + EXAMPLES:: + sage: A = ClusterAlgebra(matrix([[0,-2],[3,0]]), principal_coefficients=True) + sage: f = (A.theta_basis_element((1,0)) + A.theta_basis_element((0,1)))**2 + A.coefficient(1)* A.theta_basis_element((1,1)) + sage: f.theta_basis_decomposition() + {(1, 1): y1 + 2, (2, 0): 1, (0, 2): 1} + sage: sum(_[g] * A.theta_basis_element(g) for g in _) - f + 0 + """ + zero = self.parent()(0) + components = map(lambda x: x._homogeneous_theta_basis_decomposition(), self.homogeneous_components().values()) + out = {} + for cpt in components: + for g in cpt: + out[g] = out.get(g, zero) + cpt[g] + return out + + def _homogeneous_theta_basis_decomposition(self): + r""" + Return the decomposition of ``self`` in the theta basis. + + OUTPUT: + + A dictionary whose keys are the g-vectors and whose values are the coefficients + in the decoposition of ``self`` in the theta basis. + + WARNING: + + This method only works when ``self`` is homogeneous. + + EXAMPLES:: + sage: A = ClusterAlgebra(matrix([[0,-2],[3,0]]), principal_coefficients=True) + sage: f = A.theta_basis_element((4,-4))*A.theta_basis_element((1,-1)) + sage: f.theta_basis_decomposition() # indirect doctest + {(5, -5): 1, (3, -2): y0*y1, (1, -2): y0*y1^2} + sage: sum(_[g] * A.theta_basis_element(g) for g in _) - f + 0 + """ + f_poly = self.F_polynomial() + g_vect = vector(self.g_vector()) + + A = self.parent() + B = A.b_matrix() + n = A.rank() + U = f_poly.parent() + out = {} + + while f_poly != U(0): + y_exp = min(f_poly.dict()) + coeff = f_poly.dict()[y_exp] + g_theta = tuple(g_vect + B*vector(y_exp)) + out[g_theta] = A({(0,)*n + tuple(y_exp):coeff}) + f_poly -= U({y_exp:coeff}) * A.theta_basis_F_polynomial(g_theta) + return out + ############################################################################## # Seeds @@ -1494,12 +1557,12 @@ def coxeter_element(self): sage: A.coxeter_element() Traceback (most recent call last): ... - ValueError: The initial exchange matrix is not acyclic. + ValueError: the initial exchange matrix is not acyclic. """ dg = DiGraph(self.b_matrix().apply_map(lambda x: ZZ(0) if x <= 0 else ZZ(1))) acyclic, coxeter = dg.is_directed_acyclic(certificate=True) if not acyclic: - raise ValueError("The initial exchange matrix is not acyclic.") + raise ValueError("the initial exchange matrix is not acyclic.") return coxeter @cached_method @@ -1572,12 +1635,12 @@ def set_current_seed(self, seed): sage: A.set_current_seed(A1.initial_seed()) Traceback (most recent call last): ... - ValueError: This is not a seed in this cluster algebra + ValueError: this is not a seed in this cluster algebra """ if self.contains_seed(seed): self._seed = seed else: - raise ValueError("This is not a seed in this cluster algebra") + raise ValueError("this is not a seed in this cluster algebra") def reset_current_seed(self): r""" @@ -1691,11 +1754,11 @@ def euler_matrix(self): sage: A.euler_matrix() Traceback (most recent call last): ... - ValueError: The initial exchange matrix is not acyclic. + ValueError: the initial exchange matrix is not acyclic. """ if not self.is_acyclic(): - raise ValueError("The initial exchange matrix is not acyclic.") + raise ValueError("the initial exchange matrix is not acyclic.") return 1 + self.b_matrix().apply_map(lambda x: min(ZZ(0), x)) def d_vector_to_g_vector(self, d): From 40f692921b3fa420a30d0cab729b93510cbd8471 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 29 Jan 2021 17:21:07 +0100 Subject: [PATCH 071/232] Better implementation --- src/sage/algebras/cluster_algebra.py | 84 +++++++++++----------------- 1 file changed, 33 insertions(+), 51 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 6dbb7723b3e..3319dd32af0 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -495,11 +495,9 @@ def g_vector(self): ... ValueError: this element does not have a well defined g-vector """ - components = self.homogeneous_components() - if len(components) != 1: + if not self.is_homogeneous(): raise ValueError("this element does not have a well defined g-vector") - k, = components - return k + return self._g_vector def F_polynomial(self): r""" @@ -541,7 +539,7 @@ def is_homogeneous(self): sage: x.is_homogeneous() False """ - return len(self.homogeneous_components()) == 1 + return getattr(self, '_is_homogeneous', len(self.homogeneous_components()) == 1) def homogeneous_components(self): r""" @@ -570,6 +568,12 @@ def homogeneous_components(self): components[g_vect] += self.parent().retract(x.monomial_coefficient(m) * m) else: components[g_vect] = self.parent().retract(x.monomial_coefficient(m) * m) + for g_vect in components: + components[g_vect]._is_homogeneous = True + components[g_vect]._g_vector = g_vect + self._is_homogeneous = (len(components) == 1) + if self._is_homogeneous: + self._g_vector = list(components.keys())[0] return components def theta_basis_decomposition(self): @@ -584,57 +588,35 @@ def theta_basis_decomposition(self): EXAMPLES:: sage: A = ClusterAlgebra(matrix([[0,-2],[3,0]]), principal_coefficients=True) sage: f = (A.theta_basis_element((1,0)) + A.theta_basis_element((0,1)))**2 + A.coefficient(1)* A.theta_basis_element((1,1)) - sage: f.theta_basis_decomposition() - {(1, 1): y1 + 2, (2, 0): 1, (0, 2): 1} - sage: sum(_[g] * A.theta_basis_element(g) for g in _) - f - 0 - """ - zero = self.parent()(0) - components = map(lambda x: x._homogeneous_theta_basis_decomposition(), self.homogeneous_components().values()) - out = {} - for cpt in components: - for g in cpt: - out[g] = out.get(g, zero) + cpt[g] - return out - - def _homogeneous_theta_basis_decomposition(self): - r""" - Return the decomposition of ``self`` in the theta basis. - - OUTPUT: - - A dictionary whose keys are the g-vectors and whose values are the coefficients - in the decoposition of ``self`` in the theta basis. - - WARNING: - - This method only works when ``self`` is homogeneous. - - EXAMPLES:: - sage: A = ClusterAlgebra(matrix([[0,-2],[3,0]]), principal_coefficients=True) + sage: decomposition = f.theta_basis_decomposition() + sage: sum(decomposition[g] * A.theta_basis_element(g) for g in decomposition) == f + True sage: f = A.theta_basis_element((4,-4))*A.theta_basis_element((1,-1)) - sage: f.theta_basis_decomposition() # indirect doctest - {(5, -5): 1, (3, -2): y0*y1, (1, -2): y0*y1^2} - sage: sum(_[g] * A.theta_basis_element(g) for g in _) - f - 0 + sage: decomposition = f.theta_basis_decomposition() + sage: sum(decomposition[g] * A.theta_basis_element(g) for g in decomposition) == f + True """ - f_poly = self.F_polynomial() - g_vect = vector(self.g_vector()) - A = self.parent() B = A.b_matrix() - n = A.rank() - U = f_poly.parent() - out = {} - - while f_poly != U(0): - y_exp = min(f_poly.dict()) - coeff = f_poly.dict()[y_exp] - g_theta = tuple(g_vect + B*vector(y_exp)) - out[g_theta] = A({(0,)*n + tuple(y_exp):coeff}) - f_poly -= U({y_exp:coeff}) * A.theta_basis_F_polynomial(g_theta) - return out + U = A._U + out = dict() + zero_A = A(0) + zero_U = U(0) + zero_t = (0,)*A.rank() + components = self.homogeneous_components() + + for g_vect in components: + f_poly = components[g_vect].F_polynomial() + g_vect = vector(g_vect) + while f_poly != zero_U: + y_exp = min(f_poly.dict()) + coeff = f_poly.dict()[y_exp] + g_theta = tuple(g_vect + B*vector(y_exp)) + out[g_theta] = out.get(g_theta, zero_A) + A({zero_t + tuple(y_exp):coeff}) + f_poly -= U({y_exp:coeff}) * A.theta_basis_F_polynomial(g_theta) + + return out ############################################################################## # Seeds From f4d878f6ca89ff0ba7b8e2f45a2e787f35da012f Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 29 Jan 2021 17:24:06 +0100 Subject: [PATCH 072/232] Blank spaces --- src/sage/algebras/cluster_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 3319dd32af0..3fe6ce4ba1b 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -570,7 +570,7 @@ def homogeneous_components(self): components[g_vect] = self.parent().retract(x.monomial_coefficient(m) * m) for g_vect in components: components[g_vect]._is_homogeneous = True - components[g_vect]._g_vector = g_vect + components[g_vect]._g_vector = g_vect self._is_homogeneous = (len(components) == 1) if self._is_homogeneous: self._g_vector = list(components.keys())[0] @@ -605,7 +605,7 @@ def theta_basis_decomposition(self): zero_t = (0,)*A.rank() components = self.homogeneous_components() - + for g_vect in components: f_poly = components[g_vect].F_polynomial() g_vect = vector(g_vect) From 5844caed348d8602ef57f092a9e86999dbef1e0f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 1 Feb 2021 18:39:36 +0100 Subject: [PATCH 073/232] 30352: new tarball version 20210201 --- build/pkgs/database_knotinfo/SPKG.rst | 26 ++++- build/pkgs/database_knotinfo/checksums.ini | 10 +- .../create_knotinfo_tarball.py | 108 ++++++++++++++++++ .../database_knotinfo/package-version.txt | 2 +- src/sage/databases/knotinfo_db.py | 98 ++-------------- src/sage/features/databases.py | 10 +- src/sage/knots/all.py | 3 + src/sage/knots/knotinfo.py | 73 +++++++++++- src/sage/knots/link.py | 27 +++-- 9 files changed, 248 insertions(+), 109 deletions(-) create mode 100755 build/pkgs/database_knotinfo/create_knotinfo_tarball.py diff --git a/build/pkgs/database_knotinfo/SPKG.rst b/build/pkgs/database_knotinfo/SPKG.rst index 460416cc621..07632060453 100644 --- a/build/pkgs/database_knotinfo/SPKG.rst +++ b/build/pkgs/database_knotinfo/SPKG.rst @@ -1,5 +1,5 @@ -database_knotinfo -================= +database_knotinfo: Tables of Knots and Links from the KnotInfo Databases +======================================================================== Description ----------- @@ -17,6 +17,18 @@ Dependencies - Sage library + +Upstream Contact +---------------- + +- Charles Livingston +- Allison H. Moore + +Update Instructions +------------------- + +- See the Python script ``create_knotinfo_tarball.py`` in the current directory. + Changelog --------- @@ -33,3 +45,13 @@ Changelog The second file has been changed manually deleting one character: a trailing "}" occuring in the homfly_polynomial column of the last link ``L11n459{1,1,1}``. + +- 20210201 (Sebastian Oehms, 1 February 2021, :trac:`30352`, upgrade) + + Three new columns have been added to ``knotinfo_data_complete.xls`` + (``montesinos_notation``, ``boundary_slopes`` and ``pretzel_notation``). + ``linkinfo_data_complete.xlsx`` remains unchanged. + + The tarball has been created using ``create_knotinfo_tarball.py``. + The fix concerning the misplaced character for ``L11n459{1,1,1}`` + is performed in :meth:`KnotInfoBase.homfly_polynomial`, now. diff --git a/build/pkgs/database_knotinfo/checksums.ini b/build/pkgs/database_knotinfo/checksums.ini index 985cba4b86c..4042c7c9a38 100644 --- a/build/pkgs/database_knotinfo/checksums.ini +++ b/build/pkgs/database_knotinfo/checksums.ini @@ -1,5 +1,5 @@ -tarball=knotinfo-20200713.tar.bz2 -sha1=ec6c8436d5565fdd140cd3e4b301d215cd62b0d0 -md5=1d84f176290bdd3a752757242512fca2 -cksum=3419853512 -upstream_url=https://trac.sagemath.org/raw-attachment/ticket/30352/knotinfo-20200713.tar.bz2 +tarball=knotinfo-20210201.tar.bz2 +sha1=a8a69dacd1f61f19a921d8e5b90d6cfdea85d859 +md5=5f53bd7e3a672648d41460c4d22d52b3 +cksum=1608275975 +upstream_url=https://github.com/soehms/sagemath_knotinfo/blob/main/knotinfo-20210201.tar.bz2?raw=true diff --git a/build/pkgs/database_knotinfo/create_knotinfo_tarball.py b/build/pkgs/database_knotinfo/create_knotinfo_tarball.py new file mode 100755 index 00000000000..47e799bf4b9 --- /dev/null +++ b/build/pkgs/database_knotinfo/create_knotinfo_tarball.py @@ -0,0 +1,108 @@ +#!/usr/bin/python + +r""" +Python script to create a tarball for the sage-package ``database_knotinfo`` +in the given path. This utility should be used in case of a switch to a +new version of the data files (that is if the original files on the +KnotInfo LinkInfo web-page have changed). In that case an invocation of +``sage -package update database_knotinfo `` and +``sage -package fix-checksum database_knotinfo`` will be necessary. + +..NOTE:: + + This function demands the Python package ``pandas``, ``xlrd`` and + ``xlsx2csv`` to be installed. If not you have to run:: + + pip install pandas + pip install xlrd + pip install xlsx2csv + + before using this function. + +INPUT: + +- ``version`` -- string, name of the new version to be created + (by default date of the day of creation) +- ``path`` -- string of the path where the tarball should be stored + (by default ``pwd``) + +EXAMPLES:: + + ~/sage $ build/pkgs/database_knotinfo/create_knotinfo_tarball.py 20210201 upstream + src/ + src/knotinfo_data_complete.csv + src/linkinfo_data_complete.csv +""" + +import sys, os +from xlsx2csv import Xlsx2csv +from pandas import read_excel + +############################################################################## +# Copyright (C) 2021 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 +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +############################################################################## + + +cmdline_args = sys.argv[1:] + +version = None +path = None + +if len(cmdline_args) > 1: + path = cmdline_args[1] + +if len(cmdline_args) > 0: + version = cmdline_args[0] + + +if not version: + from datetime import datetime + version = str(datetime.today().date()).replace('-','') + +if not path: + path = os.environ['PWD'] + +path_temp = os.path.join(path, 'special_knotinfo_spkg_temp_dir') +path_src = os.path.join(path_temp, 'src') +os.makedirs(path_temp) +os.makedirs(path_src) + +def convert(path_src, url, filen, reader): + if reader == Xlsx2csv: + excel = filen + '.xlsx' + else: + excel = filen + '.xls' + csv = filen + '.csv' + inp = os.path.join(url, excel) + out = os.path.join(path_src, csv) + if reader == Xlsx2csv: + from six.moves.urllib.request import urlopen + f = urlopen(inp) + url_data = f.read() + temp_file = os.path.join(path_temp, 'temp.xlsx') + f = open(temp_file, 'wt') + f.write(url_data) + f.close() + data = reader(temp_file, delimiter='|', skip_empty_lines=True) + data.convert(out) + else: + data = reader(inp) + data.to_csv(out, sep='|', index=False) + +# first KnotInfo (using pandas and xlrd) +convert(path_src, 'https://knotinfo.math.indiana.edu/', 'knotinfo_data_complete', read_excel) + +# now LinkInfo (using xlsx2csv) +convert(path_src, 'https://linkinfo.sitehost.iu.edu/', 'linkinfo_data_complete', Xlsx2csv) + +tar_file = 'knotinfo-%s.tar.bz2' %version +path_tar = os.path.join(path_temp, tar_file) + +os.system('cd %s; tar -cvjSf %s src' %(path_temp, tar_file)) +os.system('mv %s %s; rm -rf %s' %(path_tar, path, path_temp)) diff --git a/build/pkgs/database_knotinfo/package-version.txt b/build/pkgs/database_knotinfo/package-version.txt index f6f6326f811..4fe1baedd44 100644 --- a/build/pkgs/database_knotinfo/package-version.txt +++ b/build/pkgs/database_knotinfo/package-version.txt @@ -1 +1 @@ -20200713 +20210201 diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py index 9d226af64d4..ba3eecc3df4 100644 --- a/src/sage/databases/knotinfo_db.py +++ b/src/sage/databases/knotinfo_db.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -KontInfo Database +KnotInfo Database This module contains the class :class:`KnotInfoDataBase` and auxilary classes for it which serves as an interface to the lists of named knots and links provided @@ -34,7 +34,6 @@ from sage.misc.persist import save, load from sage.misc.verbose import verbose from sage.misc.cachefunc import cached_method -from sage.env import SAGE_SHARE, SAGE_ROOT class KnotInfoColumnTypes(Enum): @@ -228,35 +227,6 @@ def csv(self): """ return '%s.csv' %(self.value[1]) - def sobj_path(self): - r""" - Return the path name under which the data is stored internally - in ``sobj`` files. - - Examples:: - - sage: from sage.databases.knotinfo_db import KnotInfoDataBase - sage: ki_db = KnotInfoDataBase() - sage: ki_db.filename.knots.sobj_path().endswith('knotinfo') - True - """ - return os.path.join(SAGE_SHARE, 'knotinfo') - - - def sobj_num_knots(self): - r""" - Return the file name under which the number of knots - is stored as in python int in a sobj-file. - - Examples:: - - sage: from sage.databases.knotinfo_db import KnotInfoDataBase - sage: ki_db = KnotInfoDataBase() - sage: ki_db.filename.knots.sobj_num_knots() - 'num_knots.sobj' - """ - return 'num_knots.sobj' - def sobj_row(self): r""" Return the file name under which the row-data of the csv-File @@ -381,28 +351,21 @@ def __init__(self, install=False): self._names_column = 'name' self._components_column = 'components' self._knot_prefix = 'K' - self._sobj_path = KnotInfoFilename.knots.sobj_path() self._knot_list = None self._link_list = None self._demo = None self._num_knots = None + from sage.features.databases import DatabaseKnotInfo + self._feature = DatabaseKnotInfo() + self._sobj_path = self._feature.search_path[0] + if install: knot_list = self.knot_list() num_knots = len(knot_list) - 1 print('Setting the number of Knots: %s!' %num_knots) - save(num_knots, '%s/%s' %(self._sobj_path, self.filename.knots.sobj_num_knots())) - - from sage.features.databases import DatabaseKnotInfo - self._feature = DatabaseKnotInfo() - - version_file = os.path.join(SAGE_ROOT, 'build/pkgs/%s/package-version.txt' %self._feature.spkg) - f = open(version_file) - self._version = f.read().splitlines()[0] - f.close() - - if install: + save(num_knots, '%s/%s' %(self._sobj_path, self._feature.filename)) self._feature._cache_is_present = None # must be reset for package installation self._create_col_dict_sobj() self._create_data_sobj() @@ -428,48 +391,6 @@ def demo_version(self): self._num_knots = len([v for v in row_demo_sample.values() if v[1]==1]) return self._demo - - def create_spkg_tarball(self, path_for_src=None): - r""" - Create a tarball for the sage-package ``database_knotinfo`` in the - ``upstream`` directory. This utility should only be used by users who - know what they do in case of a switch to a new version of the data files - (that is if the original files on KnotInfo web-page have changed). In that - case an invocation of ``sage -package update database_knotinfo `` - and ``sage -package fix-checksum database_knotinfo`` will be necessary. - - INPUT: - - - ``path_for_src`` -- string of the path under which the source are - stored in a subdirectory called ``src``. In that directory there should - be the data files in csv format (for example - ``KnotInfoDataBase.filename.knots.csv()``) - - EXAMPLES:: - - sage: from sage.databases.knotinfo_db import KnotInfoDataBase - sage: ki_db = KnotInfoDataBase() - sage: ki_db.create_spkg_tarball() # not tested (internet access) - """ - if not path_for_src: - path_for_src = os.environ['PWD'] - - os.system('cd %s; tar -cvjSf %s/upstream/%s-%s.tar.bz2 src' %(path_for_src, SAGE_ROOT, self._feature.spkg, self._version) ) - - - def version(self): - r""" - Return the current version. - - EXAMPLES:: - - sage: from sage.databases.knotinfo_db import KnotInfoDataBase - sage: ki_db = KnotInfoDataBase() - sage: ki_db.version() - '20200713' - """ - return self._version - def knot_list(self): r""" Return the KnotInfo table rows as Python list. @@ -525,7 +446,6 @@ def _create_col_dict_sobj(self): """ knot_list = self.knot_list() knot_column_names = knot_list[0] - len_knots = len(knot_list) link_list = self.link_list() link_column_names = link_list[0] @@ -664,7 +584,7 @@ def read_column_dict(self): sage: from sage.databases.knotinfo_db import KnotInfoDataBase sage: ki_db = KnotInfoDataBase() sage: len(ki_db.read_column_dict()) # optional - database_knotinfo - 122 + 125 """ if self.demo_version(): return column_demo_sample @@ -772,7 +692,7 @@ def read(self, column): sage: ki_db = KnotInfoDataBase() """ if not isinstance(column, KnotInfoColumns): - raise TypeError('column must be an instance of enum %s' (KnotInfoColumns)) + raise TypeError('column must be an instance of enum %s' %(KnotInfoColumns)) if self.demo_version(): return data_demo_sample[column] @@ -1003,7 +923,7 @@ def _test_database(self, **options): ], dc.homfly_polynomial: [ '', - '(2*v^2-v^4)+ (v^2)*z^2', + '(2*v^2-v^4)+(v^2)*z^2', '(v^(-2)-1+ v^2)+ (-1)*z^2', '(3*v^4-2*v^6)+ (4*v^4-v^6)*z^2+ (v^4)*z^4', '(v^2+ v^4-v^6)+ (v^2+ v^4)*z^2', diff --git a/src/sage/features/databases.py b/src/sage/features/databases.py index fb1595ac672..25e2e24441f 100644 --- a/src/sage/features/databases.py +++ b/src/sage/features/databases.py @@ -87,9 +87,11 @@ def __init__(self): sage: isinstance(DatabaseKnotInfo(), DatabaseKnotInfo) True """ - from sage.databases.knotinfo_db import KnotInfoFilename + from sage.env import SAGE_SHARE + import os StaticFile.__init__(self, "KnotInfo and LinkInfo databases", - filename=KnotInfoFilename.knots.sobj_num_knots(), + filename='num_knots.sobj', spkg='database_knotinfo', - search_path = [KnotInfoFilename.knots.sobj_path()], - url=KnotInfoFilename.knots.url()) + search_path = [os.path.join(SAGE_SHARE, 'knotinfo')], + url='https://knotinfo.math.indiana.edu/' + ) diff --git a/src/sage/knots/all.py b/src/sage/knots/all.py index a3daecd2fa5..02223efef57 100644 --- a/src/sage/knots/all.py +++ b/src/sage/knots/all.py @@ -1,4 +1,7 @@ from sage.misc.lazy_import import lazy_import +from sage.features.databases import DatabaseKnotInfo lazy_import('sage.knots.knot', ['Knot', 'Knots']) lazy_import('sage.knots.link', 'Link') +if DatabaseKnotInfo().is_present(): + lazy_import('sage.knots.knotinfo', ['KnotInfo', 'KnotInfoSeries']) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 71e0eb2a507..48eae65bad6 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -21,6 +21,10 @@ The installation of the complete database will be necessary in order to have access to all the properties recorded in the databases, as well. +If the entire database is installed as explained above, the import instructions +for :class:`KnotInfo` and :class:`KnotInfoSeries`, which can be seen in the opening +lines of the examples, are unnecessary. + Be aware that there are a couple of conventions used differently on KnotInfo as in Sage, especially concerning the selection of the symmetry version of the link. @@ -733,6 +737,14 @@ def determinant(self): r""" Return the determinant of ``self``. + From the KnotInfo description page: + + The determinant of a knot is `\det(V + V^t)`, where `V` is a Seifert + matrix for the knot. + + To read the complete description type + ``KnotInfo.K0_1.items.determinant.description_webpage()``. + .. NOTE:: KnotInfo's value for the unknot ``0_1`` is zero. This is not @@ -755,6 +767,56 @@ def determinant(self): return 1 return int(self[self.items.determinant]) + @cached_method + def three_genus(self): + r""" + Return the three genus of ``self``. + + From the KnotInfo description page: + + The three-genus of a knot is defined to be the minimal genus of + a Seifert surface for a knot. + + To read the complete description type + ``KnotInfo.K0_1.items.three_genus.description_webpage()``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.three_genus() # optional - databsase_knotinfo + 1 + + Note that this differs from the corresponding result in Sage + since the latter is obtained for a Seifert surface that does not + have the minimal genus:: + + sage: KnotInfo.K5_2.link().genus() + 3 + """ + return int(self[self.items.three_genus]) + + @cached_method + def signature(self): + r""" + Return the signature of ``self``. + + From the KnotInfo description page: + + The signature of a knot, `\sigma (K)`, is equal to `\sigma (V + V^t)`, + the signature of `V + V^t` where `V` is a Seifert matrix for the knot + and `V^t` is its transpose. + + To read the complete description type + ``KnotInfo.K0_1.items.signatur.description_webpage()``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.signature() # optional - databsase_knotinfo + 1 + """ + return int(self[self.items.signature]) + @cached_method def is_knot(self): r""" @@ -1120,7 +1182,7 @@ def homfly_polynomial(self, var1='v', var2='z', original=False): sage: PK3_1 = K3_1.homfly_polynomial(); PK3_1 -v^4 + v^2*z^2 + 2*v^2 sage: K3_1.homfly_polynomial(original=True) - '(2*v^2-v^4)+ (v^2)*z^2' + '(2*v^2-v^4)+(v^2)*z^2' sage: PK3_1 == K3_1.link().homfly_polynomial(normalization='vz') True @@ -1145,6 +1207,7 @@ def homfly_polynomial(self, var1='v', var2='z', original=False): TESTS:: + sage: H = KnotInfo.L11n459_1_1_1.homfly_polynomial() # optional - database_knotinfo sage: all(L.homfly_polynomial() == L.link().homfly_polynomial(normalization='vz')\ for L in KnotInfo if L.crossing_number() < 7) True @@ -1165,6 +1228,14 @@ def homfly_polynomial(self, var1='v', var2='z', original=False): if not homfly_polynomial and self.crossing_number() == 0: return R.one() + # As of February 2021 there is a wrong sign for the link in the + # last row of the database. This is removed here (see SPKG.rst and + # the test above). Once this is fixed upstream, the following three + # lines of code can be removed again: + if self.value == 'L11n459{1,1,1}': + if homfly_polynomial.endswith('}'): + homfly_polynomial = homfly_polynomial.strip('}') + L, M = R.gens() lc = {'v': L, 'z':M} return eval_knotinfo(homfly_polynomial, locals=lc) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 54bb4ee24ae..80084c5b218 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -3460,7 +3460,7 @@ def _markov_move_cmp(self, braid): @cached_method def _knotinfo_matching_list(self): r""" - Return a list of links from the KontInfo and LinkInfo databases which match + Return a list of links from the KnotInfo and LinkInfo databases which match the properties of ``self`` as much as possible. OUTPUT: @@ -3549,7 +3549,7 @@ def _knotinfo_matching_list(self): def get_knotinfo(self, mirror_version=True, unique=True): r""" - Identify this link as an item of the KontInfo database (if possible). + Identify this link as an item of the KnotInfo database (if possible). INPUT: @@ -3769,7 +3769,20 @@ def get_knotinfo(self, mirror_version=True, unique=True): (, False) sage: Ks10_83.sage_link().get_knotinfo() # optional - snappy (, True) + + TESTS: + + sage: L = KnotInfo.L10a171_1_1_0 # optional - database_knotinfo + sage: l = L.link(L.items.braid_notation) # optional - database_knotinfo + sage: l.get_knotinfo(unique=False) # optional - database_knotinfo + [(, True), + (, True), + (, False), + (, False)] """ + # ToDo: extension to non prime links in which case an element of the monoid + # over :class:`KnotInfo` should be returned + non_unique_hint = '\nuse keyword argument `unique` to obtain more details' def answer(L): r""" @@ -3789,9 +3802,9 @@ def answer(L): if not chiral: mirrored = None - elif proved_m and not proved_s: + elif proved_m and not proved_s and L in lm: mirrored = True - elif proved_s and not proved_m: + elif proved_s and not proved_m and L in l: mirrored = False else: # nothing proved @@ -3857,6 +3870,9 @@ def answer_list(l): lm, proved_m = self_m._knotinfo_matching_list() l = list(set(ls + lm)) + if l and not unique: + return answer_list(l) + if proved_s and proved_m: return answer_list(l) @@ -3868,9 +3884,6 @@ def answer_list(l): # here we come if we cannot be sure about the found result - if l and not unique: - return answer_list(l) - uniq_txt = ('', '') if l: uniq_txt = (' uniquely', non_unique_hint) From 267b1b354e276d264066bbe5e2edb3c3161b0446 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Wed, 10 Feb 2021 11:12:42 +0100 Subject: [PATCH 074/232] Trac #21203: replace "left" by "right" in description of "right" --- src/sage/combinat/k_regular_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index e5e1421168e..a2cfc23bd91 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -124,7 +124,7 @@ def __init__(self, parent, mu, left=None, right=None): - ``right`` -- (default: ``None``) a vector. When evaluating the sequence, this vector is multiplied - from the left to the matrix product. If ``None``, then this + from the right to the matrix product. If ``None``, then this multiplication is skipped. When created via the parent :class:`kRegularSequenceSpace`, then From 236002760637db85c7c6f4d4d57054d2ef5a1a02 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Wed, 10 Feb 2021 11:13:39 +0100 Subject: [PATCH 075/232] Trac #21203: fix ReST-Syntax --- src/sage/combinat/k_regular_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index a2cfc23bd91..ed7c1006252 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -134,7 +134,7 @@ def __init__(self, parent, mu, left=None, right=None): each of the matrices in :meth:`mu ` is transposed. Additionally the vectors - :meth`left ` + :meth:`left ` and :meth:`right ` are switched. From a08b61bbf70720a3d760e89e6f64a6296ef7b073 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Wed, 10 Feb 2021 12:14:59 +0100 Subject: [PATCH 076/232] Trac #21203: Fix indentation --- src/sage/combinat/k_regular_sequence.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index ed7c1006252..ddc5626c6ad 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -131,14 +131,14 @@ def __init__(self, parent, mu, left=None, right=None): the following option is available. - ``transpose`` -- (default: ``False``) a boolean. If set, then - each of the matrices in - :meth:`mu ` - is transposed. Additionally the vectors - :meth:`left ` - and - :meth:`right ` - are switched. - (This is done by calling :meth:`~sage.combinat.recognizable_series.RecognizableSeries.transposed`.) + each of the matrices in + :meth:`mu ` + is transposed. Additionally the vectors + :meth:`left ` + and + :meth:`right ` + are switched. + (This is done by calling :meth:`~sage.combinat.recognizable_series.RecognizableSeries.transposed`.) EXAMPLES:: From f54cccc3ab925e569adb22becf743ae158bbd247 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 10 Feb 2021 17:26:53 +0100 Subject: [PATCH 077/232] Thetha basis elements do *not* satisfy the separation of additions formula --- src/sage/algebras/cluster_algebra.py | 31 ++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 3fe6ce4ba1b..09e81e37c6a 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -2591,6 +2591,23 @@ def theta_basis_element(self, g_vector): ... NotImplementedError: Currently only implemented for cluster algebras of rank 2. + .. NOTE:: + + Elements of the theta basis correspond with the associated cluster + monomial only for appropriate coefficient choices. For example:: + + sage: A = ClusterAlgebra(matrix([[0,-1],[1,0],[-1,0]])) + sage: A.theta_basis_element((-1,0)) + (x1 + y0)/(x0*y0) + + while:: + + sage: _ = A.find_g_vector((-1,0)); + sage: A.cluster_variable((-1,0)) + (x1 + y0)/x0 + + In particular theta basis elements do not satisfy a separation of additions formula. + .. WARNING:: Currently only cluster algebras of rank 2 are supported @@ -2600,11 +2617,11 @@ def theta_basis_element(self, g_vector): :meth:`sage.algebras.cluster_algebra.theta_basis_F_polynomial` """ g_vector = tuple(g_vector) - F = self.theta_basis_F_polynomial(g_vector) - F_std = F.subs(self._yhat) + F = self.theta_basis_F_polynomial(g_vector).subs(self._yhat) g_mon = prod(self.ambient().gen(i) ** g_vector[i] for i in range(self.rank())) - F_trop = self.ambient()(F.subs(self._y))._fraction_pair()[1] - return self.retract(g_mon * F_std * F_trop) + # we only return the monomal g_mon times the evaluated F-polynomial because this is how + # theta basis elements behave. + return self.retract(g_mon * F) @cached_method(key=lambda a, b: tuple(b)) def theta_basis_F_polynomial(self, g_vector): @@ -2615,6 +2632,12 @@ def theta_basis_F_polynomial(self, g_vector): - ``g_vector`` -- tuple; the g-vector of the F-polynomial to compute + .. WARNING:: + + Elements of the theta basis do not satisfy a separation of additions formula. + See the implementation of :meth:`sage.algebras.cluster_algebra.theta_basis_F_polynomial` + for further details. + ALGORITHM: This method uses the fact that the greedy basis and the theta basis From 6e5e1e55a76985668cd351265f65e898b059b6b6 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Mon, 15 Feb 2021 09:08:08 +0100 Subject: [PATCH 078/232] adaption to new beta version and some typo and style fixes --- build/pkgs/database_knotinfo/SPKG.rst | 16 ++++++++-------- build/pkgs/database_knotinfo/spkg-install.in | 2 +- src/sage/databases/knotinfo_db.py | 2 +- src/sage/knots/knotinfo.py | 6 +++--- src/sage/knots/link.py | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/build/pkgs/database_knotinfo/SPKG.rst b/build/pkgs/database_knotinfo/SPKG.rst index 07632060453..37107aa286c 100644 --- a/build/pkgs/database_knotinfo/SPKG.rst +++ b/build/pkgs/database_knotinfo/SPKG.rst @@ -34,17 +34,17 @@ Changelog - 20200713 (Sebastian Oehms, 13 Juli 2020, :trac:`30352`, initial version) - The tarball has been created from the both download files at the - given date: + The tarball has been created from the both download files at the + given date: - ``knotinfo_data_complete.xls`` - ``linkinfo_data_complete.xlsx`` + ``knotinfo_data_complete.xls`` + ``linkinfo_data_complete.xlsx`` - exporting them to CSV via LibreOffice. + exporting them to CSV via LibreOffice. - The second file has been changed manually deleting one character: - a trailing "}" occuring in the homfly_polynomial column of the last - link ``L11n459{1,1,1}``. + The second file has been changed manually deleting one character: + a trailing "}" occuring in the homfly_polynomial column of the last + link ``L11n459{1,1,1}``. - 20210201 (Sebastian Oehms, 1 February 2021, :trac:`30352`, upgrade) diff --git a/build/pkgs/database_knotinfo/spkg-install.in b/build/pkgs/database_knotinfo/spkg-install.in index e987af7791d..93af702d4c7 100644 --- a/build/pkgs/database_knotinfo/spkg-install.in +++ b/build/pkgs/database_knotinfo/spkg-install.in @@ -17,5 +17,5 @@ fi if [ "$INSTALL" = "yes" ] then - exec sage-python23 spkg-install.py + exec python3 spkg-install.py fi diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py index ba3eecc3df4..13350f44895 100644 --- a/src/sage/databases/knotinfo_db.py +++ b/src/sage/databases/knotinfo_db.py @@ -372,7 +372,7 @@ def __init__(self, install=False): def demo_version(self): r""" - Return wether the KnotInfo databases are installed completely or + Return whether the KnotInfo databases are installed completely or just the demo version is used. EXAMPLES:: diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 48eae65bad6..b6aedd6af8b 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -1228,7 +1228,7 @@ def homfly_polynomial(self, var1='v', var2='z', original=False): if not homfly_polynomial and self.crossing_number() == 0: return R.one() - # As of February 2021 there is a wrong sign for the link in the + # As of February 2021 there is a wrong character for the link in the # last row of the database. This is removed here (see SPKG.rst and # the test above). Once this is fixed upstream, the following three # lines of code can be removed again: @@ -2034,9 +2034,9 @@ class KnotInfoSeries(UniqueRepresentation, SageObject): - ``crossing_number`` -- integer giving the crossing numer of this series of links - - ``is_knot`` -- boolean wether this series is a series of knots + - ``is_knot`` -- boolean whether this series is a series of knots or proper links - - ``is_alternating`` -- boolean wether this series is restriced to + - ``is_alternating`` -- boolean whether this series is restriced to alternating links or not This is not relevant for knots with less than 11 crossings - ``name_unoriented`` -- string restricting the series to all links with diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 80084c5b218..9fb5d7e0bb6 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -3914,7 +3914,7 @@ def answer_list(l): def is_isotopic(self, other): r""" - Check wether ``self`` is isotopic to ``other``. + Check whether ``self`` is isotopic to ``other``. INPUT: From 70c7f92a4a1312f5cd742133ca94197a8023b14b Mon Sep 17 00:00:00 2001 From: David Roe Date: Thu, 18 Mar 2021 15:34:39 -0400 Subject: [PATCH 079/232] Change how Galois subgroups print, add categories for subgroups, change subgroup inheritance around, etc --- src/sage/groups/abelian_gps/abelian_group.py | 22 +- .../groups/abelian_gps/abelian_group_gap.py | 67 ++++- src/sage/groups/galois_group.py | 27 +- src/sage/groups/group.pyx | 1 + src/sage/groups/perm_gps/permgroup.py | 24 +- src/sage/rings/number_field/galois_group.py | 250 ++++++++++-------- .../rings/number_field/number_field_ideal.py | 8 +- 7 files changed, 261 insertions(+), 138 deletions(-) diff --git a/src/sage/groups/abelian_gps/abelian_group.py b/src/sage/groups/abelian_gps/abelian_group.py index e51d2651daf..94fecf83743 100644 --- a/src/sage/groups/abelian_gps/abelian_group.py +++ b/src/sage/groups/abelian_gps/abelian_group.py @@ -507,7 +507,7 @@ class AbelianGroup_class(UniqueRepresentation, AbelianGroupBase): """ Element = AbelianGroupElement - def __init__(self, generator_orders, names): + def __init__(self, generator_orders, names, category=None): """ The Python constructor @@ -533,12 +533,13 @@ def __init__(self, generator_orders, names): n = len(generator_orders) names = normalize_names(n, names) self._assign_names(names) - cat = Groups().Commutative() + if category is None: + category = Groups().Commutative() if all(order > 0 for order in generator_orders): - cat = cat.Finite().Enumerated() + category = category.Finite().Enumerated() else: - cat = cat.Infinite() - AbelianGroupBase.__init__(self, category=cat) + category = category.Infinite() + AbelianGroupBase.__init__(self, category=category) def is_isomorphic(left, right): """ @@ -1225,7 +1226,7 @@ def subgroup(self, gensH, names="f"): for h in gensH: if h not in G: raise TypeError('Subgroup generators must belong to the given group.') - return AbelianGroup_subgroup(self, gensH, names) + return self.Subgroup(self, gensH, names) def list(self): """ @@ -1578,7 +1579,7 @@ class AbelianGroup_subgroup(AbelianGroup_class): There should be a way to coerce an element of a subgroup into the ambient group. """ - def __init__(self, ambient, gens, names="f"): + def __init__(self, ambient, gens, names="f", category=None): """ EXAMPLES:: @@ -1699,7 +1700,9 @@ def __init__(self, ambient, gens, names="f"): for x in Hgens0: invs.append(0) invs = tuple(ZZ(i) for i in invs) - AbelianGroup_class.__init__(self, invs, names) + if category is None: + category = Groups().Commutative().Subobjects() + AbelianGroup_class.__init__(self, invs, names, category=category) def __contains__(self, x): """ @@ -1864,3 +1867,6 @@ def gen(self, n): """ return self._gens[n] +# We allow subclasses to override this, analogous to Element +AbelianGroup_class.Subgroup = AbelianGroup_subgroup + diff --git a/src/sage/groups/abelian_gps/abelian_group_gap.py b/src/sage/groups/abelian_gps/abelian_group_gap.py index 285b5378bb3..c3ded7c6e39 100644 --- a/src/sage/groups/abelian_gps/abelian_group_gap.py +++ b/src/sage/groups/abelian_gps/abelian_group_gap.py @@ -356,8 +356,10 @@ def _element_constructor_(self, x, check=True): if len(exp) != len(gens_gap): raise ValueError("input does not match the number of generators") x = gens_gap[0]**0 - for i in range(len(exp)): - x *= gens_gap[i]**(exp[i] % orders[i]) + for g, e, m in zip(gens_gap, exp, orders): + if m != 0: + e = e % m + x *= g**e x = x.gap() return self.element_class(self, x, check=check) @@ -784,8 +786,7 @@ def __init__(self, ambient, gens): category = category.Finite() else: category = category.Infinite() - # FIXME: Tell the category that it is a Subobjects() category - # category = category.Subobjects() + category = category.Subobjects() AbelianGroup_gap.__init__(self, G, ambient=ambient, category=category) def _repr_(self): @@ -826,6 +827,64 @@ def __reduce__(self): gens = tuple([amb(g) for g in self.gens()]) return amb.subgroup, (gens,) + def lift(self, x): + """ + Coerce to the ambient group. + + The terminology comes from the category framework and the more general notion of a subquotient. + + INPUT: + + - ``x`` -- an element of this subgroup + + OUTPUT: + + The corresponding element of the ambient group + + EXAMPLES:: + + sage: from sage.groups.abelian_gps.abelian_group_gap import AbelianGroupGap + sage: G = AbelianGroupGap([4]) + sage: g = G.gen(0) + sage: H = G.subgroup([g^2]) + sage: h = H.gen(0); h + f2 + sage: h.parent() + Subgroup of Abelian group with gap, generator orders (4,) generated by (f2,) + sage: H.lift(h) + f2 + sage: H.lift(h).parent() + Abelian group with gap, generator orders (4,) + """ + return self.ambient()(x) + + def retract(self, x): + """ + Convert an element of the ambient group into this subgroup. + + The terminology comes from the category framework and the more general notion of a subquotient. + + INPUT: + + - ``x`` -- an element of the ambient group that actually lies in this subgroup. + + OUTPUT: + + The corresponding element of this subgroup + + EXAMPLES:: + + sage: from sage.groups.abelian_gps.abelian_group_gap import AbelianGroupGap + sage: G = AbelianGroupGap([4]) + sage: g = G.gen(0) + sage: H = G.subgroup([g^2]) + sage: H.retract(g^2) + f2 + sage: H.retract(g^2).parent() + Subgroup of Abelian group with gap, generator orders (4,) generated by (f2,) + """ + return self(x) + class AbelianGroupQuotient_gap(AbelianGroup_gap): r""" Quotients of abelian groups by a subgroup. diff --git a/src/sage/groups/galois_group.py b/src/sage/groups/galois_group.py index b692ee0a958..7111be0d0c5 100644 --- a/src/sage/groups/galois_group.py +++ b/src/sage/groups/galois_group.py @@ -10,8 +10,8 @@ - David Roe (2019): initial version """ -from sage.groups.perm_gps.permgroup import PermutationGroup_generic -from sage.groups.abelian_gps.abelian_group import AbelianGroup_class +from sage.groups.perm_gps.permgroup import PermutationGroup_generic, PermutationGroup_subgroup +from sage.groups.abelian_gps.abelian_group import AbelianGroup_class, AbelianGroup_subgroup from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from sage.misc.lazy_attribute import lazy_attribute from sage.misc.cachefunc import cached_method @@ -417,3 +417,26 @@ def signature(self): 1 """ return ZZ(1) if (self._field.degree() % 2) else ZZ(-1) + +class GaloisSubgroup_perm(PermutationGroup_subgroup): + """ + Subgroups of Galois groups are specified by giving a list of generators. + + Unlike ambient Galois groups, where we work hard to enable creation without + determining a list of generators, we require that generators for a subgroup + be specified during initialization. + + Subclasses are encouraged to implement a fixed_field method. + """ + pass + +class GaloisSubgroup_ab(AbelianGroup_subgroup): + """ + Subgroups of abelian Galois groups. + + Subclasses are encouraged to implement a fixed_field method. + """ + pass + +GaloisGroup_perm.Subgroup = GaloisSubgroup_perm +GaloisGroup_ab.Subgroup = GaloisSubgroup_ab diff --git a/src/sage/groups/group.pyx b/src/sage/groups/group.pyx index 9590eba4e0f..d54fdc7b5f2 100644 --- a/src/sage/groups/group.pyx +++ b/src/sage/groups/group.pyx @@ -22,6 +22,7 @@ import random from sage.structure.parent cimport Parent from sage.rings.infinity import infinity from sage.rings.integer_ring import ZZ +from sage.misc.lazy_attribute import lazy_attribute def is_Group(x): diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index e07c2eefec6..cfb57e38b01 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -2800,10 +2800,8 @@ def subgroup(self, gens=None, gap_group=None, domain=None, category=None, canoni sage: G.subgroup([g]) Subgroup generated by [(1,2,3)] of (Permutation Group with generators [(3,4,5), (1,2,3)]) """ - if gap_group is None: - gap_group = self.gap().Subgroup([self(g).gap() for g in gens]) - return PermutationGroup_subgroup(self, gens=gens, gap_group=gap_group, domain=None, - category=category, canonicalize=canonicalize, check=check) + return self.Subgroup(self, gens=gens, gap_group=gap_group, domain=None, + category=category, canonicalize=canonicalize, check=check) def _subgroup_constructor(self, libgap_group): """ @@ -4739,8 +4737,8 @@ def __init__(self, ambient, gens=None, gap_group=None, domain=None, - ``gens`` - the generators of the subgroup - - ``from_group`` - ``True``: subgroup is generated from a Gap - string representation of the generators (default: ``False``) + - ``gap_group`` - a gap permutation group contained in the ambient group; + constructed from ``gens`` if not given. - ``check`` - ``True``: checks if ``gens`` are indeed elements of the ambient group @@ -4774,14 +4772,18 @@ def __init__(self, ambient, gens=None, gap_group=None, domain=None, sage: S = PermutationGroup_subgroup(G,list(gens)) Traceback (most recent call last): ... - TypeError: each generator must be in the ambient group + ValueError: permutation (1,2,3) not in Dihedral group of order 8 as a permutation group """ if not isinstance(ambient, PermutationGroup_generic): raise TypeError("ambient (=%s) must be perm group"%ambient) if domain is None: domain = ambient.domain() if category is None: - category = ambient.category() + category = ambient.category().Subobjects() + if gap_group is None: + gap_group = ambient.gap().Subgroup([ambient(g, check=check).gap() for g in gens]) + # the ambient element constructor checks if needed + check = False PermutationGroup_generic.__init__(self, gens=gens, gap_group=gap_group, domain=domain, category=category, @@ -4891,8 +4893,7 @@ def _repr_(self): sage: S._repr_() 'Subgroup generated by [(1,2,3,4)] of (Dihedral group of order 8 as a permutation group)' """ - s = "Subgroup generated by %s of (%s)"%(self.gens(), self.ambient_group()) - return s + return "Subgroup generated by %s of (%s)" % (self.gens(), self.ambient_group()) def _latex_(self): r""" @@ -4957,5 +4958,8 @@ def is_normal(self, other=None): other = self.ambient_group() return PermutationGroup_generic.is_normal(self, other) +# Allow for subclasses to use a different subgroup class +PermutationGroup_generic.Subgroup = PermutationGroup_subgroup + from sage.misc.rest_index_of_methods import gen_rest_table_index __doc__ = __doc__.format(METHODS_OF_PermutationGroup_generic=gen_rest_table_index(PermutationGroup_generic)) diff --git a/src/sage/rings/number_field/galois_group.py b/src/sage/rings/number_field/galois_group.py index 0335450b224..e4a3530380e 100644 --- a/src/sage/rings/number_field/galois_group.py +++ b/src/sage/rings/number_field/galois_group.py @@ -17,7 +17,7 @@ """ from sage.structure.sage_object import SageObject -from sage.groups.galois_group import _alg_key, GaloisGroup_perm +from sage.groups.galois_group import _alg_key, GaloisGroup_perm, GaloisSubgroup_perm from sage.groups.perm_gps.permgroup import PermutationGroup_generic, standardize_generator from sage.groups.perm_gps.permgroup_element import PermutationGroupElement @@ -25,10 +25,13 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute from sage.libs.pari.all import pari +from sage.libs.gap.libgap import libgap from sage.rings.infinity import infinity from sage.rings.number_field.number_field import refine_embedding from sage.rings.number_field.morphism import NumberFieldHomomorphism_im_gens from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.rings.integer import Integer class GaloisGroup_v1(SageObject): @@ -215,6 +218,34 @@ class GaloisGroup_v2(GaloisGroup_perm): The 'arithmetical' features (decomposition and ramification groups, Artin symbols etc) are only available for Galois fields. + + EXAMPLES:: + + sage: G = NumberField(x^3 - x - 1, 'a').galois_closure('b').galois_group() + sage: G.subgroup([G([(1,2,3),(4,5,6)])]) + Subgroup generated by [(1,2,3)(4,5,6)] of (Galois group 6T2 ([3]2) with order 6 of x^6 - 6*x^4 + 9*x^2 + 23) + + Subgroups can be specified using generators (:trac:`26816`):: + + sage: K. = NumberField(x^6 - 6*x^4 + 9*x^2 + 23) + sage: G = K.galois_group() + sage: list(G) + [(), + (1,2,3)(4,5,6), + (1,3,2)(4,6,5), + (1,4)(2,6)(3,5), + (1,5)(2,4)(3,6), + (1,6)(2,5)(3,4)] + sage: g = G[1] + sage: h = G[3] + sage: list(G.subgroup([])) + [()] + sage: list(G.subgroup([g])) + [(), (1,2,3)(4,5,6), (1,3,2)(4,6,5)] + sage: list(G.subgroup([h])) + [(), (1,4)(2,6)(3,5)] + sage: sorted(G.subgroup([g,h])) == sorted(G) + True """ def __init__(self, number_field, algorithm='pari', names=None, gc_numbering=None, _type=None): @@ -712,40 +743,6 @@ def __iter__(self): """ return iter(self._elts) - def subgroup(self, elts): - r""" - Return the subgroup of self with the given elements. Mostly for internal use. - - EXAMPLES:: - - sage: G = NumberField(x^3 - x - 1, 'a').galois_closure('b').galois_group() - sage: G.subgroup([ G(1), G([(1,2,3),(4,5,6)]), G([(1,3,2),(4,6,5)]) ]) - Subgroup [(), (1,2,3)(4,5,6), (1,3,2)(4,6,5)] of Galois group 6T2 ([3]2) with order 6 of x^6 - 6*x^4 + 9*x^2 + 23 - - Subgroups can be specified using generators (:trac:`26816`):: - - sage: K. = NumberField(x^6 - 6*x^4 + 9*x^2 + 23) - sage: G = K.galois_group() - sage: list(G) - [(), - (1,2,3)(4,5,6), - (1,3,2)(4,6,5), - (1,4)(2,6)(3,5), - (1,5)(2,4)(3,6), - (1,6)(2,5)(3,4)] - sage: g = G[1] - sage: h = G[3] - sage: list(G.subgroup([])) - [()] - sage: list(G.subgroup([g])) - [(), (1,2,3)(4,5,6), (1,3,2)(4,6,5)] - sage: list(G.subgroup([h])) - [(), (1,4)(2,6)(3,5)] - sage: list(G.subgroup([g,h])) == list(G) - True - """ - return GaloisGroup_subgroup(self, elts) - # Proper number theory starts here. All the functions below make no sense # unless the field is Galois. @@ -796,7 +793,7 @@ def decomposition_group(self, P): sage: P = K.ideal([17, a^2]) sage: G = K.galois_group() sage: G.decomposition_group(P) - Subgroup [(), (1,8)(2,7)(3,6)(4,5)] of Galois group 8T4 ([4]2) with order 8 of x^8 - 20*x^6 + 104*x^4 - 40*x^2 + 1156 + Subgroup generated by [(1,8)(2,7)(3,6)(4,5)] of (Galois group 8T4 ([4]2) with order 8 of x^8 - 20*x^6 + 104*x^4 - 40*x^2 + 1156) sage: G.decomposition_group(P^2) Traceback (most recent call last): ... @@ -818,9 +815,9 @@ def decomposition_group(self, P): if isinstance(P, NumberFieldHomomorphism_im_gens): if self.number_field().is_totally_real(): - return self.subgroup([self.identity()]) + return self.subgroup([]) else: - return self.subgroup([self.identity(), self.complex_conjugation(P)]) + return self.subgroup([self.complex_conjugation(P)]) else: return self.ramification_group(P, -1) @@ -879,9 +876,9 @@ def ramification_group(self, P, v): sage: G=K.galois_group() sage: P = K.primes_above(3)[0] sage: G.ramification_group(P, 3) - Subgroup [(), (1,2,4)(3,5,6), (1,4,2)(3,6,5)] of Galois group 6T2 ([3]2) with order 6 of x^6 + 243 + Subgroup generated by [(1,2,4)(3,5,6)] of (Galois group 6T2 ([3]2) with order 6 of x^6 + 243) sage: G.ramification_group(P, 5) - Subgroup [()] of Galois group 6T2 ([3]2) with order 6 of x^6 + 243 + Subgroup generated by [()] of (Galois group 6T2 ([3]2) with order 6 of x^6 + 243) """ if not self.is_galois(): raise TypeError("Ramification groups only defined for Galois extensions") @@ -891,6 +888,7 @@ def ramification_group(self, P, v): elif v + 1 >= len(ramdata): return self.subgroup([]) else: + p = P.smallest_integer() return self.subgroup(ramdata[v + 1][0]) def inertia_group(self, P): @@ -903,9 +901,9 @@ def inertia_group(self, P): sage: K. = NumberField(x^2 - 3,'a') sage: G = K.galois_group() sage: G.inertia_group(K.primes_above(2)[0]) - Subgroup [(), (1,2)] of Galois group 2T1 (S2) with order 2 of x^2 - 3 + Subgroup generated by [(1,2)] of (Galois group 2T1 (S2) with order 2 of x^2 - 3) sage: G.inertia_group(K.primes_above(5)[0]) - Subgroup [()] of Galois group 2T1 (S2) with order 2 of x^2 - 3 + Subgroup generated by [()] of (Galois group 2T1 (S2) with order 2 of x^2 - 3) """ if not self.is_galois(): raise TypeError("Inertia groups only defined for Galois extensions") @@ -970,79 +968,108 @@ def artin_symbol(self, P): t = [] gens = self.number_field().ring_of_integers().ring_generators() for s in self.decomposition_group(P): - w = [(s(g) - g**p).valuation(P) for g in gens] + w = [(s.as_hom()(g) - g**p).valuation(P) for g in gens] if min(w) >= 1: t.append(s) if len(t) > 1: raise ValueError("%s is ramified" % P) return t[0] -class GaloisGroup_subgroup(GaloisGroup_v2): +class GaloisGroup_subgroup(GaloisSubgroup_perm): r""" A subgroup of a Galois group, as returned by functions such as ``decomposition_group``. - """ - def __init__(self, ambient, elts): - r""" - Return the subgroup of this Galois group generated by the - given elements. + INPUT: - It is generally better to use the :meth:`subgroup` method of - the parent group. + - ``ambient`` -- the ambient Galois group - EXAMPLES:: + - ``gens`` -- a list of generators for the group - sage: from sage.rings.number_field.galois_group import GaloisGroup_subgroup - sage: G = NumberField(x^3 - x - 1, 'a').galois_closure('b').galois_group() - sage: GaloisGroup_subgroup( G, [ G(1), G([(1,2,3),(4,5,6)]), G([(1,3,2),(4,6,5)])]) - Subgroup [(), (1,2,3)(4,5,6), (1,3,2)(4,6,5)] of Galois group 6T2 ([3]2) with order 6 of x^6 - 6*x^4 + 9*x^2 + 23 + - ``gap_group`` -- a gap or libgap permutation group, or a string + defining one (default: ``None``) - TESTS: + - ``domain`` -- a set on which this permutation group acts; extracted from ``ambient`` if not specified + + - ``category`` -- the category for this object + + - ``canonicalize`` -- if true, sorts and removes duplicates - Check that :trac:`17664` is fixed:: + - ``check`` -- whether to check that generators actually lie in the ambient group - sage: L. = QuadraticField(-1) - sage: P = L.primes_above(5)[0] + EXAMPLES:: + + sage: from sage.rings.number_field.galois_group import GaloisGroup_subgroup + sage: G = NumberField(x^3 - x - 1, 'a').galois_closure('b').galois_group() + sage: GaloisGroup_subgroup( G, [G([(1,2,3),(4,5,6)])]) + Subgroup generated by [(1,2,3)(4,5,6)] of (Galois group 6T2 ([3]2) with order 6 of x^6 - 6*x^4 + 9*x^2 + 23) + + sage: K. = NumberField(x^6-3*x^2-1) + sage: L. = K.galois_closure() + sage: G = L.galois_group() + sage: P = L.primes_above(3)[0] + sage: H = G.decomposition_group(P) + sage: H.order() + 3 + + sage: G = NumberField(x^3 - x - 1, 'a').galois_closure('b').galois_group() + sage: H = G.subgroup([G([(1,2,3),(4,5,6)])]) + sage: H + Subgroup generated by [(1,2,3)(4,5,6)] of (Galois group 6T2 ([3]2) with order 6 of x^6 - 6*x^4 + 9*x^2 + 23) + + TESTS: + + Check that :trac:`17664` is fixed:: + + sage: L. = QuadraticField(-1) + sage: P = L.primes_above(5)[0] + sage: G = L.galois_group() + sage: H = G.decomposition_group(P) + sage: H.domain() + {1, 2} + sage: G.artin_symbol(P) + () + """ + def splitting_field(self): + """ + The splitting field of this subgroup is just the splitting field of the ambient Galois group. + + EXAMPLES:: + + sage: L. = NumberField(x^4 + 1) sage: G = L.galois_group() - sage: H = G.decomposition_group(P) - sage: H.domain() - {1, 2} - sage: G.artin_symbol(P) - () + sage: H = G.decomposition_group(L.primes_above(3)[0]) + sage: H.splitting_field() + Number Field in a with defining polynomial x^4 + 1 """ - # XXX This should be fixed so that this can use GaloisGroup_v2.__init__ - PermutationGroup_generic.__init__(self, elts, canonicalize=True, - domain=ambient.domain()) - self._ambient = ambient - self._field = ambient.number_field() - self._galois_closure = ambient._galois_closure - self._pari_data = ambient._pari_data - self._gc_map = ambient._gc_map - self._default_algorithm = ambient._default_algorithm - self._type = None # Backward compatibility - self._elts = sorted(self.iteration()) + return self._ambient_group.splitting_field() - def order(self): + @lazy_attribute + def _pari_data(self): """ - Return the order of this subgroup. + Access to Pari information for the ambient Galois group. EXAMPLES:: - sage: K. = NumberField(x^6-3*x^2-1) - sage: L. = K.galois_closure() + sage: L. = NumberField(x^4 + 1) sage: G = L.galois_group() - sage: P = L.primes_above(3)[0] - sage: H = G.decomposition_group(P) - sage: H.order() - 3 + sage: H = G.decomposition_group(L.primes_above(3)[0]) + sage: H._pari_data + [y^4 + 1, ...] """ - return ZZ(len(self._elts)) + return self._ambient_group._pari_data - def fixed_field(self): + def fixed_field(self, name=None, polred=None, threshold=None): r""" Return the fixed field of this subgroup (as a subfield of the Galois closure of the number field associated to the ambient Galois group). + INPUT: + + - ``polred`` -- whether to optimize the generator of the newly created field. + Defaults to True when the degree of the fixed field is at most 8. + + - ``threshold`` -- positive number; polred only performed if the cost is at most this threshold + EXAMPLES:: sage: L. = NumberField(x^4 + 1) @@ -1060,30 +1087,31 @@ def fixed_field(self): sage: H = G.subgroup([G.identity()]) sage: H.fixed_field() - (Number Field in a0 with defining polynomial x^4 + 1 with a0 = a, - Ring morphism: - From: Number Field in a0 with defining polynomial x^4 + 1 with a0 = a - To: Number Field in a with defining polynomial x^4 + 1 - Defn: a0 |--> a) - """ - vecs = [pari(g.domain()).Vecsmall() for g in self._elts] - v = self._ambient._pari_data.galoisfixedfield(vecs) - x = self._galois_closure(v[1]) - return self._galois_closure.subfield(x) - - def _repr_(self): - r""" - String representation of self. - - EXAMPLES:: - - sage: G = NumberField(x^3 - x - 1, 'a').galois_closure('b').galois_group() - sage: H = G.subgroup([ G(1), G([(1,2,3),(4,5,6)]), G([(1,3,2),(4,6,5)])]) - sage: H._repr_() - 'Subgroup [(), (1,2,3)(4,5,6), (1,3,2)(4,6,5)] of Galois group 6T2 ([3]2) with order 6 of x^6 - 6*x^4 + 9*x^2 + 23' - """ - return "Subgroup %s of %s" % (self._elts, self._ambient) - + (Number Field in a with defining polynomial x^4 + 1, + Identity endomorphism of Number Field in a with defining polynomial x^4 + 1) + """ + G = self._ambient_group + L = G._galois_closure + if self.order() == G.order(): + return QQ, L.coerce_map_from(QQ) + elif self.order() == 1: + return L, L.coerce_map_from(L) + vecs = [pari(g.domain()).Vecsmall() for g in self.iteration()] + v = G._pari_data.galoisfixedfield(vecs) + x = v[1] + if polred is None: + index = G.order() // self.order() + polred = (index <= 8) + if polred: + f = x.minpoly() + bitsize = QQ(f[0]).numerator().nbits() + QQ(f[0]).denominator().nbits() + # time(polredbest) ≈ b²d⁵ + if threshold is None or cost <= threshold: + f, elt_back = f.polredbest(flag=1) + x = elt_back.modreverse().lift()(x) + if name is None: + name = G._field.variable_name() + '0' + return L.subfield(x, name=name) class GaloisGroupElement(PermutationGroupElement): r""" @@ -1186,6 +1214,8 @@ def ramification_degree(self, P): return min(w) GaloisGroup_v2.Element = GaloisGroupElement +GaloisGroup_v2.Subgroup = GaloisGroup_subgroup +GaloisGroup_subgroup.Element = GaloisGroupElement # For unpickling purposes we rebind GaloisGroup as GaloisGroup_v1. diff --git a/src/sage/rings/number_field/number_field_ideal.py b/src/sage/rings/number_field/number_field_ideal.py index 01e5d479024..42aac98407a 100644 --- a/src/sage/rings/number_field/number_field_ideal.py +++ b/src/sage/rings/number_field/number_field_ideal.py @@ -1417,7 +1417,7 @@ def decomposition_group(self): EXAMPLES:: sage: QuadraticField(-23, 'w').primes_above(7)[0].decomposition_group() - Subgroup [(), (1,2)] of Galois group 2T1 (S2) with order 2 of x^2 + 23 + Subgroup generated by [(1,2)] of (Galois group 2T1 (S2) with order 2 of x^2 + 23) """ return self.number_field().galois_group().decomposition_group(self) @@ -1433,9 +1433,9 @@ def ramification_group(self, v): EXAMPLES:: sage: QuadraticField(-23, 'w').primes_above(23)[0].ramification_group(0) - Subgroup [(), (1,2)] of Galois group 2T1 (S2) with order 2 of x^2 + 23 + Subgroup generated by [(1,2)] of (Galois group 2T1 (S2) with order 2 of x^2 + 23) sage: QuadraticField(-23, 'w').primes_above(23)[0].ramification_group(1) - Subgroup [()] of Galois group 2T1 (S2) with order 2 of x^2 + 23 + Subgroup generated by [()] of (Galois group 2T1 (S2) with order 2 of x^2 + 23) """ return self.number_field().galois_group().ramification_group(self, v) @@ -1451,7 +1451,7 @@ def inertia_group(self): EXAMPLES:: sage: QuadraticField(-23, 'w').primes_above(23)[0].inertia_group() - Subgroup [(), (1,2)] of Galois group 2T1 (S2) with order 2 of x^2 + 23 + Subgroup generated by [(1,2)] of (Galois group 2T1 (S2) with order 2 of x^2 + 23) """ return self.ramification_group(0) From a1b793ef49ac96f1b55f4328e556885dbb335a1c Mon Sep 17 00:00:00 2001 From: David Roe Date: Fri, 19 Mar 2021 18:14:06 -0400 Subject: [PATCH 080/232] Fix pyflakes, add example of polred to fixed_field for galois groups --- src/sage/rings/number_field/galois_group.py | 40 ++++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/sage/rings/number_field/galois_group.py b/src/sage/rings/number_field/galois_group.py index e4a3530380e..fa232636373 100644 --- a/src/sage/rings/number_field/galois_group.py +++ b/src/sage/rings/number_field/galois_group.py @@ -18,20 +18,18 @@ from sage.structure.sage_object import SageObject from sage.groups.galois_group import _alg_key, GaloisGroup_perm, GaloisSubgroup_perm -from sage.groups.perm_gps.permgroup import PermutationGroup_generic, standardize_generator +from sage.groups.perm_gps.permgroup import standardize_generator from sage.groups.perm_gps.permgroup_element import PermutationGroupElement from sage.misc.superseded import deprecation from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute from sage.libs.pari.all import pari -from sage.libs.gap.libgap import libgap from sage.rings.infinity import infinity from sage.rings.number_field.number_field import refine_embedding from sage.rings.number_field.morphism import NumberFieldHomomorphism_im_gens from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ -from sage.rings.integer import Integer class GaloisGroup_v1(SageObject): @@ -888,7 +886,6 @@ def ramification_group(self, P, v): elif v + 1 >= len(ramdata): return self.subgroup([]) else: - p = P.smallest_integer() return self.subgroup(ramdata[v + 1][0]) def inertia_group(self, P): @@ -1065,8 +1062,9 @@ def fixed_field(self, name=None, polred=None, threshold=None): INPUT: - - ``polred`` -- whether to optimize the generator of the newly created field. - Defaults to True when the degree of the fixed field is at most 8. + - ``polred`` -- whether to optimize the generator of the newly created field + for a simpler polynomial, using pari's polredbest. + Defaults to ``True`` when the degree of the fixed field is at most 8. - ``threshold`` -- positive number; polred only performed if the cost is at most this threshold @@ -1082,13 +1080,36 @@ def fixed_field(self, name=None, polred=None, threshold=None): To: Number Field in a with defining polynomial x^4 + 1 Defn: a0 |--> a^3 + a) + You can use the ``polred`` option to get a simpler defining polynomial:: + + sage: K. = NumberField(x^5 - 5*x^2 - 3) + sage: G = K.galois_group(); G + Galois group 5T2 (5:2) with order 10 of x^5 - 5*x^2 - 3 + sage: sigma, tau = G.gens() + sage: H = G.subgroup([tau]) + sage: H.fixed_field(polred=False) + (Number Field in a0 with defining polynomial x^2 + 84375 with a0 = 5*ac^5 + 25*ac^3, + Ring morphism: + From: Number Field in a0 with defining polynomial x^2 + 84375 with a0 = 5*ac^5 + 25*ac^3 + To: Number Field in ac with defining polynomial x^10 + 10*x^8 + 25*x^6 + 3375 + Defn: a0 |--> 5*ac^5 + 25*ac^3) + sage: H.fixed_field(polred=True) + (Number Field in a0 with defining polynomial x^2 - x + 4 with a0 = -1/30*ac^5 - 1/6*ac^3 + 1/2, + Ring morphism: + From: Number Field in a0 with defining polynomial x^2 - x + 4 with a0 = -1/30*ac^5 - 1/6*ac^3 + 1/2 + To: Number Field in ac with defining polynomial x^10 + 10*x^8 + 25*x^6 + 3375 + Defn: a0 |--> -1/30*ac^5 - 1/6*ac^3 + 1/2) + sage: G.splitting_field() + Number Field in ac with defining polynomial x^10 + 10*x^8 + 25*x^6 + 3375 + + An embedding is returned also if the subgroup is trivial (:trac:`26817`):: - sage: H = G.subgroup([G.identity()]) + sage: H = G.subgroup([]) sage: H.fixed_field() - (Number Field in a with defining polynomial x^4 + 1, - Identity endomorphism of Number Field in a with defining polynomial x^4 + 1) + (Number Field in ac with defining polynomial x^10 + 10*x^8 + 25*x^6 + 3375, + Identity endomorphism of Number Field in ac with defining polynomial x^10 + 10*x^8 + 25*x^6 + 3375) """ G = self._ambient_group L = G._galois_closure @@ -1105,6 +1126,7 @@ def fixed_field(self, name=None, polred=None, threshold=None): if polred: f = x.minpoly() bitsize = QQ(f[0]).numerator().nbits() + QQ(f[0]).denominator().nbits() + cost = 2 * bitsize.nbits() + 5 * f.degree().nbits() # time(polredbest) ≈ b²d⁵ if threshold is None or cost <= threshold: f, elt_back = f.polredbest(flag=1) From cfb6a78952c4f566affe384fa6e168c2e52e0883 Mon Sep 17 00:00:00 2001 From: David Roe Date: Fri, 19 Mar 2021 18:17:49 -0400 Subject: [PATCH 081/232] Fix test failure and other plugin problems --- .../explicit_methods_in_number_theory/nf_galois_groups.rst | 2 +- src/sage/rings/number_field/galois_group.py | 1 + src/sage/rings/number_field/number_field_ideal.py | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst index 07e84de55ea..e1ed2b11f76 100644 --- a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst +++ b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst @@ -40,7 +40,7 @@ Some more advanced number-theoretical tools are available via G: sage: P = K.primes_above(2)[0] sage: G.inertia_group(P) - Subgroup [(), (1,4,6)(2,5,3), (1,6,4)(2,3,5)] of Galois group 6T2 ([3]2) with order 6 of x^6 + 40*x^3 + 1372 + Subgroup generated by [(1,4,6)(2,5,3)] of (Galois group 6T2 ([3]2) with order 6 of x^6 + 40*x^3 + 1372) sage: sorted([G.artin_symbol(Q) for Q in K.primes_above(5)]) # random order, see Trac #18308 [(1,3)(2,6)(4,5), (1,2)(3,4)(5,6), (1,5)(2,4)(3,6)] diff --git a/src/sage/rings/number_field/galois_group.py b/src/sage/rings/number_field/galois_group.py index fa232636373..78bc8c74c93 100644 --- a/src/sage/rings/number_field/galois_group.py +++ b/src/sage/rings/number_field/galois_group.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Galois Groups of Number Fields diff --git a/src/sage/rings/number_field/number_field_ideal.py b/src/sage/rings/number_field/number_field_ideal.py index 42aac98407a..9686bcdb659 100644 --- a/src/sage/rings/number_field/number_field_ideal.py +++ b/src/sage/rings/number_field/number_field_ideal.py @@ -836,7 +836,8 @@ def gens_two(self): # Check whether the ideal is generated by an integer, i.e. # whether HNF is a multiple of the identity matrix if HNF.gequal(HNF[0,0]): - a = HNF[0,0]; alpha = 0 + a = HNF[0,0] + alpha = 0 else: a, alpha = K.pari_nf().idealtwoelt(HNF) self.__two_generators = (K(a), K(alpha)) @@ -2512,7 +2513,7 @@ def _pari_bid_(self, flag=1): bid = self._bid if flag==2: # Try to access generators, we get PariError if this fails. - bid.bid_get_gen(); + bid.bid_get_gen() except (AttributeError, PariError): k = self.number_field() bid = k.pari_nf().idealstar(self.pari_hnf(), flag) From 539b0ca5eb4280958d54c8c9734fc6be47387d20 Mon Sep 17 00:00:00 2001 From: David Roe Date: Fri, 19 Mar 2021 19:19:33 -0400 Subject: [PATCH 082/232] Fix some test failures, factor _GaloisMixin for subgrous --- src/sage/groups/galois_group.py | 145 ++++++++++++-------- src/sage/rings/finite_rings/galois_group.py | 1 - src/sage/rings/number_field/galois_group.py | 18 +-- src/sage/rings/qqbar.py | 2 +- 4 files changed, 87 insertions(+), 79 deletions(-) diff --git a/src/sage/groups/galois_group.py b/src/sage/groups/galois_group.py index 7111be0d0c5..5d5d5fbee40 100644 --- a/src/sage/groups/galois_group.py +++ b/src/sage/groups/galois_group.py @@ -39,42 +39,94 @@ def _alg_key(self, algorithm=None, recompute=False): algorithm = self._get_algorithm(algorithm) return algorithm -class _GaloisMixin: +class _GMixin: r""" This class provides some methods for Galois groups to be used for both permutation groups - and abelian groups. + and abelian groups, subgroups and full Galois groups. """ - def _repr_(self): - """ - String representation of this Galois group + def _get_algorithm(self, algorithm): + r""" + Allows overriding the default algorithm specified at object creation. EXAMPLES:: - sage: from sage.groups.galois_group import GaloisGroup_perm sage: R. = ZZ[] sage: K. = NumberField(x^3 + 2*x + 2) sage: G = K.galois_group() - sage: GaloisGroup_perm._repr_(G) - 'Galois group of x^3 + 2*x + 2' + sage: G._get_algorithm(None) + 'pari' + sage: G._get_algorithm('magma') + 'magma' """ - f = self._field.defining_polynomial() - return "Galois group of %s" % f + return self._default_algorithm if algorithm is None else algorithm - def _get_algorithm(self, algorithm): + @lazy_attribute + def _galois_closure(self): r""" - Allows overriding the default algorithm specified at object creation. + The Galois closure of the top field. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) + sage: G = K.galois_group(names='b') + sage: G._galois_closure + Number Field in b with defining polynomial x^6 + 12*x^4 + 36*x^2 + 140 + """ + return self._gcdata[0] + + def splitting_field(self): + r""" + The Galois closure of the top field. + + EXAMPLES:: + + sage: K = NumberField(x^3 - x + 1, 'a') + sage: K.galois_group(names='b').splitting_field() + Number Field in b with defining polynomial x^6 - 6*x^4 + 9*x^2 + 23 + sage: L = QuadraticField(-23, 'c'); L.galois_group().splitting_field() is L + True + """ + return self._galois_closure + + @lazy_attribute + def _gc_map(self): + r""" + The inclusion of the top field into the Galois closure. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) + sage: G = K.galois_group(names='b') + sage: G._gc_map + Ring morphism: + From: Number Field in a with defining polynomial x^3 + 2*x + 2 + To: Number Field in b with defining polynomial x^6 + 12*x^4 + 36*x^2 + 140 + Defn: a |--> 1/36*b^4 + 5/18*b^2 - 1/2*b + 4/9 + """ + return self._gcdata[1] + +class _GaloisMixin(_GMixin): + """ + This class provides methods for Galois groups, allowing concrete instances + to inherit from both permutation group and abelian group classes. + """ + def _repr_(self): + """ + String representation of this Galois group EXAMPLES:: + sage: from sage.groups.galois_group import GaloisGroup_perm sage: R. = ZZ[] sage: K. = NumberField(x^3 + 2*x + 2) sage: G = K.galois_group() - sage: G._get_algorithm(None) - 'pari' - sage: G._get_algorithm('magma') - 'magma' + sage: GaloisGroup_perm._repr_(G) + 'Galois group of x^3 + 2*x + 2' """ - return self._default_algorithm if algorithm is None else algorithm + f = self._field.defining_polynomial() + return "Galois group of %s" % f def top_field(self): r""" @@ -129,54 +181,25 @@ def is_galois(self): """ return self.order() == self._field.degree() +class _SubGaloisMixin(_GMixin): + """ + This class provides methods for subgroups of Galois groups, allowing concrete instances + to inherit from both permutation group and abelian group classes. + """ @lazy_attribute - def _galois_closure(self): - r""" - The Galois closure of the top field. - - EXAMPLES:: - - sage: R. = ZZ[] - sage: K. = NumberField(x^3 + 2*x + 2) - sage: G = K.galois_group(names='b') - sage: G._galois_closure - Number Field in b with defining polynomial x^6 + 12*x^4 + 36*x^2 + 140 - """ - return self._gcdata[0] - - def splitting_field(self): - r""" - The Galois closure of the top field. - - EXAMPLES:: - - sage: K = NumberField(x^3 - x + 1, 'a') - sage: K.galois_group(names='b').splitting_field() - Number Field in b with defining polynomial x^6 - 6*x^4 + 9*x^2 + 23 - sage: L = QuadraticField(-23, 'c'); L.galois_group().splitting_field() is L - True + def _gcdata(self): """ - return self._galois_closure - - @lazy_attribute - def _gc_map(self): - r""" - The inclusion of the top field into the Galois closure. + The Galois closure data is just that of the ambient group. EXAMPLES:: - sage: R. = ZZ[] - sage: K. = NumberField(x^3 + 2*x + 2) - sage: G = K.galois_group(names='b') - sage: G._gc_map - Ring morphism: - From: Number Field in a with defining polynomial x^3 + 2*x + 2 - To: Number Field in b with defining polynomial x^6 + 12*x^4 + 36*x^2 + 140 - Defn: a |--> 1/36*b^4 + 5/18*b^2 - 1/2*b + 4/9 + sage: L. = NumberField(x^4 + 1) + sage: G = L.galois_group() + sage: H = G.decomposition_group(L.primes_above(3)[0]) + sage: H.splitting_field() # indirect doctest + Number Field in a with defining polynomial x^4 + 1 """ - return self._gcdata[1] - - + return self._ambient_group._gcdata class GaloisGroup_perm(_GaloisMixin, PermutationGroup_generic): r""" @@ -418,7 +441,7 @@ def signature(self): """ return ZZ(1) if (self._field.degree() % 2) else ZZ(-1) -class GaloisSubgroup_perm(PermutationGroup_subgroup): +class GaloisSubgroup_perm(PermutationGroup_subgroup, _SubGaloisMixin): """ Subgroups of Galois groups are specified by giving a list of generators. @@ -430,7 +453,7 @@ class GaloisSubgroup_perm(PermutationGroup_subgroup): """ pass -class GaloisSubgroup_ab(AbelianGroup_subgroup): +class GaloisSubgroup_ab(AbelianGroup_subgroup, _SubGaloisMixin): """ Subgroups of abelian Galois groups. diff --git a/src/sage/rings/finite_rings/galois_group.py b/src/sage/rings/finite_rings/galois_group.py index 6637cff97f3..df7babbab3f 100644 --- a/src/sage/rings/finite_rings/galois_group.py +++ b/src/sage/rings/finite_rings/galois_group.py @@ -65,7 +65,6 @@ def __init__(self, field): sage: TestSuite(GF(9).galois_group()).run() """ - self._field = field GaloisGroup_cyc.__init__(self, field, (field.degree(),), gen_names="Frob") def _repr_(self): diff --git a/src/sage/rings/number_field/galois_group.py b/src/sage/rings/number_field/galois_group.py index 78bc8c74c93..295b99f5638 100644 --- a/src/sage/rings/number_field/galois_group.py +++ b/src/sage/rings/number_field/galois_group.py @@ -966,7 +966,7 @@ def artin_symbol(self, P): t = [] gens = self.number_field().ring_of_integers().ring_generators() for s in self.decomposition_group(P): - w = [(s.as_hom()(g) - g**p).valuation(P) for g in gens] + w = [(s(g) - g**p).valuation(P) for g in gens] if min(w) >= 1: t.append(s) if len(t) > 1: @@ -1027,20 +1027,6 @@ class GaloisGroup_subgroup(GaloisSubgroup_perm): sage: G.artin_symbol(P) () """ - def splitting_field(self): - """ - The splitting field of this subgroup is just the splitting field of the ambient Galois group. - - EXAMPLES:: - - sage: L. = NumberField(x^4 + 1) - sage: G = L.galois_group() - sage: H = G.decomposition_group(L.primes_above(3)[0]) - sage: H.splitting_field() - Number Field in a with defining polynomial x^4 + 1 - """ - return self._ambient_group.splitting_field() - @lazy_attribute def _pari_data(self): """ @@ -1127,7 +1113,7 @@ def fixed_field(self, name=None, polred=None, threshold=None): if polred: f = x.minpoly() bitsize = QQ(f[0]).numerator().nbits() + QQ(f[0]).denominator().nbits() - cost = 2 * bitsize.nbits() + 5 * f.degree().nbits() + cost = 2 * bitsize + 5 * ZZ(f.poldegree()).nbits() # time(polredbest) ≈ b²d⁵ if threshold is None or cost <= threshold: f, elt_back = f.polredbest(flag=1) diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index f3d3f0e6a83..d94dd115a3f 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -2137,7 +2137,7 @@ def do_polred(poly, threshold=32): parent = poly.parent() bitsize = ZZ(poly[0].numerator().nbits() + poly[0].denominator().nbits()) # time(polredbest) ≈ b²d⁵ - cost = 2 * bitsize.nbits() + 5 * poly.degree().nbits() + cost = 2 * bitsize + 5 * poly.degree().nbits() if cost > threshold: return parent.gen(), parent.gen(), poly new_poly, elt_back = poly.__pari__().polredbest(flag=1) From 61873da0d4af1854950834b926f3344c3b842e16 Mon Sep 17 00:00:00 2001 From: David Roe Date: Sat, 20 Mar 2021 01:48:35 -0400 Subject: [PATCH 083/232] Fix bitsize --- src/sage/rings/number_field/galois_group.py | 4 ++-- src/sage/rings/qqbar.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/number_field/galois_group.py b/src/sage/rings/number_field/galois_group.py index 295b99f5638..7c94a60949e 100644 --- a/src/sage/rings/number_field/galois_group.py +++ b/src/sage/rings/number_field/galois_group.py @@ -1112,8 +1112,8 @@ def fixed_field(self, name=None, polred=None, threshold=None): polred = (index <= 8) if polred: f = x.minpoly() - bitsize = QQ(f[0]).numerator().nbits() + QQ(f[0]).denominator().nbits() - cost = 2 * bitsize + 5 * ZZ(f.poldegree()).nbits() + bitsize = ZZ(QQ(f[0]).numerator().nbits() + QQ(f[0]).denominator().nbits()) + cost = 2 * bitsize.nbits() + 5 * ZZ(f.poldegree()).nbits() # time(polredbest) ≈ b²d⁵ if threshold is None or cost <= threshold: f, elt_back = f.polredbest(flag=1) diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index d94dd115a3f..f3d3f0e6a83 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -2137,7 +2137,7 @@ def do_polred(poly, threshold=32): parent = poly.parent() bitsize = ZZ(poly[0].numerator().nbits() + poly[0].denominator().nbits()) # time(polredbest) ≈ b²d⁵ - cost = 2 * bitsize + 5 * poly.degree().nbits() + cost = 2 * bitsize.nbits() + 5 * poly.degree().nbits() if cost > threshold: return parent.gen(), parent.gen(), poly new_poly, elt_back = poly.__pari__().polredbest(flag=1) From 7e17cced3c6dd8c3efc8a29c82690b097b825951 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 17 Aug 2020 19:56:34 +0200 Subject: [PATCH 084/232] Implement theta basis in rank 2 --- src/doc/en/reference/references/index.rst | 3 + src/sage/algebras/cluster_algebra.py | 250 ++++++++++++++++++---- 2 files changed, 217 insertions(+), 36 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 86b4fb35b48..bafc5916597 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4737,6 +4737,9 @@ REFERENCES: .. [Rea2009] Nathan Reading, *Noncrossing partitions and the shard intersection order*, DMTCS Proceedings of FPSAC 2009, 745--756 +.. [ReSt2020] Nathan Reading and Salvatore Stella, *An affine almost positive + roots model*, J. Comb. Algebra Volume 4, Issue 1, 2020, pp. 1--59 + .. [Red2001] Maria Julia Redondo. *Hochschild cohomology: some methods for computations*. Resenhas IME-USP 5 (2), 113-137 (2001). http://inmabb.criba.edu.ar/gente/mredondo/crasp.pdfc diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index dae5f0650f7..8e987814d77 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -363,6 +363,7 @@ from sage.functions.other import binomial from sage.geometry.cone import Cone from sage.geometry.fan import Fan +from sage.graphs.digraph import DiGraph from sage.matrix.constructor import identity_matrix, matrix from sage.matrix.special import block_matrix from sage.misc.cachefunc import cached_method @@ -1477,6 +1478,48 @@ def _coerce_map_from_(self, other): # everything that is in the base can be coerced to self return self.base().has_coerce_map_from(other) + @cached_method + def coxeter_element(self): + r""" + Return the Coxeter element associated to the initial exchange matrix, if acyclic. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.coxeter_element() + [0, 1, 2] + + Raise an error if the initial exchange matrix is not acyclic:: + + sage: A = ClusterAlgebra(matrix([[0,1,-1],[-1,0,1],[1,-1,0]])) + sage: A.coxeter_element() + Traceback (most recent call last): + ... + ValueError: The initial exchange matrix is not acyclic. + """ + dg = DiGraph(self.b_matrix().apply_map(lambda x: ZZ(0) if x <= 0 else ZZ(1))) + acyclic, coxeter = dg.is_directed_acyclic(certificate=True) + if not acyclic: + raise ValueError("The initial exchange matrix is not acyclic.") + return coxeter + + @cached_method + def is_acyclic(self): + r""" + Return ``True`` if the exchange matrix in the initial seed is acyclic, ``False`` otherwise. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.is_acyclic() + True + sage: A = ClusterAlgebra(matrix([[0,1,-1],[-1,0,1],[1,-1,0]])) + sage: A.is_acyclic() + False + """ + dg = DiGraph(self.b_matrix().apply_map(lambda x: ZZ(0) if x <= 0 else ZZ(1))) + return dg.is_directed_acyclic() + def rank(self): r""" Return the rank of ``self``, i.e. the number of cluster variables @@ -1627,6 +1670,94 @@ def b_matrix(self): n = self.rank() return copy(self._B0[:n, :]) + def euler_matrix(self): + r""" + Return the Euler matrix associated to ``self``. + + ALGORITHM: + + This method returns the matrix of the biliinear form defined in Equation (2.1) of [ReSt2020]_ . + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.euler_matrix() + [ 1 0 0] + [-1 1 0] + [-1 -1 1] + + Raise an error if the initial exchange matrix is not acyclic:: + + sage: A = ClusterAlgebra(matrix([[0,1,-1],[-1,0,1],[1,-1,0]])) + sage: A.euler_matrix() + Traceback (most recent call last): + ... + ValueError: The initial exchange matrix is not acyclic. + """ + + if not self.is_acyclic(): + raise ValueError("The initial exchange matrix is not acyclic.") + return 1 + self.b_matrix().apply_map(lambda x: min(ZZ(0), x)) + + def d_vector_to_g_vector(self, d): + r""" + Return the g-vector of an element of ``self`` having d-vector ``d`` + + INPUT: + + - ``d`` -- the d-vector + + ALGORITHM: + + This method implements the piecewise-linear map `\\nu_c` introduced in Section 9.1 of [ReSt2020]_. + + .. WARNING: + + This implementation works only when the initial exchange matrix is acyclic. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.d_vector_to_g_vector((1,0,-1)) + (-1, 1, 2) + """ + dm = vector(( x if x < 0 else 0 for x in d)) + dp = vector(d) - dm + return tuple(- dm - self.euler_matrix()*dp) + + def g_vector_to_d_vector(self, g): + r""" + Return the d-vector of an element of ``self`` having g-vector ``g`` + + INPUT: + + - ``g`` -- the g-vector + + ALGORITHM: + + This method implements the inverse of the piecewise-linear map `\\nu_c` introduced in Section 9.1 of [ReSt2020]_. + + .. WARNING: + + This implementation works only when the initial exchange matrix is acyclic. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.g_vector_to_d_vector((-1,1,2)) + (1, 0, -1) + """ + E = -self.euler_matrix() + c = self.coxeter_element() + dp = vector(ZZ, self.rank()) + g = vector(g) + for i in c: + dp[i] = -min(g[i], 0) + g += min(g[i],0)*E.column(i) + return tuple(-g+dp) + + return d + def g_vectors(self, mutating_F=True): r""" Return an iterator producing all the g-vectors of ``self``. @@ -2394,28 +2525,89 @@ def greedy_element(self, d_vector): if self.rank() != 2: raise ValueError('greedy elements are only defined in rank 2') - if len(self.coefficients()) != 0: - raise NotImplementedError('can only compute greedy elements in the coefficient-free case') + return self.theta_basis_element(self.d_vector_to_g_vector(d_vector)) - b = abs(self.b_matrix()[0, 1]) - c = abs(self.b_matrix()[1, 0]) - a1, a2 = d_vector - # Here we use the generators of self.ambient() because cluster variables - # do not have an inverse. - x1, x2 = self.ambient().gens() - if a1 < 0: - if a2 < 0: - return self.retract(x1 ** (-a1) * x2 ** (-a2)) - else: - return self.retract(x1 ** (-a1) * ((1 + x2 ** c) / x1) ** a2) - elif a2 < 0: - return self.retract(((1 + x1 ** b) / x2) ** a1 * x2 ** (-a2)) - output = 0 - for p in range(a2 + 1): - for q in range(a1 + 1): - output += self._greedy_coefficient(d_vector, p, q) * x1 ** (b * p) * x2 ** (c * q) - return self.retract(x1 ** (-a1) * x2 ** (-a2) * output) + @cached_method(key=lambda a, b: tuple(b)) + def theta_basis_element(self, g_vector): + r""" + Return the element of the theta basis of ``self`` with g-vector ``g_vector``. + + INPUT: + + - ``g_vector`` -- tuple; the g-vector of the element to compute + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,-3],[2,0]]), principal_coefficients=True) + sage: A.theta_basis_element((-1,-1)) + (x1^8*y0^4*y1 + 4*x1^6*y0^3*y1 + 6*x1^4*y0^2*y1 + x0^3*x1^2*y0 + 4*x1^2*y0*y1 + x0^3 + y1)/(x0^4*x1) + + sage: A = ClusterAlgebra(['F', 4]) + sage: A.theta_basis_element((1, 0, 0, 0)) + Traceback (most recent call last): + ... + NotImplementedError: Currently only implemented for cluster algebras of rank 2. + + .. WARNING:: + + Currently only cluster algebras of rank 2 are supported + + .. SEEALSO:: + + :meth:`sage.algebras.cluster_algebra.theta_basis_F_polynomial` + """ + g_vector = tuple(g_vector) + F = self.theta_basis_F_polynomial(g_vector) + F_std = F.subs(self._yhat) + g_mon = prod(self.ambient().gen(i) ** g_vector[i] for i in range(self.rank())) + F_trop = self.ambient()(F.subs(self._y))._fraction_pair()[1] + return self.retract(g_mon * F_std * F_trop) + @cached_method(key=lambda a, b: tuple(b)) + def theta_basis_F_polynomial(self, g_vector): + r""" + Return the F-polynomial of the element of the theta basis of ``self`` with g-vector ``g_vector``. + + INPUT: + + - ``g_vector`` -- tuple; the g-vector of the F-polynomial to compute + + ALGORITHM: + + This method uses the fact that the greedy basis and the theta basis + coincide in rank 2 and uses the former defining recursion (Equation + (1.5) from [LLZ2014]_) to compute. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,-3],[2,0]]), principal_coefficients=True) + sage: A.theta_basis_F_polynomial((-1,-1)) + u0^4*u1 + 4*u0^3*u1 + 6*u0^2*u1 + 4*u0*u1 + u0 + u1 + 1 + + sage: A = ClusterAlgebra(['F', 4]) + sage: A.theta_basis_F_polynomial((1, 0, 0, 0)) + Traceback (most recent call last): + ... + NotImplementedError: Currently only implemented for cluster algebras of rank 2. + """ + if self.rank() != 2: + raise NotImplementedError("Currently only implemented for cluster algebras of rank 2.") + + # extract the part of g_vector not coming from the initial cluster + d = tuple( max(x, 0) for x in self.g_vector_to_d_vector(g_vector) ) + g = self.d_vector_to_g_vector(d) + + shifts = ((d[0]+g[0])/self._B0[0, 1], (d[1]+g[1])/self._B0[1, 0] ) + signs = ( sign(self._B0[0, 1]), sign(self._B0[1, 0]) ) + + u = list(self._U.gens()) + output = self._U.zero() + for p in range(0, d[1] + 1): + for q in range(0, d[0] + 1): + output += self._greedy_coefficient(d, p, q) * u[1] ** (signs[0]*p - shifts[0]) * u[0] ** (signs[1]*q - shifts[1]) + return output + + @cached_method def _greedy_coefficient(self, d_vector, p, q): r""" Return the coefficient of the monomial ``x1 ** (b * p) * x2 ** (c * q)`` @@ -2431,8 +2623,8 @@ def _greedy_coefficient(self, d_vector, p, q): sage: A._greedy_coefficient((1, 1), 1, 0) 1 """ - b = abs(self.b_matrix()[0, 1]) - c = abs(self.b_matrix()[1, 0]) + b = abs(self._B0[0, 1]) + c = abs(self._B0[1, 0]) a1, a2 = d_vector p = Integer(p) q = Integer(q) @@ -2495,17 +2687,3 @@ def lower_bound(self): NotImplementedError: not implemented yet """ raise NotImplementedError("not implemented yet") - - def theta_basis_element(self, g_vector): - r""" - Return the element of the theta basis with g-vector ``g_vector``. - - EXAMPLES:: - - sage: A = ClusterAlgebra(['F', 4]) - sage: A.theta_basis_element((1, 0, 0, 0)) - Traceback (most recent call last): - ... - NotImplementedError: not implemented yet - """ - raise NotImplementedError("not implemented yet") From d1c2029c40d3552fff2d481d5569c10cf929fa62 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 21 Jan 2021 15:10:37 +0100 Subject: [PATCH 085/232] Implement theta basis decomposition --- src/sage/algebras/cluster_algebra.py | 85 ++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 8e987814d77..4b8cca6f620 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -493,11 +493,11 @@ def g_vector(self): sage: sum(A.initial_cluster_variables()).g_vector() Traceback (most recent call last): ... - ValueError: this element is not homogeneous + ValueError: this element does not have a well defined g-vector """ components = self.homogeneous_components() if len(components) != 1: - raise ValueError("this element is not homogeneous") + raise ValueError("this element does not have a well defined g-vector") k, = components return k @@ -515,17 +515,17 @@ def F_polynomial(self): sage: sum(A.initial_cluster_variables()).F_polynomial() Traceback (most recent call last): ... - ValueError: this element is not homogeneous + ValueError: this element does not have a well defined g-vector """ if not self.is_homogeneous(): - raise ValueError("this element is not homogeneous") + raise ValueError("this element does not have a well defined g-vector") subs_dict = {} A = self.parent() for x in A.initial_cluster_variables(): subs_dict[x.lift()] = A._U(1) for i in range(A.rank()): subs_dict[A.coefficient(i).lift()] = A._U.gen(i) - return self.lift().substitute(subs_dict) + return A._U(self.lift().substitute(subs_dict)) def is_homogeneous(self): r""" @@ -572,6 +572,69 @@ def homogeneous_components(self): components[g_vect] = self.parent().retract(x.monomial_coefficient(m) * m) return components + def theta_basis_decomposition(self): + r""" + Return the decomposition of ``self`` in the theta basis. + + OUTPUT: + + A dictionary whose keys are the g-vectors and whose values are the coefficients + in the decoposition of ``self`` in the theta basis. + + EXAMPLES:: + sage: A = ClusterAlgebra(matrix([[0,-2],[3,0]]), principal_coefficients=True) + sage: f = (A.theta_basis_element((1,0)) + A.theta_basis_element((0,1)))**2 + A.coefficient(1)* A.theta_basis_element((1,1)) + sage: f.theta_basis_decomposition() + {(1, 1): y1 + 2, (2, 0): 1, (0, 2): 1} + sage: sum(_[g] * A.theta_basis_element(g) for g in _) - f + 0 + """ + zero = self.parent()(0) + components = map(lambda x: x._homogeneous_theta_basis_decomposition(), self.homogeneous_components().values()) + out = {} + for cpt in components: + for g in cpt: + out[g] = out.get(g, zero) + cpt[g] + return out + + def _homogeneous_theta_basis_decomposition(self): + r""" + Return the decomposition of ``self`` in the theta basis. + + OUTPUT: + + A dictionary whose keys are the g-vectors and whose values are the coefficients + in the decoposition of ``self`` in the theta basis. + + WARNING: + + This method only works when ``self`` is homogeneous. + + EXAMPLES:: + sage: A = ClusterAlgebra(matrix([[0,-2],[3,0]]), principal_coefficients=True) + sage: f = A.theta_basis_element((4,-4))*A.theta_basis_element((1,-1)) + sage: f.theta_basis_decomposition() # indirect doctest + {(5, -5): 1, (3, -2): y0*y1, (1, -2): y0*y1^2} + sage: sum(_[g] * A.theta_basis_element(g) for g in _) - f + 0 + """ + f_poly = self.F_polynomial() + g_vect = vector(self.g_vector()) + + A = self.parent() + B = A.b_matrix() + n = A.rank() + U = f_poly.parent() + out = {} + + while f_poly != U(0): + y_exp = min(f_poly.dict()) + coeff = f_poly.dict()[y_exp] + g_theta = tuple(g_vect + B*vector(y_exp)) + out[g_theta] = A({(0,)*n + tuple(y_exp):coeff}) + f_poly -= U({y_exp:coeff}) * A.theta_basis_F_polynomial(g_theta) + return out + ############################################################################## # Seeds @@ -1495,12 +1558,12 @@ def coxeter_element(self): sage: A.coxeter_element() Traceback (most recent call last): ... - ValueError: The initial exchange matrix is not acyclic. + ValueError: the initial exchange matrix is not acyclic. """ dg = DiGraph(self.b_matrix().apply_map(lambda x: ZZ(0) if x <= 0 else ZZ(1))) acyclic, coxeter = dg.is_directed_acyclic(certificate=True) if not acyclic: - raise ValueError("The initial exchange matrix is not acyclic.") + raise ValueError("the initial exchange matrix is not acyclic.") return coxeter @cached_method @@ -1573,12 +1636,12 @@ def set_current_seed(self, seed): sage: A.set_current_seed(A1.initial_seed()) Traceback (most recent call last): ... - ValueError: This is not a seed in this cluster algebra + ValueError: this is not a seed in this cluster algebra """ if self.contains_seed(seed): self._seed = seed else: - raise ValueError("This is not a seed in this cluster algebra") + raise ValueError("this is not a seed in this cluster algebra") def reset_current_seed(self): r""" @@ -1692,11 +1755,11 @@ def euler_matrix(self): sage: A.euler_matrix() Traceback (most recent call last): ... - ValueError: The initial exchange matrix is not acyclic. + ValueError: the initial exchange matrix is not acyclic. """ if not self.is_acyclic(): - raise ValueError("The initial exchange matrix is not acyclic.") + raise ValueError("the initial exchange matrix is not acyclic.") return 1 + self.b_matrix().apply_map(lambda x: min(ZZ(0), x)) def d_vector_to_g_vector(self, d): From 342be59aa34086ccd9be74ffabcad2a2a64bf5e0 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 29 Jan 2021 17:21:07 +0100 Subject: [PATCH 086/232] Better implementation --- src/sage/algebras/cluster_algebra.py | 84 +++++++++++----------------- 1 file changed, 33 insertions(+), 51 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 4b8cca6f620..f98efecc033 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -495,11 +495,9 @@ def g_vector(self): ... ValueError: this element does not have a well defined g-vector """ - components = self.homogeneous_components() - if len(components) != 1: + if not self.is_homogeneous(): raise ValueError("this element does not have a well defined g-vector") - k, = components - return k + return self._g_vector def F_polynomial(self): r""" @@ -541,7 +539,7 @@ def is_homogeneous(self): sage: x.is_homogeneous() False """ - return len(self.homogeneous_components()) == 1 + return getattr(self, '_is_homogeneous', len(self.homogeneous_components()) == 1) def homogeneous_components(self): r""" @@ -570,6 +568,12 @@ def homogeneous_components(self): components[g_vect] += self.parent().retract(x.monomial_coefficient(m) * m) else: components[g_vect] = self.parent().retract(x.monomial_coefficient(m) * m) + for g_vect in components: + components[g_vect]._is_homogeneous = True + components[g_vect]._g_vector = g_vect + self._is_homogeneous = (len(components) == 1) + if self._is_homogeneous: + self._g_vector = list(components.keys())[0] return components def theta_basis_decomposition(self): @@ -584,57 +588,35 @@ def theta_basis_decomposition(self): EXAMPLES:: sage: A = ClusterAlgebra(matrix([[0,-2],[3,0]]), principal_coefficients=True) sage: f = (A.theta_basis_element((1,0)) + A.theta_basis_element((0,1)))**2 + A.coefficient(1)* A.theta_basis_element((1,1)) - sage: f.theta_basis_decomposition() - {(1, 1): y1 + 2, (2, 0): 1, (0, 2): 1} - sage: sum(_[g] * A.theta_basis_element(g) for g in _) - f - 0 - """ - zero = self.parent()(0) - components = map(lambda x: x._homogeneous_theta_basis_decomposition(), self.homogeneous_components().values()) - out = {} - for cpt in components: - for g in cpt: - out[g] = out.get(g, zero) + cpt[g] - return out - - def _homogeneous_theta_basis_decomposition(self): - r""" - Return the decomposition of ``self`` in the theta basis. - - OUTPUT: - - A dictionary whose keys are the g-vectors and whose values are the coefficients - in the decoposition of ``self`` in the theta basis. - - WARNING: - - This method only works when ``self`` is homogeneous. - - EXAMPLES:: - sage: A = ClusterAlgebra(matrix([[0,-2],[3,0]]), principal_coefficients=True) + sage: decomposition = f.theta_basis_decomposition() + sage: sum(decomposition[g] * A.theta_basis_element(g) for g in decomposition) == f + True sage: f = A.theta_basis_element((4,-4))*A.theta_basis_element((1,-1)) - sage: f.theta_basis_decomposition() # indirect doctest - {(5, -5): 1, (3, -2): y0*y1, (1, -2): y0*y1^2} - sage: sum(_[g] * A.theta_basis_element(g) for g in _) - f - 0 + sage: decomposition = f.theta_basis_decomposition() + sage: sum(decomposition[g] * A.theta_basis_element(g) for g in decomposition) == f + True """ - f_poly = self.F_polynomial() - g_vect = vector(self.g_vector()) - A = self.parent() B = A.b_matrix() - n = A.rank() - U = f_poly.parent() - out = {} - - while f_poly != U(0): - y_exp = min(f_poly.dict()) - coeff = f_poly.dict()[y_exp] - g_theta = tuple(g_vect + B*vector(y_exp)) - out[g_theta] = A({(0,)*n + tuple(y_exp):coeff}) - f_poly -= U({y_exp:coeff}) * A.theta_basis_F_polynomial(g_theta) - return out + U = A._U + out = dict() + zero_A = A(0) + zero_U = U(0) + zero_t = (0,)*A.rank() + components = self.homogeneous_components() + + for g_vect in components: + f_poly = components[g_vect].F_polynomial() + g_vect = vector(g_vect) + while f_poly != zero_U: + y_exp = min(f_poly.dict()) + coeff = f_poly.dict()[y_exp] + g_theta = tuple(g_vect + B*vector(y_exp)) + out[g_theta] = out.get(g_theta, zero_A) + A({zero_t + tuple(y_exp):coeff}) + f_poly -= U({y_exp:coeff}) * A.theta_basis_F_polynomial(g_theta) + + return out ############################################################################## # Seeds From 173e30a71b83b8100dac6fbe6e6ce495517de8b7 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 29 Jan 2021 17:24:06 +0100 Subject: [PATCH 087/232] Blank spaces --- src/sage/algebras/cluster_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index f98efecc033..ee7264fadc1 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -570,7 +570,7 @@ def homogeneous_components(self): components[g_vect] = self.parent().retract(x.monomial_coefficient(m) * m) for g_vect in components: components[g_vect]._is_homogeneous = True - components[g_vect]._g_vector = g_vect + components[g_vect]._g_vector = g_vect self._is_homogeneous = (len(components) == 1) if self._is_homogeneous: self._g_vector = list(components.keys())[0] @@ -605,7 +605,7 @@ def theta_basis_decomposition(self): zero_t = (0,)*A.rank() components = self.homogeneous_components() - + for g_vect in components: f_poly = components[g_vect].F_polynomial() g_vect = vector(g_vect) From 2a786e587839155ede8302f4e3125159bf1d700c Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 10 Feb 2021 17:26:53 +0100 Subject: [PATCH 088/232] Thetha basis elements do *not* satisfy the separation of additions formula --- src/sage/algebras/cluster_algebra.py | 31 ++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index ee7264fadc1..b7125505bff 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -2593,6 +2593,23 @@ def theta_basis_element(self, g_vector): ... NotImplementedError: Currently only implemented for cluster algebras of rank 2. + .. NOTE:: + + Elements of the theta basis correspond with the associated cluster + monomial only for appropriate coefficient choices. For example:: + + sage: A = ClusterAlgebra(matrix([[0,-1],[1,0],[-1,0]])) + sage: A.theta_basis_element((-1,0)) + (x1 + y0)/(x0*y0) + + while:: + + sage: _ = A.find_g_vector((-1,0)); + sage: A.cluster_variable((-1,0)) + (x1 + y0)/x0 + + In particular theta basis elements do not satisfy a separation of additions formula. + .. WARNING:: Currently only cluster algebras of rank 2 are supported @@ -2602,11 +2619,11 @@ def theta_basis_element(self, g_vector): :meth:`sage.algebras.cluster_algebra.theta_basis_F_polynomial` """ g_vector = tuple(g_vector) - F = self.theta_basis_F_polynomial(g_vector) - F_std = F.subs(self._yhat) + F = self.theta_basis_F_polynomial(g_vector).subs(self._yhat) g_mon = prod(self.ambient().gen(i) ** g_vector[i] for i in range(self.rank())) - F_trop = self.ambient()(F.subs(self._y))._fraction_pair()[1] - return self.retract(g_mon * F_std * F_trop) + # we only return the monomal g_mon times the evaluated F-polynomial because this is how + # theta basis elements behave. + return self.retract(g_mon * F) @cached_method(key=lambda a, b: tuple(b)) def theta_basis_F_polynomial(self, g_vector): @@ -2617,6 +2634,12 @@ def theta_basis_F_polynomial(self, g_vector): - ``g_vector`` -- tuple; the g-vector of the F-polynomial to compute + .. WARNING:: + + Elements of the theta basis do not satisfy a separation of additions formula. + See the implementation of :meth:`sage.algebras.cluster_algebra.theta_basis_F_polynomial` + for further details. + ALGORITHM: This method uses the fact that the greedy basis and the theta basis From 5b225961f67c6a01b238e657549cf6a9e7e11d05 Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Mon, 5 Apr 2021 10:50:31 +0200 Subject: [PATCH 089/232] Add method vector() to DifferentiableManifold --- src/sage/manifolds/differentiable/manifold.py | 109 ++++++++++++++++++ .../differentiable/tangent_vector.py | 20 ++++ 2 files changed, 129 insertions(+) diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index 61d94a414f8..461b0730011 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -4108,3 +4108,112 @@ class :class:`~sage.manifolds.differentiable.diff_map.DiffMap` else: signat = 2 - dim return vmodule.metric(name, signature=signat, latex_name=latex_name) + + def vector(self, *args, **kwargs): + r""" + Define a tangent vector at a point of ``self``. + + INPUT: + + - ``point`` -- :class:`~sage.manifolds.point.ManifoldPoint`; + point `p` on ``self`` + - ``comp`` -- components of the vector with respect to the basis + specified by the argument ``basis``, either as an iterable or as a + sequence of `n` components, `n` being the dimension of ``self`` (see + examples below) + - ``basis`` -- (default: ``None``) + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis`; + basis of the tangent space at `p` with respect to which the + components are defined; if ``None``, the default basis of the tangent + space is used + - ``name`` -- (default: ``None``) string; symbol given to the vector + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote + the vector; if ``None``, ``name`` will be used + + OUTPUT: + + - :class:`~sage.manifolds.differentiable.tangent_vector.TangentVector` + representing the tangent vector at point `p` + + + EXAMPLES: + + Vector at some point `p` of the Euclidean plane:: + + sage: E.= EuclideanSpace() + sage: p = E((1, 2), name='p') + sage: v = E.vector(p, -1, 3, name='v'); v + Vector v at Point p on the Euclidean plane E^2 + sage: v.display() + v = -e_x + 3 e_y + sage: v.parent() + Tangent space at Point p on the Euclidean plane E^2 + sage: v in E.tangent_space(p) + True + + The components can be passed as a tuple or a list:: + + sage: v1 = E.vector(p, (-1, 3)); v1 + Vector at Point p on the Euclidean plane E^2 + sage: v1 == v + True + + or as a ``vector`` object:: + + sage: v2 = E.vector(p, vector([-1, 3])); v2 + Vector at Point p on the Euclidean plane E^2 + sage: v2 == v + True + + Example of use with the options ``basis`` and ``latex_name``:: + + sage: polar_basis = E.polar_frame().at(p) + sage: polar_basis + Basis (e_r,e_ph) on the Tangent space at Point p on the Euclidean plane E^2 + sage: v = E.vector(p, 2, -1, basis=polar_basis, name='v', + ....: latex_name=r'\vec{v}') + sage: v + Vector v at Point p on the Euclidean plane E^2 + sage: v.display(polar_basis) + v = 2 e_r - e_ph + sage: v.display() + v = 4/5*sqrt(5) e_x + 3/5*sqrt(5) e_y + sage: latex(v) + \vec{v} + + TESTS:: + + sage: E.vector(-1, 3) + Traceback (most recent call last): + ... + TypeError: -1 is not a manifold point + sage: E.vector([-1, 3]) + Traceback (most recent call last): + ... + TypeError: a point and a set of components must be provided + sage: E.vector(p, 4, 2, 1) + Traceback (most recent call last): + ... + ValueError: 2 components must be provided + + + """ + basis = kwargs.pop('basis', None) + name = kwargs.pop('name', None) + latex_name = kwargs.pop('latex_name', None) + if len(args) < 2: + raise TypeError("a point and a set of components must be provided") + point = args[0] + tspace = self.tangent_space(point) # checks on point are performed here + comp0 = args[1] + if hasattr(comp0, '__len__') and hasattr(comp0, '__getitem__'): + # comp0 is a list/vector of components + comp = comp0 + else: + # the components are provided as args[1], args[2], ..., args[dim] + dim = self._dim + if len(args) != dim + 1: + raise ValueError("{} components must be provided".format(dim)) + comp = args[1:dim + 1] + return tspace._element_constructor_(comp=comp, basis=basis, name=name, + latex_name=latex_name) diff --git a/src/sage/manifolds/differentiable/tangent_vector.py b/src/sage/manifolds/differentiable/tangent_vector.py index a58c832eb4b..a1ac0262537 100644 --- a/src/sage/manifolds/differentiable/tangent_vector.py +++ b/src/sage/manifolds/differentiable/tangent_vector.py @@ -62,6 +62,26 @@ class TangentVector(FiniteRankFreeModuleElement): sage: v in Tp True + Tangent vectors can also be constructed via the manifold method + :meth:`~sage.manifolds.differentiable.manifold.DifferentiableManifold.vector`:: + + sage: v = M.vector(p, (-2, 1), name='v'); v + Tangent vector v at Point p on the 2-dimensional differentiable + manifold M + sage: v.display() + v = -2 d/dx + d/dy + + or via the method + :meth:`~sage.manifolds.differentiable.tensorfield_paral.TensorFieldParal.at` + of vector fields:: + + sage: vf = M.vector_field(x - 4*y/3, (x-y)^2, name='v') + sage: v = vf.at(p); v + Tangent vector v at Point p on the 2-dimensional differentiable + manifold M + sage: v.display() + v = -2 d/dx + d/dy + By definition, a tangent vector at `p\in M` is a *derivation at* `p` on the space `C^\infty(M)` of smooth scalar fields on `M`. Indeed let us consider a generic scalar field `f`:: From 59a2633341e84152ee1cd9a54ad76b8b6b10f4c7 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 5 Apr 2021 19:54:39 +0200 Subject: [PATCH 090/232] Remove white spaces --- src/sage/algebras/cluster_algebra.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index b7125505bff..928cf5696b3 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -2594,14 +2594,14 @@ def theta_basis_element(self, g_vector): NotImplementedError: Currently only implemented for cluster algebras of rank 2. .. NOTE:: - + Elements of the theta basis correspond with the associated cluster monomial only for appropriate coefficient choices. For example:: sage: A = ClusterAlgebra(matrix([[0,-1],[1,0],[-1,0]])) sage: A.theta_basis_element((-1,0)) (x1 + y0)/(x0*y0) - + while:: sage: _ = A.find_g_vector((-1,0)); @@ -2621,7 +2621,7 @@ def theta_basis_element(self, g_vector): g_vector = tuple(g_vector) F = self.theta_basis_F_polynomial(g_vector).subs(self._yhat) g_mon = prod(self.ambient().gen(i) ** g_vector[i] for i in range(self.rank())) - # we only return the monomal g_mon times the evaluated F-polynomial because this is how + # we only return the monomal g_mon times the evaluated F-polynomial because this is how # theta basis elements behave. return self.retract(g_mon * F) @@ -2637,7 +2637,7 @@ def theta_basis_F_polynomial(self, g_vector): .. WARNING:: Elements of the theta basis do not satisfy a separation of additions formula. - See the implementation of :meth:`sage.algebras.cluster_algebra.theta_basis_F_polynomial` + See the implementation of :meth:`sage.algebras.cluster_algebra.theta_basis_F_polynomial` for further details. ALGORITHM: From e608be536c58a666bdde1515da85d7032a98105a Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 5 Apr 2021 21:04:16 +0200 Subject: [PATCH 091/232] cleanup --- src/sage/algebras/cluster_algebra.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 928cf5696b3..99dd4166cc3 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -583,9 +583,10 @@ def theta_basis_decomposition(self): OUTPUT: A dictionary whose keys are the g-vectors and whose values are the coefficients - in the decoposition of ``self`` in the theta basis. + in the decomposition of ``self`` in the theta basis. EXAMPLES:: + sage: A = ClusterAlgebra(matrix([[0,-2],[3,0]]), principal_coefficients=True) sage: f = (A.theta_basis_element((1,0)) + A.theta_basis_element((0,1)))**2 + A.coefficient(1)* A.theta_basis_element((1,1)) sage: decomposition = f.theta_basis_decomposition() @@ -1721,7 +1722,7 @@ def euler_matrix(self): ALGORITHM: - This method returns the matrix of the biliinear form defined in Equation (2.1) of [ReSt2020]_ . + This method returns the matrix of the bilinear form defined in Equation (2.1) of [ReSt2020]_ . EXAMPLES:: From c4a76e92ff90733bf484265c1d0421152db252c8 Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Mon, 5 Apr 2021 23:25:37 +0200 Subject: [PATCH 092/232] Use f-string in DifferentialManifold.vector --- src/sage/manifolds/differentiable/manifold.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index 461b0730011..93078c39105 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -4213,7 +4213,7 @@ def vector(self, *args, **kwargs): # the components are provided as args[1], args[2], ..., args[dim] dim = self._dim if len(args) != dim + 1: - raise ValueError("{} components must be provided".format(dim)) + raise ValueError(f"{dim} components must be provided") comp = args[1:dim + 1] return tspace._element_constructor_(comp=comp, basis=basis, name=name, latex_name=latex_name) From 2d8e44ee466b796196a1173285db2661e11943f3 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 6 Apr 2021 08:44:42 +0200 Subject: [PATCH 093/232] Remove leftorvers --- src/sage/algebras/cluster_algebra.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 99dd4166cc3..0b53cac387d 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1802,8 +1802,6 @@ def g_vector_to_d_vector(self, g): g += min(g[i],0)*E.column(i) return tuple(-g+dp) - return d - def g_vectors(self, mutating_F=True): r""" Return an iterator producing all the g-vectors of ``self``. From b5d7a8f800fb496de28a5e89cabac2250b124c6f Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Tue, 6 Apr 2021 11:42:14 +0200 Subject: [PATCH 094/232] Rename vector to tangent_vector and make vector an alias to it --- src/sage/manifolds/differentiable/manifold.py | 18 ++++++++++++------ .../manifolds/differentiable/tangent_vector.py | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index 93078c39105..fee73573774 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -4109,9 +4109,9 @@ class :class:`~sage.manifolds.differentiable.diff_map.DiffMap` signat = 2 - dim return vmodule.metric(name, signature=signat, latex_name=latex_name) - def vector(self, *args, **kwargs): + def tangent_vector(self, *args, **kwargs): r""" - Define a tangent vector at a point of ``self``. + Define a tangent vector at a given point of ``self``. INPUT: @@ -4138,11 +4138,11 @@ def vector(self, *args, **kwargs): EXAMPLES: - Vector at some point `p` of the Euclidean plane:: + Vector at a point `p` of the Euclidean plane:: sage: E.= EuclideanSpace() sage: p = E((1, 2), name='p') - sage: v = E.vector(p, -1, 3, name='v'); v + sage: v = E.tangent_vector(p, -1, 3, name='v'); v Vector v at Point p on the Euclidean plane E^2 sage: v.display() v = -e_x + 3 e_y @@ -4151,6 +4151,11 @@ def vector(self, *args, **kwargs): sage: v in E.tangent_space(p) True + An alias of ``tangent_vector`` is ``vector``:: + + sage: v = E.vector(p, -1, 3, name='v'); v + Vector v at Point p on the Euclidean plane E^2 + The components can be passed as a tuple or a list:: sage: v1 = E.vector(p, (-1, 3)); v1 @@ -4158,7 +4163,7 @@ def vector(self, *args, **kwargs): sage: v1 == v True - or as a ``vector`` object:: + or as an object created by the ``vector`` function:: sage: v2 = E.vector(p, vector([-1, 3])); v2 Vector at Point p on the Euclidean plane E^2 @@ -4196,7 +4201,6 @@ def vector(self, *args, **kwargs): ... ValueError: 2 components must be provided - """ basis = kwargs.pop('basis', None) name = kwargs.pop('name', None) @@ -4217,3 +4221,5 @@ def vector(self, *args, **kwargs): comp = args[1:dim + 1] return tspace._element_constructor_(comp=comp, basis=basis, name=name, latex_name=latex_name) + + vector = tangent_vector diff --git a/src/sage/manifolds/differentiable/tangent_vector.py b/src/sage/manifolds/differentiable/tangent_vector.py index a1ac0262537..75febeca837 100644 --- a/src/sage/manifolds/differentiable/tangent_vector.py +++ b/src/sage/manifolds/differentiable/tangent_vector.py @@ -63,9 +63,9 @@ class TangentVector(FiniteRankFreeModuleElement): True Tangent vectors can also be constructed via the manifold method - :meth:`~sage.manifolds.differentiable.manifold.DifferentiableManifold.vector`:: + :meth:`~sage.manifolds.differentiable.manifold.DifferentiableManifold.tangent_vector`:: - sage: v = M.vector(p, (-2, 1), name='v'); v + sage: v = M.tangent_vector(p, (-2, 1), name='v'); v Tangent vector v at Point p on the 2-dimensional differentiable manifold M sage: v.display() From 8b1b7359526309c1d16ee77a7ebf418c64e40a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 6 Apr 2021 13:20:48 +0200 Subject: [PATCH 095/232] faster order complex for posets --- src/sage/combinat/posets/posets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 2d385834ff6..0a2ce11d568 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -6776,7 +6776,7 @@ def maximal_chains_iterator(self, partial=None): :meth:`antichains_iterator` """ - if partial is None or not partial: + if not partial: start = self.minimal_elements() partial = [] else: @@ -6874,7 +6874,7 @@ def order_complex(self, on_ints=False): else: facets.append([a.element for a in f]) - return SimplicialComplex(facets) + return SimplicialComplex(facets, maximality_check=False) def order_polytope(self): r""" From 51489c4857c114097f22cb14e54c47d599be7a93 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Thu, 8 Apr 2021 09:47:27 -0400 Subject: [PATCH 096/232] Trac #31614: drop old, unused _cholesky_decomposition_() method. --- src/sage/matrix/matrix2.pyx | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 885fe8572e3..d37e17c3f8e 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -12199,31 +12199,6 @@ cdef class Matrix(Matrix1): else: return subspace - def _cholesky_decomposition_(self): - r""" - Return the Cholesky decomposition of ``self``; see ``cholesky_decomposition``. - - This generic implementation uses a standard recursion. - """ - L = self.fetch('cholesky_broken') - if L is None: - A = self.__copy__() - L = A.parent()(0) - n = self.nrows() - for k in range(0, n-1 + 1): - try: - L[k, k] = A[k, k].sqrt() - except TypeError: - raise ValueError("The input matrix was not symmetric and positive definite") - - for s in range(k+1, n): - L[s, k] = A[s, k] / L[k, k] - for j in range(k+1, n): - for i in range(j, n): - A[i, j] -= L[i, k]*L[j, k].conjugate() - L.set_immutable() - self.cache('cholesky_broken', L) - return L def cholesky(self): r""" From 3f4a8ef719fecfb4d663e3fb563735cd2178f9c7 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Mon, 16 Dec 2019 17:36:11 +0000 Subject: [PATCH 097/232] Trac #28890: Pass --disable-static to autoconf packages by default. For libatomic_ops we keep the static lib--I think gc likes to link it statically if possible for performance reasons, but this should be double-checked. --- build/bin/sage-dist-helpers | 12 ++++++------ build/pkgs/cliquer/spkg-install.in | 2 +- build/pkgs/libatomic_ops/spkg-install.in | 2 +- src/doc/en/developer/packaging.rst | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build/bin/sage-dist-helpers b/build/bin/sage-dist-helpers index 95b5770b748..743b643ad93 100644 --- a/build/bin/sage-dist-helpers +++ b/build/bin/sage-dist-helpers @@ -39,10 +39,10 @@ # # - sdh_configure [...] # -# Runs `./configure --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib"`, -# (for autoconf'd projects with extra -# --disable-maintainer-mode --disable-dependency-tracking) -# Additional arguments to `./configure` may be given as arguments. +# Runs `./configure --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib"` +# --disable-static, (for autoconf'd projects with extra +# --disable-maintainer-mode --disable-dependency-tracking) Additional +# arguments to `./configure` may be given as arguments. # # - sdh_make [...] # @@ -160,9 +160,9 @@ sdh_configure() { if [ -z "$CONFIG_SHELL" ]; then export CONFIG_SHELL=`command -v bash` fi - ./configure --prefix="$SAGE_INST_LOCAL" --libdir="$SAGE_INST_LOCAL/lib" --disable-maintainer-mode --disable-dependency-tracking "$@" + ./configure --prefix="$SAGE_INST_LOCAL" --libdir="$SAGE_INST_LOCAL/lib" --disable-static --disable-maintainer-mode --disable-dependency-tracking "$@" if [ $? -ne 0 ]; then # perhaps it is a non-autoconf'd project - ./configure --prefix="$SAGE_INST_LOCAL" --libdir="$SAGE_INST_LOCAL/lib" "$@" + ./configure --prefix="$SAGE_INST_LOCAL" --libdir="$SAGE_INST_LOCAL/lib" --disable-static "$@" if [ $? -ne 0 ]; then if [ -f "$(pwd)/config.log" ]; then sdh_die <<_EOF_ diff --git a/build/pkgs/cliquer/spkg-install.in b/build/pkgs/cliquer/spkg-install.in index 3a9487dbd22..a863950189e 100644 --- a/build/pkgs/cliquer/spkg-install.in +++ b/build/pkgs/cliquer/spkg-install.in @@ -1,5 +1,5 @@ cd src -sdh_configure --disable-static +sdh_configure sdh_make sdh_make_install diff --git a/build/pkgs/libatomic_ops/spkg-install.in b/build/pkgs/libatomic_ops/spkg-install.in index 8bcf5eec8d7..4953f4b0c1a 100644 --- a/build/pkgs/libatomic_ops/spkg-install.in +++ b/build/pkgs/libatomic_ops/spkg-install.in @@ -9,6 +9,6 @@ if [ "$UNAME" = "CYGWIN" ]; then LIBATOMIC_OPS_CONFIGURE="$LIBATOMIC_OPS_CONFIGURE --enable-shared --disable-static" fi -sdh_configure $LIBATOMIC_OPS_CONFIGURE +sdh_configure --enable-static $LIBATOMIC_OPS_CONFIGURE sdh_make sdh_make_install diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index 0fa04587099..5032d95744c 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -385,9 +385,9 @@ begin with ``sdh_``, which stands for "Sage-distribution helper". - ``sdh_configure [...]``: Runs ``./configure`` with arguments ``--prefix="$SAGE_LOCAL"``, ``--libdir="$SAGE_LOCAL/lib"``, - ``--disable-maintainer-mode``, and - ``--disable-dependency-tracking``. Additional arguments to - ``./configure`` may be given as arguments. + ``--disable-static``, ``--disable-maintainer-mode``, and + ``--disable-dependency-tracking``. Additional arguments to ``./configure`` + may be given as arguments. - ``sdh_make [...]``: Runs ``$MAKE`` with the default target. Additional arguments to ``$MAKE`` may be given as arguments. From 8a8efee2f5a124d34ef9ff4d4a561e8ffa5416b8 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Mon, 30 Dec 2019 15:11:25 +0000 Subject: [PATCH 098/232] Trac #28890: Don't install PARI's static library. --- build/pkgs/pari/spkg-install.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/pari/spkg-install.in b/build/pkgs/pari/spkg-install.in index 2da3fa54e4e..ca122bbed17 100644 --- a/build/pkgs/pari/spkg-install.in +++ b/build/pkgs/pari/spkg-install.in @@ -143,4 +143,4 @@ sdh_make $PARI_MAKEFLAGS gp # install non-parallel (-j1) because of race conditions -sdh_make_install -j1 install-lib-sta +sdh_make_install -j1 From cd39c76bffd159da42e8c7aabaa297c78386b17e Mon Sep 17 00:00:00 2001 From: "E. Madison Bray" Date: Thu, 16 Jan 2020 15:12:33 +0100 Subject: [PATCH 099/232] Trac #28890: Don't use sdh_configure to configure zlib; it does not use an autoconf-generated configure script, and so may have minor incompatibilities. In particular, zlib does not support configuring to not install a static lib. --- build/pkgs/zlib/spkg-install.in | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build/pkgs/zlib/spkg-install.in b/build/pkgs/zlib/spkg-install.in index c78a77614b6..642f177cab0 100644 --- a/build/pkgs/zlib/spkg-install.in +++ b/build/pkgs/zlib/spkg-install.in @@ -12,7 +12,10 @@ if [ "$UNAME" = CYGWIN ]; then # We want to install shared objects sed -i 's/SHARED_MODE=0/SHARED_MODE=1/' Makefile else - sdh_configure --shared + # Trac #28890: zlib does not use a standard autoconf-generated configure + # script, so don't use the sdh_configure helper as it may have minor + # incompatibilities + ./configure --shared --prefix="$SAGE_LOCAL" --libdir="SAGE_LOCAL/lib" fi sdh_make From e301965d65a740879019563cb26cd52ec88320e7 Mon Sep 17 00:00:00 2001 From: Peter Bruin Date: Sat, 3 Apr 2021 11:46:06 +0200 Subject: [PATCH 100/232] Trac 31618: improve documentation of Brandt modules --- src/sage/modular/quatalg/brandt.py | 98 ++++++++++++++++-------------- 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/src/sage/modular/quatalg/brandt.py b/src/sage/modular/quatalg/brandt.py index 568f28e6649..6824ecf458e 100644 --- a/src/sage/modular/quatalg/brandt.py +++ b/src/sage/modular/quatalg/brandt.py @@ -2,82 +2,90 @@ Brandt Modules Introduction -============ +------------ -This tutorial outlines the construction of Brandt modules in Sage. The -importance of this construction is that it provides us with a method -to compute modular forms on `\Gamma_0(N)` as outlined in Pizer's paper -[Piz1980]_. In fact there exists a non-canonical Hecke algebra isomorphism -between the Brandt modules and a certain subspace of -`S_{2}(\Gamma_0(pM))` which contains all the newforms. +The construction of Brandt modules provides us with a method to +compute modular forms, as outlined in Pizer's paper [Piz1980]_. -The Brandt module is the free abelian group on right ideal classes of -a quaternion order together with a natural Hecke action determined by -Brandt matrices. +Given a prime number `p` and a positive integer `M` with `p\nmid M`, +the *Brandt module* `B(p, M)` is the free abelian group on right ideal +classes of a quaternion order of level `pM` in the quaternion algebra +ramified precisely at the places `p` and `\infty`. This Brandt module +carries a natural Hecke action given by Brandt matrices. There exists +a non-canonical Hecke algebra isomorphism between `B(p, M)` and a +certain subspace of `S_{2}(\Gamma_0(pM))` containing the newforms. Quaternion Algebras ------------------- A quaternion algebra over `\QQ` is a central simple algebra of -dimension 4 over `\QQ`. Such an algebra `A` is said to be -ramified at a place `v` of `\QQ` if and only if `A_v=A\otimes -\QQ_v` is a division algebra. Otherwise `A` is said to be split -at `v`. +dimension 4 over `\QQ`. Such an algebra `A` is said to be ramified at +a place `v` of `\QQ` if and only if `A \otimes \QQ_v` is a division +algebra. Otherwise `A` is said to be split at `v`. ``A = QuaternionAlgebra(p)`` returns the quaternion algebra `A` over `\QQ` ramified precisely at the places `p` and `\infty`. -``A = QuaternionAlgebra(k,a,b)`` returns a quaternion algebra with basis -`\{1,i,j,j\}` over `\mathbb{K}` such that `i^2=a`, `j^2=b` and `ij=k.` +``A = QuaternionAlgebra(a, b)`` returns the quaternion algebra `A` +over `\QQ` with basis `\{1, i, j, k\}` such that `i^2 = a`, `j^2 = b` +and `ij = -ji = k.` -An order `R` in a quaternion algebra is a 4-dimensional lattice on `A` -which is also a subring containing the identity. +An order `R` in a quaternion algebra `A` over `\QQ` is a 4-dimensional +lattice in `A` which is also a subring containing the identity. A +maximal order is one that is not properly contained in another order. + +To each order `\mathcal{O}` one attaches a positive integer called the +level of `\mathcal{O}`; see Definition 1.2 in [Piz1980]_. This is an +integer `N` such that every prime that ramifies in `A` divides `N` to +an odd power. An order is maximal if and only if its level equals the +discriminant of `A`. ``R = A.maximal_order()`` returns a maximal order `R` in the quaternion algebra `A.` -An Eichler order `\mathcal{O}` in a quaternion algebra is the -intersection of two maximal orders. The level of `\mathcal{O}` is its -index in any maximal order containing it. +A right `\mathcal{O}`-ideal `I` is a lattice in `A` such that for +every prime `p` there exists `a_p\in A_p^*` with `I_p = +a_p\mathcal{O}_p`. Two right `\mathcal{O}`-ideals `I` and `J` are said +to belong to the same class if `I=aJ` for some `a \in A^*`. Left +`\mathcal{O}`-ideals are defined in a similar fashion. -``O = A.order_of_level_N`` returns an Eichler order `\mathcal{O}` in `A` -of level `N` where `p` does not divide `N`. +The right order of `I` is the subring of `A` consisting of elements +`a` with `Ia \subseteq I`. +Brandt Modules +-------------- -A right `\mathcal{O}`-ideal `I` is a lattice on `A` such that -`I_p=a_p\mathcal{O}` (for some `a_p\in A_p^*`) for all `p<\infty`. Two -right `\mathcal{O}`-ideals `I` and `J` are said to belong to the same -class if `I=aJ` for some `a \in A^*`. (Left `\mathcal{O}`-ideals are -defined in a similar fashion.) +``B = BrandtModule(p, M=1)`` returns the Brandt module associated to +the prime number `p` and the integer `M`, with `p` not dividing `M`. -The right order of `I` is defined to be the set of elements in `A` -which fix `I` under right multiplication. +``A = B.quaternion_algebra()`` returns the quaternion algebra attached +to `B`; this is the quaternion algebra over `\QQ` ramified exactly at +`p` and `\infty`. -``right_order(R, basis)`` returns the right ideal of `I` in `R` given a -basis for the right ideal `I` contained in the maximal order `R.` +``O = B.order_of_level_N()`` returns an order `\mathcal{O}` of level +`N = pM` in `A`. -``ideal_classes(self)`` returns a tuple of all right ideal classes in self -which, for the purpose of constructing the Brandt module B(p,M), is -taken to be an Eichler order of level M. +``B.right_ideals()`` returns a tuple of representatives for all right +ideal classes of `\mathcal{O}`. The implementation of this method is especially interesting. It depends on the construction of a Hecke module defined as a free abelian group on right ideal classes of a quaternion algebra with the -following action +following action: .. MATH:: T_n[I] = \sum_{\phi} [J] where `(n,pM)=1` and the sum is over cyclic `\mathcal{O}`-module -homomorphisms `\phi :I\rightarrow J` of degree `n` up to isomorphism -of `J`. Equivalently one can sum over the inclusions of the submodules -`J \rightarrow n^{-1}I`. The rough idea is to start with the trivial -ideal class containing the order `\mathcal{O}` itself. Using the -method ``cyclic_submodules(self, I, p)`` one computes `T_p([\mathcal{O}])` -for some prime integer `p` not dividing the level of the order -`\mathcal{O}`. Apply this method repeatedly and test for equivalence -among resulting ideals. A theorem of Serre asserts that one gets a +homomorphisms `\phi\colon I\rightarrow J` of degree `n` up to +isomorphism of `J`. Equivalently one can sum over the inclusions of +the submodules `J \rightarrow n^{-1}I`. The rough idea is to start +with the trivial ideal class containing the order `\mathcal{O}` +itself. Using the method ``cyclic_submodules(self, I, q)`` one then +repeatedly computes `T_q([\mathcal{O}])` for some prime `q` not +dividing the level of `\mathcal{O}` and tests for equivalence among +the resulting ideals. A theorem of Serre asserts that one gets a complete set of ideal class representatives after a finite number of repetitions. @@ -828,7 +836,7 @@ def maximal_order(self): @cached_method def order_of_level_N(self): """ - Return Eichler order of level `N = p^{2 r + 1} M` in the + Return an order of level `N = p^{2 r + 1} M` in the quaternion algebra. EXAMPLES:: From c921e9738ac5d828eec7f8a0b0cbd60eba81a9f0 Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Fri, 9 Apr 2021 18:55:06 +0200 Subject: [PATCH 101/232] Simplify VectorField.__call__ --- .../vector_calculus/vector_calc_plane.rst | 3 +- src/sage/manifolds/differentiable/chart.py | 16 ++-- src/sage/manifolds/differentiable/manifold.py | 4 +- .../manifolds/differentiable/vectorfield.py | 96 ++++--------------- 4 files changed, 28 insertions(+), 91 deletions(-) diff --git a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_plane.rst b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_plane.rst index 19ee71e386e..a2458128e58 100644 --- a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_plane.rst +++ b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_plane.rst @@ -647,8 +647,7 @@ A vector field evaluated at a point $p$ is a vector in the tangent space sage: isinstance(Tp, FiniteRankFreeModule) True sage: sorted(Tp.bases(), key=str) - [Basis (d/dr,d/dph) on the Tangent space at Point p on the Euclidean plane E^2, - Basis (e_r,e_ph) on the Tangent space at Point p on the Euclidean plane E^2, + [Basis (e_r,e_ph) on the Tangent space at Point p on the Euclidean plane E^2, Basis (e_x,e_y) on the Tangent space at Point p on the Euclidean plane E^2] diff --git a/src/sage/manifolds/differentiable/chart.py b/src/sage/manifolds/differentiable/chart.py index 396792d3792..a74e685ba8c 100644 --- a/src/sage/manifolds/differentiable/chart.py +++ b/src/sage/manifolds/differentiable/chart.py @@ -439,17 +439,17 @@ def frame(self): sage: ey = c_xy.frame()[1] ; ey Vector field d/dy on the 2-dimensional differentiable manifold M sage: ex(M.scalar_field(x)).display() - M --> R - (x, y) |--> 1 + 1: M --> R + (x, y) |--> 1 sage: ex(M.scalar_field(y)).display() - M --> R - (x, y) |--> 0 + zero: M --> R + (x, y) |--> 0 sage: ey(M.scalar_field(x)).display() - M --> R - (x, y) |--> 0 + zero: M --> R + (x, y) |--> 0 sage: ey(M.scalar_field(y)).display() - M --> R - (x, y) |--> 1 + 1: M --> R + (x, y) |--> 1 """ return self._frame diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index 61d94a414f8..56242aa2aba 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -393,12 +393,12 @@ The vector field `v` acting on the scalar field `f`:: sage: v(f) - Scalar field v(f) on the 1-dimensional complex manifold C* + Scalar field zero on the 1-dimensional complex manifold C* Since `f` is constant, `v(f)` is vanishing:: sage: v(f).display() - v(f): C* --> C + zero: C* --> C on U: z |--> 0 on V: w |--> 0 diff --git a/src/sage/manifolds/differentiable/vectorfield.py b/src/sage/manifolds/differentiable/vectorfield.py index a69dae09087..94f998ce04c 100644 --- a/src/sage/manifolds/differentiable/vectorfield.py +++ b/src/sage/manifolds/differentiable/vectorfield.py @@ -346,33 +346,21 @@ def __call__(self, scalar): return scalar(self) if scalar._tensor_type != (0,0): raise TypeError("the argument must be a scalar field") - #!# Could it be simply - # return scalar.differential()(self) - # ? - dom_resu = self._domain.intersection(scalar._domain) - self_r = self.restrict(dom_resu) - scalar_r = scalar.restrict(dom_resu) - if scalar_r._is_zero: - return dom_resu._zero_scalar_field - if isinstance(self_r, VectorFieldParal): - return self_r(scalar_r) - # Creation of the result: - if self._name is not None and scalar._name is not None: - resu_name = "{}({})".format(self._name, scalar._name) - else: - resu_name = None - if self._latex_name is not None and scalar._latex_name is not None: - resu_latex = r"{}\left({}\right)".format(self._latex_name , - scalar._latex_name) - else: - resu_latex = None - resu = dom_resu.scalar_field(name=resu_name, latex_name=resu_latex) - for dom, rst in self_r._restrictions.items(): - resu_rst = rst(scalar_r.restrict(dom)) - for chart, funct in resu_rst._express.items(): - resu._express[chart] = funct + resu = scalar.differential()(self) + if not resu.is_immutable(): + if self._name is not None and scalar._name is not None: + name = f"{self._name}({scalar._name})" + else: + name = None + if self._latex_name is not None and scalar._latex_name is not None: + latex_name = r"{}\left({}\right)".format(self._latex_name, + scalar._latex_name) + else: + latex_name = None + resu.set_name(name=name, latex_name=latex_name) return resu + @options(max_range=8, scale=1, color='blue') def plot(self, chart=None, ambient_coords=None, mapping=None, chart_domain=None, fixed_coords=None, ranges=None, @@ -1746,57 +1734,7 @@ def __call__(self, scalar): (x, y) |--> 2*x^2*y - y^3 """ - from sage.manifolds.differentiable.vectorframe import CoordFrame - if scalar._tensor_type == (0,1): - # This is actually the action of the vector field on a 1-form, - # as a tensor field of type (1,0): - return scalar(self) - if scalar._tensor_type != (0,0): - raise TypeError("the argument must be a scalar field") - #!# Could it be simply - # return scalar.differential()(self) - # ? - dom_resu = self._domain.intersection(scalar._domain) - self_r = self.restrict(dom_resu) - scalar_r = scalar.restrict(dom_resu) - if scalar_r._is_zero: - return dom_resu._zero_scalar_field - # Creation of the result: - if self._name is not None and scalar._name is not None: - resu_name = self._name + "(" + scalar._name + ")" - else: - resu_name = None - if self._latex_name is not None and scalar._latex_name is not None: - resu_latex = (self._latex_name + r"\left(" + scalar._latex_name - + r"\right)") - else: - resu_latex = None - resu = dom_resu.scalar_field(name=resu_name, latex_name=resu_latex) - # Search for common charts for the computation: - common_charts = set() - for chart in scalar_r._express: - try: - self_r.comp(chart._frame) - common_charts.add(chart) - except ValueError: - pass - for frame in self_r._components: - if isinstance(frame, CoordFrame): - chart = frame._chart - try: - scalar_r.coord_function(chart) - common_charts.add(chart) - except ValueError: - pass - if not common_charts: - raise ValueError("no common chart found") - # The computation: - manif = scalar._manifold - for chart in common_charts: - v = self_r.comp(chart._frame) - f = scalar_r.coord_function(chart) - res = 0 - for i in manif.irange(): - res += v[i, chart] * f.diff(i) - resu._express[chart] = res - return resu + # This method enforces VectorField.__call__ + # instead of FiniteRankFreeModuleElement.__call__, which would have + # been inheritated otherwise + return VectorField.__call__(self, scalar) From 9622d83ba544358bf27a2054c83b3ccdfecee2ac Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 9 Apr 2021 10:30:52 -0700 Subject: [PATCH 102/232] build/pkgs/zlib/spkg-install.in: Fixup --- build/pkgs/zlib/spkg-install.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/zlib/spkg-install.in b/build/pkgs/zlib/spkg-install.in index 642f177cab0..e2a4fe38af8 100644 --- a/build/pkgs/zlib/spkg-install.in +++ b/build/pkgs/zlib/spkg-install.in @@ -15,7 +15,7 @@ else # Trac #28890: zlib does not use a standard autoconf-generated configure # script, so don't use the sdh_configure helper as it may have minor # incompatibilities - ./configure --shared --prefix="$SAGE_LOCAL" --libdir="SAGE_LOCAL/lib" + ./configure --shared --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib" fi sdh_make From 3d701f896fbe428d70a5e9f20efd66f9a91ddb6c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 9 Apr 2021 10:38:31 -0700 Subject: [PATCH 103/232] build/pkgs/zlib/spkg-install.in: Handle errors from configure --- build/pkgs/zlib/spkg-install.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/zlib/spkg-install.in b/build/pkgs/zlib/spkg-install.in index e2a4fe38af8..6b21192934b 100644 --- a/build/pkgs/zlib/spkg-install.in +++ b/build/pkgs/zlib/spkg-install.in @@ -15,7 +15,7 @@ else # Trac #28890: zlib does not use a standard autoconf-generated configure # script, so don't use the sdh_configure helper as it may have minor # incompatibilities - ./configure --shared --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib" + ./configure --shared --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib" || sdh_die "Error configuring $PKG_NAME" fi sdh_make From bc257815fcd42a6ba49eb0639ad1dc7e6a172f03 Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Fri, 9 Apr 2021 21:58:56 +0200 Subject: [PATCH 104/232] Combine raw string and f-string in VectorField.__call__ --- src/sage/manifolds/differentiable/vectorfield.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/manifolds/differentiable/vectorfield.py b/src/sage/manifolds/differentiable/vectorfield.py index 94f998ce04c..b9ac1ade6de 100644 --- a/src/sage/manifolds/differentiable/vectorfield.py +++ b/src/sage/manifolds/differentiable/vectorfield.py @@ -353,8 +353,7 @@ def __call__(self, scalar): else: name = None if self._latex_name is not None and scalar._latex_name is not None: - latex_name = r"{}\left({}\right)".format(self._latex_name, - scalar._latex_name) + latex_name = fr"{self._latex_name}\left({scalar._latex_name}\right)" else: latex_name = None resu.set_name(name=name, latex_name=latex_name) From 1d1f54f33e115d544674332ac23615d3f799dbb2 Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Sun, 11 Apr 2021 11:24:24 +0200 Subject: [PATCH 105/232] Update test, sympy can now solve this sum --- src/sage/calculus/calculus.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/sage/calculus/calculus.py b/src/sage/calculus/calculus.py index aa1d6db8388..eafc2d3138a 100644 --- a/src/sage/calculus/calculus.py +++ b/src/sage/calculus/calculus.py @@ -544,7 +544,7 @@ def symbolic_sum(expression, v, a, b, algorithm='maxima', hold=False): sage: assumptions() # check the assumptions were really forgotten [] - This summation only Mathematica can perform:: + A summation performed by Mathematica:: sage: symbolic_sum(1/(1+k^2), k, -oo, oo, algorithm = 'mathematica') # optional - mathematica pi*coth(pi) @@ -554,13 +554,10 @@ def symbolic_sum(expression, v, a, b, algorithm='maxima', hold=False): sage: symbolic_sum(1/(1+k^2), k, -oo, oo, algorithm = 'giac') (pi*e^(2*pi) - pi*e^(-2*pi))/(e^(2*pi) + e^(-2*pi) - 2) - SymPy can't solve that summation:: + The same summation is solved by SymPy:: sage: symbolic_sum(1/(1+k^2), k, -oo, oo, algorithm = 'sympy') - Traceback (most recent call last): - ... - AttributeError: Unable to convert SymPy result (=Sum(1/(k**2 + 1), - (k, -oo, oo))) into Sage + pi/tanh(pi) SymPy and Maxima 5.39.0 can do the following (see :trac:`22005`):: From f5ff6efdaecb220dccfc3600ea2b25044a1bc18f Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Sun, 11 Apr 2021 11:26:40 +0200 Subject: [PATCH 106/232] Update sympy to 1.8 --- build/pkgs/sympy/checksums.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/sympy/checksums.ini b/build/pkgs/sympy/checksums.ini index a1ca862ccdf..1cde11210f8 100644 --- a/build/pkgs/sympy/checksums.ini +++ b/build/pkgs/sympy/checksums.ini @@ -1,5 +1,5 @@ tarball=sympy-VERSION.tar.gz -sha1=a96e0827a8fd7325b4f0099bb92ba89ae88bace4 -md5=58764f19e5d8ee1acec6264456191699 -cksum=4154398619 +sha1=c52dd135f675cee79e46984b8454d9bb6b127edd +md5=37af34367e3f05692e6ddede95eccddb +cksum=4053721036 upstream_url=https://github.com/sympy/sympy/releases/download/sympy-VERSION/sympy-VERSION.tar.gz From 56178ab6ff9dca4cf50e995387affe27a3330eed Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 11 Apr 2021 09:54:54 -0700 Subject: [PATCH 107/232] build/bin/sage-dist-helpers (sdh_configure): On Cygwin, do not use --disable-static (until #30814 is done) --- build/bin/sage-dist-helpers | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/build/bin/sage-dist-helpers b/build/bin/sage-dist-helpers index 743b643ad93..bab23d0aee9 100644 --- a/build/bin/sage-dist-helpers +++ b/build/bin/sage-dist-helpers @@ -160,9 +160,16 @@ sdh_configure() { if [ -z "$CONFIG_SHELL" ]; then export CONFIG_SHELL=`command -v bash` fi - ./configure --prefix="$SAGE_INST_LOCAL" --libdir="$SAGE_INST_LOCAL/lib" --disable-static --disable-maintainer-mode --disable-dependency-tracking "$@" + if [ "$UNAME" == "CYGWIN" ]; then + # TODO: To use --disable-static for all packages on Cygwin, need + # #30814: Cygwin: Fix remaining packages to build shared libraries, using AM_LDFLAGS=-no-undefined + DISABLE_STATIC= + else + DISABLE_STATIC=--disable-static + fi + ./configure --prefix="$SAGE_INST_LOCAL" --libdir="$SAGE_INST_LOCAL/lib" $DISABLE_STATIC --disable-maintainer-mode --disable-dependency-tracking "$@" if [ $? -ne 0 ]; then # perhaps it is a non-autoconf'd project - ./configure --prefix="$SAGE_INST_LOCAL" --libdir="$SAGE_INST_LOCAL/lib" --disable-static "$@" + ./configure --prefix="$SAGE_INST_LOCAL" --libdir="$SAGE_INST_LOCAL/lib" $DISABLE_STATIC "$@" if [ $? -ne 0 ]; then if [ -f "$(pwd)/config.log" ]; then sdh_die <<_EOF_ From c8ccab680254f106ecaf1ce97393492b13813c23 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 11 Apr 2021 10:06:45 -0700 Subject: [PATCH 108/232] build/bin/sage-dist-helpers: Fixup: = instead of == --- build/bin/sage-dist-helpers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/bin/sage-dist-helpers b/build/bin/sage-dist-helpers index bab23d0aee9..d26beeb155c 100644 --- a/build/bin/sage-dist-helpers +++ b/build/bin/sage-dist-helpers @@ -160,7 +160,7 @@ sdh_configure() { if [ -z "$CONFIG_SHELL" ]; then export CONFIG_SHELL=`command -v bash` fi - if [ "$UNAME" == "CYGWIN" ]; then + if [ "$UNAME" = "CYGWIN" ]; then # TODO: To use --disable-static for all packages on Cygwin, need # #30814: Cygwin: Fix remaining packages to build shared libraries, using AM_LDFLAGS=-no-undefined DISABLE_STATIC= From dad63d0b25201d6f2c7a1a0cf93763feadc3b9aa Mon Sep 17 00:00:00 2001 From: Vincent Delecroix <20100.delecroix@gmail.com> Date: Sun, 11 Apr 2021 21:07:15 +0200 Subject: [PATCH 109/232] 31650: improvements to lazy_import --- src/sage/misc/lazy_import.pyx | 184 +++++++++++++--------------------- 1 file changed, 68 insertions(+), 116 deletions(-) diff --git a/src/sage/misc/lazy_import.pyx b/src/sage/misc/lazy_import.pyx index 336567e22ca..c98e1dd8922 100644 --- a/src/sage/misc/lazy_import.pyx +++ b/src/sage/misc/lazy_import.pyx @@ -9,7 +9,6 @@ use. EXAMPLES:: - sage: from sage.misc.lazy_import import lazy_import sage: lazy_import('sage.rings.all', 'ZZ') sage: type(ZZ) @@ -124,7 +123,6 @@ cpdef test_fake_startup(): EXAMPLES:: sage: sage.misc.lazy_import.test_fake_startup() - sage: from sage.misc.lazy_import import lazy_import sage: lazy_import('sage.rings.all', 'ZZ', 'my_ZZ') sage: my_ZZ(123) Resolving lazy import ZZ during startup @@ -166,11 +164,12 @@ cdef class LazyImport(object): EXAMPLES:: sage: from sage.misc.lazy_import import LazyImport - sage: my_isprime = LazyImport('sage.all', 'is_prime') - sage: my_isprime(5) - True - sage: my_isprime(55) - False + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: type(lazy_ZZ) + + sage: assert lazy_ZZ._get_object() is ZZ + sage: type(lazy_ZZ) + """ self._object = None self._module = module @@ -219,12 +218,14 @@ cdef class LazyImport(object): print(f"Resolving lazy import {self._name} during startup") elif self._at_startup and not startup_guard: print(f"Option ``at_startup=True`` for lazy import {self._name} not needed anymore") + try: self._object = getattr(__import__(self._module, {}, {}, [self._name]), self._name) except ImportError as e: if self._feature: raise FeatureNotPresentError(self._feature, reason=f'Importing {self._name} failed: {e}') raise + name = self._as_name if self._deprecation is not None: from sage.misc.superseded import deprecation @@ -338,9 +339,8 @@ cdef class LazyImport(object): EXAMPLES:: sage: from sage.misc.lazy_import import LazyImport - sage: my_ZZ = LazyImport('sage.rings.all', 'ZZ') - sage: dir(my_ZZ) == dir(ZZ) - True + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: assert dir(lazy_ZZ) == dir(ZZ) """ return dir(self.get_object()) @@ -352,10 +352,8 @@ cdef class LazyImport(object): sage: from sage.misc.lazy_import import LazyImport sage: my_isprime = LazyImport('sage.all', 'is_prime') - sage: my_isprime(12) - False - sage: my_isprime(13) - True + sage: assert is_prime(12) == my_isprime(12) + sage: assert is_prime(13) == my_isprime(13) """ return self.get_object()(*args, **kwds) @@ -363,13 +361,9 @@ cdef class LazyImport(object): """ TESTS:: - sage: lazy_import('sage.all', 'ZZ'); lazy_ZZ = ZZ - sage: type(lazy_ZZ) - - sage: lazy_ZZ - Integer Ring - sage: repr(lazy_ZZ) - 'Integer Ring' + sage: from sage.misc.lazy_import import LazyImport + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: assert repr(lazy_ZZ) == repr(ZZ) """ try: obj = self.get_object() @@ -381,9 +375,9 @@ cdef class LazyImport(object): """ TESTS:: - sage: lazy_import('sage.all', 'ZZ'); lazy_ZZ = ZZ - sage: str(lazy_ZZ) - 'Integer Ring' + sage: from sage.misc.lazy_import import LazyImport + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: assert str(lazy_ZZ) == str(ZZ) """ return str(self.get_object()) @@ -391,28 +385,29 @@ cdef class LazyImport(object): """ TESTS:: - sage: lazy_import('sage.all', 'ZZ'); lazy_ZZ = ZZ - sage: str(lazy_ZZ) - u'Integer Ring' + sage: from sage.misc.lazy_import import LazyImport + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: assert str(lazy_ZZ) == str(ZZ) """ return unicode(self.get_object()) - def __nonzero__(self): + def __bool__(self): """ TESTS:: - sage: lazy_import('sage.all', 'ZZ'); lazy_ZZ = ZZ - sage: not lazy_ZZ - True + sage: from sage.misc.lazy_import import LazyImport + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: assert bool(lazy_ZZ) == bool(ZZ) """ - return not self.get_object() + return bool(self.get_object()) def __hash__(self): """ TESTS:: - sage: lazy_import('sage.all', 'ZZ'); lazy_ZZ = ZZ - sage: hash(lazy_ZZ) == hash(1.parent()) + sage: from sage.misc.lazy_import import LazyImport + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: hash(lazy_ZZ) == hash(ZZ) True """ return hash(self.get_object()) @@ -421,11 +416,12 @@ cdef class LazyImport(object): """ TESTS:: - sage: lazy_import('sage.all', 'ZZ'); lazy_ZZ = ZZ + sage: from sage.misc.lazy_import import LazyImport + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: lazy_ZZ == ZZ + True sage: lazy_ZZ == RR False - sage: lazy_ZZ == 1.parent() - True """ return PyObject_RichCompare(obj(left), obj(right), op) @@ -529,8 +525,6 @@ cdef class LazyImport(object): sage: import sys sage: py_version = sys.version_info[0] sage: lazy_import('sys', 'version_info') - sage: type(version_info) - sage: version_info[0] == py_version True """ @@ -540,12 +534,13 @@ cdef class LazyImport(object): """ TESTS:: + sage: from sage.misc.lazy_import import LazyImport sage: sage.all.foo = list(range(10)) - sage: lazy_import('sage.all', 'foo') - sage: type(foo) - - sage: foo[1] = 100 - sage: print(foo) + sage: lazy_foo = LazyImport('sage.all', 'foo') + sage: lazy_foo[1] = 100 + sage: print(lazy_foo) + [0, 100, 2, 3, 4, 5, 6, 7, 8, 9] + sage: sage.all.foo [0, 100, 2, 3, 4, 5, 6, 7, 8, 9] """ self.get_object()[key] = value @@ -554,12 +549,13 @@ cdef class LazyImport(object): """ TESTS:: + sage: from sage.misc.lazy_import import LazyImport sage: sage.all.foo = list(range(10)) - sage: lazy_import('sage.all', 'foo') - sage: type(foo) - - sage: del foo[1] - sage: print(foo) + sage: lazy_foo = LazyImport('sage.all', 'foo') + sage: del lazy_foo[1] + sage: print(lazy_foo) + [0, 2, 3, 4, 5, 6, 7, 8, 9] + sage: print(sage.all.foo) [0, 2, 3, 4, 5, 6, 7, 8, 9] """ del self.get_object()[key] @@ -569,8 +565,6 @@ cdef class LazyImport(object): TESTS:: sage: lazy_import('sys', 'version_info') - sage: type(version_info) - sage: iter(version_info) <...iterator object at ...> """ @@ -583,14 +577,10 @@ cdef class LazyImport(object): sage: import sys sage: py_version = sys.version_info[0] sage: lazy_import('sys', 'version_info') - sage: type(version_info) - sage: py_version in version_info True sage: lazy_import('sys', 'version_info') - sage: type(version_info) - sage: 2000 not in version_info True """ @@ -602,8 +592,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo + 1 11 """ @@ -615,8 +603,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo - 1 9 """ @@ -628,8 +614,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo * 2 20 """ @@ -642,8 +626,6 @@ cdef class LazyImport(object): sage: from sympy import Matrix sage: sage.all.foo = Matrix([[1,1],[0,1]]) sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo.__matmul__(foo) Matrix([ [1, 2], @@ -657,8 +639,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo // 3 3 """ @@ -670,8 +650,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: operator.truediv(foo, 3) 10/3 """ @@ -683,8 +661,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo ** 2 100 """ @@ -696,8 +672,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo % 7 3 """ @@ -709,8 +683,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo << 3 80 """ @@ -722,8 +694,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo >> 2 2 """ @@ -735,8 +705,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo & 7 2 """ @@ -748,8 +716,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo | 7 15 """ @@ -761,8 +727,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo ^^ 7 13 """ @@ -774,8 +738,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: -foo -10 """ @@ -787,8 +749,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: +foo 10 """ @@ -800,8 +760,6 @@ cdef class LazyImport(object): sage: sage.all.foo = -1000 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: abs(foo) 1000 """ @@ -813,8 +771,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: ~foo 1/10 """ @@ -826,8 +782,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: complex(foo) (10+0j) """ @@ -839,8 +793,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: int(foo) 10 """ @@ -852,8 +804,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: float(foo) 10.0 """ @@ -865,8 +815,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: oct(foo) # py2 doctest:warning...: DeprecationWarning: use the method .oct instead @@ -883,8 +831,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: hex(foo) # py2 doctest:warning...: DeprecationWarning: use the method .hex instead @@ -901,8 +847,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: list(range(100))[foo] 10 """ @@ -914,14 +858,19 @@ cdef class LazyImport(object): TESTS:: - sage: sage.all.foo = 10 - sage: lazy_import('sage.all', 'foo') - sage: type(foo) - - sage: copy(foo) - 10 + + sage: from sage.misc.lazy_import import LazyImport + sage: sage.all.foo = [[1,2], 3] + sage: lazy_foo = LazyImport('sage.all', 'foo') + sage: a = copy(lazy_foo) + sage: a is sage.all.foo # copy + False + sage: a[0] is sage.all.foo[0] # copy but not deep + True + sage: assert type(lazy_foo) is LazyImport """ - return self.get_object() + import copy + return copy.copy(self.get_object()) def __deepcopy__(self, memo=None): """ @@ -929,14 +878,18 @@ cdef class LazyImport(object): TESTS:: - sage: sage.all.foo = 10 - sage: lazy_import('sage.all', 'foo') - sage: type(foo) - - sage: deepcopy(foo) - 10 + sage: from sage.misc.lazy_import import LazyImport + sage: sage.all.foo = [[1,2], 3] + sage: lazy_foo = LazyImport('sage.all', 'foo') + sage: a = deepcopy(lazy_foo) + sage: a is sage.all.foo # copy + False + sage: a[0] is sage.all.foo[0] # deep copy + False + sage: assert type(lazy_foo) is LazyImport """ - return self.get_object() + import copy + return copy.deepcopy(self.get_object()) def __instancecheck__(self, x): """ @@ -1000,7 +953,6 @@ def lazy_import(module, names, as_=None, *, EXAMPLES:: - sage: from sage.misc.lazy_import import lazy_import sage: lazy_import('sage.rings.all', 'ZZ') sage: type(ZZ) From 324419b60f662a24c255cf2aad07bf8027dc672f Mon Sep 17 00:00:00 2001 From: Vincent Delecroix <20100.delecroix@gmail.com> Date: Sun, 11 Apr 2021 21:09:29 +0200 Subject: [PATCH 110/232] 31650: resolve all lazy imports in a doctest --- src/sage/coding/all.py | 5 +---- src/sage/combinat/designs/all.py | 2 +- src/sage/libs/all.py | 9 +++++---- src/sage/misc/all.py | 3 ++- src/sage/tests/lazy_imports.py | 26 ++++++++++++++++++++++++++ 5 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 src/sage/tests/lazy_imports.py diff --git a/src/sage/coding/all.py b/src/sage/coding/all.py index b918bfda74a..89778395ff3 100644 --- a/src/sage/coding/all.py +++ b/src/sage/coding/all.py @@ -4,10 +4,7 @@ _lazy_import("sage.coding.code_constructions", ["permutation_action", "walsh_matrix"]) -_lazy_import("sage.coding.linear_code", [ - "LinearCode", - "LinearCodeFromVectorSpace", - "self_orthogonal_binary_codes"]) +_lazy_import("sage.coding.linear_code", "LinearCode") # Functions removed from the global namespace diff --git a/src/sage/combinat/designs/all.py b/src/sage/combinat/designs/all.py index 7ec907e7100..81210cf3b7f 100644 --- a/src/sage/combinat/designs/all.py +++ b/src/sage/combinat/designs/all.py @@ -3,9 +3,9 @@ """ from sage.misc.lazy_import import lazy_import -lazy_import('sage.combinat.designs.block_design', 'BlockDesign') lazy_import('sage.combinat.designs.incidence_structures', 'IncidenceStructure') +lazy_import('sage.combinat.designs.incidence_structures', 'IncidenceStructure', 'BlockDesign') lazy_import('sage.combinat.designs.incidence_structures', 'IncidenceStructure', as_='Hypergraph') diff --git a/src/sage/libs/all.py b/src/sage/libs/all.py index cb00bd33b4f..9fd69e148ff 100644 --- a/src/sage/libs/all.py +++ b/src/sage/libs/all.py @@ -8,9 +8,10 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.libs.gap.libgap', 'libgap') -lazy_import('sage.libs.eclib.all', ('mwrank_EllipticCurve', - 'mwrank_MordellWeil', 'mwrank_initprimes', 'CremonaModularSymbols')) -lazy_import('sage.libs.eclib.all', 'get_precision', 'mwrank_get_precision') -lazy_import('sage.libs.eclib.all', 'set_precision', 'mwrank_set_precision') +lazy_import('sage.libs.eclib.constructor', 'CremonaModularSymbols') +lazy_import('sage.libs.eclib.interface', ['mwrank_EllipticCurve', 'mwrank_MordellWeil']) +lazy_import('sage.libs.eclib.mwrank', 'get_precision', 'mwrank_get_precision') +lazy_import('sage.libs.eclib.mwrank', 'set_precision', 'mwrank_set_precision') +lazy_import('sage.libs.eclib.mwrank', 'initprimes', 'mwrank_initprimes') lazy_import('sage.libs.giac.giac', 'libgiac') diff --git a/src/sage/misc/all.py b/src/sage/misc/all.py index f8b17511ace..d108d4e5f74 100644 --- a/src/sage/misc/all.py +++ b/src/sage/misc/all.py @@ -62,7 +62,8 @@ lazy_import('sage.misc.sagedoc', ['browse_sage_doc', 'search_src', 'search_def', 'search_doc', 'tutorial', 'reference', 'manual', 'developer', - 'constructions', 'python_help', 'help']) + 'constructions', 'help']) +lazy_import('pydoc', 'help', 'python_help') from .classgraph import class_graph diff --git a/src/sage/tests/lazy_imports.py b/src/sage/tests/lazy_imports.py new file mode 100644 index 00000000000..83bbdfe7b50 --- /dev/null +++ b/src/sage/tests/lazy_imports.py @@ -0,0 +1,26 @@ +r""" +TESTS: + +Check that all non deprecated lazy imports resolve correctly. We avoid libgiac +on purpose because it does print stuff, see :trac:`31655`.:: + + sage: from sage.misc.lazy_import import LazyImport + sage: G = globals() + sage: for name, obj in sorted(G.items()): + ....: if name == 'libgiac': + ....: continue + ....: if type(obj) is LazyImport and obj._get_deprecation_ticket() == 0: + ....: try: + ....: _ = obj._get_object() + ....: except Exception as e: + ....: print('{} does not resolve: {}'.format(name, e)) + +Check that all deprecated lazy imports resolve correctly:: + + sage: import warnings + sage: for name, obj in sorted(G.items()): + ....: if type(obj) is LazyImport and obj._get_deprecation_ticket() != 0: + ....: with warnings.catch_warnings(record=True) as w: + ....: _ = obj._get_object() + ....: assert w[0].category == DeprecationWarning +""" From 879debd44cb5cc35db5d32b909755e481446a69f Mon Sep 17 00:00:00 2001 From: Vincent Delecroix <20100.delecroix@gmail.com> Date: Sun, 11 Apr 2021 21:42:26 +0200 Subject: [PATCH 111/232] 31650: also make all lazy imports also work in the console --- src/sage/interacts/library.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/sage/interacts/library.py b/src/sage/interacts/library.py index d174cc7d9ef..3ce3bb6b152 100644 --- a/src/sage/interacts/library.py +++ b/src/sage/interacts/library.py @@ -42,19 +42,8 @@ # It is important that this file is lazily imported for this to work from sage.repl.user_globals import get_global -# Get a bunch of functions from the user globals. In SageNB, this will -# refer to SageNB functions; in Jupyter, this will refer to Jupyter -# functions. In the command-line and for doctests, we import the -# SageNB functions as fall-back. -for name in ("interact", "checkbox", "input_box", "input_grid", - "range_slider", "selector", "slider", "text_control"): - try: - obj = get_global(name) - except NameError: - import sagenb.notebook.interact - obj = sagenb.notebook.interact.__dict__[name] - globals()[name] = obj - +from sage.repl.ipython_kernel.all_jupyter import (interact, checkbox, + input_box, input_grid, range_slider, selector, slider, text_control) def library_interact(f): """ From 27c2fcb3f19491570555506cbeca6aefbe3a40d2 Mon Sep 17 00:00:00 2001 From: Michael Jung Date: Sun, 11 Apr 2021 22:13:50 +0200 Subject: [PATCH 112/232] Trac #31658: pass names to branched copy method --- src/sage/manifolds/differentiable/tensorfield.py | 12 ++++++++++-- src/sage/manifolds/section.py | 13 ++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/sage/manifolds/differentiable/tensorfield.py b/src/sage/manifolds/differentiable/tensorfield.py index 52dff82b202..624f36ee9b7 100644 --- a/src/sage/manifolds/differentiable/tensorfield.py +++ b/src/sage/manifolds/differentiable/tensorfield.py @@ -2167,9 +2167,17 @@ def copy(self, name=None, latex_name=None): """ resu = self._new_instance() + # set resu name + if name is not None: + resu._name = name + if latex_name is None: + resu._latex_name = name + if latex_name is not None: + resu._latex_name = latex_name + # set restrictions for dom, rst in self._restrictions.items(): - resu._restrictions[dom] = rst.copy() - resu.set_name(name=name, latex_name=latex_name) + resu._restrictions[dom] = rst.copy(name=name, + latex_name=latex_name) resu._is_zero = self._is_zero return resu diff --git a/src/sage/manifolds/section.py b/src/sage/manifolds/section.py index 29ed3a88040..15446176824 100644 --- a/src/sage/manifolds/section.py +++ b/src/sage/manifolds/section.py @@ -1768,10 +1768,17 @@ def copy(self, name=None, latex_name=None): """ resu = self._new_instance() + # set resu name + if name is not None: + resu._name = name + if latex_name is None: + resu._latex_name = name + if latex_name is not None: + resu._latex_name = latex_name + # set restrictions for dom, rst in self._restrictions.items(): - resu._restrictions[dom] = rst.copy() - # Propagate names to all restrictions - resu.set_name(name=name, latex_name=latex_name) + resu._restrictions[dom] = rst.copy(name=name, + latex_name=latex_name) resu._is_zero = self._is_zero return resu From 3ea7920db5451a1d28d3f604a275c36602d2c1b3 Mon Sep 17 00:00:00 2001 From: Michael Jung Date: Sun, 11 Apr 2021 18:35:42 +0200 Subject: [PATCH 113/232] Trac #31654: add copy_from to scalarfields --- .../manifolds/differentiable/tensorfield.py | 4 +- src/sage/manifolds/scalarfield.py | 57 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/sage/manifolds/differentiable/tensorfield.py b/src/sage/manifolds/differentiable/tensorfield.py index 52dff82b202..5c7e03832af 100644 --- a/src/sage/manifolds/differentiable/tensorfield.py +++ b/src/sage/manifolds/differentiable/tensorfield.py @@ -2056,7 +2056,7 @@ def __setitem__(self, args, value): def copy_from(self, other): r""" - Make ``self`` to a copy from ``other``. + Make ``self`` to a copy of ``other``. INPUT: @@ -2092,7 +2092,7 @@ def copy_from(self, other): sage: s == t True - If the original tensor field is modified, the copy is not:: + While the original tensor field is modified, the copy is not:: sage: t[e_xy,0,0] = -1 sage: t.display(e_xy) diff --git a/src/sage/manifolds/scalarfield.py b/src/sage/manifolds/scalarfield.py index e741e67169a..81b520b4b09 100644 --- a/src/sage/manifolds/scalarfield.py +++ b/src/sage/manifolds/scalarfield.py @@ -1614,6 +1614,63 @@ def copy(self, name=None, latex_name=None): result._is_zero = self._is_zero return result + def copy_from(self, other): + r""" + Make ``self`` to a copy of ``other``. + + INPUT: + + - ``other`` -- other scalar field in the very same module from which + ``self`` should be a copy of + + .. NOTE:: + + While the derived quantities are not copied, the name is kept. + + .. WARNING:: + + All previous defined expressions and restrictions will be deleted! + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: c_xy. = M.chart() + sage: f = M.scalar_field(x*y^2, name='f') + sage: f.display() + f: M --> R + (x, y) |--> x*y^2 + sage: g = M.scalar_field(name='g') + sage: g.copy_from(f) + sage: g.display() + g: M --> R + (x, y) |--> x*y^2 + sage: f == g + True + + While the original scalar field is modified, the copy is not:: + + sage: f.set_expr(x-y) + sage: f.display() + f: M --> R + (x, y) |--> x - y + sage: g.display() + g: M --> R + (x, y) |--> x*y^2 + sage: f == g + False + + """ + if self.is_immutable(): + raise ValueError("the expressions of an immutable element " + "cannot be changed") + if other not in self.parent(): + raise TypeError("the original must be an element " + + "of {}".format(self.parent())) + self._del_derived() + for chart, funct in other._express.items(): + self._express[chart] = funct.copy() + self._is_zero = other._is_zero + def coord_function(self, chart=None, from_chart=None): r""" Return the function of the coordinates representing the scalar field From 30a90c2bf9c36564b44137def170e5db995f1521 Mon Sep 17 00:00:00 2001 From: Peter Bruin Date: Mon, 12 Apr 2021 08:55:46 +0200 Subject: [PATCH 114/232] Trac 31618: correct statement about levels --- src/sage/modular/quatalg/brandt.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/modular/quatalg/brandt.py b/src/sage/modular/quatalg/brandt.py index 6824ecf458e..272430231a1 100644 --- a/src/sage/modular/quatalg/brandt.py +++ b/src/sage/modular/quatalg/brandt.py @@ -34,11 +34,11 @@ lattice in `A` which is also a subring containing the identity. A maximal order is one that is not properly contained in another order. -To each order `\mathcal{O}` one attaches a positive integer called the -level of `\mathcal{O}`; see Definition 1.2 in [Piz1980]_. This is an -integer `N` such that every prime that ramifies in `A` divides `N` to -an odd power. An order is maximal if and only if its level equals the -discriminant of `A`. +A particularly important kind of orders are those that have a level; +see Definition 1.2 in [Piz1980]_. This is a positive integer `N` such +that every prime that ramifies in `A` divides `N` to an odd power. +The maximal orders are those that have level equal to the discriminant +of `A`. ``R = A.maximal_order()`` returns a maximal order `R` in the quaternion algebra `A.` From e15fbf3743e34666aff1b97dfc14d407e269688c Mon Sep 17 00:00:00 2001 From: Michael Jung Date: Mon, 12 Apr 2021 12:09:17 +0200 Subject: [PATCH 115/232] Trac #31654: docstring improvements --- src/sage/manifolds/differentiable/tensorfield.py | 9 ++++----- src/sage/manifolds/scalarfield.py | 9 ++++----- src/sage/manifolds/section.py | 9 ++++----- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/sage/manifolds/differentiable/tensorfield.py b/src/sage/manifolds/differentiable/tensorfield.py index 5c7e03832af..caf2e3b3156 100644 --- a/src/sage/manifolds/differentiable/tensorfield.py +++ b/src/sage/manifolds/differentiable/tensorfield.py @@ -2056,12 +2056,11 @@ def __setitem__(self, args, value): def copy_from(self, other): r""" - Make ``self`` to a copy of ``other``. + Make ``self`` a copy of ``other``. INPUT: - - ``other`` -- other tensor field in the very same module from which - ``self`` should be a copy of + - ``other`` -- other tensor field, in the same module as ``self`` .. NOTE:: @@ -2107,8 +2106,8 @@ def copy_from(self, other): raise ValueError("the components of an immutable element " "cannot be changed") if other not in self.parent(): - raise TypeError("the original must be an element " - + "of {}".format(self.parent())) + raise TypeError("the original must be an element of " + f"{self.parent()}") self._del_derived() self._del_restrictions() # delete restrictions name, latex_name = self._name, self._latex_name # keep names diff --git a/src/sage/manifolds/scalarfield.py b/src/sage/manifolds/scalarfield.py index 81b520b4b09..740336762bc 100644 --- a/src/sage/manifolds/scalarfield.py +++ b/src/sage/manifolds/scalarfield.py @@ -1616,12 +1616,11 @@ def copy(self, name=None, latex_name=None): def copy_from(self, other): r""" - Make ``self`` to a copy of ``other``. + Make ``self`` a copy of ``other``. INPUT: - - ``other`` -- other scalar field in the very same module from which - ``self`` should be a copy of + - ``other`` -- other scalar field, in the same module as ``self`` .. NOTE:: @@ -1664,8 +1663,8 @@ def copy_from(self, other): raise ValueError("the expressions of an immutable element " "cannot be changed") if other not in self.parent(): - raise TypeError("the original must be an element " - + "of {}".format(self.parent())) + raise TypeError("the original must be an element of " + f"{self.parent()}") self._del_derived() for chart, funct in other._express.items(): self._express[chart] = funct.copy() diff --git a/src/sage/manifolds/section.py b/src/sage/manifolds/section.py index 29ed3a88040..269185acac9 100644 --- a/src/sage/manifolds/section.py +++ b/src/sage/manifolds/section.py @@ -1646,12 +1646,11 @@ def __setitem__(self, args, value): def copy_from(self, other): r""" - Make ``self`` to a copy from ``other``. + Make ``self`` a copy of ``other``. INPUT: - - ``other`` -- other section in the very same module from which - ``self`` should be a copy of + - ``other`` -- other section, in the same module as ``self`` .. NOTE:: @@ -1702,8 +1701,8 @@ def copy_from(self, other): raise ValueError("the components of an immutable element " "cannot be changed") if other not in self.parent(): - raise TypeError("the original must be an element " - + "of {}".format(self.parent())) + raise TypeError("the original must be an element of " + f"{self.parent()}") self._del_derived() self._del_restrictions() # delete restrictions name, latex_name = self._name, self._latex_name # keep names From ffa35d9380ca0a717c1b90ed8b9e7fbe878f7dea Mon Sep 17 00:00:00 2001 From: Michael Jung Date: Mon, 12 Apr 2021 02:24:07 +0200 Subject: [PATCH 116/232] Trac #31658: same change for copy_from --- src/sage/manifolds/differentiable/tensorfield.py | 5 ++--- src/sage/manifolds/section.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/sage/manifolds/differentiable/tensorfield.py b/src/sage/manifolds/differentiable/tensorfield.py index 624f36ee9b7..04043cb1393 100644 --- a/src/sage/manifolds/differentiable/tensorfield.py +++ b/src/sage/manifolds/differentiable/tensorfield.py @@ -2111,10 +2111,9 @@ def copy_from(self, other): + "of {}".format(self.parent())) self._del_derived() self._del_restrictions() # delete restrictions - name, latex_name = self._name, self._latex_name # keep names for dom, rst in other._restrictions.items(): - self._restrictions[dom] = rst.copy() - self.set_name(name=name, latex_name=latex_name) + self._restrictions[dom] = rst.copy(name=self._name, + latex_name=self._latex_name) self._is_zero = other._is_zero def copy(self, name=None, latex_name=None): diff --git a/src/sage/manifolds/section.py b/src/sage/manifolds/section.py index 15446176824..b9f7ffd25f0 100644 --- a/src/sage/manifolds/section.py +++ b/src/sage/manifolds/section.py @@ -1706,10 +1706,9 @@ def copy_from(self, other): + "of {}".format(self.parent())) self._del_derived() self._del_restrictions() # delete restrictions - name, latex_name = self._name, self._latex_name # keep names for dom, rst in other._restrictions.items(): - self._restrictions[dom] = rst.copy() - self.set_name(name=name, latex_name=latex_name) + self._restrictions[dom] = rst.copy(name=self._name, + latex_name=self._latex_name) self._is_zero = other._is_zero def copy(self, name=None, latex_name=None): From fa864b36e15696450c36d54215b1e68183b29d25 Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Mon, 12 Apr 2021 18:06:52 +0200 Subject: [PATCH 117/232] Bump package version --- build/pkgs/sympy/package-version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/sympy/package-version.txt b/build/pkgs/sympy/package-version.txt index d3bdbdf1fda..6259340971b 100644 --- a/build/pkgs/sympy/package-version.txt +++ b/build/pkgs/sympy/package-version.txt @@ -1 +1 @@ -1.7 +1.8 From 1f6f2958ef44a3d9b00644d8b820851c33484aa0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 15 Apr 2021 13:33:25 -0700 Subject: [PATCH 118/232] TopologicalSubmanifold.open_subset: New --- src/sage/manifolds/topological_submanifold.py | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/sage/manifolds/topological_submanifold.py b/src/sage/manifolds/topological_submanifold.py index a5613578821..dfd585dab3c 100644 --- a/src/sage/manifolds/topological_submanifold.py +++ b/src/sage/manifolds/topological_submanifold.py @@ -254,6 +254,80 @@ def _repr_(self): return "{}-dimensional {} submanifold {} immersed in the {}".format( self._dim, self._structure.name, self._name, self._ambient) + def open_subset(self, name, latex_name=None, coord_def={}): + r""" + Create a subset of the current subset. + + INPUT: + + - ``name`` -- name given to the subset + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote + the subset; if none are provided, it is set to ``name`` + - ``is_open`` -- (default: ``False``) if ``True``, the created subset + is assumed to be open with respect to the manifold's topology + + OUTPUT: + + - the subset, as an instance of :class:`ManifoldSubset`, or + of the derived class + :class:`~sage.manifolds.manifold.topological_submanifold.TopologicalSubmanifold` + if ``is_open`` is ``True`` + + EXAMPLES:: + + sage: M = Manifold(3, 'M', structure="topological") + sage: N = Manifold(2, 'N', ambient=M, structure="topological"); N + 2-dimensional topological submanifold N immersed in the + 3-dimensional topological manifold M + sage: S = N.subset('S'); S + Subset S of the 2-dimensional topological submanifold N immersed in the 3-dimensional topological manifold M + sage: O = N.subset('O', is_open=True); O # indirect doctest + 2-dimensional topological submanifold O immersed in the + 3-dimensional topological manifold M + + sage: phi = N.continuous_map(M) + sage: N.set_embedding(phi) + sage: N + 2-dimensional topological submanifold N embedded in the + 3-dimensional topological manifold M + sage: S = N.subset('S'); S + Subset S of the 2-dimensional topological submanifold N embedded in the + 3-dimensional topological manifold M + sage: O = N.subset('O', is_open=True); O # indirect doctest + 2-dimensional topological submanifold O embedded in the + 3-dimensional topological manifold M + + """ + resu = TopologicalSubmanifold(self._dim, name, self._field, + self._structure, self._ambient, + base_manifold=self._manifold, + latex_name=latex_name, + start_index=self._sindex) + ## Copy of TopologicalManifold.open_subset. Refactor? + resu._calculus_method = self._calculus_method + resu._supersets.update(self._supersets) + for sd in self._supersets: + sd._subsets.add(resu) + self._top_subsets.add(resu) + # Charts on the result from the coordinate definition: + for chart, restrictions in coord_def.items(): + if chart not in self._atlas: + raise ValueError("the {} does not belong to ".format(chart) + + "the atlas of {}".format(self)) + chart.restrict(resu, restrictions) + # Transition maps on the result inferred from those of self: + for chart1 in coord_def: + for chart2 in coord_def: + if chart2 != chart1 and (chart1, chart2) in self._coord_changes: + self._coord_changes[(chart1, chart2)].restrict(resu) + ## Extras for Submanifold + if self._immersed: + resu.set_immersion(self._immersion.restrict(resu), + var=self._var, t_inverse=self._t_inverse) + if self._embedded: + resu.declare_embedding() + return resu + def set_immersion(self, phi, inverse=None, var=None, t_inverse=None): r""" From 58110f2ffda1a1736003fe3548586e372e673ba5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 15 Apr 2021 13:39:55 -0700 Subject: [PATCH 119/232] TopologicalSubmanifold.open_subset: Fix docstring --- src/sage/manifolds/topological_submanifold.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/sage/manifolds/topological_submanifold.py b/src/sage/manifolds/topological_submanifold.py index dfd585dab3c..c0fb5f682af 100644 --- a/src/sage/manifolds/topological_submanifold.py +++ b/src/sage/manifolds/topological_submanifold.py @@ -256,22 +256,31 @@ def _repr_(self): def open_subset(self, name, latex_name=None, coord_def={}): r""" - Create a subset of the current subset. + Create an open subset of the manifold. + + An open subset is a set that is (i) included in the manifold and (ii) + open with respect to the manifold's topology. It is a topological + manifold by itself. + + As ``self`` is a submanifold of its ambient manifold, + the new open subset is also considered a submanifold of that. + Hence the returned object is an instance of + :class:`TopologicalSubmanifold`. INPUT: - - ``name`` -- name given to the subset + - ``name`` -- name given to the open subset - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the subset; if none are provided, it is set to ``name`` - - ``is_open`` -- (default: ``False``) if ``True``, the created subset - is assumed to be open with respect to the manifold's topology + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts on the manifold and values the symbolic expressions formed + by the coordinates to define the subset OUTPUT: - - the subset, as an instance of :class:`ManifoldSubset`, or - of the derived class + - the open subset, as an instance of :class:`~sage.manifolds.manifold.topological_submanifold.TopologicalSubmanifold` - if ``is_open`` is ``True`` EXAMPLES:: From d815b63da8314abbb03cfc2afc83c40f61f21c78 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 15 Apr 2021 13:53:39 -0700 Subject: [PATCH 120/232] {DifferentiableSubmanifold,PseudoRiemannianSubmanifold}.open_subset: New --- .../differentiable_submanifold.py | 61 +++++++++++++++++ .../pseudo_riemannian_submanifold.py | 66 +++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/src/sage/manifolds/differentiable/differentiable_submanifold.py b/src/sage/manifolds/differentiable/differentiable_submanifold.py index cb21dc5dd39..1cfe0cd08ed 100644 --- a/src/sage/manifolds/differentiable/differentiable_submanifold.py +++ b/src/sage/manifolds/differentiable/differentiable_submanifold.py @@ -215,3 +215,64 @@ def _repr_(self): self._dim, self._structure.name, self._name, self._ambient) return "{}-dimensional {} submanifold {} immersed in the {}".format( self._dim, self._structure.name, self._name, self._ambient) + + def open_subset(self, name, latex_name=None, coord_def={}): + r""" + Create an open subset of the manifold. + + An open subset is a set that is (i) included in the manifold and (ii) + open with respect to the manifold's topology. It is a differentiable + manifold by itself. + + As ``self`` is a submanifold of its ambient manifold, + the new open subset is also considered a submanifold of that. + Hence the returned object is an instance of + :class:`DifferentiableSubmanifold`. + + INPUT: + + - ``name`` -- name given to the open subset + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + subset; if none is provided, it is set to ``name`` + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts in the manifold's atlas and values the symbolic expressions + formed by the coordinates to define the subset. + + OUTPUT: + + - the open subset, as an instance of :class:`DifferentiableSubmanifold` + + """ + resu = DifferentiableSubmanifold(self._dim, name, self._field, + self._structure, ambient=self._ambient, + base_manifold=self._manifold, + diff_degree=self._diff_degree, + latex_name=latex_name, + start_index=self._sindex) + ## Copy of DifferentiableManifold.open_subset. Refactor? + resu._calculus_method = self._calculus_method + resu._supersets.update(self._supersets) + for sd in self._supersets: + sd._subsets.add(resu) + self._top_subsets.add(resu) + # Charts on the result from the coordinate definition: + for chart, restrictions in coord_def.items(): + if chart not in self._atlas: + raise ValueError("the {} does not belong to ".format(chart) + + "the atlas of {}".format(self)) + chart.restrict(resu, restrictions) + # Transition maps on the result inferred from those of self: + for chart1 in coord_def: + for chart2 in coord_def: + if chart2 != chart1 and (chart1, chart2) in self._coord_changes: + self._coord_changes[(chart1, chart2)].restrict(resu) + #!# update vector frames and change of frames + # + ## Extras for Submanifold + if self._immersed: + resu.set_immersion(self._immersion.restrict(resu), + var=self._var, t_inverse=self._t_inverse) + if self._embedded: + resu.declare_embedding() + return resu diff --git a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py index df1746788ab..56ba9349cfa 100644 --- a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py +++ b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py @@ -390,6 +390,72 @@ def _repr_(self): return "{}-dimensional {} submanifold {} immersed in the {}".format( self._dim, self._structure.name, self._name, self._ambient) + def open_subset(self, name, latex_name=None, coord_def={}): + r""" + Create an open subset of ``self``. + + An open subset is a set that is (i) included in the manifold and (ii) + open with respect to the manifold's topology. It is a differentiable + manifold by itself. Moreover, equipped with the restriction of the + manifold metric to itself, it is a pseudo-Riemannian manifold. + + As ``self`` is a submanifold of its ambient manifold, + the new open subset is also considered a submanifold of that. + Hence the returned object is an instance of + :class:`PseudoRiemannianSubmanifold`. + + INPUT: + + - ``name`` -- name given to the open subset + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + subset; if none is provided, it is set to ``name`` + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts in the manifold's atlas and values the symbolic expressions + formed by the coordinates to define the subset. + + OUTPUT: + + - instance of :class:`PseudoRiemannianSubmanifold` representing the + created open subset + + """ + resu = PseudoRiemannianSubmanifold(self._dim, name, + ambient=self._ambient, + metric_name=self._metric_name, + signature=self._metric_signature, + base_manifold=self._manifold, + diff_degree=self._diff_degree, + latex_name=latex_name, + metric_latex_name=self._metric_latex_name, + start_index=self._sindex) + ## Copy of PseudoRiemannianManifold.open_subset. Refactor? + resu._calculus_method = self._calculus_method + resu._supersets.update(self._supersets) + for sd in self._supersets: + sd._subsets.add(resu) + self._top_subsets.add(resu) + # Charts on the result from the coordinate definition: + for chart, restrictions in coord_def.items(): + if chart not in self._atlas: + raise ValueError("the {} does not belong to ".format(chart) + + "the atlas of {}".format(self)) + chart.restrict(resu, restrictions) + # Transition maps on the result inferred from those of self: + for chart1 in coord_def: + for chart2 in coord_def: + if chart2 != chart1 and (chart1, chart2) in self._coord_changes: + self._coord_changes[(chart1, chart2)].restrict(resu) + #!# update non-coordinate vector frames and change of frames + # + ## Extras for Submanifold + if self._immersed: + resu.set_immersion(self._immersion.restrict(resu), + var=self._var, t_inverse=self._t_inverse) + if self._embedded: + resu.declare_embedding() + return resu + def ambient_metric(self): r""" Return the metric of the ambient manifold. From aac8835cc795dd04ef6ac14bd360d6a5945643d6 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 15 Apr 2021 14:11:14 -0700 Subject: [PATCH 121/232] {Topological,Differentiable,PseudoRiemannianSubmanifold}._repr_: Print subsets as subsets --- src/sage/manifolds/differentiable/differentiable_submanifold.py | 2 ++ .../manifolds/differentiable/pseudo_riemannian_submanifold.py | 2 ++ src/sage/manifolds/topological_submanifold.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/sage/manifolds/differentiable/differentiable_submanifold.py b/src/sage/manifolds/differentiable/differentiable_submanifold.py index 1cfe0cd08ed..91b9a6065dc 100644 --- a/src/sage/manifolds/differentiable/differentiable_submanifold.py +++ b/src/sage/manifolds/differentiable/differentiable_submanifold.py @@ -208,6 +208,8 @@ def _repr_(self): 3-dimensional differentiable manifold M """ + if self is not self._manifold: + return "Open subset {} of the {}".format(self._name, self._manifold) if self._ambient is None: return super(DifferentiableManifold, self).__repr__() if self._embedded: diff --git a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py index 56ba9349cfa..a6526829f9b 100644 --- a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py +++ b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py @@ -382,6 +382,8 @@ def _repr_(self): 3-dimensional Lorentzian manifold M """ + if self is not self._manifold: + return "Open subset {} of the {}".format(self._name, self._manifold) if self._ambient is None: return super(PseudoRiemannianManifold, self).__repr__() if self._embedded: diff --git a/src/sage/manifolds/topological_submanifold.py b/src/sage/manifolds/topological_submanifold.py index c0fb5f682af..094b68e41b6 100644 --- a/src/sage/manifolds/topological_submanifold.py +++ b/src/sage/manifolds/topological_submanifold.py @@ -246,6 +246,8 @@ def _repr_(self): 3-dimensional topological manifold M """ + if self is not self._manifold: + return "Open subset {} of the {}".format(self._name, self._manifold) if self._ambient is self: return super(TopologicalManifold, self).__repr__() if self._embedded: From 004c23a2a059909105224fec4b8a9b046dc4885a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 15 Apr 2021 14:21:46 -0700 Subject: [PATCH 122/232] Add/update examples --- .../differentiable_submanifold.py | 29 +++++++++++++++++++ .../pseudo_riemannian_submanifold.py | 29 +++++++++++++++++++ src/sage/manifolds/topological_submanifold.py | 15 ++++++---- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/sage/manifolds/differentiable/differentiable_submanifold.py b/src/sage/manifolds/differentiable/differentiable_submanifold.py index 91b9a6065dc..e06e6205774 100644 --- a/src/sage/manifolds/differentiable/differentiable_submanifold.py +++ b/src/sage/manifolds/differentiable/differentiable_submanifold.py @@ -245,6 +245,35 @@ def open_subset(self, name, latex_name=None, coord_def={}): - the open subset, as an instance of :class:`DifferentiableSubmanifold` + EXAMPLES:: + + sage: M = Manifold(3, 'M', structure="differentiable") + sage: N = Manifold(2, 'N', ambient=M, structure="differentiable"); N + 2-dimensional differentiable submanifold N immersed in the + 3-dimensional differentiable manifold M + sage: S = N.subset('S'); S + Subset S of the + 2-dimensional differentiable submanifold N immersed in the + 3-dimensional differentiable manifold M + sage: O = N.subset('O', is_open=True); O # indirect doctest + Open subset O of the + 2-dimensional differentiable submanifold N immersed in the + 3-dimensional differentiable manifold M + + sage: phi = N.diff_map(M) + sage: N.set_embedding(phi) + sage: N + 2-dimensional differentiable submanifold N embedded in the + 3-dimensional differentiable manifold M + sage: S = N.subset('S'); S + Subset S of the + 2-dimensional differentiable submanifold N embedded in the + 3-dimensional differentiable manifold M + sage: O = N.subset('O', is_open=True); O # indirect doctest + Open subset O of the + 2-dimensional differentiable submanifold N embedded in the + 3-dimensional differentiable manifold M + """ resu = DifferentiableSubmanifold(self._dim, name, self._field, self._structure, ambient=self._ambient, diff --git a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py index a6526829f9b..29eb5120dac 100644 --- a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py +++ b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py @@ -421,6 +421,35 @@ def open_subset(self, name, latex_name=None, coord_def={}): - instance of :class:`PseudoRiemannianSubmanifold` representing the created open subset + EXAMPLES:: + + sage: M = Manifold(3, 'M', structure="Riemannian") + sage: N = Manifold(2, 'N', ambient=M, structure="Riemannian"); N + 2-dimensional Riemannian submanifold N immersed in the + 3-dimensional Riemannian manifold M + sage: S = N.subset('S'); S + Subset S of the + 2-dimensional Riemannian submanifold N immersed in the + 3-dimensional Riemannian manifold M + sage: O = N.subset('O', is_open=True); O # indirect doctest + Open subset O of the + 2-dimensional Riemannian submanifold N immersed in the + 3-dimensional Riemannian manifold M + + sage: phi = N.diff_map(M) + sage: N.set_embedding(phi) + sage: N + 2-dimensional Riemannian submanifold N embedded in the + 3-dimensional Riemannian manifold M + sage: S = N.subset('S'); S + Subset S of the + 2-dimensional Riemannian submanifold N embedded in the + 3-dimensional Riemannian manifold M + sage: O = N.subset('O', is_open=True); O # indirect doctest + Open subset O of the + 2-dimensional Riemannian submanifold N embedded in the + 3-dimensional Riemannian manifold M + """ resu = PseudoRiemannianSubmanifold(self._dim, name, ambient=self._ambient, diff --git a/src/sage/manifolds/topological_submanifold.py b/src/sage/manifolds/topological_submanifold.py index 094b68e41b6..1c11f9308ce 100644 --- a/src/sage/manifolds/topological_submanifold.py +++ b/src/sage/manifolds/topological_submanifold.py @@ -293,8 +293,9 @@ def open_subset(self, name, latex_name=None, coord_def={}): sage: S = N.subset('S'); S Subset S of the 2-dimensional topological submanifold N immersed in the 3-dimensional topological manifold M sage: O = N.subset('O', is_open=True); O # indirect doctest - 2-dimensional topological submanifold O immersed in the - 3-dimensional topological manifold M + Open subset O of the + 2-dimensional topological submanifold N immersed in the + 3-dimensional topological manifold M sage: phi = N.continuous_map(M) sage: N.set_embedding(phi) @@ -302,11 +303,13 @@ def open_subset(self, name, latex_name=None, coord_def={}): 2-dimensional topological submanifold N embedded in the 3-dimensional topological manifold M sage: S = N.subset('S'); S - Subset S of the 2-dimensional topological submanifold N embedded in the - 3-dimensional topological manifold M + Subset S of the + 2-dimensional topological submanifold N embedded in the + 3-dimensional topological manifold M sage: O = N.subset('O', is_open=True); O # indirect doctest - 2-dimensional topological submanifold O embedded in the - 3-dimensional topological manifold M + Open subset O of the + 2-dimensional topological submanifold N embedded in the + 3-dimensional topological manifold M """ resu = TopologicalSubmanifold(self._dim, name, self._field, From ef1614aafe5cabdce11443809ca932125105253b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 15 Apr 2021 14:58:12 -0700 Subject: [PATCH 123/232] Refactor Manifold.open_subset methods through new method _init_open_subset --- .../differentiable/degenerate_submanifold.py | 2 +- .../differentiable_submanifold.py | 26 +------------------ src/sage/manifolds/differentiable/manifold.py | 22 ++++------------ .../differentiable/pseudo_riemannian.py | 19 +------------- .../pseudo_riemannian_submanifold.py | 26 +------------------ src/sage/manifolds/manifold.py | 5 +++- src/sage/manifolds/topological_submanifold.py | 23 ++++------------ 7 files changed, 18 insertions(+), 105 deletions(-) diff --git a/src/sage/manifolds/differentiable/degenerate_submanifold.py b/src/sage/manifolds/differentiable/degenerate_submanifold.py index a3d37fbece6..2001dbfdb9a 100644 --- a/src/sage/manifolds/differentiable/degenerate_submanifold.py +++ b/src/sage/manifolds/differentiable/degenerate_submanifold.py @@ -12,7 +12,7 @@ lightlike submanifold) and in General Relativity. In geometry of lightlike submanifolds, according to the dimension `r` of the radical distribution (see below for definition of radical distribution), degenerate submanifolds -have been classify into 4 subgroups: `r`-lightlike submanifolds, Coisotropic +have been classified into 4 subgroups: `r`-lightlike submanifolds, Coisotropic submanifolds, Isotropic submanifolds and Totally lightlike submanifolds. (See the book of Krishan L. Duggal and Aurel Bejancu [DS2010]_.) diff --git a/src/sage/manifolds/differentiable/differentiable_submanifold.py b/src/sage/manifolds/differentiable/differentiable_submanifold.py index e06e6205774..973551e6372 100644 --- a/src/sage/manifolds/differentiable/differentiable_submanifold.py +++ b/src/sage/manifolds/differentiable/differentiable_submanifold.py @@ -281,29 +281,5 @@ def open_subset(self, name, latex_name=None, coord_def={}): diff_degree=self._diff_degree, latex_name=latex_name, start_index=self._sindex) - ## Copy of DifferentiableManifold.open_subset. Refactor? - resu._calculus_method = self._calculus_method - resu._supersets.update(self._supersets) - for sd in self._supersets: - sd._subsets.add(resu) - self._top_subsets.add(resu) - # Charts on the result from the coordinate definition: - for chart, restrictions in coord_def.items(): - if chart not in self._atlas: - raise ValueError("the {} does not belong to ".format(chart) + - "the atlas of {}".format(self)) - chart.restrict(resu, restrictions) - # Transition maps on the result inferred from those of self: - for chart1 in coord_def: - for chart2 in coord_def: - if chart2 != chart1 and (chart1, chart2) in self._coord_changes: - self._coord_changes[(chart1, chart2)].restrict(resu) - #!# update vector frames and change of frames - # - ## Extras for Submanifold - if self._immersed: - resu.set_immersion(self._immersion.restrict(resu), - var=self._var, t_inverse=self._t_inverse) - if self._embedded: - resu.declare_embedding() + self._init_open_subset(resu, coord_def=coord_def) return resu diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index 61d94a414f8..5527ec0f9ff 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -826,25 +826,13 @@ def open_subset(self, name, latex_name=None, coord_def={}): diff_degree=self._diff_degree, latex_name=latex_name, start_index=self._sindex) - resu._calculus_method = self._calculus_method - resu._supersets.update(self._supersets) - for sd in self._supersets: - sd._subsets.add(resu) - self._top_subsets.add(resu) - # Charts on the result from the coordinate definition: - for chart, restrictions in coord_def.items(): - if chart not in self._atlas: - raise ValueError("the {} does not belong to ".format(chart) + - "the atlas of {}".format(self)) - chart.restrict(resu, restrictions) - # Transition maps on the result inferred from those of self: - for chart1 in coord_def: - for chart2 in coord_def: - if chart2 != chart1 and (chart1, chart2) in self._coord_changes: - self._coord_changes[(chart1, chart2)].restrict(resu) - #!# update vector frames and change of frames + self._init_open_subset(resu, coord_def=coord_def) return resu + def _init_open_subset(self, resu, coord_def): + super()._init_open_subset(resu, coord_def=coord_def) + #!# update vector frames and change of frames + def diff_map(self, codomain, coord_functions=None, chart1=None, chart2=None, name=None, latex_name=None): r""" diff --git a/src/sage/manifolds/differentiable/pseudo_riemannian.py b/src/sage/manifolds/differentiable/pseudo_riemannian.py index 1192bcee2ee..1625fec00b4 100644 --- a/src/sage/manifolds/differentiable/pseudo_riemannian.py +++ b/src/sage/manifolds/differentiable/pseudo_riemannian.py @@ -745,22 +745,5 @@ def open_subset(self, name, latex_name=None, coord_def={}): latex_name=latex_name, metric_latex_name=self._metric_latex_name, start_index=self._sindex) - resu._calculus_method = self._calculus_method - resu._supersets.update(self._supersets) - for sd in self._supersets: - sd._subsets.add(resu) - self._top_subsets.add(resu) - # Charts on the result from the coordinate definition: - for chart, restrictions in coord_def.items(): - if chart not in self._atlas: - raise ValueError("the {} does not belong to ".format(chart) + - "the atlas of {}".format(self)) - chart.restrict(resu, restrictions) - # Transition maps on the result inferred from those of self: - for chart1 in coord_def: - for chart2 in coord_def: - if chart2 != chart1 and (chart1, chart2) in self._coord_changes: - self._coord_changes[(chart1, chart2)].restrict(resu) - #!# update non-coordinate vector frames and change of frames - # + self._init_open_subset(resu, coord_def=coord_def) return resu diff --git a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py index 29eb5120dac..ae3e1d436d5 100644 --- a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py +++ b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py @@ -460,31 +460,7 @@ def open_subset(self, name, latex_name=None, coord_def={}): latex_name=latex_name, metric_latex_name=self._metric_latex_name, start_index=self._sindex) - ## Copy of PseudoRiemannianManifold.open_subset. Refactor? - resu._calculus_method = self._calculus_method - resu._supersets.update(self._supersets) - for sd in self._supersets: - sd._subsets.add(resu) - self._top_subsets.add(resu) - # Charts on the result from the coordinate definition: - for chart, restrictions in coord_def.items(): - if chart not in self._atlas: - raise ValueError("the {} does not belong to ".format(chart) + - "the atlas of {}".format(self)) - chart.restrict(resu, restrictions) - # Transition maps on the result inferred from those of self: - for chart1 in coord_def: - for chart2 in coord_def: - if chart2 != chart1 and (chart1, chart2) in self._coord_changes: - self._coord_changes[(chart1, chart2)].restrict(resu) - #!# update non-coordinate vector frames and change of frames - # - ## Extras for Submanifold - if self._immersed: - resu.set_immersion(self._immersion.restrict(resu), - var=self._var, t_inverse=self._t_inverse) - if self._embedded: - resu.declare_embedding() + self._init_open_subset(resu, coord_def=coord_def) return resu def ambient_metric(self): diff --git a/src/sage/manifolds/manifold.py b/src/sage/manifolds/manifold.py index 41fb5a18234..93eccb9386b 100644 --- a/src/sage/manifolds/manifold.py +++ b/src/sage/manifolds/manifold.py @@ -872,6 +872,10 @@ def open_subset(self, name, latex_name=None, coord_def={}): base_manifold=self._manifold, latex_name=latex_name, start_index=self._sindex) + self._init_open_subset(resu, coord_def=coord_def) + return resu + + def _init_open_subset(self, resu, coord_def): resu._calculus_method = self._calculus_method resu._supersets.update(self._supersets) for sd in self._supersets: @@ -888,7 +892,6 @@ def open_subset(self, name, latex_name=None, coord_def={}): for chart2 in coord_def: if chart2 != chart1 and (chart1, chart2) in self._coord_changes: self._coord_changes[(chart1, chart2)].restrict(resu) - return resu def get_chart(self, coordinates, domain=None): r""" diff --git a/src/sage/manifolds/topological_submanifold.py b/src/sage/manifolds/topological_submanifold.py index 1c11f9308ce..0841e408484 100644 --- a/src/sage/manifolds/topological_submanifold.py +++ b/src/sage/manifolds/topological_submanifold.py @@ -317,30 +317,17 @@ def open_subset(self, name, latex_name=None, coord_def={}): base_manifold=self._manifold, latex_name=latex_name, start_index=self._sindex) - ## Copy of TopologicalManifold.open_subset. Refactor? - resu._calculus_method = self._calculus_method - resu._supersets.update(self._supersets) - for sd in self._supersets: - sd._subsets.add(resu) - self._top_subsets.add(resu) - # Charts on the result from the coordinate definition: - for chart, restrictions in coord_def.items(): - if chart not in self._atlas: - raise ValueError("the {} does not belong to ".format(chart) + - "the atlas of {}".format(self)) - chart.restrict(resu, restrictions) - # Transition maps on the result inferred from those of self: - for chart1 in coord_def: - for chart2 in coord_def: - if chart2 != chart1 and (chart1, chart2) in self._coord_changes: - self._coord_changes[(chart1, chart2)].restrict(resu) + self._init_open_subset(resu, coord_def=coord_def) + return resu + + def _init_open_subset(self, resu, coord_def): + super()._init_open_subset(resu, coord_def=coord_def) ## Extras for Submanifold if self._immersed: resu.set_immersion(self._immersion.restrict(resu), var=self._var, t_inverse=self._t_inverse) if self._embedded: resu.declare_embedding() - return resu def set_immersion(self, phi, inverse=None, var=None, t_inverse=None): From 3e8203807cfee4f0951820b1f6bfc82a0561d33e Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Fri, 16 Apr 2021 15:13:53 +0200 Subject: [PATCH 124/232] 31676: initial version --- src/sage/libs/mpmath/ext_main.pyx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/sage/libs/mpmath/ext_main.pyx b/src/sage/libs/mpmath/ext_main.pyx index 694cc089e7c..5821be79073 100644 --- a/src/sage/libs/mpmath/ext_main.pyx +++ b/src/sage/libs/mpmath/ext_main.pyx @@ -2491,11 +2491,21 @@ cdef class mpc(mpnumber): """ Returns the hash value of self :: + EXAMPLES:: + sage: from mpmath import mp sage: hash(mp.mpc(2,3)) == hash(complex(2,3)) True + + TESTS: + + Check that :trac:`31676` is fixed:: + + sage: from mpmath import mpc + sage: hash(mpc(1, -1)) == hash(mpc(-1, -1)) # should not return OverflowError: Python int too large to convert to C ssize_t + False """ - return libmp.mpc_hash(self._mpc_) + return hash(libmp.mpc_hash(self._mpc_)) def __neg__(s): """ From 265b0e26d3db8fea35eec87b5ef9497b8f0ddfab Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 16 Apr 2021 08:42:09 -0700 Subject: [PATCH 125/232] TopologicalManifold._init_open_subset: Add docstring --- src/sage/manifolds/manifold.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/sage/manifolds/manifold.py b/src/sage/manifolds/manifold.py index 93eccb9386b..0b6808bf66d 100644 --- a/src/sage/manifolds/manifold.py +++ b/src/sage/manifolds/manifold.py @@ -876,6 +876,29 @@ def open_subset(self, name, latex_name=None, coord_def={}): return resu def _init_open_subset(self, resu, coord_def): + r""" + Initialize ``resu`` as an open subset of ``self``. + + INPUT: + + - ``resu`` -- an instance of ``:class:`TopologicalManifold` or + a subclass. + + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts on the manifold and values the symbolic expressions formed + by the coordinates to define the subset + + EXAMPLES:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: from sage.manifolds.manifold import TopologicalManifold + sage: U = TopologicalManifold(2, 'U', field=M._field, structure=M._structure, base_manifold=M) + sage: M._init_open_subset(U, coord_def={c_cart: x^2+y^2<1}) + sage: U + Open subset U of the 2-dimensional topological manifold R^2 + """ resu._calculus_method = self._calculus_method resu._supersets.update(self._supersets) for sd in self._supersets: From a93f7ed196b5a8ac98dadb0afed641c60a9827cf Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 16 Apr 2021 21:47:29 -0700 Subject: [PATCH 126/232] TopologicalSubmanifold._init_open_subset: Add docstring --- src/sage/manifolds/topological_submanifold.py | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/sage/manifolds/topological_submanifold.py b/src/sage/manifolds/topological_submanifold.py index 0841e408484..1b9ca7aebbd 100644 --- a/src/sage/manifolds/topological_submanifold.py +++ b/src/sage/manifolds/topological_submanifold.py @@ -291,7 +291,9 @@ def open_subset(self, name, latex_name=None, coord_def={}): 2-dimensional topological submanifold N immersed in the 3-dimensional topological manifold M sage: S = N.subset('S'); S - Subset S of the 2-dimensional topological submanifold N immersed in the 3-dimensional topological manifold M + Subset S of the + 2-dimensional topological submanifold N immersed in the + 3-dimensional topological manifold M sage: O = N.subset('O', is_open=True); O # indirect doctest Open subset O of the 2-dimensional topological submanifold N immersed in the @@ -321,6 +323,42 @@ def open_subset(self, name, latex_name=None, coord_def={}): return resu def _init_open_subset(self, resu, coord_def): + r""" + Initialize ``resu`` as an open subset of ``self``. + + INPUT: + + - ``resu`` -- an instance of ``:class:`TopologicalManifold` or + a subclass. + + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts on the manifold and values the symbolic expressions formed + by the coordinates to define the subset + + EXAMPLES: + + sage: M = Manifold(3, 'M', structure="topological") + sage: N = Manifold(2, 'N', ambient=M, structure="topological") + sage: phi = N.continuous_map(M) + sage: N.set_embedding(phi) + sage: N + 2-dimensional topological submanifold N embedded in the + 3-dimensional topological manifold M + sage: from sage.manifolds.topological_submanifold import TopologicalSubmanifold + sage: O = TopologicalSubmanifold(3, 'O', field=M._field, structure=M._structure, + ....: ambient=M, base_manifold=N) + sage: N._init_open_subset(O, {}) + sage: O + Open subset O of the + 2-dimensional topological submanifold N embedded in the + 3-dimensional topological manifold M + sage: O.embedding() + Continuous map + from the Open subset O of the 2-dimensional topological submanifold N + embedded in the 3-dimensional topological manifold M + to the 3-dimensional topological manifold M + """ super()._init_open_subset(resu, coord_def=coord_def) ## Extras for Submanifold if self._immersed: From 58b43f7902296384dade8a617441e205956d9824 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 16 Apr 2021 21:47:50 -0700 Subject: [PATCH 127/232] DifferentiableManifold._init_open_subset: Add docstring --- src/sage/manifolds/differentiable/manifold.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index 5527ec0f9ff..93f03723861 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -830,6 +830,29 @@ def open_subset(self, name, latex_name=None, coord_def={}): return resu def _init_open_subset(self, resu, coord_def): + r""" + Initialize ``resu`` as an open subset of ``self``. + + INPUT: + + - ``resu`` -- an instance of ``:class:`TopologicalManifold` or + a subclass. + + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts on the manifold and values the symbolic expressions formed + by the coordinates to define the subset + + EXAMPLES:: + + sage: M = Manifold(2, 'R^2', structure='differentiable') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: from sage.manifolds.differentiable.manifold import DifferentiableManifold + sage: U = DifferentiableManifold(2, 'U', field=M._field, structure=M._structure, base_manifold=M) + sage: M._init_open_subset(U, coord_def={c_cart: x^2+y^2<1}) + sage: U + Open subset U of the 2-dimensional differentiable manifold R^2 + """ super()._init_open_subset(resu, coord_def=coord_def) #!# update vector frames and change of frames From 280a82d6426dc847bf661170c83cccfb74dae1ee Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sun, 18 Apr 2021 11:36:26 +0200 Subject: [PATCH 128/232] trac #31681: fix sorting in layout_acyclic_dummy --- src/sage/graphs/digraph.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index eecd1bed77b..160795f5c4b 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -3317,12 +3317,28 @@ def layout_acyclic_dummy(self, heights=None, rankdir='up', **options): ... ValueError: `self` should be an acyclic graph + TESTS: + + :trac:`31681` is fixed:: + + sage: H = DiGraph({0: [1], 'X': [1]}, format='dict_of_lists') + sage: pos = H.layout_acyclic_dummy(rankdir='up') + sage: pos['X'][1] == 0 and pos[0][1] == 0 + True + sage: pos[1][1] == 1 + True """ if heights is None: if not self.is_directed_acyclic(): raise ValueError("`self` should be an acyclic graph") levels = self.level_sets() - levels = [sorted(z) for z in levels] + # Sort vertices in each level in best effort mode + for i in range(len(levels)): + try: + l = sorted(levels[i]) + levels[i] = l + except: + continue if rankdir=='down' or rankdir=='left': levels.reverse() heights = {i: levels[i] for i in range(len(levels))} From 90571936ea2089aebbb2d004387fd1d4d68a6cdd Mon Sep 17 00:00:00 2001 From: Martin Rejmon Date: Sun, 18 Apr 2021 15:52:19 +0200 Subject: [PATCH 129/232] Make the assumption in _language_naive clearer --- src/sage/combinat/words/morphism.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/combinat/words/morphism.py b/src/sage/combinat/words/morphism.py index 1879e4e1833..2880677eef9 100644 --- a/src/sage/combinat/words/morphism.py +++ b/src/sage/combinat/words/morphism.py @@ -2038,6 +2038,8 @@ def _language_naive(self, n, u): The language of the substitution is the DOL language which consist of factors of `s^n(u)`. + This method assumes this substitution is non-erasing. + INPUT: - ``n`` -- non-negative integer - length of the words in the language From c3379330dc9442fb84bb9c72a236b081183ccee2 Mon Sep 17 00:00:00 2001 From: Martin Rejmon Date: Sun, 18 Apr 2021 15:54:38 +0200 Subject: [PATCH 130/232] Make the argument in _language_naive also a factor --- src/sage/combinat/words/morphism.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/words/morphism.py b/src/sage/combinat/words/morphism.py index 2880677eef9..ce32830f8fb 100644 --- a/src/sage/combinat/words/morphism.py +++ b/src/sage/combinat/words/morphism.py @@ -2059,9 +2059,17 @@ def _language_naive(self, n, u): sage: s._language_naive(3, W()) set() + sage: W([1, 1]) in s._language_naive(3, W([1, 1])) + True """ - L = set(u.parent()()) - todo = [u] + L = set() + todo = [] + for i in range(len(u)): + for j in range(i+1, min(len(u)+1, i+n)): + f = u[i:j] + if f not in L: + todo.append(f) + L.add(f) while todo: u = todo.pop() v = self(u) From e6d88b22bd01983a817f95394674e9650df2d2d1 Mon Sep 17 00:00:00 2001 From: Martin Rejmon Date: Sun, 18 Apr 2021 15:59:45 +0200 Subject: [PATCH 131/232] Speed up _language_naive --- src/sage/combinat/words/morphism.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/words/morphism.py b/src/sage/combinat/words/morphism.py index ce32830f8fb..bc088a75e0d 100644 --- a/src/sage/combinat/words/morphism.py +++ b/src/sage/combinat/words/morphism.py @@ -2073,13 +2073,24 @@ def _language_naive(self, n, u): while todo: u = todo.pop() v = self(u) - for i in range(len(v)): - for j in range(i+1, min(len(v)+1, i+n)): - f = v[i:j] - if f not in L: - todo.append(f) - L.add(f) - + if u.length() == 1: + for i in range(len(v)): + for j in range(i+1, min(len(v)+1, i+n)): + f = v[i:j] + if f not in L: + todo.append(f) + L.add(f) + else: + l = self.image(u[0]).length() + r = self.image(u[-1]).length() + m = v.length() - l - r + x = n - 1 - m + for i in range(l - min(x - 1, l), l): + for j in range(l + m + 1, l + m + 1 + min(x - l + i, r)): + f = v[i:j] + if f not in L: + todo.append(f) + L.add(f) return L def language(self, n, u=None): From 9a49d59b96a65f8c304c5325947255f44c6bc389 Mon Sep 17 00:00:00 2001 From: Martin Rejmon Date: Mon, 19 Apr 2021 13:23:40 +0200 Subject: [PATCH 132/232] 31684: Inline a function call --- src/sage/combinat/words/morphism.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/words/morphism.py b/src/sage/combinat/words/morphism.py index bc088a75e0d..606d9cc3ad4 100644 --- a/src/sage/combinat/words/morphism.py +++ b/src/sage/combinat/words/morphism.py @@ -2081,8 +2081,8 @@ def _language_naive(self, n, u): todo.append(f) L.add(f) else: - l = self.image(u[0]).length() - r = self.image(u[-1]).length() + l = self._morph[u[0]].length() + r = self._morph[u[-1]].length() m = v.length() - l - r x = n - 1 - m for i in range(l - min(x - 1, l), l): From d54241202c54c606dee609ff71821d5ebdb0c930 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 19 Apr 2021 14:58:52 -0700 Subject: [PATCH 133/232] ManifoldSubset.open_supersets: New generator, use it to fix loops that assume open supersets --- src/sage/manifolds/chart.py | 16 ++++++---------- src/sage/manifolds/differentiable/chart.py | 8 ++++---- src/sage/manifolds/differentiable/manifold.py | 10 +++++----- .../differentiable/vectorfield_module.py | 2 +- .../manifolds/differentiable/vectorframe.py | 8 ++++---- src/sage/manifolds/subset.py | 19 +++++++++++++++++++ 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/sage/manifolds/chart.py b/src/sage/manifolds/chart.py index 5a2b7070867..6fb017d0082 100644 --- a/src/sage/manifolds/chart.py +++ b/src/sage/manifolds/chart.py @@ -318,9 +318,9 @@ def __init__(self, domain, coordinates='', names=None, calc_method=None): self._restrictions = [] # to be set with method add_restrictions() # # The chart is added to the domain's atlas, as well as to all the - # atlases of the domain's supersets; moreover the fist defined chart + # atlases of the domain's supersets; moreover the first defined chart # is considered as the default chart - for sd in self._domain._supersets: + for sd in self._domain.open_supersets(): # the chart is added in the top charts only if its coordinates have # not been used: for chart in sd._atlas: @@ -346,13 +346,9 @@ def __init__(self, domain, coordinates='', names=None, calc_method=None): # The null and one functions of the coordinates: # Expression in self of the zero and one scalar fields of open sets # containing the domain of self: - for dom in self._domain._supersets: - if hasattr(dom, '_zero_scalar_field'): - # dom is an open set - dom._zero_scalar_field._express[self] = self.function_ring().zero() - if hasattr(dom, '_one_scalar_field'): - # dom is an open set - dom._one_scalar_field._express[self] = self.function_ring().one() + for dom in self._domain.open_supersets(): + dom._zero_scalar_field._express[self] = self.function_ring().zero() + dom._one_scalar_field._express[self] = self.function_ring().one() def _init_coordinates(self, coord_list): r""" @@ -3010,7 +3006,7 @@ def __init__(self, chart1, chart2, *transformations): # is added to the subset (and supersets) dictionary: if chart1._domain == chart2._domain: domain = chart1._domain - for sdom in domain._supersets: + for sdom in domain.open_supersets(): sdom._coord_changes[(chart1, chart2)] = self def _repr_(self): diff --git a/src/sage/manifolds/differentiable/chart.py b/src/sage/manifolds/differentiable/chart.py index 396792d3792..8b9cd9bafa7 100644 --- a/src/sage/manifolds/differentiable/chart.py +++ b/src/sage/manifolds/differentiable/chart.py @@ -563,7 +563,7 @@ def restrict(self, subset, restrictions=None): sframe._restrictions[subset] = resu._frame # The subchart frame is not a "top frame" in the supersets # (including self._domain): - for dom in self._domain._supersets: + for dom in self._domain.open_supersets(): if resu._frame in dom._top_frames: # it was added by the Chart constructor invoked in # Chart.restrict above @@ -1046,7 +1046,7 @@ def restrict(self, subset, restrictions=None): sframe._restrictions[subset] = resu._frame # The subchart frame is not a "top frame" in the supersets # (including self._domain): - for dom in self._domain._supersets: + for dom in self._domain.open_supersets(): if resu._frame in dom._top_frames: # it was added by the Chart constructor invoked in # Chart.restrict above @@ -1138,7 +1138,7 @@ def __init__(self, chart1, chart2, *transformations): ch_basis.add_comp(frame1)[:, chart1] = self._jacobian ch_basis.add_comp(frame2)[:, chart1] = self._jacobian vf_module._basis_changes[(frame2, frame1)] = ch_basis - for sdom in domain._supersets: + for sdom in domain.open_supersets(): sdom._frame_changes[(frame2, frame1)] = ch_basis # The inverse is computed only if it does not exist already # (because if it exists it may have a simpler expression than that @@ -1146,7 +1146,7 @@ def __init__(self, chart1, chart2, *transformations): if (frame1, frame2) not in vf_module._basis_changes: ch_basis_inv = ch_basis.inverse() vf_module._basis_changes[(frame1, frame2)] = ch_basis_inv - for sdom in domain._supersets: + for sdom in domain.open_supersets(): sdom._frame_changes[(frame1, frame2)] = ch_basis_inv def jacobian(self): diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index 93f03723861..b66d1ae12dc 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -2754,7 +2754,7 @@ def orientation(self): """ if not self._orientation: # try to get an orientation from super domains: - for sdom in self._supersets: + for sdom in self.open_supersets(): sorient = sdom._orientation if sorient: rst_orient = [f.restrict(self) for f in sorient] @@ -2962,11 +2962,11 @@ def set_change_of_frame(self, frame1, frame2, change_of_frame, "instance of AutomorphismFieldParal") fmodule.set_change_of_basis(frame1, frame2, change_of_frame, compute_inverse=compute_inverse) - for sdom in self._supersets: + for sdom in self.open_supersets(): sdom._frame_changes[(frame1, frame2)] = change_of_frame if compute_inverse: if (frame2, frame1) not in self._frame_changes: - for sdom in self._supersets: + for sdom in self.open_supersets(): sdom._frame_changes[(frame2, frame1)] = change_of_frame.inverse() def vector_frame(self, *args, **kwargs): @@ -3152,7 +3152,7 @@ def vector_frame(self, *args, **kwargs): # dictionary _frame_changes of self and its supersets: for frame_pair, chge in resu._fmodule._basis_changes.items(): if resu in frame_pair: - for sdom in self._supersets: + for sdom in self.open_supersets(): sdom._frame_changes[frame_pair] = chge return resu @@ -3179,7 +3179,7 @@ def _set_covering_frame(self, frame): self._covering_frames.append(frame) self._parallelizable_parts = set([self]) # if self contained smaller parallelizable parts, they are forgotten - for sd in self._supersets: + for sd in self.open_supersets(): if not sd.is_manifestly_parallelizable(): sd._parallelizable_parts.add(self) diff --git a/src/sage/manifolds/differentiable/vectorfield_module.py b/src/sage/manifolds/differentiable/vectorfield_module.py index 3fe82857b8e..5df8aa2e1fd 100644 --- a/src/sage/manifolds/differentiable/vectorfield_module.py +++ b/src/sage/manifolds/differentiable/vectorfield_module.py @@ -1436,7 +1436,7 @@ def __init__(self, domain, dest_map=None): # basis is added to the restrictions of bases on a larger # domain - for dom in domain._supersets: + for dom in domain.open_supersets(): if dom is not domain: for supbase in dom._frames: if (supbase.domain() is dom and diff --git a/src/sage/manifolds/differentiable/vectorframe.py b/src/sage/manifolds/differentiable/vectorframe.py index 355ee322021..a726c96f397 100644 --- a/src/sage/manifolds/differentiable/vectorframe.py +++ b/src/sage/manifolds/differentiable/vectorframe.py @@ -321,7 +321,7 @@ def __init__(self, frame, symbol, latex_symbol=None, indices=None, latex_indices=latex_indices) # The coframe is added to the domain's set of coframes, as well as to # all the superdomains' sets of coframes - for sd in self._domain._supersets: + for sd in self._domain.open_supersets(): sd._coframes.append(self) def _repr_(self): @@ -728,7 +728,7 @@ def __init__(self, vector_field_module, symbol, latex_symbol=None, # the superdomains' sets of frames; moreover the first defined frame # is considered as the default one dest_map = self._dest_map - for sd in self._domain._supersets: + for sd in self._domain.open_supersets(): sd._frames.append(self) sd._top_frames.append(self) if sd._def_frame is None: @@ -1069,7 +1069,7 @@ def new_frame(self, change_of_frame, symbol, latex_symbol=None, latex_indices=latex_indices, symbol_dual=symbol_dual, latex_symbol_dual=latex_symbol_dual) - for sdom in self._domain._supersets: + for sdom in self._domain.open_supersets(): sdom._frame_changes[(self, the_new_frame)] = \ self._fmodule._basis_changes[(self, the_new_frame)] sdom._frame_changes[(the_new_frame, self)] = \ @@ -1163,7 +1163,7 @@ def restrict(self, subdomain): res._from_frame = self._from_frame - for dom in subdomain._supersets: + for dom in subdomain.open_supersets(): if dom is not subdomain: dom._top_frames.remove(res) # since it was added by # VectorFrame constructor diff --git a/src/sage/manifolds/subset.py b/src/sage/manifolds/subset.py index 6ee2826bca1..a963e89374f 100644 --- a/src/sage/manifolds/subset.py +++ b/src/sage/manifolds/subset.py @@ -572,6 +572,25 @@ def open_covers(self): """ return list(self._open_covers) + def open_supersets(self): + r""" + Generate the open supersets of ``self``. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: V = U.subset('V') + sage: W = V.subset('W') + sage: sorted(W.open_supersets(), key=lambda S: S._name) + [2-dimensional topological manifold M, + Open subset U of the 2-dimensional topological manifold M] + + """ + for superset in self._supersets: + if superset.is_open(): + yield superset + def subsets(self): r""" Return the set of subsets that have been defined on the From c821a75beec3166aad45e72ce56dce64cb4e8c8a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 19 Apr 2021 15:11:55 -0700 Subject: [PATCH 134/232] {Topological,...}Manifold.open_subset: New optional parameter supersets --- .../differentiable/differentiable_submanifold.py | 9 +++++++-- src/sage/manifolds/differentiable/manifold.py | 9 +++++++-- src/sage/manifolds/differentiable/pseudo_riemannian.py | 9 +++++++-- .../differentiable/pseudo_riemannian_submanifold.py | 9 +++++++-- src/sage/manifolds/manifold.py | 9 +++++++-- src/sage/manifolds/topological_submanifold.py | 9 +++++++-- 6 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/sage/manifolds/differentiable/differentiable_submanifold.py b/src/sage/manifolds/differentiable/differentiable_submanifold.py index 973551e6372..9aa5de8a363 100644 --- a/src/sage/manifolds/differentiable/differentiable_submanifold.py +++ b/src/sage/manifolds/differentiable/differentiable_submanifold.py @@ -218,7 +218,7 @@ def _repr_(self): return "{}-dimensional {} submanifold {} immersed in the {}".format( self._dim, self._structure.name, self._name, self._ambient) - def open_subset(self, name, latex_name=None, coord_def={}): + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): r""" Create an open subset of the manifold. @@ -240,6 +240,8 @@ def open_subset(self, name, latex_name=None, coord_def={}): terms of coordinates; ``coord_def`` must a be dictionary with keys charts in the manifold's atlas and values the symbolic expressions formed by the coordinates to define the subset. + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of OUTPUT: @@ -281,5 +283,8 @@ def open_subset(self, name, latex_name=None, coord_def={}): diff_degree=self._diff_degree, latex_name=latex_name, start_index=self._sindex) - self._init_open_subset(resu, coord_def=coord_def) + if supersets is None: + supersets = [self] + for superset in supersets: + superset._init_open_subset(resu, coord_def=coord_def) return resu diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index b66d1ae12dc..e2a0d4e8f60 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -723,7 +723,7 @@ def diff_degree(self): """ return self._diff_degree - def open_subset(self, name, latex_name=None, coord_def={}): + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): r""" Create an open subset of the manifold. @@ -741,6 +741,8 @@ def open_subset(self, name, latex_name=None, coord_def={}): terms of coordinates; ``coord_def`` must a be dictionary with keys charts in the manifold's atlas and values the symbolic expressions formed by the coordinates to define the subset. + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of OUTPUT: @@ -826,7 +828,10 @@ def open_subset(self, name, latex_name=None, coord_def={}): diff_degree=self._diff_degree, latex_name=latex_name, start_index=self._sindex) - self._init_open_subset(resu, coord_def=coord_def) + if supersets is None: + supersets = [self] + for superset in supersets: + superset._init_open_subset(resu, coord_def=coord_def) return resu def _init_open_subset(self, resu, coord_def): diff --git a/src/sage/manifolds/differentiable/pseudo_riemannian.py b/src/sage/manifolds/differentiable/pseudo_riemannian.py index 1625fec00b4..713f0807c89 100644 --- a/src/sage/manifolds/differentiable/pseudo_riemannian.py +++ b/src/sage/manifolds/differentiable/pseudo_riemannian.py @@ -672,7 +672,7 @@ def volume_form(self, contra=0): """ return self.metric().volume_form(contra=contra) - def open_subset(self, name, latex_name=None, coord_def={}): + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): r""" Create an open subset of ``self``. @@ -692,6 +692,8 @@ def open_subset(self, name, latex_name=None, coord_def={}): terms of coordinates; ``coord_def`` must a be dictionary with keys charts in the manifold's atlas and values the symbolic expressions formed by the coordinates to define the subset. + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of OUTPUT: @@ -745,5 +747,8 @@ def open_subset(self, name, latex_name=None, coord_def={}): latex_name=latex_name, metric_latex_name=self._metric_latex_name, start_index=self._sindex) - self._init_open_subset(resu, coord_def=coord_def) + if supersets is None: + supersets = [self] + for superset in supersets: + superset._init_open_subset(resu, coord_def=coord_def) return resu diff --git a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py index ae3e1d436d5..5b7ef8b72c2 100644 --- a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py +++ b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py @@ -392,7 +392,7 @@ def _repr_(self): return "{}-dimensional {} submanifold {} immersed in the {}".format( self._dim, self._structure.name, self._name, self._ambient) - def open_subset(self, name, latex_name=None, coord_def={}): + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): r""" Create an open subset of ``self``. @@ -415,6 +415,8 @@ def open_subset(self, name, latex_name=None, coord_def={}): terms of coordinates; ``coord_def`` must a be dictionary with keys charts in the manifold's atlas and values the symbolic expressions formed by the coordinates to define the subset. + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of OUTPUT: @@ -460,7 +462,10 @@ def open_subset(self, name, latex_name=None, coord_def={}): latex_name=latex_name, metric_latex_name=self._metric_latex_name, start_index=self._sindex) - self._init_open_subset(resu, coord_def=coord_def) + if supersets is None: + supersets = [self] + for superset in supersets: + superset._init_open_subset(resu, coord_def=coord_def) return resu def ambient_metric(self): diff --git a/src/sage/manifolds/manifold.py b/src/sage/manifolds/manifold.py index 0b6808bf66d..3dccc5ea6cb 100644 --- a/src/sage/manifolds/manifold.py +++ b/src/sage/manifolds/manifold.py @@ -782,7 +782,7 @@ def __contains__(self, point): return True return False - def open_subset(self, name, latex_name=None, coord_def={}): + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): r""" Create an open subset of the manifold. @@ -800,6 +800,8 @@ def open_subset(self, name, latex_name=None, coord_def={}): terms of coordinates; ``coord_def`` must a be dictionary with keys charts on the manifold and values the symbolic expressions formed by the coordinates to define the subset + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of OUTPUT: @@ -872,7 +874,10 @@ def open_subset(self, name, latex_name=None, coord_def={}): base_manifold=self._manifold, latex_name=latex_name, start_index=self._sindex) - self._init_open_subset(resu, coord_def=coord_def) + if supersets is None: + supersets = [self] + for superset in supersets: + superset._init_open_subset(resu, coord_def=coord_def) return resu def _init_open_subset(self, resu, coord_def): diff --git a/src/sage/manifolds/topological_submanifold.py b/src/sage/manifolds/topological_submanifold.py index 1b9ca7aebbd..4d83e9582fc 100644 --- a/src/sage/manifolds/topological_submanifold.py +++ b/src/sage/manifolds/topological_submanifold.py @@ -256,7 +256,7 @@ def _repr_(self): return "{}-dimensional {} submanifold {} immersed in the {}".format( self._dim, self._structure.name, self._name, self._ambient) - def open_subset(self, name, latex_name=None, coord_def={}): + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): r""" Create an open subset of the manifold. @@ -278,6 +278,8 @@ def open_subset(self, name, latex_name=None, coord_def={}): terms of coordinates; ``coord_def`` must a be dictionary with keys charts on the manifold and values the symbolic expressions formed by the coordinates to define the subset + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of OUTPUT: @@ -319,7 +321,10 @@ def open_subset(self, name, latex_name=None, coord_def={}): base_manifold=self._manifold, latex_name=latex_name, start_index=self._sindex) - self._init_open_subset(resu, coord_def=coord_def) + if supersets is None: + supersets = [self] + for superset in supersets: + superset._init_open_subset(resu, coord_def=coord_def) return resu def _init_open_subset(self, resu, coord_def): From b35eca14d542d604421cf82de0c24f9287e6b05f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 19 Apr 2021 15:18:04 -0700 Subject: [PATCH 135/232] ManifoldSubset.{open_subset,_init_open_subset}: New --- src/sage/manifolds/subset.py | 107 +++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/src/sage/manifolds/subset.py b/src/sage/manifolds/subset.py index a963e89374f..68a2afadcaa 100644 --- a/src/sage/manifolds/subset.py +++ b/src/sage/manifolds/subset.py @@ -896,6 +896,113 @@ def subset(self, name, latex_name=None, is_open=False): self._top_subsets.add(res) return res + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): + r""" + Create an open subset of the manifold that is a subset of ``self``. + + An open subset is a set that is (i) included in the manifold and (ii) + open with respect to the manifold's topology. It is a topological + manifold by itself. Hence the returned object is an instance of + :class:`TopologicalManifold`. + + INPUT: + + - ``name`` -- name given to the open subset + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote + the subset; if none are provided, it is set to ``name`` + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts on the manifold and values the symbolic expressions formed + by the coordinates to define the subset + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of + + OUTPUT: + + - the open subset, as an instance of :class:`TopologicalManifold` + or one of its subclasses + + EXAMPLES:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: cl_D = M.subset('cl_D'); cl_D + Subset cl_D of the 2-dimensional topological manifold R^2 + sage: D = cl_D.open_subset('D', coord_def={c_cart: x^2+y^2<1}); D + Open subset D of the 2-dimensional topological manifold R^2 + sage: D.is_subset(cl_D) + True + sage: D.is_subset(M) + True + + sage: M = Manifold(2, 'R^2', structure='differentiable') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: cl_D = M.subset('cl_D'); cl_D + Subset cl_D of the 2-dimensional differentiable manifold R^2 + sage: D = cl_D.open_subset('D', coord_def={c_cart: x^2+y^2<1}); D + Open subset D of the 2-dimensional differentiable manifold R^2 + sage: D.is_subset(cl_D) + True + sage: D.is_subset(M) + True + + sage: M = Manifold(2, 'R^2', structure='Riemannian') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: cl_D = M.subset('cl_D'); cl_D + Subset cl_D of the 2-dimensional Riemannian manifold R^2 + sage: D = cl_D.open_subset('D', coord_def={c_cart: x^2+y^2<1}); D + Open subset D of the 2-dimensional Riemannian manifold R^2 + sage: D.is_subset(cl_D) + True + sage: D.is_subset(M) + True + + """ + if supersets is None: + supersets = set() + else: + supersets = set(supersets) + supersets.update([self]) + # Delegate to the manifold's method. + return self._manifold.open_subset(name, latex_name=latex_name, + coord_def=coord_def, + supersets=supersets) + + def _init_open_subset(self, resu, coord_def): + r""" + Initialize ``resu`` as an open subset of ``self``. + + INPUT: + + - ``resu`` -- an instance of ``:class:`TopologicalManifold` or + a subclass. + + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts on the manifold and values the symbolic expressions formed + by the coordinates to define the subset + + EXAMPLES:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: cl_D = M.subset('cl_D') + sage: coord_def = {c_cart: x^2+y^2<1} + sage: D = M.open_subset('D', coord_def=coord_def) + sage: D.is_subset(cl_D) + False + sage: cl_D._init_open_subset(D, coord_def) + sage: D.is_subset(cl_D) + True + + """ + resu._supersets.update(self._supersets) + self._subsets.add(resu) + # Recursively delegate to the supersets. + for superset in self._supersets: + if superset is not self: + superset._init_open_subset(resu, coord_def=coord_def) + def superset(self, name, latex_name=None, is_open=False): r""" Create a superset of the current subset. From 89373d44ad06e6eeb9ca837c6d919e1647b0d169 Mon Sep 17 00:00:00 2001 From: Michael Jung Date: Tue, 20 Apr 2021 18:24:25 +0200 Subject: [PATCH 136/232] Trac #31692: refactor display_expansion --- .../manifolds/differentiable/mixed_form.py | 174 +++++++++++------- 1 file changed, 106 insertions(+), 68 deletions(-) diff --git a/src/sage/manifolds/differentiable/mixed_form.py b/src/sage/manifolds/differentiable/mixed_form.py index e9fdcdb9916..979a69859bb 100644 --- a/src/sage/manifolds/differentiable/mixed_form.py +++ b/src/sage/manifolds/differentiable/mixed_form.py @@ -97,7 +97,7 @@ class MixedForm(AlgebraElement): sage: A[:] = [f, omega, eta]; A.display() # display names A = f + omega + eta sage: A.display_expansion() # display in coordinates - A = [x] + [x*y dx] + [x*y^2 dx/\dy] + A = x + x*y dx + x*y^2 dx/\dy sage: A[0] Scalar field f on the 2-dimensional differentiable manifold M sage: A[0] is f @@ -124,26 +124,26 @@ class MixedForm(AlgebraElement): Mixed differential form x/\A on the 2-dimensional differentiable manifold M sage: C.display_expansion() - x/\A = [x^2] + [x^2*y dx] + [x^2*y^2 dx/\dy] + x/\A = x^2 + x^2*y dx + x^2*y^2 dx/\dy sage: D = A+C; D Mixed differential form A+x/\A on the 2-dimensional differentiable manifold M sage: D.display_expansion() - A+x/\A = [x^2 + x] + [(x^2 + x)*y dx] + [(x^2 + x)*y^2 dx/\dy] + A+x/\A = x^2 + x + (x^2 + x)*y dx + (x^2 + x)*y^2 dx/\dy sage: E = A*C; E Mixed differential form A/\(x/\A) on the 2-dimensional differentiable manifold M sage: E.display_expansion() - A/\(x/\A) = [x^3] + [2*x^3*y dx] + [2*x^3*y^2 dx/\dy] + A/\(x/\A) = x^3 + 2*x^3*y dx + 2*x^3*y^2 dx/\dy Coercions are fully implemented:: sage: F = omega*A sage: F.display_expansion() - omega/\A = [0] + [x^2*y dx] + [0] + omega/\A = x^2*y dx sage: G = omega+A sage: G.display_expansion() - omega+A = [x] + [2*x*y dx] + [x*y^2 dx/\dy] + omega+A = x + 2*x*y dx + x*y^2 dx/\dy Moreover, it is possible to compute the exterior derivative of a mixed form:: @@ -151,7 +151,7 @@ class MixedForm(AlgebraElement): sage: dA = A.exterior_derivative(); dA.display() dA = zero + df + domega sage: dA.display_expansion() - dA = [0] + [dx] + [-x dx/\dy] + dA = dx - x dx/\dy Initialize a mixed form on a 2-dimensional non-parallelizable differentiable manifold:: @@ -181,11 +181,10 @@ class MixedForm(AlgebraElement): eta = u*v^2 du/\dv sage: A.add_comp_by_continuation(e_uv, W, c_uv) sage: A.display_expansion(e_uv) - A = [1/2*u + 1/2*v] + [(1/8*u^2 - 1/8*v^2) du + (1/8*u^2 - 1/8*v^2) dv] - + [u*v^2 du/\dv] + A = 1/2*u + 1/2*v + (1/8*u^2 - 1/8*v^2) du + (1/8*u^2 - 1/8*v^2) dv + u*v^2 du/\dv sage: A.add_comp_by_continuation(e_xy, W, c_xy) sage: A.display_expansion(e_xy) - A = [x] + [x*y dx] + [(-2*x^3 + 2*x^2*y + 2*x*y^2 - 2*y^3) dx/\dy] + A = x + x*y dx + (-2*x^3 + 2*x^2*y + 2*x*y^2 - 2*y^3) dx/\dy Since zero and one are special elements, their components cannot be changed:: @@ -366,22 +365,20 @@ def display_expansion(self, frame=None, chart=None, from_chart=None): sage: F.display() # display names of homogeneous components F = zero + omega + eta sage: F.display_expansion(e_uv) - F = [0] + [(1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv] + [u*v du/\dv] + F = (1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv + u*v du/\dv sage: F.display_expansion(e_xy) - F = [0] + [x dx] + [(2*x^2 - 2*y^2) dx/\dy] + F = x dx + (2*x^2 - 2*y^2) dx/\dy """ from sage.misc.latex import latex - from sage.tensor.modules.format_utilities import FormattedExpansion - ### + from sage.tensor.modules.format_utilities import (is_atomic, + FormattedExpansion) # In case, no frame is given: if frame is None: frame = self._domain._def_frame - ### # In case, no chart is given: if chart is None: chart = frame._chart - ### # Check names: if self._name is not None: resu_txt = self._name + " = " @@ -391,18 +388,74 @@ def display_expansion(self, frame=None, chart=None, from_chart=None): resu_latex = self._latex_name + r" = " else: resu_latex = "" - ### - # Scalar field: - resu_txt += "[" + repr(self[0].expr(chart, from_chart)) + "]" - resu_latex += r"\left[" + latex(self[0].expr(chart, from_chart)) + \ - r"\right]_0" - ### - # Differential forms: + # Get terms + terms_txt = [] + terms_latex = [] + # Scalar field term: + if not self[0].is_trivial_zero(): + terms_txt.append(repr(self[0].expr(chart, from_chart))) + terms_latex.append(latex(self[0].expr(chart, from_chart))) + # Differential form terms: for j in self.irange(1): rst = self[j].restrict(frame._domain, dest_map=frame._dest_map) - rst_exp = rst._display_expansion(basis=frame, format_spec=chart) - resu_txt += " + [" + repr(rst_exp) + "]" - resu_latex += r"+ \left[" + latex(rst_exp) + r"\right]_{}".format(j) + basis, format_spec = rst._preparse_display(basis=frame, + format_spec=chart) + cobasis = basis.dual_basis() + comp = rst.comp(basis) + for ind in comp.non_redundant_index_generator(): + ind_arg = ind + (format_spec,) + coef = comp[ind_arg] + # Check whether the coefficient is zero, preferably via + # the fast method is_trivial_zero(): + if hasattr(coef, 'is_trivial_zero'): + zero_coef = coef.is_trivial_zero() + else: + zero_coef = coef == 0 + if not zero_coef: + bases_txt = [] + bases_latex = [] + for k in range(rst._tensor_rank): + bases_txt.append(cobasis[ind[k]]._name) + bases_latex.append(latex(cobasis[ind[k]])) + basis_term_txt = "/\\".join(bases_txt) + basis_term_latex = r"\wedge ".join(bases_latex) + coef_txt = repr(coef) + if coef_txt == "1": + terms_txt.append(basis_term_txt) + terms_latex.append(basis_term_latex) + elif coef_txt == "-1": + terms_txt.append("-" + basis_term_txt) + terms_latex.append("-" + basis_term_latex) + else: + coef_latex = latex(coef) + if is_atomic(coef_txt): + terms_txt.append(coef_txt + " " + basis_term_txt) + else: + terms_txt.append("(" + coef_txt + ") " + + basis_term_txt) + if is_atomic(coef_latex): + terms_latex.append(coef_latex + basis_term_latex) + else: + terms_latex.append(r"\left(" + coef_latex + \ + r"\right)" + basis_term_latex) + if not terms_txt: + resu_txt += "0" + else: + resu_txt += terms_txt[0] + for term in terms_txt[1:]: + if term[0] == "-": + resu_txt += " - " + term[1:] + else: + resu_txt += " + " + term + if not terms_latex: + resu_latex += "0" + else: + resu_latex += terms_latex[0] + for term in terms_latex[1:]: + if term[0] == "-": + resu_latex += term + else: + resu_latex += "+" + term return FormattedExpansion(resu_txt, resu_latex) disp_exp = display_expansion @@ -430,7 +483,6 @@ def display(self): """ from sage.misc.latex import latex from sage.tensor.modules.format_utilities import FormattedExpansion - ### # Mixed form name: if self._name is not None: resu_txt = self._name + " = " @@ -440,7 +492,6 @@ def display(self): resu_latex = self._latex_name + r" = " else: resu_latex = "" - ### # Scalar field: if self[0]._name is None: resu_txt += "(unnamed scalar field) " @@ -450,7 +501,6 @@ def display(self): resu_latex += r"\mbox{(unnamed scalar field)}" else: resu_latex += latex(self[0]) - ### # Differential forms: for j in self.irange(1): if self[j]._name is None: @@ -638,15 +688,13 @@ def _add_(self, other): Mixed differential form A+B on the 2-dimensional differentiable manifold M sage: A.display_expansion(e_uv) - A = [1/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv] - + [0] + A = 1/2*u + 1/2*v + (1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv sage: B.display_expansion(e_xy) - B = [x + y] + [(x - y) dx + (-x + y) dy] + [0] + B = x + y + (x - y) dx + (-x + y) dy sage: C.display_expansion(e_xy) - A+B = [2*x + y] + [(2*x - y) dx + (-x + y) dy] + [0] + A+B = 2*x + y + (2*x - y) dx + (-x + y) dy sage: C.display_expansion(e_uv) - A+B = [3/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u + 5/4*v) dv] - + [0] + A+B = 3/2*u + 1/2*v + (1/4*u + 1/4*v) du + (1/4*u + 5/4*v) dv sage: C == A + B # indirect doctest True sage: Z = A.parent().zero(); Z @@ -714,13 +762,13 @@ def _sub_(self, other): Mixed differential form A-B on the 2-dimensional differentiable manifold M sage: A.display_expansion(e_uv) - A = [1/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv] + [0] + A = 1/2*u + 1/2*v + (1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv sage: B.display_expansion(e_xy) - B = [x + y] + [(x - y) dx + (-x + y) dy] + [0] + B = x + y + (x - y) dx + (-x + y) dy sage: C.display_expansion(e_xy) - A-B = [-y] + [y dx + (x - y) dy] + [0] + A-B = -y + y dx + (x - y) dy sage: C.display_expansion(e_uv) - A-B = [-1/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u - 3/4*v) dv] + [0] + A-B = -1/2*u + 1/2*v + (1/4*u + 1/4*v) du + (1/4*u - 3/4*v) dv sage: C == A - B # indirect doctest True sage: Z = A.parent().zero(); Z @@ -814,10 +862,10 @@ def wedge(self, other): mu = z dx/\dz sage: A = M.mixed_form([f, omega, mu, 0], name='A') sage: A.display_expansion() - A = [x] + [x dx] + [z dx/\dz] + [0] + A = x + x dx + z dx/\dz sage: B = M.mixed_form([g, eta, mu, 0], name='B') sage: B.display_expansion() - B = [y] + [y dy] + [z dx/\dz] + [0] + B = y + y dy + z dx/\dz The wedge product of ``A`` and ``B`` yields:: @@ -825,14 +873,12 @@ def wedge(self, other): Mixed differential form A/\B on the 3-dimensional differentiable manifold M sage: C.display_expansion() - A/\B = [x*y] + [x*y dx + x*y dy] + [x*y dx/\dy + (x + y)*z dx/\dz] + - [-y*z dx/\dy/\dz] + A/\B = x*y + x*y dx + x*y dy + x*y dx/\dy + (x + y)*z dx/\dz - y*z dx/\dy/\dz sage: D = B.wedge(A); D # Don't even try, it's not commutative! Mixed differential form B/\A on the 3-dimensional differentiable manifold M sage: D.display_expansion() # I told you so! - B/\A = [x*y] + [x*y dx + x*y dy] + [-x*y dx/\dy + (x + y)*z dx/\dz] - + [-y*z dx/\dy/\dz] + B/\A = x*y + x*y dx + x*y dy - x*y dx/\dy + (x + y)*z dx/\dz - y*z dx/\dy/\dz Alternatively, the multiplication symbol can be used:: @@ -845,9 +891,9 @@ def wedge(self, other): Yet, the multiplication includes coercions:: sage: E = x*A; E.display_expansion() - x/\A = [x^2] + [x^2 dx] + [x*z dx/\dz] + [0] + x/\A = x^2 + x^2 dx + x*z dx/\dz sage: F = A*eta; F.display_expansion() - A/\eta = [0] + [x*y dy] + [x*y dx/\dy] + [-y*z dx/\dy/\dz] + A/\eta = x*y dy + x*y dx/\dy - y*z dx/\dy/\dz """ # Case zero: @@ -898,7 +944,7 @@ def _lmul_(self, other): Mixed differential form y/\(x/\F) on the 2-dimensional differentiable manifold M sage: A.display_expansion() - y/\(x/\F) = [0] + [x^2*y^2 dx] + [0] + y/\(x/\F) = x^2*y^2 dx """ try: @@ -971,7 +1017,7 @@ def exterior_derivative(self): dF = zero + df + dzero + da sage: dF = F.exterior_derivative() sage: dF.display_expansion() - dF = [0] + [2*z dz] + [0] + [(2*x + 1) dx/\dy/\dz] + dF = 2*z dz + (2*x + 1) dx/\dy/\dz Due to long calculation times, the result is cached:: @@ -1032,7 +1078,7 @@ def copy(self, name=None, latex_name=None): sage: A = M.mixed_form([f, omega, 0], name='A'); A.display() A = f + omega + zero sage: A.display_expansion(e_uv) - A = [1/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv] + [0] + A = 1/2*u + 1/2*v + (1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv An exact copy is made. The copy is an entirely new instance and has a different name, but has the very same values:: @@ -1040,7 +1086,7 @@ def copy(self, name=None, latex_name=None): sage: B = A.copy(); B.display() (unnamed scalar field) + (unnamed 1-form) + (unnamed 2-form) sage: B.display_expansion(e_uv) - [1/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv] + [0] + 1/2*u + 1/2*v + (1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv sage: A == B True sage: A is B @@ -1053,9 +1099,9 @@ def copy(self, name=None, latex_name=None): sage: omega[e_xy,0] = y; omega.display() omega = y dx sage: A.display_expansion(e_xy) - A = [x] + [y dx] + [0] + A = x + y dx sage: B.display_expansion(e_xy) - [x] + [x dx] + [0] + x + x dx """ resu = self._new_instance() @@ -1087,7 +1133,7 @@ def __setitem__(self, index, values): sage: A[1:3] = [a, b]; A.display() A = f + a + b sage: A.display_expansion() - A = [x] + [y dx] + [x*y dx/\dy] + A = x + y dx + x*y dx/\dy """ if self is self.parent().one() or self is self.parent().zero(): @@ -1178,7 +1224,7 @@ def set_restriction(self, rst): Mixed differential form A on the Open subset U of the 2-dimensional differentiable manifold M sage: AU.display_expansion(e_xy) - A = [x] + [y dx] + [0] + A = x + y dx A mixed form on ``M`` can be specified by some mixed form on a subset:: @@ -1187,11 +1233,10 @@ def set_restriction(self, rst): manifold M sage: A.set_restriction(AU) sage: A.display_expansion(e_xy) - A = [x] + [y dx] + [0] + A = x + y dx sage: A.add_comp_by_continuation(e_uv, W, c_uv) sage: A.display_expansion(e_uv) - A = [u/(u^2 + v^2)] + [-(u^2*v - v^3)/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + - v^6) du - 2*u*v^2/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) dv] + [0] + A = u/(u^2 + v^2) - (u^2*v - v^3)/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) du - 2*u*v^2/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) dv sage: A.restrict(U) == AU True @@ -1268,11 +1313,7 @@ def restrict(self, subdomain, dest_map=None): 2-form eta on the Open subset V of the 2-dimensional differentiable manifold M] sage: FV.display_expansion(e_uv) - F = [u^2/(u^4 + 2*u^2*v^2 + v^4)] + [-(u^2*v^2 - v^4)/(u^8 + - 4*u^6*v^2 + 6*u^4*v^4 + 4*u^2*v^6 + v^8) du - 2*u*v^3/(u^8 + - 4*u^6*v^2 + 6*u^4*v^4 + 4*u^2*v^6 + v^8) dv] + [-u^2*v^2/(u^12 + - 6*u^10*v^2 + 15*u^8*v^4 + 20*u^6*v^6 + 15*u^4*v^8 + 6*u^2*v^10 + - v^12) du/\dv] + F = u^2/(u^4 + 2*u^2*v^2 + v^4) - (u^2*v^2 - v^4)/(u^8 + 4*u^6*v^2 + 6*u^4*v^4 + 4*u^2*v^6 + v^8) du - 2*u*v^3/(u^8 + 4*u^6*v^2 + 6*u^4*v^4 + 4*u^2*v^6 + v^8) dv - u^2*v^2/(u^12 + 6*u^10*v^2 + 15*u^8*v^4 + 20*u^6*v^6 + 15*u^4*v^8 + 6*u^2*v^10 + v^12) du/\dv """ resu = type(self)(subdomain.mixed_form_algebra(dest_map=dest_map), @@ -1326,12 +1367,9 @@ def add_comp_by_continuation(self, frame, subdomain, chart=None): sage: F.add_comp_by_continuation(e_uv, W, c_uv) sage: F.add_comp_by_continuation(e_xy, W, c_xy) # Now, F is fully defined sage: F.display_expansion(e_xy) - F = [x] + [x dx] + [-x*y/(x^8 + 4*x^6*y^2 + 6*x^4*y^4 + 4*x^2*y^6 + - y^8) dx/\dy] + F = x + x dx - x*y/(x^8 + 4*x^6*y^2 + 6*x^4*y^4 + 4*x^2*y^6 + y^8) dx/\dy sage: F.display_expansion(e_uv) - F = [u/(u^2 + v^2)] + [-(u^3 - u*v^2)/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + - v^6) du - 2*u^2*v/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) dv] + - [u*v du/\dv] + F = u/(u^2 + v^2) - (u^3 - u*v^2)/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) du - 2*u^2*v/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) dv + u*v du/\dv """ if chart is None: From 8a0a9b65a2380ddee5a8720c12bf5b67f171d6fb Mon Sep 17 00:00:00 2001 From: Michael Jung Date: Tue, 20 Apr 2021 18:28:33 +0200 Subject: [PATCH 137/232] Trac #31692: fix doctests --- .../manifolds/differentiable/characteristic_class.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/sage/manifolds/differentiable/characteristic_class.py b/src/sage/manifolds/differentiable/characteristic_class.py index 72972152f0f..0ec2ea8b37e 100644 --- a/src/sage/manifolds/differentiable/characteristic_class.py +++ b/src/sage/manifolds/differentiable/characteristic_class.py @@ -122,7 +122,7 @@ obtained via :meth:`get_form`. sage: ch_form = ch.get_form(nab); ch_form.display_expansion() - ch(E, nabla^E) = [1] + [0] + [1/2*d(A)/dt/pi dt/\dx] + ch(E, nabla^E) = 1 + 1/2*d(A)/dt/pi dt/\dx .. _multiplicative: @@ -197,7 +197,7 @@ base space 2-dimensional differentiable manifold CP^1 sage: c_form = c.get_form(nab) sage: c_form.display_expansion(c_comp.frame(), chart=c_comp) - c(gamma^1, nabla) = [1] + [0] + [1/2*I/(pi + pi*z^2*zbar^2 + 2*pi*z*zbar) dz/\dzbar] + c(gamma^1, nabla) = 1 + 1/2*I/(pi + pi*z^2*zbar^2 + 2*pi*z*zbar) dz/\dzbar Since `U` and `\CC\mathbf{P}^1` differ only by a point and therefore a null set, it is enough to integrate the top form over the domain `U`:: @@ -302,8 +302,7 @@ sage: cmatrices = {eU: cmatrix_U, eV: cmatrix_V} sage: e_class_form = e_class.get_form(nab, cmatrices) sage: e_class_form.display_expansion() - e(TS2, nabla_g) = [0] + [0] + [2/(pi + pi*x^4 + pi*y^4 + 2*pi*x^2 + - 2*(pi + pi*x^2)*y^2) dx/\dy] + e(TS2, nabla_g) = 2/(pi + pi*x^4 + pi*y^4 + 2*pi*x^2 + 2*(pi + pi*x^2)*y^2) dx/\dy Let us check whether this form represents the Euler class correctly:: @@ -706,7 +705,7 @@ def get_form(self, connection, cmatrices=None): sage: ch_form.display() ch(E, nabla^E) = ch_0(E, nabla^E) + zero + ch_1(E, nabla^E) sage: ch_form.display_expansion() - ch(E, nabla^E) = [1] + [0] + [1/2*d(A)/dt/pi dt/\dx] + ch(E, nabla^E) = 1 + 1/2*d(A)/dt/pi dt/\dx Due to long computation times, the form is saved:: From 99ad00758cd9b37574c9b0cfa01d607b8c5de217 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix <20100.delecroix@gmail.com> Date: Tue, 20 Apr 2021 20:32:21 +0200 Subject: [PATCH 138/232] 31650: remove assert in doctests --- src/sage/misc/lazy_import.pyx | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/sage/misc/lazy_import.pyx b/src/sage/misc/lazy_import.pyx index c98e1dd8922..cc91cbf0c19 100644 --- a/src/sage/misc/lazy_import.pyx +++ b/src/sage/misc/lazy_import.pyx @@ -167,7 +167,8 @@ cdef class LazyImport(object): sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') sage: type(lazy_ZZ) - sage: assert lazy_ZZ._get_object() is ZZ + sage: lazy_ZZ._get_object() is ZZ + True sage: type(lazy_ZZ) """ @@ -340,7 +341,8 @@ cdef class LazyImport(object): sage: from sage.misc.lazy_import import LazyImport sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') - sage: assert dir(lazy_ZZ) == dir(ZZ) + sage: dir(lazy_ZZ) == dir(ZZ) + True """ return dir(self.get_object()) @@ -352,8 +354,10 @@ cdef class LazyImport(object): sage: from sage.misc.lazy_import import LazyImport sage: my_isprime = LazyImport('sage.all', 'is_prime') - sage: assert is_prime(12) == my_isprime(12) - sage: assert is_prime(13) == my_isprime(13) + sage: is_prime(12) == my_isprime(12) + True + sage: is_prime(13) == my_isprime(13) + True """ return self.get_object()(*args, **kwds) @@ -363,7 +367,8 @@ cdef class LazyImport(object): sage: from sage.misc.lazy_import import LazyImport sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') - sage: assert repr(lazy_ZZ) == repr(ZZ) + sage: repr(lazy_ZZ) == repr(ZZ) + True """ try: obj = self.get_object() @@ -377,7 +382,8 @@ cdef class LazyImport(object): sage: from sage.misc.lazy_import import LazyImport sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') - sage: assert str(lazy_ZZ) == str(ZZ) + sage: str(lazy_ZZ) == str(ZZ) + True """ return str(self.get_object()) @@ -387,7 +393,8 @@ cdef class LazyImport(object): sage: from sage.misc.lazy_import import LazyImport sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') - sage: assert str(lazy_ZZ) == str(ZZ) + sage: str(lazy_ZZ) == str(ZZ) + True """ return unicode(self.get_object()) @@ -397,7 +404,8 @@ cdef class LazyImport(object): sage: from sage.misc.lazy_import import LazyImport sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') - sage: assert bool(lazy_ZZ) == bool(ZZ) + sage: bool(lazy_ZZ) == bool(ZZ) + True """ return bool(self.get_object()) @@ -867,7 +875,8 @@ cdef class LazyImport(object): False sage: a[0] is sage.all.foo[0] # copy but not deep True - sage: assert type(lazy_foo) is LazyImport + sage: type(lazy_foo) is LazyImport + True """ import copy return copy.copy(self.get_object()) @@ -886,7 +895,8 @@ cdef class LazyImport(object): False sage: a[0] is sage.all.foo[0] # deep copy False - sage: assert type(lazy_foo) is LazyImport + sage: type(lazy_foo) is LazyImport + True """ import copy return copy.deepcopy(self.get_object()) From 8704bb9d9a4391b2487b7027668047997b73f9f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Leli=C3=A8vre?= Date: Wed, 21 Apr 2021 14:30:57 +0200 Subject: [PATCH 139/232] 31686: Use cyclotomics to factor finite field unit order --- .../rings/finite_rings/finite_field_base.pyx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index 972f07e6de8..7bebda850c8 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -873,8 +873,25 @@ cdef class FiniteField(Field): sage: GF(7^2,'a').factored_unit_order() (2^4 * 3,) + + TESTS: + + Check that :trac:`31686` is fixed:: + + sage: p = 1100585370631 + sage: F = GF(p^24, 'a') + sage: F.factored_unit_order() + (2^6 * 3^2 * 5 * 7 * 11 * 13 * 17 * 53 * 97 * 229 * 337 * 421 + * 3929 * 215417 * 249737 * 262519 * 397897 * 59825761 * 692192057 + * 12506651939 * 37553789761 * 46950147799 * 172462808473 * 434045140817 + * 81866093016401 * 617237859576697 * 659156729361017707 + * 268083135725348991493995910983015600019336657 + * 90433843562394341719266736354746485652016132372842876085423636587989263202299569913,) """ - F = (self.order() - 1).factor() + from sage.structure.factorization import Factorization + from sage.rings.polynomial.cyclotomic import cyclotomic_value as cv + p, d = self.characteristic(), self.degree() + F = Factorization(f for n in d.divisors() for f in cv(n, p).factor()) return (F,) def cardinality(self): From 6c714efcfdcd9d9ce7755574eab8c7202ad08c25 Mon Sep 17 00:00:00 2001 From: Michael Jung Date: Wed, 21 Apr 2021 18:18:23 +0200 Subject: [PATCH 140/232] Trac #31692: doctest fix --- src/sage/manifolds/differentiable/vector_bundle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/manifolds/differentiable/vector_bundle.py b/src/sage/manifolds/differentiable/vector_bundle.py index 21b62ccaa87..ad50447ec89 100644 --- a/src/sage/manifolds/differentiable/vector_bundle.py +++ b/src/sage/manifolds/differentiable/vector_bundle.py @@ -240,7 +240,7 @@ def characteristic_class(self, func, **kwargs): sage: p.function() x + 1 sage: p_form = p.get_form(nab); p_form.display_expansion() - p(TM, nabla_g) = [1] + [0] + [0] + [0] + [0] + p(TM, nabla_g) = 1 .. SEEALSO:: From 4746ddb61e6d2da8e7d7b5e76d39772b286f56a2 Mon Sep 17 00:00:00 2001 From: Michael Jung Date: Tue, 20 Apr 2021 19:04:50 +0200 Subject: [PATCH 141/232] Trac #31691: turn mixed form algebra into de rham complex --- src/sage/manifolds/differentiable/manifold.py | 2 + .../manifolds/differentiable/mixed_form.py | 2 + .../differentiable/mixed_form_algebra.py | 596 +++++++++++++++++- 3 files changed, 593 insertions(+), 7 deletions(-) diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index 61d94a414f8..f720158cba3 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -1534,6 +1534,8 @@ def mixed_form_algebra(self, dest_map=None): vmodule = self.vector_field_module(dest_map=dest_map) return MixedFormAlgebra(vmodule) + de_rham_complex = mixed_form_algebra + def multivector_module(self, degree, dest_map=None): r""" Return the set of multivector fields of a given degree defined diff --git a/src/sage/manifolds/differentiable/mixed_form.py b/src/sage/manifolds/differentiable/mixed_form.py index e9fdcdb9916..2b755c47ffe 100644 --- a/src/sage/manifolds/differentiable/mixed_form.py +++ b/src/sage/manifolds/differentiable/mixed_form.py @@ -990,6 +990,8 @@ def exterior_derivative(self): resu._latex_name = format_unop_latex(r'\mathrm{d}', self._latex_name) return resu + derivative = exterior_derivative + def copy(self, name=None, latex_name=None): r""" Return an exact copy of ``self``. diff --git a/src/sage/manifolds/differentiable/mixed_form_algebra.py b/src/sage/manifolds/differentiable/mixed_form_algebra.py index 5edb7b22955..88f2041f293 100644 --- a/src/sage/manifolds/differentiable/mixed_form_algebra.py +++ b/src/sage/manifolds/differentiable/mixed_form_algebra.py @@ -6,7 +6,11 @@ denoted by `\Omega^*(M,\varphi)`, is given by the direct sum `\bigoplus^n_{j=0} \Omega^j(M,\varphi)` of differential form modules, where `n=\dim(N)`. With the wedge product, `\Omega^*(M,\varphi)` inherits the -structure of a graded algebra. +structure of a graded algebra. See :class:`MixedFormAlgebra` for details. + +This algebra is endowed with a natural chain complex structure induced by the +exterior derivative. The corresponding homology is called *de Rham cohomology*. +See :class:`DeRhamCohomologyRing` for details. AUTHORS: @@ -15,7 +19,7 @@ """ #****************************************************************************** -# Copyright (C) 2019 Michael Jung +# Copyright (C) 2019-2021 Michael Jung # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -26,14 +30,18 @@ from sage.misc.cachefunc import cached_method from sage.structure.parent import Parent from sage.categories.graded_algebras import GradedAlgebras +from sage.structure.element import RingElement +from sage.categories.rings import Rings +from sage.categories.chain_complexes import ChainComplexes +from sage.categories.morphism import SetMorphism from sage.structure.unique_representation import UniqueRepresentation from sage.symbolic.ring import SR from sage.manifolds.differentiable.mixed_form import MixedForm class MixedFormAlgebra(Parent, UniqueRepresentation): r""" - An instance of this class represents the graded algebra of mixed form. That - is, if `\varphi: M \to N` is a differentiable map between two + An instance of this class represents the graded algebra of mixed forms. + That is, if `\varphi: M \to N` is a differentiable map between two differentiable manifolds `M` and `N`, the *graded algebra of mixed forms* `\Omega^*(M,\varphi)` *along* `\varphi` is defined via the direct sum `\bigoplus^{n}_{j=0} \Omega^j(M,\varphi)` consisting of differential form @@ -57,6 +65,20 @@ class MixedFormAlgebra(Parent, UniqueRepresentation): \Omega^k(M,\varphi) \wedge \Omega^l(M,\varphi) \subset \Omega^{k+l}(M,\varphi). + Moreover, `\Omega^*(M,\varphi)` inherits the structure of a chain complex, + called *de Rham complex*, with the exterior derivative as boundary map, + that is + + .. MATH:: + + 0 \rightarrow \Omega^0(M,\varphi) \xrightarrow{\mathrm{d}_0} + \Omega^1(M,\varphi) \xrightarrow{\mathrm{d}_1} \dots + \xrightarrow{\mathrm{d}_{n-1}} \Omega^n(M,\varphi) + \xrightarrow{\mathrm{d}_{n}} 0. + + The induced cohomology is called *de Rham cohomology*, see + :meth:`cohomology` or :class:`DeRhamCohomologyRing` respectively. + INPUT: - ``vector_field_module`` -- module `\mathfrak{X}(M,\varphi)` of vector @@ -72,7 +94,8 @@ class MixedFormAlgebra(Parent, UniqueRepresentation): Graded algebra Omega^*(M) of mixed differential forms on the 3-dimensional differentiable manifold M sage: Omega.category() - Category of graded algebras over Symbolic Ring + Join of Category of graded algebras over Symbolic Ring and Category of + chain complexes over Symbolic Ring sage: Omega.base_ring() Symbolic Ring sage: Omega.vector_field_module() @@ -165,8 +188,8 @@ def __init__(self, vector_field_module): base_field = domain.base_field() if domain.base_field_type() in ['real', 'complex']: base_field = SR - Parent.__init__(self, base=base_field, - category=GradedAlgebras(base_field)) + category = GradedAlgebras(base_field) & ChainComplexes(base_field) + Parent.__init__(self, base=base_field, category=category) # Define attributes: self._domain = domain self._ambient_domain = vector_field_module._ambient_domain @@ -407,6 +430,83 @@ def _latex_(self): """ return self._latex_name + def differential(self, degree=None): + r""" + Return the differential of the de Rham complex ``self`` given by the + exterior derivative. + + INPUT: + + - ``degree`` -- (default: ``None``) degree of the differential + operator; if none is provided, the differential operator on + ``self`` is returned. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: d = C.differential(); d + Generic endomorphism of Graded algebra Omega^*(M) of mixed + differential forms on the 2-dimensional differentiable manifold M + sage: d0 = C.differential(0); d0 + Generic morphism: + From: Algebra of differentiable scalar fields on the + 2-dimensional differentiable manifold M + To: Free module Omega^1(M) of 1-forms on the 2-dimensional + differentiable manifold M + sage: f = M.scalar_field(x, name='f'); f.display() + f: M --> R + (x, y) |--> x + sage: d0(f).display() + df = dx + + """ + if degree is None: + domain = codomain = self + else: + domain = self._domain.diff_form_module(degree) + codomain = self._domain.diff_form_module(degree + 1) + return SetMorphism(domain.Hom(codomain), lambda x: x.derivative()) + + def cohomology(self, *args, **kwargs): + r""" + Return the de Rham cohomology of the de Rham complex ``self``. + + The `n`-th de Rham cohomology is given by + + .. MATH:: + + H^k_{\mathrm{dR}}(M, \varphi) = + \left. \mathrm{ker}(\mathrm{d}_k) \middle/ + \mathrm{im}(\mathrm{d}_{k-1}) \right. . + + The corresponding ring is given by + + .. MATH:: + + H^*_{\mathrm{dR}}(M, \varphi) = \bigoplus^n_{k=0} H^k_{\mathrm{dR}}(M, \varphi), + + endowed with the cup product as multiplication induced by the wedge + product. + + .. SEEALSO:: + + See :class:`DeRhamCohomologyRing` for details. + + EXAMPLES:: + + sage: M = Manifold(3, 'M', latex_name=r'\mathcal{M}') + sage: A = M.mixed_form_algebra() + sage: A.cohomology() + De Rham cohomology ring on the 3-dimensional differentiable + manifold M + + """ + return DeRhamCohomologyRing(self) + + homology = cohomology + def irange(self, start=None): r""" Single index generator. @@ -442,3 +542,485 @@ def irange(self, start=None): while i < imax: yield i i += 1 + + def lift_from_homology(self, x): + r""" + Lift a cohomology class to the algebra of mixed differential forms. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: alpha = M.diff_form(1, [1,1], name='alpha') + sage: alpha.display() + alpha = dx + dy + sage: a = H(alpha); a + [alpha] + sage: C.lift_from_homology(a) + Mixed differential form alpha on the 2-dimensional differentiable + manifold M + + """ + return x.lift() + +############################################################################### +## De Rham Cohomology + +class DeRhamCohomologyClass(RingElement): + r""" + Define a cohomology class in the de Rham cohomology ring. + + INPUT: + + - ``parent`` -- de Rham cohomology ring represented by an instance of + :class:`DeRhamCohomologyRing` + - ``representative`` -- a closed (mixed) differential form representing the + cohomology class + + .. NOTE:: + + The current implementation only provides basic features. Comparison via + exact forms are not supported at the time being. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: u = H(omega); u + [omega] + + Cohomology classes can be lifted to the algebra of mixed differential + forms:: + + sage: u.lift() + Mixed differential form omega on the 2-dimensional differentiable + manifold M + + However, comparison of two cohomology classes is limited the time being:: + + sage: eta = M.diff_form(1, [1,1], name='eta') + sage: H(eta) == u + True + sage: H.one() == u + Traceback (most recent call last): + ... + NotImplementedError: comparison via exact forms is currently not supported + + """ + def __init__(self, parent, representative): + r""" + Construct an element of the de Rham cohomology ring. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega', latex_name=r'\omega') + sage: u = H(omega) + sage: TestSuite(u).run(skip=['_test_eq', '_test_nonzero_equal']) # equality not fully supported yet + + """ + super().__init__(parent=parent) + self._representative = representative + + def _repr_(self): + r""" + Return a string representation of the object. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H.an_element() # indirect doctest + [one] + sage: H.an_element()._repr_() + '[one]' + + """ + name = self._representative._name + if name is None: + name = 'unnamed form' + return f"[{name}]" + + def _latex_(self): + r""" + Return a LaTeX representation of the object. + + TESTS:: + + sage: M = Manifold(2, 'M', latex_name=r'\mathcal{M}') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega', latex_name=r'\omega') + sage: u = H(omega) + sage: latex(u) # indirect doctest + \left[\omega\right] + sage: u._latex_() + '\\left[\\omega\\right]' + + """ + latex_name = self._representative._latex_name + if latex_name is None: + latex_name = r'\mathrm{unnamed form}' + return rf"\left[{latex_name}\right]" + + def representative(self): + r""" + Return a representative of ``self`` in the associated de Rham + complex. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(2, name='omega') + sage: omega[0,1] = x + sage: omega.display() + omega = x dx/\dy + sage: u = H(omega); u + [omega] + sage: u.representative() + Mixed differential form omega on the 2-dimensional differentiable + manifold M + + """ + return self._representative + + lift = representative + + def _add_(self, other): + r""" + Addition of two cohomology classes. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,-1], name='eta') + sage: H(omega) + H(eta) + [omega+eta] + + """ + return self.parent()(self._representative + other._representative) + + def cup(self, other): + r""" + Cup product of two cohomology classes. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,-1], name='eta') + sage: H(omega).cup(H(eta)) + [omega/\eta] + + """ + return self.parent()(self._representative.wedge(other._representative)) + + _mul_ = cup + + def _sub_(self, other): + r""" + Subtraction of two cohomology classes. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,-1], name='eta') + sage: H(omega) - H(eta) + [omega-eta] + + """ + return self.parent()(self._representative - other._representative) + + def __eq__(self, other): + r""" + Comparison (equality) operator. + + .. WARNING:: + + At current stage, the equality operator only checks whether the + representatives are equal. No further checks are supported so far. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,1], name='eta') + sage: H(omega) == H(eta) + True + sage: H(omega) == H.one() + Traceback (most recent call last): + ... + NotImplementedError: comparison via exact forms is currently not supported + + """ + if self is other: + return True + if isinstance(other, type(self)): + if self.representative() == other.representative(): + return True + raise NotImplementedError('comparison via exact forms is currently not supported') + +class DeRhamCohomologyRing(Parent, UniqueRepresentation): + r""" + Define the de Rham cohomology ring of a de Rham complex. + + Let + + .. MATH:: + + 0 \rightarrow \Omega^0(M,\varphi) \xrightarrow{\mathrm{d}_0} + \Omega^1(M,\varphi) \xrightarrow{\mathrm{d}_1} \dots + \xrightarrow{\mathrm{d}_{n-1}} \Omega^n(M,\varphi) + \xrightarrow{\mathrm{d}_{n}} 0 + + be the de Rham complex on a differentiable manifold `M` along a + differentiable map `\varphi: M \to N`. Then the `k`-th de Rham cohomology + is given by + + .. MATH:: + + H^k_{\mathrm{dR}}(M, \varphi) = + \left. \mathrm{ker}(\mathrm{d}_k) \middle/ + \mathrm{im}(\mathrm{d}_{k-1}) \right. , + + and corresponding ring is obtained by + + .. MATH:: + + H^*_{\mathrm{dR}}(M, \varphi) = \bigoplus^n_{k=0} H^k_{\mathrm{dR}}(M, \varphi). + + This ring is naturally endowed with a multiplication induced by the wedge + product, called *cup product*, see :meth:`DeRhamCohomologyClass.cup`. + + .. NOTE:: + + The current implementation only provides basic features. Comparison via + exact forms are not supported at the time being. + + INPUT: + + - ``de_rham_complex`` -- a de Rham complex, typically an instance of + :class:`MixedFormAlgebra` + + EXAMPLES: + + We define the de Rham cohomology ring on a parallelizable manifold `M`:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology(); H + De Rham cohomology ring on the 2-dimensional differentiable manifold M + + Its elements are induced by closed differential forms on `M`:: + + sage: beta = M.diff_form(1, [1,0], name='beta') + sage: beta.display() + beta = dx + sage: d1 = C.differential(1) + sage: d1(beta).display() + dbeta = 0 + sage: b = H(beta); b + [beta] + + Cohomology classes can be lifted to the algebra of mixed differential + forms:: + + sage: b.representative() + Mixed differential form beta on the 2-dimensional differentiable + manifold M + + The ring admits a zero and unit element:: + + sage: H.zero() + [zero] + sage: H.one() + [one] + + """ + def __init__(self, de_rham_complex): + r""" + Construct the de Rham cohomology ring. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology(); H + De Rham cohomology ring on the 2-dimensional differentiable + manifold M + sage: TestSuite(H).run(skip=['_test_elements', + ....: '_test_elements_eq_symmetric', + ....: '_test_elements_eq_transitive', + ....: '_test_elements_neq']) # equality not fully supported yet + + """ + Parent.__init__(self, category=Rings()) + self._de_rham_complex = self._module = de_rham_complex + self._manifold = de_rham_complex._domain + + Element = DeRhamCohomologyClass + + def _element_constructor_(self, x): + r""" + Construct an element of ``self``. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H._element_constructor_(C.one()) + [one] + + Non-cycle element:: + + sage: omega = M.diff_form(1, name='omega') + sage: omega[0] = y + sage: omega.display() + omega = y dx + sage: H(omega) + Traceback (most recent call last): + ... + ValueError: Mixed differential form omega on the 2-dimensional + differentiable manifold M must be a closed form + + """ + if x not in self._module: + raise TypeError(f"{x} must be an element of {self._module}") + x = self._module(x) + if x.derivative() != 0: + raise ValueError(f"{x} must be a closed form") + return self.element_class(self, x) + + def _repr_(self): + r""" + Return a string representation of the object. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology(); H + De Rham cohomology ring on the 2-dimensional differentiable + manifold M + + """ + desc = "De Rham cohomology ring " + if self._module._dest_map is self._manifold.identity_map(): + desc += "on the {}".format(self._manifold) + else: + desc += "along the {} mapped ".format(self._manifold) + desc += "into the {} ".format(self._module._ambient_domain) + if self._module._dest_map._name is None: + dm_name = "unnamed map" + else: + dm_name = self._module._dest_map._name + desc += "via " + dm_name + return desc + + def _latex_(self): + r""" + Return a LaTeX representation of the object. + + TESTS:: + + sage: M = Manifold(3, 'M', latex_name=r'\mathcal{M}') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H._latex_() + 'H^*_{\\mathrm{dR}}\\left(\\mathcal{M}\\right)' + sage: latex(H) # indirect doctest + H^*_{\mathrm{dR}}\left(\mathcal{M}\right) + + """ + latex_name = r"H^*_{\mathrm{dR}}\left(" + self._manifold._latex_name + if self._module._dest_map is not self._manifold.identity_map(): + dm_latex_name = self._module._dest_map._latex_name + if dm_latex_name is None: + dm_latex_name = r"\mathrm{unnamed\; map}" + latex_name += "," + dm_latex_name + latex_name += r"\right)" + return latex_name + + def _an_element_(self): + r""" + Return an element of ``self``. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H.an_element() + [one] + + """ + return self.one() + + @cached_method + def zero(self): + r""" + Return the zero element of ``self``. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H.zero() + [zero] + sage: H.zero().representative() + Mixed differential form zero on the 2-dimensional differentiable + manifold M + + """ + return self.element_class(self, self._module.zero()) + + @cached_method + def one(self): + r""" + Return the one element of ``self``. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H.one() + [one] + sage: H.one().representative() + Mixed differential form one on the 2-dimensional differentiable + manifold M + + """ + return self.element_class(self, self._module.one()) From fb8331abc00f6886b511819bff1e4116bb2e6d30 Mon Sep 17 00:00:00 2001 From: Michael Jung Date: Wed, 28 Apr 2021 09:21:46 +0200 Subject: [PATCH 142/232] Trac #31691: fix doctest in manifold.py --- src/sage/manifolds/differentiable/manifold.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index f720158cba3..d53ffb7cca5 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -1521,7 +1521,7 @@ def mixed_form_algebra(self, dest_map=None): Graded algebra Omega^*(M) of mixed differential forms on the 2-dimensional differentiable manifold M sage: M.mixed_form_algebra().category() - Category of graded algebras over Symbolic Ring + Join of Category of graded algebras over Symbolic Ring and Category of chain complexes over Symbolic Ring sage: M.mixed_form_algebra().base_ring() Symbolic Ring From f8160a3c07931345e8c1a8ee33740d75f9c319d8 Mon Sep 17 00:00:00 2001 From: David Roe Date: Wed, 28 Apr 2021 18:46:31 -0400 Subject: [PATCH 143/232] Fix trivial test failures --- src/sage/modular/local_comp/local_comp.py | 2 +- src/sage/modular/local_comp/smoothchar.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/modular/local_comp/local_comp.py b/src/sage/modular/local_comp/local_comp.py index 645b03c3d75..8bfbb4db16f 100644 --- a/src/sage/modular/local_comp/local_comp.py +++ b/src/sage/modular/local_comp/local_comp.py @@ -24,7 +24,7 @@ from sage.rings.all import ZZ, QQbar, PolynomialRing, polygen from sage.misc.abstract_method import abstract_method from sage.misc.cachefunc import cached_method -from sage.misc.misc import verbose +from sage.misc.verbose import verbose from sage.modular.modform.element import Newform from sage.structure.sequence import Sequence diff --git a/src/sage/modular/local_comp/smoothchar.py b/src/sage/modular/local_comp/smoothchar.py index 3ef3daae36a..5e5f7765c2e 100644 --- a/src/sage/modular/local_comp/smoothchar.py +++ b/src/sage/modular/local_comp/smoothchar.py @@ -48,7 +48,7 @@ from sage.misc.abstract_method import abstract_method from sage.misc.cachefunc import cached_method from sage.misc.misc_c import prod -from sage.misc.misc import verbose +from sage.misc.verbose import verbose from sage.misc.mrange import xmrange from sage.modular.dirichlet import DirichletGroup from sage.rings.all import QQ, ZZ, Zmod, NumberField From 44702c545badda7f83dd0a9fcbc789019540dd97 Mon Sep 17 00:00:00 2001 From: David Roe Date: Thu, 29 Apr 2021 17:29:07 -0400 Subject: [PATCH 144/232] Add some comments for attributes required by galois mixin classes --- src/sage/groups/galois_group.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sage/groups/galois_group.py b/src/sage/groups/galois_group.py index 4c29950f723..8fe9651cf99 100644 --- a/src/sage/groups/galois_group.py +++ b/src/sage/groups/galois_group.py @@ -43,7 +43,14 @@ class _GMixin: r""" This class provides some methods for Galois groups to be used for both permutation groups and abelian groups, subgroups and full Galois groups. + + It is just intended to provide common functionality between various different Galois group classes. """ + # This class uses the following attributes, which should be defined in any subclass + # * _default_algorithm -- a string, the default algorithm used to compute the Galois group + # * _gcdata -- a pair, the Galois closure and an embedding of the top field into it + + def _get_algorithm(self, algorithm): r""" Allows overriding the default algorithm specified at object creation. @@ -112,6 +119,8 @@ class _GaloisMixin(_GMixin): This class provides methods for Galois groups, allowing concrete instances to inherit from both permutation group and abelian group classes. """ + # In addition to the attributes from _Gmixin, this class uses the following attributes, which should be defined in any subclass + # * _field -- the top field def _repr_(self): """ String representation of this Galois group @@ -205,6 +214,9 @@ class _SubGaloisMixin(_GMixin): This class provides methods for subgroups of Galois groups, allowing concrete instances to inherit from both permutation group and abelian group classes. """ + # In addition to the attributes from _Gmixin, this class uses the following attributes, which should be defined in any subclass + # * _ambient_group -- the ambient Galois group of which this is a subgroup + @lazy_attribute def _gcdata(self): """ From 1681b79a2a889c9a9d21eed4d4eea406ed06196c Mon Sep 17 00:00:00 2001 From: David Roe Date: Fri, 30 Apr 2021 04:47:06 -0400 Subject: [PATCH 145/232] Fix doctest failure --- src/sage/modular/local_comp/local_comp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/modular/local_comp/local_comp.py b/src/sage/modular/local_comp/local_comp.py index 1fccce9083c..06b49fc17bc 100644 --- a/src/sage/modular/local_comp/local_comp.py +++ b/src/sage/modular/local_comp/local_comp.py @@ -703,8 +703,8 @@ def characters(self): sage: Newforms(DirichletGroup(64, QQ).1, 2, names='a')[0].local_component(2).characters() # long time [ - Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> -1/4*a0, 4*s + 1 |--> -1, -1 |--> 1, 2 |--> 1, - Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> -1/4*a0, 4*s + 1 |--> 1, -1 |--> 1, 2 |--> 1 + Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> 1/2*a0, 4*s + 1 |--> 1, -1 |--> 1, 2 |--> 1, + Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> 1/2*a0, 4*s + 1 |--> -1, -1 |--> 1, 2 |--> 1 ] sage: Newform('243a',names='a').local_component(3).characters() # long time [ From 566176a74a480456b7836c13a173e217b721b70a Mon Sep 17 00:00:00 2001 From: Michael Jung Date: Fri, 30 Apr 2021 23:38:57 +0200 Subject: [PATCH 146/232] Trac #31691: new file + improved doc + cup product delegates to * --- .../en/reference/manifolds/diff_manifold.rst | 2 + .../differentiable/de_rham_cohomology.py | 505 ++++++++++++++++++ .../differentiable/mixed_form_algebra.py | 472 +--------------- 3 files changed, 513 insertions(+), 466 deletions(-) create mode 100644 src/sage/manifolds/differentiable/de_rham_cohomology.py diff --git a/src/doc/en/reference/manifolds/diff_manifold.rst b/src/doc/en/reference/manifolds/diff_manifold.rst index 0d061007fe2..31e86015d97 100644 --- a/src/doc/en/reference/manifolds/diff_manifold.rst +++ b/src/doc/en/reference/manifolds/diff_manifold.rst @@ -24,6 +24,8 @@ Differentiable Manifolds mixed_form + sage/manifolds/differentiable/de_rham_cohomology + multivector sage/manifolds/differentiable/affine_connection diff --git a/src/sage/manifolds/differentiable/de_rham_cohomology.py b/src/sage/manifolds/differentiable/de_rham_cohomology.py new file mode 100644 index 00000000000..9bcc82efe45 --- /dev/null +++ b/src/sage/manifolds/differentiable/de_rham_cohomology.py @@ -0,0 +1,505 @@ +r""" +De Rham Cohomology + +Let `M` and `N` be differentiable manifolds and `\varphi\colon M \to N` be +a differentiable map. Then the associated de Rham complex is given by + +.. MATH:: + + 0 \rightarrow \Omega^0(M,\varphi) \xrightarrow{\mathrm{d}_0} + \Omega^1(M,\varphi) \xrightarrow{\mathrm{d}_1} \dots + \xrightarrow{\mathrm{d}_{n-1}} \Omega^n(M,\varphi) + \xrightarrow{\mathrm{d}_{n}} 0, + +where `\Omega^k(M,\varphi)` is the module of differential forms of degree `k`, +and `d_k` is the associated exterior derivative. Then the `k`-*th de Rham +cohomology group* is given by + +.. MATH:: + + H^k_{\mathrm{dR}}(M, \varphi) = + \left. \mathrm{ker}(\mathrm{d}_k) \middle/ + \mathrm{im}(\mathrm{d}_{k-1}) \right. , + +and the corresponding ring is obtained by + +.. MATH:: + + H^*_{\mathrm{dR}}(M, \varphi) = \bigoplus^n_{k=0} H^k_{\mathrm{dR}}(M, \varphi). + +The de Rham cohomology ring is implemented via :class:`DeRhamCohomologyRing`. +Its elements, the cohomology classes, are represented by +:class:`DeRhamCohomologyClass`. + +AUTHORS: + +- Michael Jung (2021) : initial version + +""" + +#****************************************************************************** +# Copyright (C) 2021 Michael Jung +# +# 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/ +#****************************************************************************** + +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.cachefunc import cached_method +from sage.structure.parent import Parent +from sage.structure.element import RingElement +from sage.categories.rings import Rings + +class DeRhamCohomologyClass(RingElement): + r""" + Define a cohomology class in the de Rham cohomology ring. + + INPUT: + + - ``parent`` -- de Rham cohomology ring represented by an instance of + :class:`DeRhamCohomologyRing` + - ``representative`` -- a closed (mixed) differential form representing the + cohomology class + + .. NOTE:: + + The current implementation only provides basic features. Comparison via + exact forms are not supported at the time being. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: u = H(omega); u + [omega] + + Cohomology classes can be lifted to the algebra of mixed differential + forms:: + + sage: u.lift() + Mixed differential form omega on the 2-dimensional differentiable + manifold M + + However, comparison of two cohomology classes is limited the time being:: + + sage: eta = M.diff_form(1, [1,1], name='eta') + sage: H(eta) == u + True + sage: H.one() == u + Traceback (most recent call last): + ... + NotImplementedError: comparison via exact forms is currently not supported + + """ + def __init__(self, parent, representative): + r""" + Construct an element of the de Rham cohomology ring. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega', latex_name=r'\omega') + sage: u = H(omega) + sage: TestSuite(u).run(skip=['_test_eq', '_test_nonzero_equal']) # equality not fully supported yet + + """ + super().__init__(parent=parent) + self._representative = representative + + def _repr_(self): + r""" + Return a string representation of the object. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H.an_element() # indirect doctest + [one] + sage: H.an_element()._repr_() + '[one]' + + """ + name = self._representative._name + if name is None: + name = 'unnamed form' + return f"[{name}]" + + def _latex_(self): + r""" + Return a LaTeX representation of the object. + + TESTS:: + + sage: M = Manifold(2, 'M', latex_name=r'\mathcal{M}') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega', latex_name=r'\omega') + sage: u = H(omega) + sage: latex(u) # indirect doctest + \left[\omega\right] + sage: u._latex_() + '\\left[\\omega\\right]' + + """ + latex_name = self._representative._latex_name + if latex_name is None: + latex_name = r'\mathrm{unnamed form}' + return rf"\left[{latex_name}\right]" + + def representative(self): + r""" + Return a representative of ``self`` in the associated de Rham + complex. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(2, name='omega') + sage: omega[0,1] = x + sage: omega.display() + omega = x dx/\dy + sage: u = H(omega); u + [omega] + sage: u.representative() + Mixed differential form omega on the 2-dimensional differentiable + manifold M + + """ + return self._representative + + lift = representative + + def _add_(self, other): + r""" + Addition of two cohomology classes. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,-1], name='eta') + sage: H(omega) + H(eta) + [omega+eta] + + """ + return self.parent()(self.representative() + other.representative()) + + def cup(self, other): + r""" + Cup product of two cohomology classes. + + INPUT: + + - ``other``-- another cohomology class in the de Rham cohomology + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,-1], name='eta') + sage: H(omega).cup(H(eta)) + [omega/\eta] + + """ + return self * other + + def _mul_(self, other): + r""" + Cup product of two cohomology classes. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,-1], name='eta') + sage: H(omega) * H(eta) + [omega/\eta] + + """ + return self.parent()(self.representative().wedge(other.representative())) + + def _sub_(self, other): + r""" + Subtraction of two cohomology classes. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,-1], name='eta') + sage: H(omega) - H(eta) + [omega-eta] + + """ + return self.parent()(self.representative() - other.representative()) + + def __eq__(self, other): + r""" + Comparison (equality) operator. + + .. WARNING:: + + At current stage, the equality operator only checks whether the + representatives are equal. No further checks are supported so far. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,1], name='eta') + sage: H(omega) == H(eta) + True + sage: H(omega) == H.one() + Traceback (most recent call last): + ... + NotImplementedError: comparison via exact forms is currently not supported + + """ + if self is other: + return True + if isinstance(other, type(self)): + if self.representative() == other.representative(): + return True + raise NotImplementedError('comparison via exact forms is currently not supported') + +class DeRhamCohomologyRing(Parent, UniqueRepresentation): + r""" + The de Rham cohomology ring of a de Rham complex. + + This ring is naturally endowed with a multiplication induced by the wedge + product, called *cup product*, see :meth:`DeRhamCohomologyClass.cup`. + + .. NOTE:: + + The current implementation only provides basic features. Comparison via + exact forms are not supported at the time being. + + INPUT: + + - ``de_rham_complex`` -- a de Rham complex, typically an instance of + :class:`~sage.manifolds.differentiable.mixed_form_algebra.MixedFormAlgebra` + + EXAMPLES: + + We define the de Rham cohomology ring on a parallelizable manifold `M`:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology(); H + De Rham cohomology ring on the 2-dimensional differentiable manifold M + + Its elements are induced by closed differential forms on `M`:: + + sage: beta = M.diff_form(1, [1,0], name='beta') + sage: beta.display() + beta = dx + sage: d1 = C.differential(1) + sage: d1(beta).display() + dbeta = 0 + sage: b = H(beta); b + [beta] + + Cohomology classes can be lifted to the algebra of mixed differential + forms:: + + sage: b.representative() + Mixed differential form beta on the 2-dimensional differentiable + manifold M + + The ring admits a zero and unit element:: + + sage: H.zero() + [zero] + sage: H.one() + [one] + + """ + def __init__(self, de_rham_complex): + r""" + Construct the de Rham cohomology ring. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology(); H + De Rham cohomology ring on the 2-dimensional differentiable + manifold M + sage: TestSuite(H).run(skip=['_test_elements', + ....: '_test_elements_eq_symmetric', + ....: '_test_elements_eq_transitive', + ....: '_test_elements_neq']) # equality not fully supported yet + + """ + Parent.__init__(self, category=Rings()) + self._de_rham_complex = self._module = de_rham_complex + self._manifold = de_rham_complex._domain + + Element = DeRhamCohomologyClass + + def _element_constructor_(self, x): + r""" + Construct an element of ``self``. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H._element_constructor_(C.one()) + [one] + + Non-cycle element:: + + sage: omega = M.diff_form(1, name='omega') + sage: omega[0] = y + sage: omega.display() + omega = y dx + sage: H(omega) + Traceback (most recent call last): + ... + ValueError: Mixed differential form omega on the 2-dimensional + differentiable manifold M must be a closed form + + """ + if x not in self._module: + raise TypeError(f"{x} must be an element of {self._module}") + x = self._module(x) + if x.derivative() != 0: + raise ValueError(f"{x} must be a closed form") + return self.element_class(self, x) + + def _repr_(self): + r""" + Return a string representation of the object. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology(); H + De Rham cohomology ring on the 2-dimensional differentiable + manifold M + + """ + desc = "De Rham cohomology ring " + if self._module._dest_map is self._manifold.identity_map(): + desc += "on the {}".format(self._manifold) + else: + desc += "along the {} mapped ".format(self._manifold) + desc += "into the {} ".format(self._module._ambient_domain) + if self._module._dest_map._name is None: + dm_name = "unnamed map" + else: + dm_name = self._module._dest_map._name + desc += "via " + dm_name + return desc + + def _latex_(self): + r""" + Return a LaTeX representation of the object. + + TESTS:: + + sage: M = Manifold(3, 'M', latex_name=r'\mathcal{M}') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H._latex_() + 'H^*_{\\mathrm{dR}}\\left(\\mathcal{M}\\right)' + sage: latex(H) # indirect doctest + H^*_{\mathrm{dR}}\left(\mathcal{M}\right) + + """ + latex_name = r"H^*_{\mathrm{dR}}\left(" + self._manifold._latex_name + if self._module._dest_map is not self._manifold.identity_map(): + dm_latex_name = self._module._dest_map._latex_name + if dm_latex_name is None: + dm_latex_name = r"\mathrm{unnamed\; map}" + latex_name += "," + dm_latex_name + latex_name += r"\right)" + return latex_name + + def _an_element_(self): + r""" + Return an element of ``self``. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H.an_element() + [one] + + """ + return self.one() + + @cached_method + def zero(self): + r""" + Return the zero element of ``self``. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H.zero() + [zero] + sage: H.zero().representative() + Mixed differential form zero on the 2-dimensional differentiable + manifold M + + """ + return self.element_class(self, self._module.zero()) + + @cached_method + def one(self): + r""" + Return the one element of ``self``. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H.one() + [one] + sage: H.one().representative() + Mixed differential form one on the 2-dimensional differentiable + manifold M + + """ + return self.element_class(self, self._module.one()) diff --git a/src/sage/manifolds/differentiable/mixed_form_algebra.py b/src/sage/manifolds/differentiable/mixed_form_algebra.py index 88f2041f293..145fb64cf96 100644 --- a/src/sage/manifolds/differentiable/mixed_form_algebra.py +++ b/src/sage/manifolds/differentiable/mixed_form_algebra.py @@ -30,8 +30,6 @@ from sage.misc.cachefunc import cached_method from sage.structure.parent import Parent from sage.categories.graded_algebras import GradedAlgebras -from sage.structure.element import RingElement -from sage.categories.rings import Rings from sage.categories.chain_complexes import ChainComplexes from sage.categories.morphism import SetMorphism from sage.structure.unique_representation import UniqueRepresentation @@ -437,9 +435,9 @@ def differential(self, degree=None): INPUT: - - ``degree`` -- (default: ``None``) degree of the differential - operator; if none is provided, the differential operator on - ``self`` is returned. + - ``degree`` -- (default: ``None``) degree of the differential + operator; if none is provided, the differential operator on + ``self`` is returned. EXAMPLES:: @@ -492,7 +490,8 @@ def cohomology(self, *args, **kwargs): .. SEEALSO:: - See :class:`DeRhamCohomologyRing` for details. + See :class:`~sage.manifolds.differentiable.de_rham_cohomology.DeRhamCohomologyRing` + for details. EXAMPLES:: @@ -503,6 +502,7 @@ def cohomology(self, *args, **kwargs): manifold M """ + from .de_rham_cohomology import DeRhamCohomologyRing return DeRhamCohomologyRing(self) homology = cohomology @@ -564,463 +564,3 @@ def lift_from_homology(self, x): """ return x.lift() - -############################################################################### -## De Rham Cohomology - -class DeRhamCohomologyClass(RingElement): - r""" - Define a cohomology class in the de Rham cohomology ring. - - INPUT: - - - ``parent`` -- de Rham cohomology ring represented by an instance of - :class:`DeRhamCohomologyRing` - - ``representative`` -- a closed (mixed) differential form representing the - cohomology class - - .. NOTE:: - - The current implementation only provides basic features. Comparison via - exact forms are not supported at the time being. - - EXAMPLES:: - - sage: M = Manifold(2, 'M') - sage: X. = M.chart() - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: omega = M.diff_form(1, [1,1], name='omega') - sage: u = H(omega); u - [omega] - - Cohomology classes can be lifted to the algebra of mixed differential - forms:: - - sage: u.lift() - Mixed differential form omega on the 2-dimensional differentiable - manifold M - - However, comparison of two cohomology classes is limited the time being:: - - sage: eta = M.diff_form(1, [1,1], name='eta') - sage: H(eta) == u - True - sage: H.one() == u - Traceback (most recent call last): - ... - NotImplementedError: comparison via exact forms is currently not supported - - """ - def __init__(self, parent, representative): - r""" - Construct an element of the de Rham cohomology ring. - - TESTS:: - - sage: M = Manifold(2, 'M') - sage: X. = M.chart() - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: omega = M.diff_form(1, [1,1], name='omega', latex_name=r'\omega') - sage: u = H(omega) - sage: TestSuite(u).run(skip=['_test_eq', '_test_nonzero_equal']) # equality not fully supported yet - - """ - super().__init__(parent=parent) - self._representative = representative - - def _repr_(self): - r""" - Return a string representation of the object. - - TESTS:: - - sage: M = Manifold(2, 'M') - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: H.an_element() # indirect doctest - [one] - sage: H.an_element()._repr_() - '[one]' - - """ - name = self._representative._name - if name is None: - name = 'unnamed form' - return f"[{name}]" - - def _latex_(self): - r""" - Return a LaTeX representation of the object. - - TESTS:: - - sage: M = Manifold(2, 'M', latex_name=r'\mathcal{M}') - sage: X. = M.chart() - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: omega = M.diff_form(1, [1,1], name='omega', latex_name=r'\omega') - sage: u = H(omega) - sage: latex(u) # indirect doctest - \left[\omega\right] - sage: u._latex_() - '\\left[\\omega\\right]' - - """ - latex_name = self._representative._latex_name - if latex_name is None: - latex_name = r'\mathrm{unnamed form}' - return rf"\left[{latex_name}\right]" - - def representative(self): - r""" - Return a representative of ``self`` in the associated de Rham - complex. - - EXAMPLES:: - - sage: M = Manifold(2, 'M') - sage: X. = M.chart() - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: omega = M.diff_form(2, name='omega') - sage: omega[0,1] = x - sage: omega.display() - omega = x dx/\dy - sage: u = H(omega); u - [omega] - sage: u.representative() - Mixed differential form omega on the 2-dimensional differentiable - manifold M - - """ - return self._representative - - lift = representative - - def _add_(self, other): - r""" - Addition of two cohomology classes. - - TESTS:: - - sage: M = Manifold(2, 'M') - sage: X. = M.chart() - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: omega = M.diff_form(1, [1,1], name='omega') - sage: eta = M.diff_form(1, [1,-1], name='eta') - sage: H(omega) + H(eta) - [omega+eta] - - """ - return self.parent()(self._representative + other._representative) - - def cup(self, other): - r""" - Cup product of two cohomology classes. - - EXAMPLES:: - - sage: M = Manifold(2, 'M') - sage: X. = M.chart() - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: omega = M.diff_form(1, [1,1], name='omega') - sage: eta = M.diff_form(1, [1,-1], name='eta') - sage: H(omega).cup(H(eta)) - [omega/\eta] - - """ - return self.parent()(self._representative.wedge(other._representative)) - - _mul_ = cup - - def _sub_(self, other): - r""" - Subtraction of two cohomology classes. - - TESTS:: - - sage: M = Manifold(2, 'M') - sage: X. = M.chart() - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: omega = M.diff_form(1, [1,1], name='omega') - sage: eta = M.diff_form(1, [1,-1], name='eta') - sage: H(omega) - H(eta) - [omega-eta] - - """ - return self.parent()(self._representative - other._representative) - - def __eq__(self, other): - r""" - Comparison (equality) operator. - - .. WARNING:: - - At current stage, the equality operator only checks whether the - representatives are equal. No further checks are supported so far. - - TESTS:: - - sage: M = Manifold(2, 'M') - sage: X. = M.chart() - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: omega = M.diff_form(1, [1,1], name='omega') - sage: eta = M.diff_form(1, [1,1], name='eta') - sage: H(omega) == H(eta) - True - sage: H(omega) == H.one() - Traceback (most recent call last): - ... - NotImplementedError: comparison via exact forms is currently not supported - - """ - if self is other: - return True - if isinstance(other, type(self)): - if self.representative() == other.representative(): - return True - raise NotImplementedError('comparison via exact forms is currently not supported') - -class DeRhamCohomologyRing(Parent, UniqueRepresentation): - r""" - Define the de Rham cohomology ring of a de Rham complex. - - Let - - .. MATH:: - - 0 \rightarrow \Omega^0(M,\varphi) \xrightarrow{\mathrm{d}_0} - \Omega^1(M,\varphi) \xrightarrow{\mathrm{d}_1} \dots - \xrightarrow{\mathrm{d}_{n-1}} \Omega^n(M,\varphi) - \xrightarrow{\mathrm{d}_{n}} 0 - - be the de Rham complex on a differentiable manifold `M` along a - differentiable map `\varphi: M \to N`. Then the `k`-th de Rham cohomology - is given by - - .. MATH:: - - H^k_{\mathrm{dR}}(M, \varphi) = - \left. \mathrm{ker}(\mathrm{d}_k) \middle/ - \mathrm{im}(\mathrm{d}_{k-1}) \right. , - - and corresponding ring is obtained by - - .. MATH:: - - H^*_{\mathrm{dR}}(M, \varphi) = \bigoplus^n_{k=0} H^k_{\mathrm{dR}}(M, \varphi). - - This ring is naturally endowed with a multiplication induced by the wedge - product, called *cup product*, see :meth:`DeRhamCohomologyClass.cup`. - - .. NOTE:: - - The current implementation only provides basic features. Comparison via - exact forms are not supported at the time being. - - INPUT: - - - ``de_rham_complex`` -- a de Rham complex, typically an instance of - :class:`MixedFormAlgebra` - - EXAMPLES: - - We define the de Rham cohomology ring on a parallelizable manifold `M`:: - - sage: M = Manifold(2, 'M') - sage: X. = M.chart() - sage: C = M.de_rham_complex() - sage: H = C.cohomology(); H - De Rham cohomology ring on the 2-dimensional differentiable manifold M - - Its elements are induced by closed differential forms on `M`:: - - sage: beta = M.diff_form(1, [1,0], name='beta') - sage: beta.display() - beta = dx - sage: d1 = C.differential(1) - sage: d1(beta).display() - dbeta = 0 - sage: b = H(beta); b - [beta] - - Cohomology classes can be lifted to the algebra of mixed differential - forms:: - - sage: b.representative() - Mixed differential form beta on the 2-dimensional differentiable - manifold M - - The ring admits a zero and unit element:: - - sage: H.zero() - [zero] - sage: H.one() - [one] - - """ - def __init__(self, de_rham_complex): - r""" - Construct the de Rham cohomology ring. - - TESTS:: - - sage: M = Manifold(2, 'M') - sage: X. = M.chart() - sage: C = M.de_rham_complex() - sage: H = C.cohomology(); H - De Rham cohomology ring on the 2-dimensional differentiable - manifold M - sage: TestSuite(H).run(skip=['_test_elements', - ....: '_test_elements_eq_symmetric', - ....: '_test_elements_eq_transitive', - ....: '_test_elements_neq']) # equality not fully supported yet - - """ - Parent.__init__(self, category=Rings()) - self._de_rham_complex = self._module = de_rham_complex - self._manifold = de_rham_complex._domain - - Element = DeRhamCohomologyClass - - def _element_constructor_(self, x): - r""" - Construct an element of ``self``. - - TESTS:: - - sage: M = Manifold(2, 'M') - sage: X. = M.chart() - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: H._element_constructor_(C.one()) - [one] - - Non-cycle element:: - - sage: omega = M.diff_form(1, name='omega') - sage: omega[0] = y - sage: omega.display() - omega = y dx - sage: H(omega) - Traceback (most recent call last): - ... - ValueError: Mixed differential form omega on the 2-dimensional - differentiable manifold M must be a closed form - - """ - if x not in self._module: - raise TypeError(f"{x} must be an element of {self._module}") - x = self._module(x) - if x.derivative() != 0: - raise ValueError(f"{x} must be a closed form") - return self.element_class(self, x) - - def _repr_(self): - r""" - Return a string representation of the object. - - TESTS:: - - sage: M = Manifold(2, 'M') - sage: C = M.de_rham_complex() - sage: H = C.cohomology(); H - De Rham cohomology ring on the 2-dimensional differentiable - manifold M - - """ - desc = "De Rham cohomology ring " - if self._module._dest_map is self._manifold.identity_map(): - desc += "on the {}".format(self._manifold) - else: - desc += "along the {} mapped ".format(self._manifold) - desc += "into the {} ".format(self._module._ambient_domain) - if self._module._dest_map._name is None: - dm_name = "unnamed map" - else: - dm_name = self._module._dest_map._name - desc += "via " + dm_name - return desc - - def _latex_(self): - r""" - Return a LaTeX representation of the object. - - TESTS:: - - sage: M = Manifold(3, 'M', latex_name=r'\mathcal{M}') - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: H._latex_() - 'H^*_{\\mathrm{dR}}\\left(\\mathcal{M}\\right)' - sage: latex(H) # indirect doctest - H^*_{\mathrm{dR}}\left(\mathcal{M}\right) - - """ - latex_name = r"H^*_{\mathrm{dR}}\left(" + self._manifold._latex_name - if self._module._dest_map is not self._manifold.identity_map(): - dm_latex_name = self._module._dest_map._latex_name - if dm_latex_name is None: - dm_latex_name = r"\mathrm{unnamed\; map}" - latex_name += "," + dm_latex_name - latex_name += r"\right)" - return latex_name - - def _an_element_(self): - r""" - Return an element of ``self``. - - TESTS:: - - sage: M = Manifold(2, 'M') - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: H.an_element() - [one] - - """ - return self.one() - - @cached_method - def zero(self): - r""" - Return the zero element of ``self``. - - EXAMPLES:: - - sage: M = Manifold(2, 'M') - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: H.zero() - [zero] - sage: H.zero().representative() - Mixed differential form zero on the 2-dimensional differentiable - manifold M - - """ - return self.element_class(self, self._module.zero()) - - @cached_method - def one(self): - r""" - Return the one element of ``self``. - - EXAMPLES:: - - sage: M = Manifold(2, 'M') - sage: C = M.de_rham_complex() - sage: H = C.cohomology() - sage: H.one() - [one] - sage: H.one().representative() - Mixed differential form one on the 2-dimensional differentiable - manifold M - - """ - return self.element_class(self, self._module.one()) From c2267ce939fdf1ffdfe0908025e8b3253487fe74 Mon Sep 17 00:00:00 2001 From: David Roe Date: Wed, 5 May 2021 11:55:21 -0400 Subject: [PATCH 147/232] Mostly style changes, one potential speedup --- src/sage/modular/local_comp/local_comp.py | 31 +++++++++++-------- src/sage/modular/local_comp/smoothchar.py | 36 +++++++++++++---------- src/sage/modular/local_comp/type_space.py | 13 ++++---- src/sage/modular/modform/element.py | 10 ++++--- 4 files changed, 52 insertions(+), 38 deletions(-) diff --git a/src/sage/modular/local_comp/local_comp.py b/src/sage/modular/local_comp/local_comp.py index 06b49fc17bc..38badc2f522 100644 --- a/src/sage/modular/local_comp/local_comp.py +++ b/src/sage/modular/local_comp/local_comp.py @@ -486,7 +486,7 @@ def characters(self): ] """ G = SmoothCharacterGroupQp(self.prime(), self.coefficient_field()) - t = ZZ( (self.newform().weight() - 2 - self.twist_factor()) / 2 ) + t = ZZ((self.newform().weight() - 2 - self.twist_factor()) / 2) chi1 = G.character(0, [self.newform()[self.prime()]]) * G.norm_character()**t chi2 = G.character(0, [self.prime()]) * self.central_character() / chi1 return Sequence([chi1, chi2], cr=True, universe=G) @@ -795,8 +795,10 @@ def characters(self): else: verbose(" Trace identity check works for both", level=1) - if B_fail and not A_fail: chi1, chi2 = chisA - elif A_fail and not B_fail: chi1, chi2 = chisB + if B_fail and not A_fail: + chi1, chi2 = chisA + elif A_fail and not B_fail: + chi1, chi2 = chisB else: raise ValueError("Something went wrong: can't identify the characters") @@ -817,20 +819,20 @@ def characters(self): G0 = SmoothCharacterGroupRamifiedQuadratic(p, 0, self.coefficient_field()) G1 = SmoothCharacterGroupRamifiedQuadratic(p, 1, self.coefficient_field()) q0 = G0.quotient_gens(n) - assert all([x.valuation(G0.ideal(1)) == 1 for x in q0]) + assert all(x.valuation(G0.ideal(1)) == 1 for x in q0) q1 = G1.quotient_gens(n) - assert all([x.valuation(G1.ideal(1)) == 1 for x in q1]) + assert all(x.valuation(G1.ideal(1)) == 1 for x in q1) t0 = [(~T.rho(q.matrix().list())).trace() for q in q0] t1 = [(~T.rho(q.matrix().list())).trace() for q in q1] - if all([x == 0 for x in t0 + t1]): + if all(x == 0 for x in t0 + t1): # Can't happen? raise NotImplementedError( "Can't identify ramified quadratic extension -- all traces zero" ) - elif all([x ==0 for x in t1]): - G,qs,ts = G0, q0, t0 - elif all([x==0 for x in t0]): - G,qs,ts = G1, q1, t1 + elif all(x == 0 for x in t1): + G, qs, ts = G0, q0, t0 + elif all(x == 0 for x in t0): + G, qs, ts = G1, q1, t1 else: # At least one of the traces is *always* 0, since the type # space has to be isomorphic to its twist by the (ramified @@ -890,7 +892,8 @@ def characters(self): B_fail = 1 for u in G.ideal(n).invertible_residues(): - if A_fail or B_fail: break + if A_fail or B_fail: + break x = q*u verbose("testing x = %s" % x, level=1) ti = (~T.rho(x.matrix().list())).trace() * p**ZZ((k-2+self.twist_factor())/2) @@ -900,8 +903,10 @@ def characters(self): if chisB[0](x) + chisB[1](x) != ti: B_fail = 1 - if B_fail and not A_fail: chi1, chi2 = chisA - elif A_fail and not B_fail: chi1, chi2 = chisB + if B_fail and not A_fail: + chi1, chi2 = chisA + elif A_fail and not B_fail: + chi1, chi2 = chisB else: raise ValueError("Something went wrong: can't identify the characters") diff --git a/src/sage/modular/local_comp/smoothchar.py b/src/sage/modular/local_comp/smoothchar.py index 5c3ef8d2b0f..7875f376461 100644 --- a/src/sage/modular/local_comp/smoothchar.py +++ b/src/sage/modular/local_comp/smoothchar.py @@ -1143,7 +1143,8 @@ def discrete_log(self, level, x, gens=None): [1, 2, 0] """ x = self.number_field().coerce(x) - if x == 0: raise ValueError( "cannot evaluate at zero" ) + if x == 0: + raise ValueError( "cannot evaluate at zero" ) if gens is None: n1 = x.valuation(self.ideal(1)) x1 = x / self.unit_gens(0)[-1] ** n1 @@ -1155,7 +1156,7 @@ def discrete_log(self, level, x, gens=None): P = self.ideal(1) I = self.ideal(level) gens = [self.number_field().coerce(g) for g in gens] - i = min([i for i in range(len(gens)) if gens[i].valuation(P) == 1]) # lazy! + i = min(i for i in range(len(gens)) if gens[i].valuation(P) == 1) # lazy! pi = gens[i] genvals = [] genunits = [] @@ -1231,24 +1232,25 @@ def quotient_gens(self, n): A = ZZ**d R = [A.gen(i)*es[i] for i in range(d)] r = I.smallest_integer() - S = [] - for s in Zmod(r).unit_gens() + (p,): - S.append( self.discrete_log(n, ZZ(s)) ) + S = [self.discrete_log(n, ZZ(s)) for s in Zmod(r).unit_gens() + (p,)] Q = A / A.span(R + S) + t = None qgs = [] for v in Q.gens(): # choose a "nice" representative vv = v.lift() - if vv[-1] < 0: vv *= -1 + if vv[-1] < 0: + vv *= -1 while vv[-1] not in [0, 1]: - t = self.discrete_log(n, p) + if t is None: + t = self.discrete_log(n, p) vv = [vv[i] - t[i] for i in range(d)] assert (Q(A(vv)) == v or Q(A(vv)) == -v) - qgs.append( I.reduce(prod([gs[i] ** (vv[i] % es[i]) for i in range(d-1)])) * gs[-1]**vv[-1] ) + qgs.append( I.reduce(prod(gs[i] ** (vv[i] % es[i]) for i in range(d-1))) * gs[-1]**vv[-1] ) if len(qgs) == 2: - x,y = qgs - return [x*y, y] + x, y = qgs + return [x * y, y] else: return qgs @@ -1273,7 +1275,7 @@ def _reduce_Qp(self, level, x): """ p = self.prime() r = ZZ(x.norm().valuation(p) / 2) - y = x / p ** r + y = x / p**r if p==2 and y.trace().valuation(2) < 1: raise ValueError("%s not congruent mod %s to an elt of Qp" % (x, self.ideal(level))) Y = (y.trace() / 2) % self.ideal(level).smallest_integer() @@ -1643,7 +1645,8 @@ def __init__(self, prime, flag, base_ring, names='s'): sage: TestSuite(G3).run() """ prime = ZZ(prime) - if prime == 2: raise NotImplementedError( "Wildly ramified extensions not supported" ) + if prime == 2: + raise NotImplementedError( "Wildly ramified extensions not supported" ) SmoothCharacterGroupGeneric.__init__(self, prime, base_ring) self._name = names if flag not in [0, 1]: @@ -1651,14 +1654,15 @@ def __init__(self, prime, flag, base_ring, names='s'): self._flag = flag # Find an integer a such that sqrt(a*p) generates the right field and ZZ(sqrt(a*p)) is integrally closed - for a in range(4*prime): - if (not a%prime) or (not ZZ(a).is_squarefree()) or ( (a*prime) % 4 == 1): + for a in range(4 * prime): + if (not a % prime) or (not ZZ(a).is_squarefree()) or ((a * prime) % 4 == 1): continue if (flag == 0 and Zmod(prime)(a).is_square()) or \ (flag == 1 and not Zmod(prime)(a).is_square()): - self._unif_sqr = a*prime + self._unif_sqr = a * prime break - else: raise ValueError("Can't get here") + else: + raise ValueError("Can't get here") def change_ring(self, ring): r""" diff --git a/src/sage/modular/local_comp/type_space.py b/src/sage/modular/local_comp/type_space.py index fbcef6677ea..dd5bcb05673 100644 --- a/src/sage/modular/local_comp/type_space.py +++ b/src/sage/modular/local_comp/type_space.py @@ -317,7 +317,7 @@ def group(self): n = self.tame_level() chi = self.form().character() tame_H = [i for i in chi.kernel() if (i % p**r) == 1] - wild_H = [crt(x, 1, p**r, n) for x in range(p**r) if x% (p**d) == 1] + wild_H = [crt(x, 1, p**r, n) for x in range(p**r) if x % (p**d) == 1] return GammaH(n * p**r, tame_H + wild_H) ############################################################################### @@ -688,7 +688,9 @@ def rho(self, g): a = self._a if not (f % 8): - if d % 4 == 3: return self.rho([-g[0], g[1], -g[2], g[3]]) * self.t_space.star_involution().matrix().transpose() + if d % 4 == 3: + return (self.rho([-g[0], g[1], -g[2], g[3]]) * + self.t_space.star_involution().matrix().transpose()) i = 0 while (d * a**i) % f != 1: @@ -727,6 +729,7 @@ def _unif_ramified(self): """ p = self.prime() k = self.form().weight() - return self.t_space.atkin_lehner_operator(p).matrix().transpose() \ - * p ** ( -(k-2)*self.u() ) \ - * self.t_space.diamond_bracket_matrix( crt(1, p**self.u(), p**self.u(), self.tame_level()) ).transpose() + return (self.t_space.atkin_lehner_operator(p).matrix().transpose() + * p ** ( -(k-2)*self.u() ) + * self.t_space.diamond_bracket_matrix( + crt(1, p**self.u(), p**self.u(), self.tame_level())).transpose()) diff --git a/src/sage/modular/modform/element.py b/src/sage/modular/modform/element.py index 76fa77b4c99..f3bfcc23c39 100644 --- a/src/sage/modular/modform/element.py +++ b/src/sage/modular/modform/element.py @@ -2083,7 +2083,7 @@ def twist(self, chi, level=None, check=True): - ``level`` -- (optional) the level `N` of the twisted form. If `N` is not given, the algorithm tries to compute `N` using [AL1978]_, Theorem 3.1; if this is not possible, it returns an error. If `N` is - given but incorrect, i.e.~the twisted form does not have level `N`, + given but incorrect, i.e. the twisted form does not have level `N`, then this function will attempt to detect this and return an error, but it may sometimes return an incorrect answer (a newform of level `N` whose first few coefficients agree with those of `f \otimes @@ -2229,7 +2229,7 @@ def minimal_twist(self, p=None): if not (p.is_prime() and p.divides(N)): raise ValueError("p should be prime factor of N") - if (r==c) or (r==1 and c==0): + if (r == c) or (r == 1 and c == 0): # easy cases return (self, DirichletGroup(1, self.base_ring())(1)) elif r < 2*c: @@ -2238,7 +2238,8 @@ def minimal_twist(self, p=None): # twist is minimal. candidates = [] for chi in DirichletGroup(p**(r-c), self.base_ring()): - if not chi.is_primitive(): continue + if not chi.is_primitive(): + continue try: g = self.twist(chi, level=N//p**(r-c)) candidates.append( (g, chi) ) @@ -2248,7 +2249,8 @@ def minimal_twist(self, p=None): l = ZZ(1) while len(candidates) > 1: l = l.next_prime() - if l==p: continue + if l == p: + continue candidates = [(h, chi) for (h, chi) in candidates if h[l] == chi(l)*self[l] ] if l > 10000 or len(candidates) == 0: raise RuntimeError("bug finding minimal twist") From d0c9d58f3323d86c5344b57fcd74f469ecef78d9 Mon Sep 17 00:00:00 2001 From: David Roe Date: Wed, 5 May 2021 11:56:42 -0400 Subject: [PATCH 148/232] Mark fragile doctest as random --- src/sage/modular/local_comp/local_comp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modular/local_comp/local_comp.py b/src/sage/modular/local_comp/local_comp.py index 38badc2f522..aa74ea78897 100644 --- a/src/sage/modular/local_comp/local_comp.py +++ b/src/sage/modular/local_comp/local_comp.py @@ -701,7 +701,7 @@ def characters(self): Examples where `K^\times / \QQ_p^\times` is not topologically cyclic (which complicates the computations greatly):: - sage: Newforms(DirichletGroup(64, QQ).1, 2, names='a')[0].local_component(2).characters() # long time + sage: Newforms(DirichletGroup(64, QQ).1, 2, names='a')[0].local_component(2).characters() # long time, random [ Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> 1/2*a0, 4*s + 1 |--> 1, -1 |--> 1, 2 |--> 1, Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> 1/2*a0, 4*s + 1 |--> -1, -1 |--> 1, 2 |--> 1 From 7d4dd1bc2c9899b2c1261676f858e1eae9916d7a Mon Sep 17 00:00:00 2001 From: David Roe Date: Wed, 5 May 2021 12:01:41 -0400 Subject: [PATCH 149/232] Some more style fixes --- src/sage/modular/hecke/element.py | 3 ++- src/sage/modular/local_comp/smoothchar.py | 1 - src/sage/modular/local_comp/type_space.py | 3 ++- src/sage/modular/modform/element.py | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sage/modular/hecke/element.py b/src/sage/modular/hecke/element.py index a027802606d..35053b87675 100644 --- a/src/sage/modular/hecke/element.py +++ b/src/sage/modular/hecke/element.py @@ -136,7 +136,8 @@ def _vector_(self, R=None): sage: type(vector(v, GF(2))) """ - if R is None: return self.__element + if R is None: + return self.__element return self.__element.change_ring(R) def _richcmp_(self, other, op): diff --git a/src/sage/modular/local_comp/smoothchar.py b/src/sage/modular/local_comp/smoothchar.py index 7875f376461..1907bb7703b 100644 --- a/src/sage/modular/local_comp/smoothchar.py +++ b/src/sage/modular/local_comp/smoothchar.py @@ -46,7 +46,6 @@ from sage.structure.sequence import Sequence from sage.structure.richcmp import richcmp_not_equal, richcmp from sage.rings.all import QQ, ZZ, Zmod, NumberField -from sage.rings.ring import is_Ring from sage.misc.cachefunc import cached_method from sage.misc.abstract_method import abstract_method from sage.misc.misc_c import prod diff --git a/src/sage/modular/local_comp/type_space.py b/src/sage/modular/local_comp/type_space.py index dd5bcb05673..ce78d301979 100644 --- a/src/sage/modular/local_comp/type_space.py +++ b/src/sage/modular/local_comp/type_space.py @@ -695,7 +695,8 @@ def rho(self, g): i = 0 while (d * a**i) % f != 1: i += 1 - if i > f: raise ArithmeticError + if i > f: + raise ArithmeticError return self._rho_s([a**i*g[0], g[1], a**i*g[2], g[3]]) * self._amat**(-i) # det(g) is not a unit diff --git a/src/sage/modular/modform/element.py b/src/sage/modular/modform/element.py index f3bfcc23c39..2954c4eada6 100644 --- a/src/sage/modular/modform/element.py +++ b/src/sage/modular/modform/element.py @@ -2279,7 +2279,8 @@ def minimal_twist(self, p=None): l = ZZ(1) while len(chis) > t: l = l.next_prime() - if l == p: continue + if l == p: + continue chis = [chi for chi in chis if g[l] == chi(l) * self[l] ] if l > 10000 or len(chis) == 0: raise RuntimeError("bug finding minimal twist") From 9b31fcee5e4d27ee12216b812ebaef9b3898b7cc Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 6 May 2021 09:12:44 +0200 Subject: [PATCH 150/232] Trac #21203: post-merge: fixup "transposed" to make tests pass again --- src/sage/combinat/k_regular_sequence.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index ddc5626c6ad..7a587c4b2c3 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -51,7 +51,7 @@ (0, 1, 3, 5, 9, 11, 15, 19, 27, 29) sage: U = Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), - ....: left=vector([0, 1]), right=vector([1, 0]), transpose=True) + ....: left=vector([0, 1]), right=vector([1, 0])).transposed() sage: all(U[n] == u(n) for n in srange(30)) True @@ -144,8 +144,7 @@ def __init__(self, parent, mu, left=None, right=None): sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), - ....: vector([0, 1]), vector([1, 0]), - ....: transpose=True) + ....: vector([0, 1]), vector([1, 0])).transposed() 2-regular sequence 0, 1, 3, 5, 9, 11, 15, 19, 27, 29, ... .. SEEALSO:: @@ -169,7 +168,7 @@ def _repr_(self): sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: s = Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), - ....: vector([0, 1]), vector([1, 0]), transpose=True) + ....: vector([0, 1]), vector([1, 0])).transposed() sage: repr(s) # indirect doctest '2-regular sequence 0, 1, 3, 5, 9, 11, 15, 19, 27, 29, ...' """ From 4a4ef29b6ebd40bf222bdc722f121dd45a22511f Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 6 May 2021 09:53:56 +0200 Subject: [PATCH 151/232] Trac #21203 review issue 6: move references to global file --- src/doc/en/reference/references/index.rst | 4 ++++ src/sage/combinat/k_regular_sequence.py | 5 ----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index ecd1bf6c72f..972b8332b69 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -304,6 +304,10 @@ REFERENCES: of crystals for the quantum queer superalgebra*. Preprint (2018). :arxiv:`1803.06317` +.. [AS2003] Jean-Paul Allouche, Jeffrey Shallit, + *Automatic Sequences: Theory, Applications, Generalizations*, + Cambridge University Press, 2003. + .. [As2008b] Sami Assaf. *Dual equivalence graphs and a combinatorial proof of LLT and Macdonald positivity*. (2008). :arxiv:`1005.3759v5`. diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 7a587c4b2c3..16b68e5a927 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -65,11 +65,6 @@ :mod:`sage.rings.cfinite_sequence`, :mod:`sage.combinat.binary_recurrence_sequences`. -REFERENCES: - -.. [AS2003] Jean-Paul Allouche, Jeffrey Shallit, - *Automatic Sequences: Theory, Applications, Generalizations*, - Cambridge University Press, 2003. AUTHORS: From be1a704b790a741e0ffa29013deaefd425be7f49 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 6 May 2021 09:56:03 +0200 Subject: [PATCH 152/232] Trac #21203: remove future-imports as already on Py3 --- src/sage/combinat/k_regular_sequence.py | 1 - src/sage/combinat/recognizable_series.py | 1 - 2 files changed, 2 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 16b68e5a927..95c154898ea 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -88,7 +88,6 @@ # (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** -from __future__ import absolute_import from .recognizable_series import RecognizableSeries from .recognizable_series import RecognizableSeriesSpace diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index 5fd650961af..37d2045492f 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -66,7 +66,6 @@ # (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** -from __future__ import absolute_import from sage.misc.cachefunc import cached_method from sage.misc.superseded import experimental From 96c1874f02e9bed9dda6d9c28dc061992f3246b1 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 6 May 2021 09:56:40 +0200 Subject: [PATCH 153/232] Trac #21203: update authors section --- src/sage/combinat/k_regular_sequence.py | 3 ++- src/sage/combinat/recognizable_series.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 95c154898ea..286e55d4abc 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -68,7 +68,8 @@ AUTHORS: -- Daniel Krenn (2016) +- Daniel Krenn (2016, 2021) + ACKNOWLEDGEMENT: diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index 37d2045492f..8894f67e3b6 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -44,9 +44,11 @@ :mod:`sage.rings.cfinite_sequence`, :mod:`sage.combinat.binary_recurrence_sequences`. + AUTHORS: -- Daniel Krenn (2016) +- Daniel Krenn (2016, 2021) + ACKNOWLEDGEMENT: From 9e82a65fd892663b9ee57c6558b44a1eb856dca1 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 6 May 2021 10:03:58 +0200 Subject: [PATCH 154/232] Trac #21203 review issue 7: proper punctuation, empty lines, etc in docstrings --- src/sage/combinat/k_regular_sequence.py | 49 +++++++------------------ 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 286e55d4abc..7f1759d6432 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -96,21 +96,19 @@ class kRegularSequence(RecognizableSeries): - def __init__(self, parent, mu, left=None, right=None): r""" A `k`-regular sequence. INPUT: - - ``parent`` -- an instance of :class:`kRegularSequenceSpace`. + - ``parent`` -- an instance of :class:`kRegularSequenceSpace` - ``mu`` -- a family of square matrices, all of which have the same dimension. The indices of this family are `0,...,k-1`. ``mu`` may be a list or tuple of cardinality `k` - as well. See - :meth:`~sage.combinat.recognizable_series.RecognizableSeries.mu` - for more details. + as well. See also + :meth:`~sage.combinat.recognizable_series.RecognizableSeries.mu`. - ``left`` -- (default: ``None``) a vector. When evaluating the sequence, this vector is multiplied @@ -122,19 +120,6 @@ def __init__(self, parent, mu, left=None, right=None): from the right to the matrix product. If ``None``, then this multiplication is skipped. - When created via the parent :class:`kRegularSequenceSpace`, then - the following option is available. - - - ``transpose`` -- (default: ``False``) a boolean. If set, then - each of the matrices in - :meth:`mu ` - is transposed. Additionally the vectors - :meth:`left ` - and - :meth:`right ` - are switched. - (This is done by calling :meth:`~sage.combinat.recognizable_series.RecognizableSeries.transposed`.) - EXAMPLES:: sage: Seq2 = kRegularSequenceSpace(2, ZZ) @@ -150,14 +135,13 @@ def __init__(self, parent, mu, left=None, right=None): super(kRegularSequence, self).__init__( parent=parent, mu=mu, left=left, right=right) - def _repr_(self): r""" Return a representation string of this `k`-regular sequence. OUTPUT: - A string. + A string TESTS:: @@ -174,7 +158,6 @@ def _repr_(self): opening_delimiter='', closing_delimiter='', preview=10) - @cached_method def __getitem__(self, n, **kwds): r""" @@ -182,11 +165,11 @@ def __getitem__(self, n, **kwds): INPUT: - - ``n`` -- a nonnegative integer. + - ``n`` -- a nonnegative integer OUTPUT: - An element of the universe of the sequence. + An element of the universe of the sequence EXAMPLES:: @@ -219,10 +202,9 @@ def __getitem__(self, n, **kwds): """ return self.coefficient_of_word(self.parent()._n_to_index_(n), **kwds) - def __iter__(self): r""" - Return an iterator. + Return an iterator over the coefficients of this sequence. EXAMPLES:: @@ -251,13 +233,13 @@ class kRegularSequenceSpace(RecognizableSeriesSpace): INPUT: - - ``k`` -- an integer at least `2` specifying the base. + - ``k`` -- an integer at least `2` specifying the base - ``coefficients`` -- a (semi-)ring. If not specified (``None``), then the integer ring is used. - ``category`` -- (default: ``None``) the category of this - space. + space EXAMPLES:: @@ -271,10 +253,8 @@ class kRegularSequenceSpace(RecognizableSeriesSpace): :doc:`k-regular sequence `, :class:`kRegularSequence`. """ - Element = kRegularSequence - @classmethod def __normalize__(cls, k, coefficients=None, **kwds): r""" @@ -302,14 +282,13 @@ def __normalize__(cls, k, coefficients=None, **kwds): coefficients, alphabet=srange(k), **kwds) return (k,) + nargs - def __init__(self, k, *args, **kwds): r""" See :class:`kRegularSequenceSpace` for details. INPUT: - - ``k`` -- an integer at least `2` specifying the base. + - ``k`` -- an integer at least `2` specifying the base Other input arguments are passed on to :meth:`~sage.combinat.recognizable_series.RecognizableSeriesSpace.__init__`. @@ -329,14 +308,13 @@ def __init__(self, k, *args, **kwds): self.k = k super(kRegularSequenceSpace, self).__init__(*args, **kwds) - def _repr_(self): r""" Return a representation string of this `k`-regular sequence space. OUTPUT: - A string. + A string TESTS:: @@ -345,7 +323,6 @@ def _repr_(self): """ return 'Space of {}-regular sequences over {}'.format(self.k, self.base()) - def _n_to_index_(self, n): r""" Convert `n` to an index usable by the underlying @@ -353,11 +330,11 @@ def _n_to_index_(self, n): INPUT: - - ``n`` -- a nonnegative integer. + - ``n`` -- a nonnegative integer OUTPUT: - A word. + A word TESTS:: From 3286a5ea15066aa143ffcc3066127ebba58fdd11 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 6 May 2021 13:56:18 +0200 Subject: [PATCH 155/232] Trac #21203 review issue 8: resolve "`n`th" in docstring --- src/sage/combinat/k_regular_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 7f1759d6432..5e5bc244136 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -161,7 +161,7 @@ def _repr_(self): @cached_method def __getitem__(self, n, **kwds): r""" - Return the `n`th entry of this sequence. + Return the `n`-th entry of this sequence. INPUT: From 924c6fae317aecc64f5025fafeb5efbda7b8b5c5 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 6 May 2021 14:05:06 +0200 Subject: [PATCH 156/232] Trac #21203 review issue 5: meaningful error message in _n_to_index_ --- src/sage/combinat/k_regular_sequence.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 5e5bc244136..81cbf6f670f 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -184,7 +184,7 @@ def __getitem__(self, n, **kwds): sage: S[-1] Traceback (most recent call last): ... - OverflowError: can't convert negative value to unsigned char + ValueError: value -1 of index is negative :: @@ -344,9 +344,12 @@ def _n_to_index_(self, n): sage: Seq2._n_to_index_(-1) Traceback (most recent call last): ... - OverflowError: can't convert negative value to unsigned char + ValueError: value -1 of index is negative """ from sage.rings.integer_ring import ZZ n = ZZ(n) W = self.indices() - return W(n.digits(self.k)) + try: + return W(n.digits(self.k)) + except OverflowError: + raise ValueError('value {} of index is negative'.format(n)) From 5e8b2365b12bd7c1ba0e22918629b1d1f1022297 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 6 May 2021 18:29:35 +0200 Subject: [PATCH 157/232] Trac #21203 review issue 1: better binary sum of digits --- src/sage/combinat/k_regular_sequence.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 81cbf6f670f..b5440419dfc 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -27,10 +27,11 @@ Binary sum of digits -------------------- -:: +The binary sum of digits `S(n)` of a nonnegative integer `n` satisfies +`S(2n) = S(n)` and `S(2n+1) = S(n) + 1`. We model this by the following:: sage: Seq2 = kRegularSequenceSpace(2, ZZ) - sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), + sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[1, 0], [1, 1]])), ....: left=vector([0, 1]), right=vector([1, 0])) sage: S 2-regular sequence 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, ... From a6a8c3b3ebc6ed5c0ce251b06ca1bfc0366ef965 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 6 May 2021 18:37:33 +0200 Subject: [PATCH 158/232] Trac #21203 review issue 2: extend odds in Pascal's triangle --- src/sage/combinat/k_regular_sequence.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index b5440419dfc..b6b438e6792 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -41,7 +41,8 @@ Number of odd entries in Pascal's triangle ------------------------------------------ -:: +Let us consider the number of odd entries in the first `n` rows +of Pascals's triangle:: sage: @cached_function ....: def u(n): @@ -51,7 +52,9 @@ sage: tuple(u(n) for n in srange(10)) (0, 1, 3, 5, 9, 11, 15, 19, 27, 29) - sage: U = Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), +There is a `2`-recursive sequence describing the numbers above as well:: + + sage: U = Seq2((Matrix([[3, 2], [0, 1]]), Matrix([[2, 0], [1, 3]])), ....: left=vector([0, 1]), right=vector([1, 0])).transposed() sage: all(U[n] == u(n) for n in srange(30)) True From be462dccd6b42ea84a6f09cf9e58f7a33c41ac0d Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 6 May 2021 18:45:31 +0200 Subject: [PATCH 159/232] Trac #21203 review issue 3: example for __getitem__ and __iter__ --- src/sage/combinat/k_regular_sequence.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index b6b438e6792..d899a157924 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -127,10 +127,23 @@ def __init__(self, parent, mu, left=None, right=None): EXAMPLES:: sage: Seq2 = kRegularSequenceSpace(2, ZZ) - sage: Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), - ....: vector([0, 1]), vector([1, 0])).transposed() + sage: S = Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: vector([0, 1]), vector([1, 0])).transposed(); S 2-regular sequence 0, 1, 3, 5, 9, 11, 15, 19, 27, 29, ... + We can access the coefficients of a sequence by + :: + + sage: S[5] + 11 + + or iterating over the first, say `10`, by + :: + + sage: from itertools import islice + sage: list(islice(S, 10)) + [0, 1, 3, 5, 9, 11, 15, 19, 27, 29] + .. SEEALSO:: :doc:`k-regular sequence `, From c4bf7ae98083d0bea37eddff2a2709f3c27cb8c8 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 6 May 2021 19:48:41 +0200 Subject: [PATCH 160/232] Trac #21203 review issue 4: rename to coefficient ring --- src/sage/combinat/k_regular_sequence.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index d899a157924..540ca13c061 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -252,8 +252,7 @@ class kRegularSequenceSpace(RecognizableSeriesSpace): - ``k`` -- an integer at least `2` specifying the base - - ``coefficients`` -- a (semi-)ring. If not specified (``None``), - then the integer ring is used. + - ``coefficient_ring`` -- a (semi-)ring. - ``category`` -- (default: ``None``) the category of this space @@ -273,7 +272,7 @@ class kRegularSequenceSpace(RecognizableSeriesSpace): Element = kRegularSequence @classmethod - def __normalize__(cls, k, coefficients=None, **kwds): + def __normalize__(cls, k, coefficient_ring, **kwds): r""" Normalizes the input in order to ensure a unique representation. @@ -285,18 +284,12 @@ def __normalize__(cls, k, coefficients=None, **kwds): sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: Seq2.category() Category of sets - - :: - - sage: Seq2 is kRegularSequenceSpace(2) - True + sage: Seq2.alphabet() + {0, 1} """ from sage.arith.srange import srange - from sage.rings.integer_ring import ZZ - if coefficients is None: - coefficients = ZZ nargs = super(kRegularSequenceSpace, cls).__normalize__( - coefficients, alphabet=srange(k), **kwds) + coefficient_ring, alphabet=srange(k), **kwds) return (k,) + nargs def __init__(self, k, *args, **kwds): From a33705e5081eed32eedc3754971e57beb748a8ac Mon Sep 17 00:00:00 2001 From: David Roe Date: Thu, 6 May 2021 21:54:14 -0400 Subject: [PATCH 161/232] Reviewer suggestions --- src/sage/groups/galois_group.py | 149 ++++++++++++++++---- src/sage/groups/perm_gps/permgroup.py | 2 +- src/sage/rings/number_field/galois_group.py | 3 +- 3 files changed, 127 insertions(+), 27 deletions(-) diff --git a/src/sage/groups/galois_group.py b/src/sage/groups/galois_group.py index c5a7940026e..abdddaa323a 100644 --- a/src/sage/groups/galois_group.py +++ b/src/sage/groups/galois_group.py @@ -14,6 +14,7 @@ from sage.groups.abelian_gps.abelian_group import AbelianGroup_class, AbelianGroup_subgroup from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from sage.misc.lazy_attribute import lazy_attribute +from sage.misc.abstract_method import abstract_method from sage.misc.cachefunc import cached_method from sage.structure.category_object import normalize_names from sage.rings.integer_ring import ZZ @@ -46,10 +47,43 @@ class _GMixin: It is just intended to provide common functionality between various different Galois group classes. """ - # This class uses the following attributes, which should be defined in any subclass - # * _default_algorithm -- a string, the default algorithm used to compute the Galois group - # * _gcdata -- a pair, the Galois closure and an embedding of the top field into it + @lazy_attribute + def _default_algorithm(self): + """ + A string, the default algorithm used for computing the Galois group + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) + sage: G = K.galois_group() + sage: G._default_algorithm + 'pari' + """ + return NotImplemented + + @lazy_attribute + def _gcdata(self): + """ + A pair: + - the Galois closure of the top field in the ambient Galois group; + + - an embedding of the top field into the Galois closure. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 - 2) + sage: G = K.galois_group() + sage: G._gcdata + (Number Field in ac with defining polynomial x^6 + 108, + Ring morphism: + From: Number Field in a with defining polynomial x^3 - 2 + To: Number Field in ac with defining polynomial x^6 + 108 + Defn: a |--> -1/36*ac^4 - 1/2*ac) + """ + return NotImplemented def _get_algorithm(self, algorithm): r""" @@ -119,8 +153,21 @@ class _GaloisMixin(_GMixin): This class provides methods for Galois groups, allowing concrete instances to inherit from both permutation group and abelian group classes. """ - # In addition to the attributes from _Gmixin, this class uses the following attributes, which should be defined in any subclass - # * _field -- the top field + @lazy_attribute + def _field(self): + """ + The top field, ie the field whose Galois closure elements of this group act upon. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) + sage: G = K.galois_group() + sage: G._field + Number Field in a with defining polynomial x^3 + 2*x + 2 + """ + return NotImplemented + def _repr_(self): """ String representation of this Galois group @@ -228,8 +275,44 @@ class _SubGaloisMixin(_GMixin): This class provides methods for subgroups of Galois groups, allowing concrete instances to inherit from both permutation group and abelian group classes. """ - # In addition to the attributes from _Gmixin, this class uses the following attributes, which should be defined in any subclass - # * _ambient_group -- the ambient Galois group of which this is a subgroup + @lazy_attribute + def _ambient_group(self): + """ + The ambient Galois group of which this is a subgroup. + + EXAMPLES:: + + sage: L. = NumberField(x^4 + 1) + sage: G = L.galois_group() + sage: H = G.decomposition_group(L.primes_above(3)[0]) + sage: H._ambient_group is G + True + """ + return NotImplemented + + @abstract_method(optional=True) + def fixed_field(self, name=None, polred=None, threshold=None): + """ + Return the fixed field of this subgroup (as a subfield of the Galois closure). + + INPUT: + + - ``name`` -- a variable name for the new field. + + - ``polred`` -- whether to optimize the generator of the newly created field + for a simpler polynomial, using pari's polredbest. + Defaults to ``True`` when the degree of the fixed field is at most 8. + + - ``threshold`` -- positive number; polred only performed if the cost is at most this threshold + + EXAMPLES:: + + sage: sage: k. = GF(3^12) + sage: g = k.galois_group()([8]) + sage: k0, embed = g.fixed_field() + sage: k0.cardinality() + 81 + """ @lazy_attribute def _gcdata(self): @@ -260,19 +343,37 @@ class GaloisGroup_perm(_GaloisMixin, PermutationGroup_generic): roots of the defining polynomial of the splitting field (versus the defining polynomial of the original extension). The default value may vary based on the type of field. """ - # Subclasses should implement the following methods and lazy attributes + @abstract_method + def transitive_number(self, algorithm=None, recompute=False): + """ + The transitive number (as in the GAP and Magma databases of transitive groups) + for the action on the roots of the defining polynomial of the top field. - # methods (taking algorithm and recompute as arguments): - # * transitive_number - # * order - # * _element_constructor_ -- for creating elements + EXAMPLES:: - # lazy_attributes - # * _gcdata -- a pair, the Galois closure and an embedding of the top field into it - # * _gens -- the list of generators of this group, as elements. This is not computed during __init__ for speed - # * _elts -- the list of all elements of this group. + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) + sage: G = K.galois_group() + sage: G.transitive_number() + 2 + """ - # * Element (for coercion) + @lazy_attribute + def _gens(self): + """ + The generators of this Galois group as permutations of the roots. It's important that this + be computed lazily, since it's often possible to compute other attributes (such as the order + or transitive number) more cheaply. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^5-2) + sage: G = K.galois_group(gc_numbering=False) + sage: G._gens + [(1,2,3,5), (1,4,3,2,5)] + """ + return NotImplemented def __init__(self, field, algorithm=None, names=None, gc_numbering=False): r""" @@ -502,21 +603,19 @@ def signature(self): class GaloisSubgroup_perm(PermutationGroup_subgroup, _SubGaloisMixin): """ - Subgroups of Galois groups are specified by giving a list of generators. + Subgroups of Galois groups (implemented as permutation groups), specified + by giving a list of generators. - Unlike ambient Galois groups, where we work hard to enable creation without - determining a list of generators, we require that generators for a subgroup - be specified during initialization. - - Subclasses are encouraged to implement a fixed_field method. + Unlike ambient Galois groups, where we use a lazy ``_gens`` attribute in order + to enable creation without determining a list of generators, + we require that generators for a subgroup be specified during initialization, + as specified in the ``__init__`` method of permutation subgroups. """ pass class GaloisSubgroup_ab(AbelianGroup_subgroup, _SubGaloisMixin): """ Subgroups of abelian Galois groups. - - Subclasses are encouraged to implement a fixed_field method. """ pass diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index cfb57e38b01..e379fcf9d4a 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -4737,7 +4737,7 @@ def __init__(self, ambient, gens=None, gap_group=None, domain=None, - ``gens`` - the generators of the subgroup - - ``gap_group`` - a gap permutation group contained in the ambient group; + - ``gap_group`` - a GAP permutation group contained in the ambient group; constructed from ``gens`` if not given. - ``check`` - ``True``: checks if ``gens`` are indeed elements of the diff --git a/src/sage/rings/number_field/galois_group.py b/src/sage/rings/number_field/galois_group.py index 7c94a60949e..63358420406 100644 --- a/src/sage/rings/number_field/galois_group.py +++ b/src/sage/rings/number_field/galois_group.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Galois Groups of Number Fields @@ -1049,6 +1048,8 @@ def fixed_field(self, name=None, polred=None, threshold=None): INPUT: + - ``name`` -- a variable name for the new field. + - ``polred`` -- whether to optimize the generator of the newly created field for a simpler polynomial, using pari's polredbest. Defaults to ``True`` when the degree of the fixed field is at most 8. From eea977a587068c2c112deaa464421be02ae52485 Mon Sep 17 00:00:00 2001 From: David Roe Date: Fri, 7 May 2021 15:09:39 -0400 Subject: [PATCH 162/232] Add utf line back in, remove sage: from doctest --- src/sage/groups/galois_group.py | 2 +- src/sage/rings/number_field/galois_group.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/groups/galois_group.py b/src/sage/groups/galois_group.py index abdddaa323a..694574a20b6 100644 --- a/src/sage/groups/galois_group.py +++ b/src/sage/groups/galois_group.py @@ -307,7 +307,7 @@ def fixed_field(self, name=None, polred=None, threshold=None): EXAMPLES:: - sage: sage: k. = GF(3^12) + sage: k. = GF(3^12) sage: g = k.galois_group()([8]) sage: k0, embed = g.fixed_field() sage: k0.cardinality() diff --git a/src/sage/rings/number_field/galois_group.py b/src/sage/rings/number_field/galois_group.py index 63358420406..45d6bf21d4c 100644 --- a/src/sage/rings/number_field/galois_group.py +++ b/src/sage/rings/number_field/galois_group.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Galois Groups of Number Fields From 2ede960f83c6ec9d04906c7101ec80f78cd714c5 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 5 Apr 2021 07:27:02 -0400 Subject: [PATCH 163/232] Trac #31619: weaken the real/complex testing in is_positive_definite(). The is_positive_definite() method checks whether or not the base ring of the given matrix is real or complex, but presently it rejects a few inexact rings, such as RR and CC. This commit tweaks that test to accept more inexact base rings. At the moment this only changes errors like, ValueError: Could not see Real Field with 53 bits of precision as a subring of the real or complex numbers into, TypeError: entries of the matrix must be in an exact ring, not Real Field with 53 bits of precision because the underlying _indefinite_factorization() method rejects inexact rings. That will be addressed separately. --- src/sage/matrix/matrix2.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 885fe8572e3..fb970560a9b 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -14344,12 +14344,12 @@ cdef class Matrix(Matrix1): from sage.rings.real_lazy import RLF,CLF R = self.base_ring() - if RLF.has_coerce_map_from(R): + if RLF.has_coerce_map_from(R) or R.has_coerce_map_from(RLF): if not self.is_symmetric(): return False L, d = self._indefinite_factorization('symmetric', check=False) real = True - elif CLF.has_coerce_map_from(R): + elif CLF.has_coerce_map_from(R) or R.has_coerce_map_from(CLF): if not self.is_hermitian(): return False L, d = self._indefinite_factorization('hermitian', check=False) From 3ebafb6474900a72d684d12e43c519451634742a Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 5 Apr 2021 10:15:18 -0400 Subject: [PATCH 164/232] Trac #31619: don't special-case real/complex in is_positive_definite(). The is_positive_definite() method has two branches for real/complex base fields, but all real fields should support conjugation at this point making the extra code path unnecessary. This commit treats them all the same, using the conjugate-transpose algorithm and comparing the eigenvalues directly against a rational zero. --- src/sage/matrix/matrix2.pyx | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index fb970560a9b..9b2b3dddb6b 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -14344,31 +14344,31 @@ cdef class Matrix(Matrix1): from sage.rings.real_lazy import RLF,CLF R = self.base_ring() - if RLF.has_coerce_map_from(R) or R.has_coerce_map_from(RLF): - if not self.is_symmetric(): - return False - L, d = self._indefinite_factorization('symmetric', check=False) - real = True - elif CLF.has_coerce_map_from(R) or R.has_coerce_map_from(CLF): - if not self.is_hermitian(): - return False - L, d = self._indefinite_factorization('hermitian', check=False) - real = False - else: + + if not (RLF.has_coerce_map_from(R) or + R.has_coerce_map_from(RLF) or + CLF.has_coerce_map_from(R) or + R.has_coerce_map_from(CLF)): + # This is necessary to avoid "going through the motions" + # with e.g. a one-by-one identity matrix over the finite + # field of order 5^2, which might otherwise look positive- + # definite. raise ValueError("Could not see {} as a subring of the " "real or complex numbers".format(R)) + if not self.is_hermitian(): + return False + + L, d = self._indefinite_factorization('hermitian', check=False) + if L is False: return False # Now have diagonal entries (hopefully real) and so can # test with a generator (which will short-circuit) # positive definite iff all entries of d are positive - zero = R.fraction_field().zero() - if real: - is_pos = all(x > zero for x in d) - else: - is_pos = all(x.real() > zero for x in d) + zero = QQ.zero() + is_pos = all(x > zero for x in d) if certificate: if is_pos: From 351e20ab43b549af10923e853fed9ba1c075b676 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 5 Apr 2021 10:28:31 -0400 Subject: [PATCH 165/232] Trac #31619: support is_positive_definite() over symbolic ring. The is_positive_definite() method for matrices implicitly rejects symbolic matrices. There's no principled way to deal with SR, but it's still nice for inexperienced users when things "just work." For example, I think we should be able to say that a square diagonal matrix with all "pi" entries on the diagonal is positive-definite. This commits updates the real/complex check in is_positive_definite() to explicitly allow SR, just in case the result will ultimately make sense. --- src/sage/matrix/matrix2.pyx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 9b2b3dddb6b..be10c0121b3 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -14341,6 +14341,7 @@ cdef class Matrix(Matrix1): sage: Matrix(0).is_positive_definite() True """ + from sage.symbolic.ring import SR from sage.rings.real_lazy import RLF,CLF R = self.base_ring() @@ -14348,7 +14349,8 @@ cdef class Matrix(Matrix1): if not (RLF.has_coerce_map_from(R) or R.has_coerce_map_from(RLF) or CLF.has_coerce_map_from(R) or - R.has_coerce_map_from(CLF)): + R.has_coerce_map_from(CLF) or + R is SR): # This is necessary to avoid "going through the motions" # with e.g. a one-by-one identity matrix over the finite # field of order 5^2, which might otherwise look positive- From 93a3d6a5e7edfa5781f878bf5f95c4fa1af51c0d Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 5 Apr 2021 13:31:55 -0400 Subject: [PATCH 166/232] Trac #31619: check reality/complexity in is_positive_semidefinite(). One of the tests for is_positive_definite() shows that misleading results can be returned over e.g. finite fields if we don't check that the base ring of our matrix is real or complex first. We copy that check into is_positive_semidefinite(), too. We also fix a few tests for number fields that were missing a complex embedding. --- src/sage/matrix/matrix2.pyx | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index be10c0121b3..bebcc1155a4 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -13970,7 +13970,7 @@ cdef class Matrix(Matrix1): sage: set_random_seed() sage: n = ZZ.random_element(6) - sage: F = NumberField(x^2 +1, 'I') + sage: F = QuadraticField(-1, 'I') sage: A = matrix.random(F, n) sage: A = A + A.conjugate_transpose() sage: P,L,D = A.block_ldlt() @@ -13983,7 +13983,7 @@ cdef class Matrix(Matrix1): sage: set_random_seed() sage: n = ZZ.random_element(6) - sage: F = NumberField(x^2 +1, 'I') + sage: F = QuadraticField(-1, 'I') sage: A = matrix.random(F, n) sage: A = A*A.conjugate_transpose() sage: P,L,D = A.block_ldlt() @@ -14138,7 +14138,7 @@ cdef class Matrix(Matrix1): return ``False``):: sage: set_random_seed() - sage: F = NumberField(x^2 + 1, 'I') + sage: F = QuadraticField(-1, 'I') sage: from sage.misc.prandom import choice sage: ring = choice([ZZ, QQ, F, RDF, CDF]) sage: A = matrix.random(ring, 10); A = A + A.conjugate_transpose() @@ -14151,7 +14151,37 @@ cdef class Matrix(Matrix1): sage: actual = A.is_positive_semidefinite() sage: actual == expected True + + We reject matrices whose base fields cannot be coerced to + either real numbers, complex numbers, or symbolics; otherwise + we risk returning nonsensical results:: + + sage: F = FiniteField(5^2) + sage: A = matrix.identity(F, 1) + sage: A.is_positive_semidefinite() + Traceback (most recent call last): + ... + ValueError: Could not see Finite Field in z2 of size 5^2 + as a subring of the real or complex numbers + """ + from sage.symbolic.ring import SR + from sage.rings.real_lazy import RLF,CLF + + R = self.base_ring() + + if not (RLF.has_coerce_map_from(R) or + R.has_coerce_map_from(RLF) or + CLF.has_coerce_map_from(R) or + R.has_coerce_map_from(CLF) or + R is SR): + # This is necessary to avoid "going through the motions" + # with e.g. a one-by-one identity matrix over the finite + # field of order 5^2, which might otherwise look positive- + # definite. + raise ValueError("Could not see {} as a subring of the " + "real or complex numbers".format(R)) + if not self.is_hermitian(): return False From 3d6b4d01c1e5320ff767d7a1655d94ba49b3f6c2 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Tue, 6 Apr 2021 07:49:49 -0400 Subject: [PATCH 167/232] Trac #31619: implement is_positive_definite() with block_ldlt(). The is_positive_definite() method lacked the ability to test approximate real/complex and symbolic matrices because the underlying indefinite_factorization() method rejects inexact rings. This commit reimplements is_positive_definite() in terms of block_ldlt() to work around that issue. In the process, the ``certificate`` argument to is_positive_definite() was dropped. This argument is unused in SageMath, and if the user wants to see the decomposition, he can just ask for it with block_ldlt(). What we gain from this is some consistency between all of the is_*definite() methods. Warning: this temporarily breaks the cholesky() method, which inappropriately relies on is_positive_definite() cacheing an indefinite factorization (and that factorization remaining cached). This will be addressed in a later commit, albeit with a temporary performance penalty since now that is_positive_definite() uses block_ldlt(), the indefinite_factorization() will have to be recomputed. --- src/sage/matrix/matrix2.pyx | 66 +++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index bebcc1155a4..344621546f7 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -14203,7 +14203,7 @@ cdef class Matrix(Matrix1): return False return True - def is_positive_definite(self, certificate=False): + def is_positive_definite(self): r""" Determines if a real or symmetric matrix is positive definite. @@ -14220,30 +14220,27 @@ cdef class Matrix(Matrix1): ALGORITHM: - A matrix is positive definite if and only if the - diagonal entries from the indefinite factorization - are all positive (see :meth:`indefinite_factorization`). - So this algorithm is of order ``n^3/3`` and may be applied - to matrices with elements of any ring that has a fraction - field contained within the reals or complexes. + A matrix is positive definite if and only if the diagonal + blocks in its :meth:`block_ldlt` factorization are all 1-by-1 + and have positive entries. This algorithm is of order + ``n^3/3`` and may be applied to matrices with elements of any + ring that has a fraction field contained within the reals or + complex numbers. INPUT: Any square matrix. - - ``certificate`` -- (default: ``False``) return the - decomposition from the indefinite factorization if possible - OUTPUT: This routine will return ``True`` if the matrix is square, symmetric or Hermitian, and meets the condition above for the quadratic form. - The base ring for the elements of the matrix needs to - have a fraction field implemented and the computations - that result from the indefinite factorization must be - convertible to real numbers that are comparable to zero. + The base ring for the elements of the matrix needs to have a + fraction field implemented and the computations that result + from the factorization must be convertible to real numbers + that are comparable to zero. EXAMPLES: @@ -14370,6 +14367,17 @@ cdef class Matrix(Matrix1): sage: Matrix(0).is_positive_definite() True + + We can check positive-definiteness of matrices over + approximate real/complex and symbolic rings:: + + sage: matrix.identity(RR,4).is_positive_definite() + True + sage: matrix.identity(CC,4).is_positive_definite() + True + sage: matrix.identity(SR,4).is_positive_definite() + True + """ from sage.symbolic.ring import SR from sage.rings.real_lazy import RLF,CLF @@ -14391,24 +14399,24 @@ cdef class Matrix(Matrix1): if not self.is_hermitian(): return False - L, d = self._indefinite_factorization('hermitian', check=False) - - if L is False: - return False + if self._nrows == 0: + return True # vacuously - # Now have diagonal entries (hopefully real) and so can - # test with a generator (which will short-circuit) - # positive definite iff all entries of d are positive - zero = QQ.zero() - is_pos = all(x > zero for x in d) + cdef list d + _,_,d = self._block_ldlt() - if certificate: - if is_pos: - return is_pos, L, d + # Check each 1x1 block for a nonpositive entry. If we don't + # find any, it's positive-definite. The presence of + # any 2x2 blocks also indicates indefiniteness. + for d_i in d: + if d_i.nrows() == 1: + if d_i <= 0: + return False else: - return is_pos, None, None - else: - return is_pos + # There's a 2x2 block + return False + return True + def principal_square_root(self, check_positivity=True): r""" From 0b4c491ebe3364eea6fcfdf19e754fc5519779b1 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Tue, 6 Apr 2021 08:53:15 -0400 Subject: [PATCH 168/232] Trac #31619: factor out implementations of is_positive_(semi)definite(). The only difference between a positive-definite and positive- semidefinite check is an equals sign in one comparison. This commit factors the test out into an internal method with a boolean "semi" flag that determines the comparison used. --- src/sage/matrix/matrix2.pxd | 1 + src/sage/matrix/matrix2.pyx | 126 +++++++++++++++--------------------- 2 files changed, 53 insertions(+), 74 deletions(-) diff --git a/src/sage/matrix/matrix2.pxd b/src/sage/matrix/matrix2.pxd index a6f5b4b0251..d3555ffbd2f 100644 --- a/src/sage/matrix/matrix2.pxd +++ b/src/sage/matrix/matrix2.pxd @@ -17,6 +17,7 @@ from .matrix1 cimport Matrix as Matrix1 cdef class Matrix(Matrix1): cdef _det_by_minors(self, Py_ssize_t level) cdef _pf_bfl(self) + cdef bint _is_positive_definite_or_semidefinite(self, bint semi) except -1 cpdef _echelon(self, str algorithm) cpdef _echelon_in_place(self, str algorithm) cpdef matrix_window(self, Py_ssize_t row=*, Py_ssize_t col=*, Py_ssize_t nrows=*, Py_ssize_t ncols=*, bint check=*) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 344621546f7..36394843a6f 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -14038,6 +14038,56 @@ cdef class Matrix(Matrix1): return (P,L,D) + cdef bint _is_positive_definite_or_semidefinite(self, bint semi) except -1: + """ + This is an internal wrapper that allows us to implement both + :meth:`is_positive_definite` and + :meth:`is_positive_semidefinite` with essentially the same + code. The boolean ``semi`` argument exists only to change + "greater than zero" into "greater than or equal to zero." + """ + from sage.symbolic.ring import SR + from sage.rings.real_lazy import RLF,CLF + + R = self.base_ring() + + if not (RLF.has_coerce_map_from(R) or + R.has_coerce_map_from(RLF) or + CLF.has_coerce_map_from(R) or + R.has_coerce_map_from(CLF) or + R is SR): + # This is necessary to avoid "going through the motions" + # with e.g. a one-by-one identity matrix over the finite + # field of order 5^2, which might otherwise look positive- + # definite. + raise ValueError("Could not see {} as a subring of the " + "real or complex numbers".format(R)) + + if not self.is_hermitian(): + return False + + if self._nrows == 0: + return True # vacuously + + cdef list d + _,_,d = self._block_ldlt() + + # Check each 1x1 block for a nonpositive (negative) entry. If + # we don't find any, the matrix is positive-(semi)definite. The + # presence of any 2x2 blocks also indicates indefiniteness. + for d_i in d: + if d_i.nrows() == 1: + if semi: + if d_i < 0: + return False + else: + if d_i <= 0: + return False + else: + # There's a 2x2 block + return False + return True + def is_positive_semidefinite(self): r""" Returns whether or not this matrix is positive-semidefinite. @@ -14165,43 +14215,7 @@ cdef class Matrix(Matrix1): as a subring of the real or complex numbers """ - from sage.symbolic.ring import SR - from sage.rings.real_lazy import RLF,CLF - - R = self.base_ring() - - if not (RLF.has_coerce_map_from(R) or - R.has_coerce_map_from(RLF) or - CLF.has_coerce_map_from(R) or - R.has_coerce_map_from(CLF) or - R is SR): - # This is necessary to avoid "going through the motions" - # with e.g. a one-by-one identity matrix over the finite - # field of order 5^2, which might otherwise look positive- - # definite. - raise ValueError("Could not see {} as a subring of the " - "real or complex numbers".format(R)) - - if not self.is_hermitian(): - return False - - if self._nrows == 0: - return True # vacuously - - cdef list d - _,_,d = self._block_ldlt() - - # Check each 1x1 block for a negative entry. If we don't - # find any, it's positive-semidefinite. The presence of - # any 2x2 blocks also indicates indefiniteness. - for d_i in d: - if d_i.nrows() == 1: - if d_i < 0: - return False - else: - # There's a 2x2 block - return False - return True + return self._is_positive_definite_or_semidefinite(True) def is_positive_definite(self): r""" @@ -14379,43 +14393,7 @@ cdef class Matrix(Matrix1): True """ - from sage.symbolic.ring import SR - from sage.rings.real_lazy import RLF,CLF - - R = self.base_ring() - - if not (RLF.has_coerce_map_from(R) or - R.has_coerce_map_from(RLF) or - CLF.has_coerce_map_from(R) or - R.has_coerce_map_from(CLF) or - R is SR): - # This is necessary to avoid "going through the motions" - # with e.g. a one-by-one identity matrix over the finite - # field of order 5^2, which might otherwise look positive- - # definite. - raise ValueError("Could not see {} as a subring of the " - "real or complex numbers".format(R)) - - if not self.is_hermitian(): - return False - - if self._nrows == 0: - return True # vacuously - - cdef list d - _,_,d = self._block_ldlt() - - # Check each 1x1 block for a nonpositive entry. If we don't - # find any, it's positive-definite. The presence of - # any 2x2 blocks also indicates indefiniteness. - for d_i in d: - if d_i.nrows() == 1: - if d_i <= 0: - return False - else: - # There's a 2x2 block - return False - return True + return self._is_positive_definite_or_semidefinite(False) def principal_square_root(self, check_positivity=True): From 4a17154dda513249e2fb609ea2218f9e322ab1f3 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 5 Apr 2021 13:40:03 -0400 Subject: [PATCH 169/232] Trac #31619: reverse cache logic in cholesky(). The entire cholesky() method is under an "if" statement; by switching the logic around we now return immediately when the answer is cached and reduce the indentation of the rest of the method by one level. --- src/sage/matrix/matrix2.pyx | 86 +++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 36394843a6f..77bccccfb8a 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -12475,50 +12475,52 @@ cdef class Matrix(Matrix1): """ from copy import copy C = self.fetch('cholesky') - if C is None: - if not self.is_square(): - msg = "matrix must be square, not {0} x {1}" - raise ValueError(msg.format(self.nrows(), self.ncols())) - if not self.base_ring().is_exact(): - msg = 'base ring of the matrix must be exact, not {0}' - raise TypeError(msg.format(self.base_ring())) - if not self.is_positive_definite(): - msg = 'matrix is not positive definite, so cannot compute Cholesky decomposition' - raise ValueError(msg) - # the successful positive definite check will cache a Hermitian - # or symmetric indefinite factorization, as appropriate - factors = self.fetch('indefinite_factorization_hermitian') - if factors is None: - factors = self.fetch('indefinite_factorization_symmetric') - from sage.rings.qqbar import AA as F_ac - else: - from sage.rings.qqbar import QQbar as F_ac - - L = factors[0] - d = factors[1] - F = L.base_ring() # field really - splits = [] # square roots of diagonal entries - extend = False - for x in d: - if not extend and x.is_square(): - sqrt = x.sqrt() - else: - extend = True - sqrt = F_ac(x).sqrt() - splits.append(sqrt) - # move square root of the diagonal matrix - # into the lower triangular matrix - # We need a copy, to break immutability - # and the field may have changed as well - if extend: - C = L.change_ring(F_ac) + if C is not None: + return C + + if not self.is_square(): + msg = "matrix must be square, not {0} x {1}" + raise ValueError(msg.format(self.nrows(), self.ncols())) + if not self.base_ring().is_exact(): + msg = 'base ring of the matrix must be exact, not {0}' + raise TypeError(msg.format(self.base_ring())) + if not self.is_positive_definite(): + msg = 'matrix is not positive definite, so cannot compute Cholesky decomposition' + raise ValueError(msg) + # the successful positive definite check will cache a Hermitian + # or symmetric indefinite factorization, as appropriate + factors = self.fetch('indefinite_factorization_hermitian') + if factors is None: + factors = self.fetch('indefinite_factorization_symmetric') + from sage.rings.qqbar import AA as F_ac + else: + from sage.rings.qqbar import QQbar as F_ac + + L = factors[0] + d = factors[1] + F = L.base_ring() # field really + splits = [] # square roots of diagonal entries + extend = False + for x in d: + if not extend and x.is_square(): + sqrt = x.sqrt() else: - C = L.__copy__() + extend = True + sqrt = F_ac(x).sqrt() + splits.append(sqrt) + # move square root of the diagonal matrix + # into the lower triangular matrix + # We need a copy, to break immutability + # and the field may have changed as well + if extend: + C = L.change_ring(F_ac) + else: + C = L.__copy__() - for c in range(C.ncols()): - C.rescale_col(c, splits[c]) - C.set_immutable() - self.cache('cholesky', C) + for c in range(C.ncols()): + C.rescale_col(c, splits[c]) + C.set_immutable() + self.cache('cholesky', C) return C def inverse_positive_definite(self): From d72c68c764ab6cf17c5dd46b93c2ce1e5be2d4ca Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 5 Apr 2021 15:42:20 -0400 Subject: [PATCH 170/232] Trac #31619: make the output of cholesky() more predictable. The cholesky() method is currently basing its choice of a base ring on the existence of a cached result; specifically on whether or not the indefinite_factorization() was called with algorithm="symmetric" or algorithm="hermitian". This is suboptimal in two ways: 1. Things shouldn't break if somebody clears the cache. 2. Computing the indefinite_factorization() with "hermitian" as the algorithm works just fine if you do it on a real matrix, but it will trick all subsequent calls to cholesky() into changing their results. This commit updates the code (and related documentation) to return a matrix over, in order of preference, (1) the fraction field of the base ring of the input matrix (2) the algebraic reals (3) the algebraic closure of field in (1). It also recomputes the indefinite factorization if one is not cached. --- src/sage/matrix/matrix2.pyx | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 77bccccfb8a..236e721d580 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -12253,6 +12253,13 @@ cdef class Matrix(Matrix1): fails to be positive definite (perhaps because it is not symmetric or Hermitian), then a ``ValueError`` results. + If possible, the output matrix will be over the fraction field + of the base ring of the input matrix. If that fraction field + is missing the requisite square roots but if no imaginaries + are encountered, then the algebraic-reals will be used. + Otherwise, the algebraic closure of the fraction field + (typically ``QQbar``) will be used. + ALGORITHM: Whether or not the matrix is positive definite is checked @@ -12487,18 +12494,27 @@ cdef class Matrix(Matrix1): if not self.is_positive_definite(): msg = 'matrix is not positive definite, so cannot compute Cholesky decomposition' raise ValueError(msg) - # the successful positive definite check will cache a Hermitian - # or symmetric indefinite factorization, as appropriate + + # Check to see if a factorization is cached first. factors = self.fetch('indefinite_factorization_hermitian') if factors is None: factors = self.fetch('indefinite_factorization_symmetric') - from sage.rings.qqbar import AA as F_ac - else: - from sage.rings.qqbar import QQbar as F_ac + if factors is None: + # The "hermitian" algorithm works on real matrices, too. + factors = self._indefinite_factorization(algorithm='hermitian', + check=False) L = factors[0] d = factors[1] F = L.base_ring() # field really + + F_ac = F + if hasattr(F, 'algebraic_closure'): + # This can fail if, say, F is the symbolic ring which does + # contain the requisite roots in its own special way but + # does not have an algebraic_closure() method. + F_ac = F.algebraic_closure() + splits = [] # square roots of diagonal entries extend = False for x in d: @@ -12513,7 +12529,14 @@ cdef class Matrix(Matrix1): # We need a copy, to break immutability # and the field may have changed as well if extend: - C = L.change_ring(F_ac) + # Try to use the algebraic reals but fall back to what is + # probably QQbar if we find an entry in "L" that won't fit + # in AA. + from sage.rings.qqbar import AA + try: + C = L.change_ring(AA) + except ValueError: # cannot coerce... + C = L.change_ring(F_ac) else: C = L.__copy__() From 25f1588022beb67cab4bcb3631979726d2d2d6a5 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 5 Apr 2021 19:23:30 -0400 Subject: [PATCH 171/232] Trac #31619: add "classical" argument to matrix block_ldlt(). This commit adds a "classical" flag to block_ldlt() that tells it to compute a standard non-block, non-pivoting LDL^{T} factorization. Such a factorization may not exist, but you are welcome to try it. The main reason for introducing this is that it almost entirely replaces the indefinite_factorization() method using the existing code for block_ldlt(). The one feature indefinite_factorization() has that block_ldlt(classical=True) does NOT have is the ability to operate on symmetric matrices that are not Hermitian. For Cholesky decompositions and positive-(semi)definite testing this makes no difference, but it is technically possible to decompose a symmetric (but not Hermitian) matrix with complex entries. Likewise for symmetric matrices over some finite fields where "conjugate" is either undefined or not what you'd expect it to be. The indefinite_factorization() method may still be useful in those cases. In the applications where block_ldlt(classical=True) CAN be used, it will be a bit nicer since we don't have to worry about what kind of symmetry the matrix has. For example, you can't waste time running the algorithm="hermitian" indefinite factorization on a symmetric matrix whose algorithm="symmetric" result is already cached. --- src/sage/matrix/matrix2.pyx | 142 +++++++++++++++++++++++++++++++++--- 1 file changed, 132 insertions(+), 10 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 236e721d580..4e71bde9667 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -13573,7 +13573,7 @@ cdef class Matrix(Matrix1): raise ValueError(msg.format(d)) return L, vector(L.base_ring(), d) - def _block_ldlt(self): + def _block_ldlt(self, classical=False): r""" Perform a user-unfriendly block-`LDL^{T}` factorization of the Hermitian matrix `A` @@ -13705,6 +13705,19 @@ cdef class Matrix(Matrix1): k += 1 continue + if classical: + try: + # This is a "step zero" that doesn't appear in the published algorithms. + # It's a back door that lets us escape with only the standard non-block + # non-pivoting LDL^T factorization. This allows us to implement e.g. + # indefinite_factorization() in terms of this method. + d.append( one_by_one_space(A_kk) ) + _block_ldlt_pivot1x1(A,k) + k += 1 + continue + except ZeroDivisionError: + raise ValueError("matrix has no classical LDL^T factorization") + # Find the largest subdiagonal entry (in magnitude) in the # kth column. This occurs prior to Step (1) in Higham, # but is part of Step (1) in Bunch and Kaufman. We adopt @@ -13842,7 +13855,7 @@ cdef class Matrix(Matrix1): return (p,A,d) - def block_ldlt(self): + def block_ldlt(self, classical=False): r""" Compute a block-`LDL^{T}` factorization of a Hermitian matrix. @@ -13868,9 +13881,23 @@ cdef class Matrix(Matrix1): Hermitian systems, and that is how we choose our row and column swaps. + INPUT: + + * ``classical`` -- (default: ``False``) whether or not to + attempt a classical non-block `LDL^{T}` factorization + with no row/column swaps. + + .. WARNING:: + + Not all matrices have a classical `LDL^{T}` factorization. + Set ``classical=True`` at your own risk, preferably after + verifying that your matrix is positive-definite and (over + inexact rings) not ill-conditioned. + OUTPUT: - If the input matrix is Hermitian, we return a triple `(P,L,D)` + If the input matrix is not Hermitian, the output from this + function is undefined. Otherwise, we return a triple `(P,L,D)` such that `A = PLDL^{*}P^{T}` and * `P` is a permutation matrix, @@ -13878,8 +13905,10 @@ cdef class Matrix(Matrix1): * `D` is a block-diagonal matrix whose blocks are of size one or two. - If the input matrix is not Hermitian, the output from this - function is undefined. + With ``classical=True``, the permutation matrix `P` is always + an identity matrix and the diagonal blocks are always + one-by-one. A ``ValueError`` is raised if the matrix has no + classical `LDL^{T}` factorization. ALGORITHM: @@ -13923,11 +13952,15 @@ cdef class Matrix(Matrix1): sage: P.transpose()*A*P == L*D*L.transpose() True - This two-by-two matrix has no standard factorization, but it + This two-by-two matrix has no classical factorization, but it constitutes its own block-factorization:: sage: A = matrix(QQ, [ [0,1], ....: [1,0] ]) + sage: A.block_ldlt(classical=True) + Traceback (most recent call last): + ... + ValueError: matrix has no classical LDL^T factorization sage: A.block_ldlt() ( [1 0] [1 0] [0 1] @@ -13938,6 +13971,10 @@ cdef class Matrix(Matrix1): sage: A = matrix(QQbar, [ [ 0,I], ....: [-I,0] ]) + sage: A.block_ldlt(classical=True) + Traceback (most recent call last): + ... + ValueError: matrix has no classical LDL^T factorization sage: A.block_ldlt() ( [1 0] [1 0] [ 0 I] @@ -13951,6 +13988,10 @@ cdef class Matrix(Matrix1): sage: A = matrix(RDF, 2, 2, [ [1e-10, 1 ], ....: [1 , 2e-10] ]) + sage: _,L,D = A.block_ldlt(classical=True) + sage: L*D*L.T + [1e-10 1.0] + [ 1.0 0.0] sage: A.block_ldlt() ( [1.0 0.0] [1.0 0.0] [1e-10 1.0] @@ -13969,6 +14010,72 @@ cdef class Matrix(Matrix1): sage: (P.T*A*P - L*D*L.H).norm() < 1e-10 True + This matrix has a singular three-by-three leading principal + submatrix, and therefore has no classical factorization:: + + sage: A = matrix(QQ, [[21, 15, 12, -2], + ....: [15, 12, 9, 6], + ....: [12, 9, 7, 3], + ....: [-2, 6, 3, 8]]) + sage: A[0:3,0:3].det() == 0 + True + sage: A.block_ldlt(classical=True) + Traceback (most recent call last): + ... + ValueError: matrix has no classical LDL^T factorization + sage: A.block_ldlt() + ( + [1 0 0 0] [ 1 0 0 0] + [0 0 1 0] [ -2/21 1 0 0] + [0 0 0 1] [ 5/7 39/41 1 0] + [0 1 0 0], [ 4/7 87/164 48/79 1], + + [ 21| 0| 0| 0] + [-------+-------+-------+-------] + [ 0| 164/21| 0| 0] + [-------+-------+-------+-------] + [ 0| 0|-237/41| 0] + [-------+-------+-------+-------] + [ 0| 0| 0| 25/316] + ) + + An indefinite symmetric matrix that happens to have a + classical factorization:: + + sage: A = matrix(QQ, [[ 3, -6, 9, 6, -9], + ....: [-6, 11, -16, -11, 17], + ....: [ 9, -16, 28, 16, -40], + ....: [ 6, -11, 16, 9, -19], + ....: [-9, 17, -40, -19, 68]]) + sage: A.block_ldlt(classical=True)[1:] + ( + [ 3| 0| 0| 0| 0] + [--+--+--+--+--] + [ 0|-1| 0| 0| 0] + [--+--+--+--+--] + [ 1 0 0 0 0] [ 0| 0| 5| 0| 0] + [-2 1 0 0 0] [--+--+--+--+--] + [ 3 -2 1 0 0] [ 0| 0| 0|-2| 0] + [ 2 -1 0 1 0] [--+--+--+--+--] + [-3 1 -3 1 1], [ 0| 0| 0| 0|-1] + ) + + An indefinite Hermitian matrix that happens to have a + classical factorization:: + + sage: F. = QuadraticField(-1) + sage: A = matrix(F, [[ 2, 4 - 2*I, 2 + 2*I], + ....: [4 + 2*I, 8, 10*I], + ....: [2 - 2*I, -10*I, -3]]) + sage: A.block_ldlt(classical=True)[1:] + ( + [ 2| 0| 0] + [--+--+--] + [ 1 0 0] [ 0|-2| 0] + [ I + 2 1 0] [--+--+--] + [ -I + 1 2*I + 1 1], [ 0| 0| 3] + ) + TESTS: All three factors should be the identity when the input matrix is:: @@ -14037,12 +14144,25 @@ cdef class Matrix(Matrix1): sage: L.base_ring() == D.base_ring() True + Ensure that a "random" real positive-definite symmetric matrix + has a classical factorization that agrees with + :meth:`indefinite_factorization`:: + + sage: set_random_seed() + sage: n = ZZ.random_element(6) + sage: A = matrix.random(QQ, n) + sage: A = A*A.transpose() + matrix.identity(QQ, n) + sage: _,L,D = A.block_ldlt(classical=True) + sage: l,d = A.indefinite_factorization() + sage: L == l and D == matrix.diagonal(d) + True + """ cdef Py_ssize_t n # size of the matrices cdef Py_ssize_t i, j # loop indices cdef Matrix P,L,D # output matrices - p,L,d = self._block_ldlt() + p,L,d = self._block_ldlt(classical=classical) MS = L.matrix_space() P = MS.matrix(lambda i,j: p[j] == i) @@ -17828,7 +17948,7 @@ class NotFullRankError(ValueError): pass -cdef inline void _block_ldlt_pivot1x1(Matrix A, Py_ssize_t k): +cdef inline bint _block_ldlt_pivot1x1(Matrix A, Py_ssize_t k) except 1: r""" Update the `n`-by-`n` matrix `A` as part of a 1x1 pivot in the ``k,k`` position (whose value is ``pivot``). Relies on the fact @@ -17836,7 +17956,9 @@ cdef inline void _block_ldlt_pivot1x1(Matrix A, Py_ssize_t k): this routine should overwrite its argument. There is no return value from this function, as its intended - effect is to update the matrix `A` in-place. + effect is to update the matrix `A` in-place, but we allow it + to return zero/one so that ``1`` can be used to indicate that + a python exception occurred. """ cdef Py_ssize_t i,j # dumy loop indices cdef Py_ssize_t n = A._nrows @@ -17862,4 +17984,4 @@ cdef inline void _block_ldlt_pivot1x1(Matrix A, Py_ssize_t k): k, A.get_unsafe(k+i+1,k)/ pivot) - return + return 0 From 2af6d9d2e6883697429c48bd59718e20e5bdd07e Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Wed, 7 Apr 2021 15:47:30 -0400 Subject: [PATCH 172/232] Trac #31619: use block_ldlt() for inverse_positive_definite(). The inverse_positive_definite() method was using indefinite_factorization() to obtain the factors that need inverting, but using block_ldlt() should be a bit more resilient to ill- conditioned matrices. --- src/sage/matrix/matrix2.pyx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 4e71bde9667..a2f5abf9328 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -12660,17 +12660,14 @@ cdef class Matrix(Matrix1): sage: actual == expected True """ - # Does it hurt if we conjugate a real number? - L, diags = self.indefinite_factorization(algorithm='hermitian', - check=False) + P,L,D = self.block_ldlt() # The default "echelonize" inverse() method works just fine for # triangular matrices. L_inv = L.inverse() - from sage.matrix.constructor import diagonal_matrix - D_inv = diagonal_matrix( ~d for d in diags ) - return L_inv.conjugate_transpose()*D_inv*L_inv + # Take A = PLDL^{*}P^{T} and simply invert. + return P*L_inv.conjugate_transpose()*D.inverse()*L_inv*P.transpose() def LU(self, pivot=None, format='plu'): From e5adbac3780fd965a0fb4f40edf2b7061ef80fae Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Wed, 7 Apr 2021 21:51:48 -0400 Subject: [PATCH 173/232] Trac #31619: support is_square() for constant non-relational expressions. The is_square() method for ring elements is supposed to return True if the given element is the square of another element in the same ring. This of course all goes to hell in the symbolic ring; nevertheless, we are currently returning things like sage: SR(5).is_square() False even though sage: sqrt(5) in SR True This commit updates is_square() to return True for all constant non-relational expressions, since you can just call sqrt() on them to find an element that, when squared, gives you what you started with. --- src/sage/arith/misc.py | 3 ++- src/sage/symbolic/expression.pyx | 38 ++++++++++++++++++++++---------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 173baa3b793..b0861d9b209 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -2810,7 +2810,8 @@ def is_square(n, root=False): sage: is_square((x-1)^2) Traceback (most recent call last): ... - NotImplementedError: is_square() not implemented for non numeric elements of Symbolic Ring + NotImplementedError: is_square() not implemented for + non-constant or relational elements of Symbolic Ring :: diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 6246c865b66..508fb4ab0df 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -2849,28 +2849,42 @@ cdef class Expression(CommutativeRingElement): def is_square(self): """ - Return ``True`` if ``self`` is a perfect square. + Return ``True`` if ``self`` is the square of another symbolic expression. + + This is ``True`` for all constant, non-relational expressions + (containing no variables or comparison), and not implemented + otherwise. EXAMPLES:: - sage: f(n,m) = n*2 + m - sage: f(2,1).is_square() - False - sage: f(3,3).is_square() + sage: SR(4).is_square() + True + sage: SR(5).is_square() True - sage: f(n,m).is_square() + sage: pi.is_square() + True + sage: x.is_square() Traceback (most recent call last): ... - NotImplementedError: is_square() not implemented for non numeric elements of Symbolic Ring - sage: SR(42).is_square() - False - sage: SR(4).is_square() - True + NotImplementedError: is_square() not implemented for non-constant + or relational elements of Symbolic Ring + sage: r = SR(4) == SR(5) + sage: r.is_square() + Traceback (most recent call last): + ... + NotImplementedError: is_square() not implemented for non-constant + or relational elements of Symbolic Ring + """ + if self.is_constant() and not self.is_relational(): + # The square root of any "number" is in SR... just call + # sqrt() on it. + return True + try: obj = self.pyobject() except TypeError as e: - raise NotImplementedError("is_square() not implemented for non numeric elements of Symbolic Ring") + raise NotImplementedError("is_square() not implemented for non-constant or relational elements of Symbolic Ring") return obj.is_square() From 5ac2754ea9354a7f8e2edf39890c52b5ebbf8465 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Wed, 7 Apr 2021 22:23:48 -0400 Subject: [PATCH 174/232] Trac #31619: implement cholesky() using block_ldlt(). This works around the missing support for inexact rings within the indefinite_factorization() method, giving us a cholesky() that works for "fields" like RR and SR and for sparse matrices with floating-point entries. --- src/sage/matrix/matrix2.pyx | 149 +++++++++++++++++++----------------- 1 file changed, 79 insertions(+), 70 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index a2f5abf9328..de11e3110c7 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -12356,7 +12356,7 @@ cdef class Matrix(Matrix1): [4.242640687119285? + 2.828427124746190?*I -2*I + 2 1.732050807568878?] sage: L.parent() Full MatrixSpace of 3 by 3 dense matrices over Algebraic Field - sage: (L*L.conjugate_transpose() - A.change_ring(QQbar)).norm() < 10^-10 + sage: L*L.conjugate_transpose() == A True @@ -12370,7 +12370,6 @@ cdef class Matrix(Matrix1): sage: L = A.cholesky() sage: L.is_immutable() True - sage: from copy import copy sage: LC = copy(L) sage: LC[0,0] = 1000 @@ -12380,50 +12379,47 @@ cdef class Matrix(Matrix1): [ 2 0 2 0] [ 1 -2 1 1] - There are a variety of situations which will prevent the computation of a Cholesky decomposition. - - The base ring must be exact. For numerical work, create a - matrix with a base ring of ``RDF`` or ``CDF`` and use the - :meth:`~sage.matrix.matrix_double_dense.Matrix_double_dense.cholesky` - method for matrices of that type. :: + The base ring need not be exact, although you should expect + the result to be inexact (correct in the norm) as well:: sage: F = RealField(100) - sage: A = matrix(F, [[1.0, 3.0], [3.0, -6.0]]) + sage: A = A = matrix(F, [[1.0, 2.0], [2.0, 6.0]]) + sage: L = A.cholesky(); L + [ 1.000... 0.000...] + [ 2.000... 1.414...] + sage: (L*L.transpose() - A).norm() < 1e-10 + True + + Even symbolic matrices can sometimes be factored:: + + sage: A = matrix(SR, [[pi,0],[0,pi]]) sage: A.cholesky() - Traceback (most recent call last): - ... - TypeError: base ring of the matrix must be exact, not Real Field with 100 bits of precision + [sqrt(pi) 0] + [ 0 sqrt(pi)] - The base ring may not have a fraction field. :: + There are a variety of situations which will prevent the + computation of a Cholesky decomposition. + + The base ring may not be able to be viewed as a subset of the + complex numbers, implying that "Hermitian" is meaningless: sage: A = matrix(Integers(6), [[2, 0], [0, 4]]) sage: A.cholesky() Traceback (most recent call last): ... - ValueError: Could not see Ring of integers modulo 6 as a subring of - the real or complex numbers + AttributeError: 'sage.rings.finite_rings.integer_mod.IntegerMod_int' + object has no attribute 'conjugate' - The base field may not have elements that are comparable to zero. :: + The matrix may not be Hermitian:: sage: F. = FiniteField(5^4) sage: A = matrix(F, [[2+a^3, 3], [3, 3]]) sage: A.cholesky() Traceback (most recent call last): ... - ValueError: Could not see Finite Field in a of size 5^4 as a subring - of the real or complex numbers - - The algebraic closure of the fraction field of the base ring may not be implemented. :: - - sage: F = Integers(7) - sage: A = matrix(F, [[4, 0], [0, 3]]) - sage: A.cholesky() - Traceback (most recent call last): - ... - ValueError: Could not see Ring of integers modulo 7 as a subring of - the real or complex numbers + ValueError: matrix is not Hermitian - The matrix may not be positive definite. :: + The matrix may not be positive-definite:: sage: C. = QuadraticField(-1) sage: B = matrix(C, [[ 2, 4 - 2*I, 2 + 2*I], @@ -12434,11 +12430,9 @@ cdef class Matrix(Matrix1): sage: B.cholesky() Traceback (most recent call last): ... - ValueError: matrix is not positive definite, - so cannot compute Cholesky decomposition + ValueError: matrix is not positive definite - The matrix could be positive semi-definite, and thus - lack a Cholesky decomposition. :: + :: sage: A = matrix(QQ, [[21, 15, 12, -3], ....: [15, 12, 9, 12], @@ -12446,13 +12440,10 @@ cdef class Matrix(Matrix1): ....: [-3, 12, 3, 8]]) sage: A.is_positive_definite() False - sage: [A[:i,:i].determinant() for i in range(1,A.nrows()+1)] - [21, 27, 0, 0] sage: A.cholesky() Traceback (most recent call last): ... - ValueError: matrix is not positive definite, - so cannot compute Cholesky decomposition + ValueError: matrix is not positive definite TESTS: @@ -12479,49 +12470,61 @@ cdef class Matrix(Matrix1): sage: E = matrix(QQ, [[2, 1], [1, 1]]) sage: E.cholesky().base_ring() Algebraic Real Field + + Check that sparse floating-point matrices can be factored + using a toy example reported as part of :trac:`13674`:: + + sage: A = matrix(RDF, [[1, 1], [1, 2]], sparse=True) + sage: A.cholesky() + [1.0 0.0] + [1.0 1.0] + sage: A = matrix(CDF, [[1, I], [-I, 2]], sparse=True) + sage: A.cholesky() + [ 1.0 0.0] + [-1.0*I 1.0] + """ - from copy import copy + cdef Matrix C # output matrix C = self.fetch('cholesky') if C is not None: return C - if not self.is_square(): - msg = "matrix must be square, not {0} x {1}" - raise ValueError(msg.format(self.nrows(), self.ncols())) - if not self.base_ring().is_exact(): - msg = 'base ring of the matrix must be exact, not {0}' - raise TypeError(msg.format(self.base_ring())) - if not self.is_positive_definite(): - msg = 'matrix is not positive definite, so cannot compute Cholesky decomposition' - raise ValueError(msg) + cdef Py_ssize_t n = self.nrows() + + if not self.is_hermitian(): + raise ValueError("matrix is not Hermitian") + + # Use classical=True to ensure that we don't get a permuted L. + cdef Matrix L # block_ldlt() results + cdef list d # block_ldlt() results + try: + _,L,d = self._block_ldlt(classical=True) + except ValueError: + # If the matrix was positive-definite, that would + # have worked. + raise ValueError("matrix is not positive definite") - # Check to see if a factorization is cached first. - factors = self.fetch('indefinite_factorization_hermitian') - if factors is None: - factors = self.fetch('indefinite_factorization_symmetric') - if factors is None: - # The "hermitian" algorithm works on real matrices, too. - factors = self._indefinite_factorization(algorithm='hermitian', - check=False) - - L = factors[0] - d = factors[1] F = L.base_ring() # field really + zero = F.zero() - F_ac = F - if hasattr(F, 'algebraic_closure'): - # This can fail if, say, F is the symbolic ring which does - # contain the requisite roots in its own special way but - # does not have an algebraic_closure() method. - F_ac = F.algebraic_closure() + cdef list splits = [] # square roots of diagonal entries + cdef bint extend = False + for X in d: + # The X are guaranteed to be one-by-one blocks. + x = X[0,0] + + if x <= zero: + raise ValueError("matrix is not positive definite") - splits = [] # square roots of diagonal entries - extend = False - for x in d: if not extend and x.is_square(): sqrt = x.sqrt() else: extend = True + if hasattr(F, 'algebraic_closure'): + # This can fail if, say, F is the symbolic ring which does + # contain the requisite roots in its own special way but + # does not have an algebraic_closure() method. + F_ac = F.algebraic_closure() sqrt = F_ac(x).sqrt() splits.append(sqrt) # move square root of the diagonal matrix @@ -12531,7 +12534,7 @@ cdef class Matrix(Matrix1): if extend: # Try to use the algebraic reals but fall back to what is # probably QQbar if we find an entry in "L" that won't fit - # in AA. + # in AA. This was Trac ticket #18381. from sage.rings.qqbar import AA try: C = L.change_ring(AA) @@ -12540,8 +12543,14 @@ cdef class Matrix(Matrix1): else: C = L.__copy__() - for c in range(C.ncols()): - C.rescale_col(c, splits[c]) + # Overwrite the (strict) upper-triangular part of "C", since a + # priori it contains junk after _block_ldlt(). + zero = C.base_ring().zero() + cdef Py_ssize_t i, j # loop indices + for i in range(n): + C.rescale_col_c(i, splits[i], 0) + for j in range(i+1,n): + C.set_unsafe(i,j,zero) C.set_immutable() self.cache('cholesky', C) return C From 7d40c7e0959ceaa616809b0b4513ee003f0bd934 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Thu, 8 Apr 2021 00:10:27 -0400 Subject: [PATCH 175/232] Trac #31619: cache the result of _block_ldlt(). The block_ldlt() factorization is powerful but relatively slow. Now that its results are used in a few other places, we cache the result of the underlying _block_ldlt() method to save time. --- src/sage/matrix/matrix2.pyx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index de11e3110c7..8c6642585bc 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -13621,6 +13621,13 @@ cdef class Matrix(Matrix1): [[1], [-4], [0]] """ + cdef str cache_string = "_block_ldlt" + if classical: + cache_string += "_classical" + cdef tuple result = self.fetch(cache_string) + if result is not None: + return result + cdef Py_ssize_t i, j, k # loop indices cdef Py_ssize_t r # another row/column index @@ -13859,7 +13866,9 @@ cdef class Matrix(Matrix1): # correctness. A.set_unsafe(i, i, one) - return (p,A,d) + result = (p,A,d) + self.cache(cache_string, result) + return result def block_ldlt(self, classical=False): r""" From 69946458bc2cecb1a0e3df62586375793bd87f1b Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Thu, 8 Apr 2021 07:43:48 -0400 Subject: [PATCH 176/232] Trac #31619: make _block_ldlt() a C method. This method is already internal; we can eliminate a tiiiiiny bit of overhead by just making it a C method. --- src/sage/matrix/matrix2.pxd | 2 +- src/sage/matrix/matrix2.pyx | 22 ++++------------------ 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/sage/matrix/matrix2.pxd b/src/sage/matrix/matrix2.pxd index d3555ffbd2f..6f99ff58d02 100644 --- a/src/sage/matrix/matrix2.pxd +++ b/src/sage/matrix/matrix2.pxd @@ -18,7 +18,7 @@ cdef class Matrix(Matrix1): cdef _det_by_minors(self, Py_ssize_t level) cdef _pf_bfl(self) cdef bint _is_positive_definite_or_semidefinite(self, bint semi) except -1 + cdef tuple _block_ldlt(self, bint classical) cpdef _echelon(self, str algorithm) cpdef _echelon_in_place(self, str algorithm) cpdef matrix_window(self, Py_ssize_t row=*, Py_ssize_t col=*, Py_ssize_t nrows=*, Py_ssize_t ncols=*, bint check=*) - diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 8c6642585bc..edf777e45c2 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -12498,7 +12498,7 @@ cdef class Matrix(Matrix1): cdef Matrix L # block_ldlt() results cdef list d # block_ldlt() results try: - _,L,d = self._block_ldlt(classical=True) + _,L,d = self._block_ldlt(True) except ValueError: # If the matrix was positive-definite, that would # have worked. @@ -13579,7 +13579,7 @@ cdef class Matrix(Matrix1): raise ValueError(msg.format(d)) return L, vector(L.base_ring(), d) - def _block_ldlt(self, classical=False): + cdef tuple _block_ldlt(self, bint classical): r""" Perform a user-unfriendly block-`LDL^{T}` factorization of the Hermitian matrix `A` @@ -13606,20 +13606,6 @@ cdef class Matrix(Matrix1): method can be found in the user-facing :meth:`block_ldlt` method. - EXAMPLES: - - Test that this method can be called directly; the returned ``l`` - is not inspected because its upper-triangular part is undefined:: - - sage: A = matrix(QQ, [[0, 1, 0], - ....: [1, 1, 2], - ....: [0, 2, 0]]) - sage: p,l,d = A._block_ldlt() - sage: p - array('I', [1, 2, 0]) - sage: d - [[1], [-4], [0]] - """ cdef str cache_string = "_block_ldlt" if classical: @@ -14177,7 +14163,7 @@ cdef class Matrix(Matrix1): cdef Py_ssize_t i, j # loop indices cdef Matrix P,L,D # output matrices - p,L,d = self._block_ldlt(classical=classical) + p,L,d = self._block_ldlt(classical) MS = L.matrix_space() P = MS.matrix(lambda i,j: p[j] == i) @@ -14230,7 +14216,7 @@ cdef class Matrix(Matrix1): return True # vacuously cdef list d - _,_,d = self._block_ldlt() + _,_,d = self._block_ldlt(False) # Check each 1x1 block for a nonpositive (negative) entry. If # we don't find any, the matrix is positive-(semi)definite. The From 8ba2be7faa15232232e04c54adc449f06e14b9fb Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Wed, 14 Apr 2021 21:24:44 -0400 Subject: [PATCH 177/232] Trac #31619: clarify matrix _is_positive_definite_or_semidefinite(). The code that computes positive-(semi)definiteness was a bit convoluted. This commit factors out the gt/ge operator to greatly simplify it. --- src/sage/matrix/matrix2.pyx | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index edf777e45c2..fce58cefc90 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -14221,18 +14221,13 @@ cdef class Matrix(Matrix1): # Check each 1x1 block for a nonpositive (negative) entry. If # we don't find any, the matrix is positive-(semi)definite. The # presence of any 2x2 blocks also indicates indefiniteness. - for d_i in d: - if d_i.nrows() == 1: - if semi: - if d_i < 0: - return False - else: - if d_i <= 0: - return False - else: - # There's a 2x2 block - return False - return True + import operator + op = operator.gt + if semi: + op = operator.ge + + return all(d_i.nrows() == 1 and op(d_i[0,0], 0) for d_i in d) + def is_positive_semidefinite(self): r""" From 4a883de3e231dd74257d96bad767aaa3e44ebe57 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 26 Apr 2021 15:54:23 -0400 Subject: [PATCH 178/232] Trac #31619: clean up documentation for is_positive_definite(). After some recent changes to the is_positive_definite() method, its documentation refers to (and tests) a few things that aren't really relevant any longer. This commit tries to clarify exactly what is happening within the method, and streamlines the test cases to check only those properties that are actually relevant to the method. --- src/sage/matrix/matrix2.pyx | 116 ++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 66 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index fce58cefc90..cb921d36338 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -14360,49 +14360,49 @@ cdef class Matrix(Matrix1): def is_positive_definite(self): r""" - Determines if a real or symmetric matrix is positive definite. + Determine if a matrix is positive-definite. - A square matrix `A` is positive definite if it is - symmetric with real entries or Hermitian with complex entries, - and for every non-zero vector `\vec{x}` + A matrix `A` is positive definite if it + :meth:`~.Matrix.is_hermitian` and if, for every non-zero + vector `x`, .. MATH:: - \vec{x}^\ast A\vec{x} > 0. - - Here `\vec{x}^\ast` is the conjugate-transpose, which can be - simplified to just the transpose in the real case. + \left\langle Ax, x \right\rangle > 0. ALGORITHM: - A matrix is positive definite if and only if the diagonal - blocks in its :meth:`block_ldlt` factorization are all 1-by-1 - and have positive entries. This algorithm is of order - ``n^3/3`` and may be applied to matrices with elements of any - ring that has a fraction field contained within the reals or - complex numbers. + A Hermitian matrix is positive-definite if and only if the + diagonal blocks in its :meth:`block_ldlt` factorization are + all 1-by-1 and have positive entries. We first check that the + matrix :meth:`~.Matrix.is_hermitian`, and then compute this + factorization. INPUT: - Any square matrix. + A matrix. OUTPUT: - This routine will return ``True`` if the matrix is square, - symmetric or Hermitian, and meets the condition above - for the quadratic form. + This routine will return ``True`` if the matrix is Hermitian + and meets the condition above for the quadratic form. + + The base ring for the elements of the matrix must + + 1. Have a fraction field implemented; and + 2. Be a subring of the real numbers, complex numbers, + or symbolic ring. + + .. SEEALSO:: - The base ring for the elements of the matrix needs to have a - fraction field implemented and the computations that result - from the factorization must be convertible to real numbers - that are comparable to zero. + :meth:`block_ldlt`, :meth:`~.Matrix.is_hermitian`, + :meth:`is_positive_semidefinite` EXAMPLES: - A real symmetric matrix that is positive definite, - as evidenced by the positive entries for the diagonal - matrix of the indefinite factorization and the positive - determinants of the leading principal submatrices. :: + A real symmetric matrix that is positive-definite, as + evidenced by the positive determinants of its leading + principal submatrices:: sage: A = matrix(QQ, [[ 4, -2, 4, 2], ....: [-2, 10, -2, -7], @@ -14410,14 +14410,12 @@ cdef class Matrix(Matrix1): ....: [ 2, -7, 4, 7]]) sage: A.is_positive_definite() True - sage: _, d = A.indefinite_factorization(algorithm='symmetric') - sage: d - (4, 9, 4, 1) sage: [A[:i,:i].determinant() for i in range(1,A.nrows()+1)] [4, 36, 144, 144] - A real symmetric matrix which is not positive definite, along - with a vector that makes the quadratic form negative. :: + A real symmetric matrix that is not positive-definite and a + vector ``u`` that makes the corresponding quadratic form + negative:: sage: A = matrix(QQ, [[ 3, -6, 9, 6, -9], ....: [-6, 11, -16, -11, 17], @@ -14426,18 +14424,13 @@ cdef class Matrix(Matrix1): ....: [-9, 17, -40, -19, 68]]) sage: A.is_positive_definite() False - sage: _, d = A.indefinite_factorization(algorithm='symmetric') - sage: d - (3, -1, 5, -2, -1) - sage: [A[:i,:i].determinant() for i in range(1,A.nrows()+1)] - [3, -3, -15, 30, -30] sage: u = vector(QQ, [2, 2, 0, 1, 0]) - sage: u.row()*A*u - (-3) + sage: (A*u).inner_product(u) + -3 - A real symmetric matrix with a singular leading - principal submatrix, that is therefore not positive definite. - The vector ``u`` makes the quadratic form zero. :: + Another real symmetric matrix that is not positive-definite + and a vector ``u`` that makes the corresponding quadratic form + zero:: sage: A = matrix(QQ, [[21, 15, 12, -2], ....: [15, 12, 9, 6], @@ -14445,13 +14438,13 @@ cdef class Matrix(Matrix1): ....: [-2, 6, 3, 8]]) sage: A.is_positive_definite() False - sage: [A[:i,:i].determinant() for i in range(1,A.nrows()+1)] - [21, 27, 0, -75] sage: u = vector(QQ, [1,1,-3,0]) - sage: u.row()*A*u - (0) + sage: (A*u).inner_product(u) + 0 - An Hermitian matrix that is positive definite. :: + A complex Hermitian matrix that is positive-definite, + confirmed by the positive determinants of its leading + principal submatrices:: sage: C. = NumberField(x^2 + 1, embedding=CC(0,1)) sage: A = matrix(C, [[ 23, 17*I + 3, 24*I + 25, 21*I], @@ -14460,14 +14453,11 @@ cdef class Matrix(Matrix1): ....: [ -21*I, -7*I + 15, -24*I + 6, 28]]) sage: A.is_positive_definite() True - sage: _, d = A.indefinite_factorization(algorithm='hermitian') - sage: d - (23, 576/23, 89885/144, 142130/17977) sage: [A[:i,:i].determinant() for i in range(1,A.nrows()+1)] [23, 576, 359540, 2842600] - An Hermitian matrix that is not positive definite. - The vector ``u`` makes the quadratic form negative. :: + An Hermitian matrix that is not positive-definite and a vector + ``u`` that makes the corresponding quadratic form negative:: sage: C. = QuadraticField(-1) sage: B = matrix(C, [[ 2, 4 - 2*I, 2 + 2*I], @@ -14475,16 +14465,13 @@ cdef class Matrix(Matrix1): ....: [2 - 2*I, -10*I, -3]]) sage: B.is_positive_definite() False - sage: _, d = B.indefinite_factorization(algorithm='hermitian') - sage: d - (2, -2, 3) - sage: [B[:i,:i].determinant() for i in range(1,B.nrows()+1)] - [2, -4, -12] sage: u = vector(C, [-5 + 10*I, 4 - 3*I, 0]) - sage: u.row().conjugate()*B*u - (-50) + sage: (B*u).hermitian_inner_product(u) + -50 - A positive definite matrix over an algebraically closed field. :: + A positive-definite matrix over an algebraically-closed field, + confirmed by the positive determinants of its leading + principal submatrices:: sage: A = matrix(QQbar, [[ 2, 4 + 2*I, 6 - 4*I], ....: [ -2*I + 4, 11, 10 - 12*I], @@ -14496,11 +14483,9 @@ cdef class Matrix(Matrix1): TESTS: - If the base ring lacks a ``conjugate`` method, it - will be assumed to not be Hermitian and thus symmetric. - If the base ring does not make sense as a subfield of - the reals, then this routine will fail since comparison - to zero is meaningless. :: + If the base ring does not make sense as a subfield of the real + numbers, complex numbers, or symbolic ring, then this routine + will fail since comparison to zero is meaningless:: sage: F. = FiniteField(5^3) sage: a.conjugate() @@ -14518,7 +14503,7 @@ cdef class Matrix(Matrix1): ValueError: Could not see Finite Field in a of size 5^3 as a subring of the real or complex numbers - The 0x0 matrix is trivially positive definite:: + The 0x0 matrix is trivially positive-definite:: sage: Matrix(0).is_positive_definite() True @@ -14532,7 +14517,6 @@ cdef class Matrix(Matrix1): True sage: matrix.identity(SR,4).is_positive_definite() True - """ return self._is_positive_definite_or_semidefinite(False) From 63e26e49c68072d814850be40ce12e88f2c1922d Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 26 Apr 2021 16:57:53 -0400 Subject: [PATCH 179/232] Trac #31619: clean up cholesky() documentation. The cholesky() method was almost entirely rewritten in a recent commit. Here we update its documentation to reflect those changes. --- src/sage/matrix/matrix2.pyx | 66 ++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index cb921d36338..94a8631092f 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -12227,17 +12227,16 @@ cdef class Matrix(Matrix1): def cholesky(self): r""" - Returns the Cholesky decomposition of a symmetric or Hermitian matrix. + Returns the Cholesky decomposition of a Hermitian matrix. INPUT: - A square matrix that is real, symmetric and positive definite. - Or a square matrix that is complex, Hermitian and positive - definite. Generally, the base ring for the entries of the - matrix needs to be a subfield of the algebraic numbers - (``QQbar``). Examples include the rational numbers (``QQ``), - some number fields, and real algebraic numbers and the - algebraic numbers themselves. + A positive-definite matrix. Generally, the base ring for the + entries of the matrix needs to be a subfield of the algebraic + numbers (``QQbar``). Examples include the rational numbers + (``QQ``), some number fields, and real algebraic numbers and + the algebraic numbers themselves. Symbolic matrices can also + occasionally be factored. OUTPUT: @@ -12248,10 +12247,9 @@ cdef class Matrix(Matrix1): A = LL^\ast - where `L^\ast` is the conjugate-transpose in the complex case, - and just the transpose in the real case. If the matrix - fails to be positive definite (perhaps because it is not - symmetric or Hermitian), then a ``ValueError`` results. + where `L^\ast` is the conjugate-transpose. If the matrix is + not positive-definite (for example, if it is not Hermitian) + then a ``ValueError`` results. If possible, the output matrix will be over the fraction field of the base ring of the input matrix. If that fraction field @@ -12262,23 +12260,24 @@ cdef class Matrix(Matrix1): ALGORITHM: - Whether or not the matrix is positive definite is checked - first in every case. This is accomplished with an - indefinite factorization (see :meth:`indefinite_factorization`) - which caches its result. This algorithm is of an order `n^3/3`. - If the matrix is positive definite, this computation always - succeeds, using just field operations. The transition to a - Cholesky decomposition "only" requires computing square roots - of the positive (real) entries of the diagonal matrix produced in - the indefinite factorization. Hence, there is no real penalty - in the positive definite check (here, or prior to calling this - routine), but a field extension with square roots may not be - implemented in all reasonable cases. + First we ensure that the matrix `A` + :meth:`~.Matrix.is_hermitian`. Afterwards, we attempt to + compute a classical :meth:`block_ldlt` factorization, `A = + LDL^{*}`, of the matrix. If that fails, then the matrix was + not positive-definite and an error is raised. Otherwise we + take the entrywise square-root `\sqrt{D}` of the diagonal + matrix `D` (whose entries are the positive eigenvalues of the + original matrix) to obtain the Cholesky factorization `A = + \left(L\sqrt{D}\right)\left(L\sqrt{D}\right)^{*}`. If the + necessary square roots cannot be taken in the fraction field + of original base ring, then we move to either its algebraic + closure or the algebraic reals, depending on whether or not + imaginary numbers are required. EXAMPLES: This simple example has a result with entries that remain - in the field of rational numbers. :: + in the field of rational numbers:: sage: A = matrix(QQ, [[ 4, -2, 4, 2], ....: [-2, 10, -2, -7], @@ -12300,7 +12299,7 @@ cdef class Matrix(Matrix1): This seemingly simple example requires first moving to the rational numbers for field operations, and then square roots necessitate that the result has entries in the field - of algebraic numbers. :: + of algebraic numbers:: sage: A = matrix(ZZ, [[ 78, -30, -37, -2], ....: [-30, 102, 179, -18], @@ -12321,7 +12320,7 @@ cdef class Matrix(Matrix1): Some subfields of the complex numbers, such as this number field of complex numbers with rational real and imaginary parts, - allow for this computation. :: + allow for this computation:: sage: C. = QuadraticField(-1) sage: A = matrix(C, [[ 23, 17*I + 3, 24*I + 25, 21*I], @@ -12342,7 +12341,7 @@ cdef class Matrix(Matrix1): True The field of algebraic numbers is an ideal setting for this - computation. :: + computation:: sage: A = matrix(QQbar, [[ 2, 4 + 2*I, 6 - 4*I], ....: [ -2*I + 4, 11, 10 - 12*I], @@ -12359,9 +12358,8 @@ cdef class Matrix(Matrix1): sage: L*L.conjugate_transpose() == A True - Results are cached, hence immutable. Use the ``copy`` function - if you need to make a change. :: + if you need to make a change:: sage: A = matrix(QQ, [[ 4, -2, 4, 2], ....: [-2, 10, -2, -7], @@ -12380,7 +12378,8 @@ cdef class Matrix(Matrix1): [ 1 -2 1 1] The base ring need not be exact, although you should expect - the result to be inexact (correct in the norm) as well:: + the result to be inexact (correct only in the norm) as well + in that case:: sage: F = RealField(100) sage: A = A = matrix(F, [[1.0, 2.0], [2.0, 6.0]]) @@ -12401,7 +12400,7 @@ cdef class Matrix(Matrix1): computation of a Cholesky decomposition. The base ring may not be able to be viewed as a subset of the - complex numbers, implying that "Hermitian" is meaningless: + complex numbers, implying that "Hermitian" is meaningless:: sage: A = matrix(Integers(6), [[2, 0], [0, 4]]) sage: A.cholesky() @@ -12447,7 +12446,7 @@ cdef class Matrix(Matrix1): TESTS: - This verifies that :trac:`11274` is resolved. :: + This verifies that :trac:`11274` is resolved:: sage: E = matrix(QQ, [[2, 1], [1, 1]]) sage: E.is_symmetric() @@ -12482,7 +12481,6 @@ cdef class Matrix(Matrix1): sage: A.cholesky() [ 1.0 0.0] [-1.0*I 1.0] - """ cdef Matrix C # output matrix C = self.fetch('cholesky') From 8986d7cc441c9876c58910873c048849564706b1 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 26 Apr 2021 17:31:08 -0400 Subject: [PATCH 180/232] Trac #31619: replace is_positive_definite(certificate=True). This optional argument has been replaced but is now deprecated in favor of the user asking for the factorization separately. Moreover, only the "L" and "d" portions of the block_ldlt() factorization are returned as a certificate. This ignores the fact that a permutation may be needed, but in most cases will agree with the old behavior, and avoids breaking the interface an additional time (by returning a triplet instead of the expected pair). --- src/sage/matrix/matrix2.pyx | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 94a8631092f..63a79119f9c 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -14356,7 +14356,7 @@ cdef class Matrix(Matrix1): """ return self._is_positive_definite_or_semidefinite(True) - def is_positive_definite(self): + def is_positive_definite(self, certificate=False): r""" Determine if a matrix is positive-definite. @@ -14378,7 +14378,10 @@ cdef class Matrix(Matrix1): INPUT: - A matrix. + - ``self`` -- a matrix + - ``certificate`` -- (default: ``False``) return the + lower-triangular and diagonal parts of the :meth:`block_ldlt` + factorization when the matrix is positive-definite. Deprecated. OUTPUT: @@ -14391,6 +14394,14 @@ cdef class Matrix(Matrix1): 2. Be a subring of the real numbers, complex numbers, or symbolic ring. + If ``certificate`` is ``True``, a triplet ``(b, L, d)`` will + be returned instead, with ``b`` containing the result (true or + false). If the matrix is positive-definite, then ``L`` and + ``d`` will contain the lower-triangular and diagonal parts of + the :meth:`block_ldlt` factorization, respectively. Or if the + matrix is not positive-definite (that is, if ``b`` is + ``False``), then both ``L`` and ``d`` will be ``None``. + .. SEEALSO:: :meth:`block_ldlt`, :meth:`~.Matrix.is_hermitian`, @@ -14516,7 +14527,22 @@ cdef class Matrix(Matrix1): sage: matrix.identity(SR,4).is_positive_definite() True """ - return self._is_positive_definite_or_semidefinite(False) + result = self._is_positive_definite_or_semidefinite(False) + if certificate: + from sage.misc.superseded import deprecation + msg = "the 'certificate' argument is deprecated; if you " + msg += "need the corresponding factorization, you can " + msg += "simply compute it yourself (the results are cached)" + deprecation(31619, msg) + L = None + d = None + if result: + from sage.modules.free_module_element import vector + _,L,D = self.block_ldlt() + d = vector(D.base_ring(), D.diagonal()) + return (result, L, d) + else: + return result def principal_square_root(self, check_positivity=True): From fa49b852c4195e75ebb31dfd8c05117138b0b3bb Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Fri, 2 Apr 2021 09:32:26 -0400 Subject: [PATCH 181/232] Trac #31594: accept giac-1.7.x from the system. We modify the upper-bounds on giac in its spkg-configure.m4 to allow 1.7.x versions. At least v1.7.0.1 works out-of-the-box on the latest sage development tree. --- build/pkgs/giac/spkg-configure.m4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/giac/spkg-configure.m4 b/build/pkgs/giac/spkg-configure.m4 index 83974a56e83..2776032a4f5 100644 --- a/build/pkgs/giac/spkg-configure.m4 +++ b/build/pkgs/giac/spkg-configure.m4 @@ -2,7 +2,7 @@ SAGE_SPKG_CONFIGURE([giac], [ SAGE_SPKG_DEPCHECK([pari], [ dnl giac does not seem to reveal its patchlevel m4_pushdef([GIAC_MIN_VERSION], [1.5.0]) - m4_pushdef([GIAC_MAX_VERSION], [1.6.999]) + m4_pushdef([GIAC_MAX_VERSION], [1.7.999]) AC_CACHE_CHECK([for giac >= ]GIAC_MIN_VERSION[, <= ]GIAC_MAX_VERSION, [ac_cv_path_GIAC], [ AC_PATH_PROGS_FEATURE_CHECK([GIAC], [giac], [ giac_version=$($ac_path_GIAC --version 2> /dev/null | tail -1) From d5d6689694c3a590728ddd5ca2a182648ebbc684 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 10 May 2021 08:23:38 -0400 Subject: [PATCH 182/232] Trac #31594: fix an interface test with newer versions of giac. We have a test in interfaces/giac.py that passes a string to giac() asking it to compute an indefinite integral. In newer versions of giac, the format of that result has changed -- but it was always complicated. Too complicated to check by differentiating, subtracting the original expression, and comparing with zero, in fact. Since the point of the test is to check that we can pass commands to giac(), I see no reason to use such a complicated integral as a test case. Instead I've changed it to use sin(x)^2 and cos(x)^2, whose integrals sum to x and can be compared (as symbolic expressions) with that expected result quickly. --- src/sage/interfaces/giac.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/sage/interfaces/giac.py b/src/sage/interfaces/giac.py index d90413e5335..9a68522ae95 100644 --- a/src/sage/interfaces/giac.py +++ b/src/sage/interfaces/giac.py @@ -605,9 +605,12 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if TESTS:: - sage: h='int(1/x*((-2*x^(1/3)+1)^(1/4))^3,x)' - sage: giac(h) - 12*(...) + sage: h1 = 'int(sin(x)^2, x)' + sage: h2 = 'int(cos(x)^2, x)' + sage: giac_result = giac(h1) + giac(h2) + sage: bool(giac_result.sage() == x) + True + """ with gc_disabled(): z = Expect._eval_line(self, line, allow_use_file=allow_use_file, From 60a307aadf9d8d724675d8d3b7343ff9dd12c26d Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 10 May 2021 16:43:50 -0400 Subject: [PATCH 183/232] Trac #31594: support giac-1.7.x in symbolic integration tests. The output of a symbolic integral in symbolic/integration/integral.py now depends on the version of giac installed; newer versions bring improved simplifications. To allow the test case in question to pass with all versions of giac, this commit checks for symbolic expression equality between the actual and expected results rather than string equality. --- src/sage/symbolic/integration/integral.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/symbolic/integration/integral.py b/src/sage/symbolic/integration/integral.py index 46b1a11a82a..b30570599d1 100644 --- a/src/sage/symbolic/integration/integral.py +++ b/src/sage/symbolic/integration/integral.py @@ -901,8 +901,11 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None, hold=False): 4 sage: f = sqrt(x + 1/x^2) - sage: integrate(f, x) - 1/3*(2*sqrt(x^3 + 1) - log(sqrt(x^3 + 1) + 1) + log(abs(sqrt(x^3 + 1) - 1)))*sgn(x) + sage: actual = integrate(f, x) + sage: expected = (1/3*(2*sqrt(x^3 + 1) - log(sqrt(x^3 + 1) + 1) + ....: + log(abs(sqrt(x^3 + 1) - 1)))*sgn(x)) + sage: bool(actual == expected) + True sage: g = abs(sin(x)*cos(x)) sage: g.integrate(x, 0, 2*pi) From 55e36139c7719c838c68e0a4bf95472d709b1470 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 10 May 2021 17:08:44 -0400 Subject: [PATCH 184/232] Trac #31594: simplify two libgiac simplify() examples. There are two examples in libs/giac/giac.pyx that call libgiac.simplify on exp(I*pi/5)^3. Older versions of giac return the an abstract result containing "rootof", but newer versions return the actual complex roots. To allow the test case to pass with all versions of giac, and since the point of the example is simply to exercise the GiacFunction class, this commit simplifies the call to, libgiac.simplify(exp(I*pi)) which both 1.6.x and 1.7.x versions of giac are able to recognize explicitly as -1. The test in question was duplicated in the docscring for the GiacFunctionNoEV class, but I can discern no reason for it being there. I've simply removed that instance. --- src/sage/libs/giac/giac.pyx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sage/libs/giac/giac.pyx b/src/sage/libs/giac/giac.pyx index 203acd318fe..9c47248e06a 100644 --- a/src/sage/libs/giac/giac.pyx +++ b/src/sage/libs/giac/giac.pyx @@ -2057,8 +2057,8 @@ class GiacFunction(Pygen): EXAMPLES:: sage: from sage.libs.giac.giac import * - sage: libgiac.simplify(exp(I*pi/5)^3) # simplify is a GiacFunction - rootof([[-1,-1+2*i,25+4*i,-7-30*i],[1,0,-30,40,5]])/32 + sage: libgiac.simplify(exp(I*pi)) # simplify is a GiacFunction + -1 sage: libgiac('a:=1') 1 sage: libgiac.purge('a') # purge is not a GiacFunction @@ -2114,8 +2114,6 @@ class GiacFunctionNoEV(Pygen): EXAMPLES:: sage: from sage.libs.giac.giac import * - sage: libgiac.simplify(exp(I*pi/5)^3) # simplify is a GiacFunction - rootof([[-1,-1+2*i,25+4*i,-7-30*i],[1,0,-30,40,5]])/32 sage: libgiac('a:=1') 1 sage: libgiac.purge('a') # purge is a GiacFunctionNoEV From d532b564ba53474c24ee668d09f2cae8d2adbf1c Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 11 May 2021 15:53:55 +0200 Subject: [PATCH 185/232] Trac #21203 review issue 10: use "raise ... from None" where approriate --- src/sage/combinat/k_regular_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 540ca13c061..ee7cefaa6ce 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -362,4 +362,4 @@ def _n_to_index_(self, n): try: return W(n.digits(self.k)) except OverflowError: - raise ValueError('value {} of index is negative'.format(n)) + raise ValueError('value {} of index is negative'.format(n)) from None From c165835c65b91c1badecd3aae58fa1b031f74a2b Mon Sep 17 00:00:00 2001 From: David Loeffler Date: Thu, 13 May 2021 13:56:39 +0100 Subject: [PATCH 186/232] Fix broken latex in docstring --- src/sage/modular/local_comp/smoothchar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modular/local_comp/smoothchar.py b/src/sage/modular/local_comp/smoothchar.py index 1907bb7703b..e54d5ad1d40 100644 --- a/src/sage/modular/local_comp/smoothchar.py +++ b/src/sage/modular/local_comp/smoothchar.py @@ -1097,7 +1097,7 @@ def quadratic_chars(self): class SmoothCharacterGroupQuadratic(SmoothCharacterGroupGeneric): r""" - The group of smooth characters of `E^\times`, where `E` is a quadratic extension of `\Qp`. + The group of smooth characters of `E^\times`, where `E` is a quadratic extension of `\QQ_p`. """ def discrete_log(self, level, x, gens=None): From dbe38b2c6a92cc470de5607d418113c6ef91fbe6 Mon Sep 17 00:00:00 2001 From: Yuan Zhou Date: Thu, 13 May 2021 11:04:47 -0400 Subject: [PATCH 187/232] change MIPVariable's repr to include useful info --- src/sage/combinat/matrices/dancing_links.pyx | 6 +- src/sage/numerical/mip.pyx | 64 ++++++++++++++------ 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/sage/combinat/matrices/dancing_links.pyx b/src/sage/combinat/matrices/dancing_links.pyx index c62780c5d74..fb34665a7e7 100644 --- a/src/sage/combinat/matrices/dancing_links.pyx +++ b/src/sage/combinat/matrices/dancing_links.pyx @@ -1018,7 +1018,7 @@ cdef class dancing_linksWrapper: OUTPUT: - MixedIntegerLinearProgram instance - - MIPVariable of dimension 1 + - MIPVariable with binary components EXAMPLES:: @@ -1029,7 +1029,7 @@ cdef class dancing_linksWrapper: sage: p Boolean Program (no objective, 4 variables, 4 constraints) sage: x - MIPVariable of dimension 1 + MIPVariable with 4 binary components In the reduction, the boolean variable x_i is True if and only if the i-th row is in the solution:: @@ -1053,7 +1053,7 @@ cdef class dancing_linksWrapper: sage: d.to_milp('gurobi') # optional - gurobi sage_numerical_backends_gurobi (Boolean Program (no objective, 4 variables, 4 constraints), - MIPVariable of dimension 1) + MIPVariable with 4 binary components) """ from sage.numerical.mip import MixedIntegerLinearProgram diff --git a/src/sage/numerical/mip.pyx b/src/sage/numerical/mip.pyx index 7a4a312711b..8f1239bc4fb 100644 --- a/src/sage/numerical/mip.pyx +++ b/src/sage/numerical/mip.pyx @@ -132,7 +132,7 @@ or by the following special syntax:: sage: mip. = MixedIntegerLinearProgram(solver='GLPK') sage: a - MIPVariable of dimension 1 + MIPVariable a with 0 real component sage: 5 + a[1] + 2*b[3] 5 + x_0 + 2*x_1 @@ -744,7 +744,7 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p = MixedIntegerLinearProgram(solver='GLPK') sage: x = p.new_variable(); x - MIPVariable of dimension 1 + MIPVariable with 0 real component sage: x0 = x[0]; x0 x_0 @@ -796,7 +796,7 @@ cdef class MixedIntegerLinearProgram(SageObject): shorthand for generating new variables with default settings:: sage: mip. = MixedIntegerLinearProgram(solver='GLPK') - sage: mip.add_constraint(x[0] + y[1] + z[2] <= 10) + sage: mip.add_constraint(x[0] + y[1] + z[2] <= 10) sage: mip.show() Maximization: @@ -835,7 +835,7 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p = MixedIntegerLinearProgram(solver='GLPK') sage: p.default_variable() - MIPVariable of dimension 1 + MIPVariable with 0 real component """ if self._default_mipvariable is None: self._default_mipvariable = self.new_variable() @@ -1309,7 +1309,7 @@ cdef class MixedIntegerLinearProgram(SageObject): name = varid_explainer[i] lb, ub = b.col_bounds(i) print(' {0} is {1} variable (min={2}, max={3})'.format( - name, var_type, + name, var_type, lb if lb is not None else "-oo", ub if ub is not None else "+oo")) @@ -1636,7 +1636,7 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p.add_constraint(x[5] + 3*x[7] == x[6] + 3) sage: p.add_constraint(x[5] + 3*x[7] <= x[6] + 3 <= x[8] + 27) - + Using this notation, the previous program can be written as:: sage: p = MixedIntegerLinearProgram(maximization=True, solver='GLPK') @@ -1819,8 +1819,8 @@ cdef class MixedIntegerLinearProgram(SageObject): raise ValueError('min and max must not be specified for (in)equalities') relation = linear_function M = relation.parent().linear_tensors().free_module() - self.add_constraint(relation.lhs() - relation.rhs(), - min=M(0) if relation.is_equation() else None, + self.add_constraint(relation.lhs() - relation.rhs(), + min=M(0) if relation.is_equation() else None, max=M(0), name=name) else: raise ValueError('argument must be a linear function or constraint, got '+str(linear_function)) @@ -2712,7 +2712,7 @@ cdef class MixedIntegerLinearProgram(SageObject): if back_end.variable_lower_bound(i) != 0: raise ValueError('Problem variables must have 0 as lower bound') if back_end.variable_upper_bound(i) is not None: - raise ValueError('Problem variables must not have upper bound') + raise ValueError('Problem variables must not have upper bound') # Construct 'A' coef_matrix = [] @@ -2847,7 +2847,7 @@ cdef class MIPVariable(SageObject): underlying linear program. - ``vtype`` (integer) -- Defines the type of the variables - (default is ``REAL``). + (default is ``REAL``, i.e., ``vtype=-1``). - ``name`` -- A name for the ``MIPVariable``. @@ -2869,7 +2869,7 @@ cdef class MIPVariable(SageObject): sage: p = MixedIntegerLinearProgram(solver='GLPK') sage: p.new_variable(nonnegative=True) - MIPVariable of dimension 1 + MIPVariable with 0 real component, >= 0 """ self._dict = {} self._p = mip @@ -2957,7 +2957,7 @@ cdef class MIPVariable(SageObject): sage: x[11] Traceback (most recent call last): ... - IndexError: 11 does not index a component of MIPVariable of dimension 1 + IndexError: 11 does not index a component of MIPVariable with 7 real components Indices can be more than just integers:: @@ -2979,7 +2979,7 @@ cdef class MIPVariable(SageObject): sage: x[0] Traceback (most recent call last): ... - IndexError: 0 does not index a component of MIPVariable of dimension 1 + IndexError: 0 does not index a component of MIPVariable with 0 real component """ cdef int j @@ -3041,7 +3041,7 @@ cdef class MIPVariable(SageObject): sage: qv[5] Traceback (most recent call last): ... - IndexError: 5 does not index a component of MIPVariable of dimension 1 + IndexError: 5 does not index a component of MIPVariable with 2 real components """ cdef MIPVariable cp = type(self)(mip, self._vtype, self._name, @@ -3131,12 +3131,38 @@ cdef class MIPVariable(SageObject): EXAMPLES:: - sage: p=MixedIntegerLinearProgram(solver='GLPK') - sage: v=p.new_variable() + sage: p = MixedIntegerLinearProgram(solver='GLPK') + sage: v = p.new_variable() sage: v - MIPVariable of dimension 1 - """ - return "MIPVariable of dimension 1" + MIPVariable with 0 real component + sage: x = p.new_variable(integer=True, nonnegative=True, name="x") + sage: x[0] + x_0 + sage: x + MIPVariable x with 1 integer component, >= 0 + sage: x[1] + x_1 + sage: x + MIPVariable x with 2 integer components, >= 0 + sage: y = p.new_variable(real=True, name="y", indices=range(5)) + sage: y.set_min(0) + sage: y.set_max(17) + sage: y + MIPVariable y with 5 real components, >= 0, <= 17 + sage: z = p.new_variable(binary=True, name="z", indices=range(7)) + sage: z + MIPVariable z with 7 binary components + """ + s = 'MIPVariable{0} with {1} {2} component{3}'.format( + " " + self._name if self._name else "", + len(self._dict), + {0:"binary", -1:"real", 1:"integer"}[self._vtype], + "s" if len(self._dict) > 1 else "") + if (self._vtype != 0) and (self._lower_bound is not None): + s += ', >= {0}'.format(self._lower_bound) + if (self._vtype != 0) and (self._upper_bound is not None): + s += ', <= {0}'.format(self._upper_bound) + return s def keys(self): r""" From b2b9b366970d9a7aaa25ddfdfab6ef467f412342 Mon Sep 17 00:00:00 2001 From: Yuan Zhou Date: Thu, 13 May 2021 16:53:27 -0400 Subject: [PATCH 188/232] zero is followed by plural countable nouns --- src/sage/numerical/mip.pyx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/numerical/mip.pyx b/src/sage/numerical/mip.pyx index 8f1239bc4fb..67414403b99 100644 --- a/src/sage/numerical/mip.pyx +++ b/src/sage/numerical/mip.pyx @@ -132,7 +132,7 @@ or by the following special syntax:: sage: mip. = MixedIntegerLinearProgram(solver='GLPK') sage: a - MIPVariable a with 0 real component + MIPVariable a with 0 real components sage: 5 + a[1] + 2*b[3] 5 + x_0 + 2*x_1 @@ -744,7 +744,7 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p = MixedIntegerLinearProgram(solver='GLPK') sage: x = p.new_variable(); x - MIPVariable with 0 real component + MIPVariable with 0 real components sage: x0 = x[0]; x0 x_0 @@ -835,7 +835,7 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p = MixedIntegerLinearProgram(solver='GLPK') sage: p.default_variable() - MIPVariable with 0 real component + MIPVariable with 0 real components """ if self._default_mipvariable is None: self._default_mipvariable = self.new_variable() @@ -2869,7 +2869,7 @@ cdef class MIPVariable(SageObject): sage: p = MixedIntegerLinearProgram(solver='GLPK') sage: p.new_variable(nonnegative=True) - MIPVariable with 0 real component, >= 0 + MIPVariable with 0 real components, >= 0 """ self._dict = {} self._p = mip @@ -2979,7 +2979,7 @@ cdef class MIPVariable(SageObject): sage: x[0] Traceback (most recent call last): ... - IndexError: 0 does not index a component of MIPVariable with 0 real component + IndexError: 0 does not index a component of MIPVariable with 0 real components """ cdef int j @@ -3134,7 +3134,7 @@ cdef class MIPVariable(SageObject): sage: p = MixedIntegerLinearProgram(solver='GLPK') sage: v = p.new_variable() sage: v - MIPVariable with 0 real component + MIPVariable with 0 real components sage: x = p.new_variable(integer=True, nonnegative=True, name="x") sage: x[0] x_0 @@ -3157,7 +3157,7 @@ cdef class MIPVariable(SageObject): " " + self._name if self._name else "", len(self._dict), {0:"binary", -1:"real", 1:"integer"}[self._vtype], - "s" if len(self._dict) > 1 else "") + "s" if len(self._dict) != 1 else "") if (self._vtype != 0) and (self._lower_bound is not None): s += ', >= {0}'.format(self._lower_bound) if (self._vtype != 0) and (self._upper_bound is not None): From 9cde99610c99144c3f434b6a1e690666a05090d1 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Sat, 15 May 2021 19:58:03 +0200 Subject: [PATCH 189/232] 30352: installation via PyPI --- build/pkgs/database_knotinfo/SPKG.rst | 53 ++------- build/pkgs/database_knotinfo/checksums.ini | 10 +- .../create_knotinfo_tarball.py | 108 ------------------ build/pkgs/database_knotinfo/dependencies | 3 +- .../database_knotinfo/install-requires.txt | 1 + .../database_knotinfo/package-version.txt | 2 +- build/pkgs/database_knotinfo/spkg-install.in | 24 +--- build/pkgs/database_knotinfo/spkg-install.py | 12 -- src/sage/databases/knotinfo_db.py | 81 ++++++++----- src/sage/features/databases.py | 16 +-- src/sage/knots/knotinfo.py | 12 +- src/sage/knots/link.py | 33 +++--- 12 files changed, 100 insertions(+), 255 deletions(-) delete mode 100755 build/pkgs/database_knotinfo/create_knotinfo_tarball.py create mode 100644 build/pkgs/database_knotinfo/install-requires.txt delete mode 100644 build/pkgs/database_knotinfo/spkg-install.py diff --git a/build/pkgs/database_knotinfo/SPKG.rst b/build/pkgs/database_knotinfo/SPKG.rst index 37107aa286c..83d03495632 100644 --- a/build/pkgs/database_knotinfo/SPKG.rst +++ b/build/pkgs/database_knotinfo/SPKG.rst @@ -1,57 +1,18 @@ -database_knotinfo: Tables of Knots and Links from the KnotInfo Databases -======================================================================== +database_knotinfo: Content of the KnotInfo and LinkInfo databases as lists of dictionaries +========================================================================================== Description ----------- -Database for named knots and links provided at +Content of the KnotInfo and LinkInfo databases as lists of dictionaries -https://knotinfo.math.indiana.edu/ - -and - -https://linkinfo.sitehost.iu.edu' - -Dependencies ------------- - -- Sage library +License +------- +GPL Upstream Contact ---------------- -- Charles Livingston -- Allison H. Moore - -Update Instructions -------------------- - -- See the Python script ``create_knotinfo_tarball.py`` in the current directory. - -Changelog ---------- - -- 20200713 (Sebastian Oehms, 13 Juli 2020, :trac:`30352`, initial version) - - The tarball has been created from the both download files at the - given date: - - ``knotinfo_data_complete.xls`` - ``linkinfo_data_complete.xlsx`` - - exporting them to CSV via LibreOffice. - - The second file has been changed manually deleting one character: - a trailing "}" occuring in the homfly_polynomial column of the last - link ``L11n459{1,1,1}``. - -- 20210201 (Sebastian Oehms, 1 February 2021, :trac:`30352`, upgrade) - - Three new columns have been added to ``knotinfo_data_complete.xls`` - (``montesinos_notation``, ``boundary_slopes`` and ``pretzel_notation``). - ``linkinfo_data_complete.xlsx`` remains unchanged. +https://pypi.org/project/database-knotinfo/ - The tarball has been created using ``create_knotinfo_tarball.py``. - The fix concerning the misplaced character for ``L11n459{1,1,1}`` - is performed in :meth:`KnotInfoBase.homfly_polynomial`, now. diff --git a/build/pkgs/database_knotinfo/checksums.ini b/build/pkgs/database_knotinfo/checksums.ini index 4042c7c9a38..c0c4e86ceb2 100644 --- a/build/pkgs/database_knotinfo/checksums.ini +++ b/build/pkgs/database_knotinfo/checksums.ini @@ -1,5 +1,5 @@ -tarball=knotinfo-20210201.tar.bz2 -sha1=a8a69dacd1f61f19a921d8e5b90d6cfdea85d859 -md5=5f53bd7e3a672648d41460c4d22d52b3 -cksum=1608275975 -upstream_url=https://github.com/soehms/sagemath_knotinfo/blob/main/knotinfo-20210201.tar.bz2?raw=true +tarball=database_knotinfo-VERSION.tar.gz +sha1=2d758c5f8bf346162d13bec1d5bccfec9d27baa1 +md5=ec20d43af0c4ecf59dfd281c6ccc4ef0 +cksum=2792610748 +upstream_url=https://pypi.io/packages/source/d/database_knotinfo/database_knotinfo-VERSION.tar.gz diff --git a/build/pkgs/database_knotinfo/create_knotinfo_tarball.py b/build/pkgs/database_knotinfo/create_knotinfo_tarball.py deleted file mode 100755 index 47e799bf4b9..00000000000 --- a/build/pkgs/database_knotinfo/create_knotinfo_tarball.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/python - -r""" -Python script to create a tarball for the sage-package ``database_knotinfo`` -in the given path. This utility should be used in case of a switch to a -new version of the data files (that is if the original files on the -KnotInfo LinkInfo web-page have changed). In that case an invocation of -``sage -package update database_knotinfo `` and -``sage -package fix-checksum database_knotinfo`` will be necessary. - -..NOTE:: - - This function demands the Python package ``pandas``, ``xlrd`` and - ``xlsx2csv`` to be installed. If not you have to run:: - - pip install pandas - pip install xlrd - pip install xlsx2csv - - before using this function. - -INPUT: - -- ``version`` -- string, name of the new version to be created - (by default date of the day of creation) -- ``path`` -- string of the path where the tarball should be stored - (by default ``pwd``) - -EXAMPLES:: - - ~/sage $ build/pkgs/database_knotinfo/create_knotinfo_tarball.py 20210201 upstream - src/ - src/knotinfo_data_complete.csv - src/linkinfo_data_complete.csv -""" - -import sys, os -from xlsx2csv import Xlsx2csv -from pandas import read_excel - -############################################################################## -# Copyright (C) 2021 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 -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# http://www.gnu.org/licenses/ -############################################################################## - - -cmdline_args = sys.argv[1:] - -version = None -path = None - -if len(cmdline_args) > 1: - path = cmdline_args[1] - -if len(cmdline_args) > 0: - version = cmdline_args[0] - - -if not version: - from datetime import datetime - version = str(datetime.today().date()).replace('-','') - -if not path: - path = os.environ['PWD'] - -path_temp = os.path.join(path, 'special_knotinfo_spkg_temp_dir') -path_src = os.path.join(path_temp, 'src') -os.makedirs(path_temp) -os.makedirs(path_src) - -def convert(path_src, url, filen, reader): - if reader == Xlsx2csv: - excel = filen + '.xlsx' - else: - excel = filen + '.xls' - csv = filen + '.csv' - inp = os.path.join(url, excel) - out = os.path.join(path_src, csv) - if reader == Xlsx2csv: - from six.moves.urllib.request import urlopen - f = urlopen(inp) - url_data = f.read() - temp_file = os.path.join(path_temp, 'temp.xlsx') - f = open(temp_file, 'wt') - f.write(url_data) - f.close() - data = reader(temp_file, delimiter='|', skip_empty_lines=True) - data.convert(out) - else: - data = reader(inp) - data.to_csv(out, sep='|', index=False) - -# first KnotInfo (using pandas and xlrd) -convert(path_src, 'https://knotinfo.math.indiana.edu/', 'knotinfo_data_complete', read_excel) - -# now LinkInfo (using xlsx2csv) -convert(path_src, 'https://linkinfo.sitehost.iu.edu/', 'linkinfo_data_complete', Xlsx2csv) - -tar_file = 'knotinfo-%s.tar.bz2' %version -path_tar = os.path.join(path_temp, tar_file) - -os.system('cd %s; tar -cvjSf %s src' %(path_temp, tar_file)) -os.system('mv %s %s; rm -rf %s' %(path_tar, path, path_temp)) diff --git a/build/pkgs/database_knotinfo/dependencies b/build/pkgs/database_knotinfo/dependencies index c1b713883fe..0738c2d7777 100644 --- a/build/pkgs/database_knotinfo/dependencies +++ b/build/pkgs/database_knotinfo/dependencies @@ -1,5 +1,4 @@ -| $(SAGERUNTIME) +$(PYTHON) | $(PYTHON_TOOLCHAIN) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/database_knotinfo/install-requires.txt b/build/pkgs/database_knotinfo/install-requires.txt new file mode 100644 index 00000000000..ef7ae65ef46 --- /dev/null +++ b/build/pkgs/database_knotinfo/install-requires.txt @@ -0,0 +1 @@ +database-knotinfo diff --git a/build/pkgs/database_knotinfo/package-version.txt b/build/pkgs/database_knotinfo/package-version.txt index 4fe1baedd44..eb49d7c7fdc 100644 --- a/build/pkgs/database_knotinfo/package-version.txt +++ b/build/pkgs/database_knotinfo/package-version.txt @@ -1 +1 @@ -20210201 +0.7 diff --git a/build/pkgs/database_knotinfo/spkg-install.in b/build/pkgs/database_knotinfo/spkg-install.in index 93af702d4c7..f9667e4a733 100644 --- a/build/pkgs/database_knotinfo/spkg-install.in +++ b/build/pkgs/database_knotinfo/spkg-install.in @@ -1,21 +1,9 @@ -INSTALL="yes" -TARGET="${SAGE_SHARE}/knotinfo" -VERSION=`cat package-version.txt` -if [ -d $TARGET ] -then - diff package-version.txt $TARGET > /dev/null 2>&1 - if [ $? -eq 0 ] - then - INSTALL="no" - echo "Version $VERSION of knotinfo already installed" - else - OLD_VERSION=`cat $TARGET/package-version.txt` - echo "Removing former version $OLD_VERSION of knotinfo" - rm -rf $TARGET - fi -fi +cd src +sdh_pip_install . -if [ "$INSTALL" = "yes" ] +FILECACHE="${SAGE_SHARE}/knotinfo" +if [ -d $FILECACHE ] then - exec python3 spkg-install.py + echo "Clearing former filecache of knotinfo" + rm -rf $FILECACHE fi diff --git a/build/pkgs/database_knotinfo/spkg-install.py b/build/pkgs/database_knotinfo/spkg-install.py deleted file mode 100644 index 3b122a13c3a..00000000000 --- a/build/pkgs/database_knotinfo/spkg-install.py +++ /dev/null @@ -1,12 +0,0 @@ -import os -from sage.env import SAGE_SHARE -from sage.misc.misc import sage_makedirs - -install_root = os.path.join(SAGE_SHARE, 'knotinfo') - -if __name__ == '__main__': - sage_makedirs(install_root) - print("Creating the KnotInfo database.") - from sage.databases.knotinfo_db import KnotInfoDataBase - KnotInfoDataBase(install=True) - os.system('cp package-version.txt %s' %install_root) diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py index 13350f44895..29433f9ee55 100644 --- a/src/sage/databases/knotinfo_db.py +++ b/src/sage/databases/knotinfo_db.py @@ -26,7 +26,6 @@ import os -import csv from enum import Enum from sage.structure.sage_object import SageObject @@ -227,6 +226,20 @@ def csv(self): """ return '%s.csv' %(self.value[1]) + def num_knots(self): + r""" + Return the file name under which the number of knots is stored + in an sobj-file. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.num_knots() + 'num_knots.sobj' + """ + return 'num_knots.sobj' + def sobj_row(self): r""" Return the file name under which the row-data of the csv-File @@ -347,10 +360,9 @@ def __init__(self, install=False): 'linkinfo_data_complete']> """ # some constants - self._delimiter = '|' - self._names_column = 'name' + self._names_column = 'name' self._components_column = 'components' - self._knot_prefix = 'K' + self._knot_prefix = 'K' self._knot_list = None self._link_list = None @@ -359,16 +371,34 @@ def __init__(self, install=False): from sage.features.databases import DatabaseKnotInfo self._feature = DatabaseKnotInfo() - self._sobj_path = self._feature.search_path[0] + self._sobj_path = self._feature._sobj_path + + def reset_filecache(self): + r""" + Reset the internal files containing the database. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.reset_filecache() # optional - database_knotinfo + """ + if not self._feature.is_present(): + return + sobj_path = self._sobj_path + os.system('rm -rf %s' %sobj_path) + from sage.misc.misc import sage_makedirs + sage_makedirs(sobj_path) + + num_knots_file = os.path.join(sobj_path, self.filename.knots.num_knots()) + knot_list = self.knot_list() + num_knots = len(knot_list) - 1 + save(num_knots, num_knots_file) + self._num_knots = num_knots + self._create_col_dict_sobj() + self._create_data_sobj() + return - if install: - knot_list = self.knot_list() - num_knots = len(knot_list) - 1 - print('Setting the number of Knots: %s!' %num_knots) - save(num_knots, '%s/%s' %(self._sobj_path, self._feature.filename)) - self._feature._cache_is_present = None # must be reset for package installation - self._create_col_dict_sobj() - self._create_data_sobj() def demo_version(self): r""" @@ -382,9 +412,14 @@ def demo_version(self): sage: ki_db.demo_version() # optional - database_knotinfo False """ - if not self._demo: + if self._demo is None: if self._feature.is_present(): - self._num_knots = load(self._feature.absolute_path()) + num_knots_file = os.path.join(self._sobj_path, self.filename.knots.num_knots()) + from builtins import FileNotFoundError + try: + self._num_knots = load(num_knots_file) + except FileNotFoundError: + self.reset_filecache() self._demo = False else: self._demo = True @@ -404,12 +439,8 @@ def knot_list(self): if self._knot_list: return self._knot_list - print('Importing KnotInfo database from SPKG!') - os.system('pwd') - knot_csv = open('src/%s' %self.filename.knots.csv()) - knot_dict = csv.DictReader(knot_csv, delimiter=self._delimiter) - self._knot_list = list(knot_dict) - knot_csv.close() + from database_knotinfo import link_list + self._knot_list = link_list() return self._knot_list @@ -426,11 +457,8 @@ def link_list(self): if self._link_list: return self._link_list - print('Importing LinkInfo database from SPKG!') - link_csv = open('src/%s' %self.filename.links.csv()) - link_dict = csv.DictReader(link_csv, delimiter=self._delimiter) - self._link_list = list(link_dict) - link_csv.close() + from database_knotinfo import link_list + self._link_list = link_list(proper_links=True) return self._link_list def _create_col_dict_sobj(self): @@ -786,6 +814,7 @@ def _test_database(self, **options): db = KnotInfoDataBase() dc = db.columns() + data_demo_sample = { dc.name: ['0_1', '3_1', '4_1', '5_1', '5_2', '6_1', '6_2', '6_3', '7_1', '7_2', 'L2a1{0}', 'L2a1{1}', 'L4a1{0}', 'L4a1{1}', 'L5a1{0}', 'L5a1{1}', diff --git a/src/sage/features/databases.py b/src/sage/features/databases.py index 25e2e24441f..82d81be8c83 100644 --- a/src/sage/features/databases.py +++ b/src/sage/features/databases.py @@ -4,7 +4,7 @@ """ -from . import StaticFile +from . import StaticFile, PythonModule from sage.env import CREMONA_MINI_DATA_DIR, CREMONA_LARGE_DATA_DIR CREMONA_DATA_DIRS = set([CREMONA_MINI_DATA_DIR, CREMONA_LARGE_DATA_DIR]) @@ -65,7 +65,7 @@ def __init__(self): spkg="database_jones_numfield") -class DatabaseKnotInfo(StaticFile): +class DatabaseKnotInfo(PythonModule): r""" A :class:`Feature` which describes the presence of the databases at the web-pages `KnotInfo `__ and @@ -77,7 +77,7 @@ class DatabaseKnotInfo(StaticFile): sage: from sage.features.databases import DatabaseKnotInfo sage: DatabaseKnotInfo().is_present() # optional: database_knotinfo - FeatureTestResult('KnotInfo and LinkInfo databases', True) + FeatureTestResult('sage.knots.knotinfo', True) """ def __init__(self): r""" @@ -87,11 +87,7 @@ def __init__(self): sage: isinstance(DatabaseKnotInfo(), DatabaseKnotInfo) True """ - from sage.env import SAGE_SHARE + PythonModule.__init__(self, 'sage.knots.knotinfo', spkg='database_knotinfo',) import os - StaticFile.__init__(self, "KnotInfo and LinkInfo databases", - filename='num_knots.sobj', - spkg='database_knotinfo', - search_path = [os.path.join(SAGE_SHARE, 'knotinfo')], - url='https://knotinfo.math.indiana.edu/' - ) + from sage.env import SAGE_SHARE + self._sobj_path = os.path.join(SAGE_SHARE, 'knotinfo') diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index b6aedd6af8b..54b7812a3b0 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -97,19 +97,11 @@ sage: L6 = KnotInfo.L6a1_0 sage: l6s = L6.link(snappy=True); l6s # optional - snappy Plink failed to import tkinter. - doctest:warning - ... - DeprecationWarning: the complex_field module is deprecated, please use sage.rings.complex_mpfr - See http://trac.sagemath.org/24483 for details. - doctest:warning - ... - DeprecationWarning: the complex_number module is deprecated, please use sage.rings.complex_mpfr - See http://trac.sagemath.org/24483 for details. sage: type(l6s) # optional - snappy - sage: l6 = L6.link() + sage: l6 = L6.link().mirror_image() sage: l6 == l6s.sage_link() # optional - snappy True sage: L6.link(L6.items.name, snappy=True) # optional - snappy @@ -220,7 +212,7 @@ - Sebastian Oehms August 2020: initial version -Thanks to Chuck Livingston and Allison Moore for their support. +Thanks to Chuck Livingston and Allison Moore for their support. For further acknowledgments see the correspondig hompages. """ diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 9fb5d7e0bb6..3792ec5556a 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -3730,14 +3730,6 @@ def get_knotinfo(self, mirror_version=True, unique=True): sage: import snappy # optional - snappy Plink failed to import tkinter. - doctest:warning - ... - DeprecationWarning: the complex_field module is deprecated, please use sage.rings.complex_mpfr - See http://trac.sagemath.org/24483 for details. - doctest:warning - ... - DeprecationWarning: the complex_number module is deprecated, please use sage.rings.complex_mpfr - See http://trac.sagemath.org/24483 for details. sage: from sage.knots.knotinfo import KnotInfoSeries sage: KnotInfoSeries(10, True, True) # optional - database_knotinfo @@ -3748,17 +3740,17 @@ def get_knotinfo(self, mirror_version=True, unique=True): ....: K = K10(i) ....: k = K.link(K.items.name, snappy=True) ....: print(k, '--->', k.sage_link().get_knotinfo()) - ---> (, True) - ---> (, False) - ---> (, True) - ---> (, True) - ---> (, True) - ---> (, True) + ---> (, False) + ---> (, True) + ---> (, False) + ---> (, False) + ---> (, False) + ---> (, False) sage: snappy.Link('10_166') # optional - snappy sage: _.sage_link().get_knotinfo() # optional - database_knotinfo snappy - (, False) + (, True) Another pair of confusion (see the corresponding `Warning `__):: @@ -3766,9 +3758,9 @@ def get_knotinfo(self, mirror_version=True, unique=True): sage: Ks10_86 = snappy.Link('10_86') # optional - snappy sage: Ks10_83 = snappy.Link('10_83') # optional - snappy sage: Ks10_86.sage_link().get_knotinfo() # optional - snappy - (, False) + (, True) sage: Ks10_83.sage_link().get_knotinfo() # optional - snappy - (, True) + (, False) TESTS: @@ -3951,27 +3943,34 @@ def is_isotopic(self, other): sage: l.is_isotopic(L7a7(3).link()) # optional - database_knotinfo False """ + from sage.misc.verbose import verbose if not isinstance(other, Link): + verbose('other is not a link') return False if self == other: # surely isotopic + verbose('identified by representation') return True if self.homfly_polynomial() != other.homfly_polynomial(): # surely non isotopic + verbose('different Homfly-PT polynomials') return False if self._markov_move_cmp(other.braid()): # surely isotopic + verbose('identified via Markov moves') return True try: ki, m = self.get_knotinfo() + verbose('KnotInfo self: %s mirrored %s' %(ki, m)) try: if ki.is_unique(): try: kio = other.get_knotinfo() + verbose('KnotInfo other: %s mirrored %s' %kio) return (ki, m) == kio except NotImplementedError: pass From 17ca3657b584162668d305546a6eb797a68c35d8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 12 May 2021 08:39:22 -0700 Subject: [PATCH 190/232] .github/workflows/tox-{optional,experimental}.yml: Switch distributions with gcc 4.x to using gcc_spkg --- .github/workflows/tox-experimental.yml | 4 +++- .github/workflows/tox-optional.yml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tox-experimental.yml b/.github/workflows/tox-experimental.yml index 7ac1ea5882b..20f8d3cba30 100644 --- a/.github/workflows/tox-experimental.yml +++ b/.github/workflows/tox-experimental.yml @@ -38,7 +38,9 @@ jobs: fail-fast: false max-parallel: 6 matrix: - tox_system_factor: [ubuntu-trusty, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, centos-7, centos-8, gentoo, archlinux-latest, opensuse-15, opensuse-15.3, opensuse-tumbleweed, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386] + # This list is different from the one in tox.yml: + # Trac #31526 switches gcc 4.x-based distributions to using the gcc_spkg configuration factor + tox_system_factor: [ubuntu-trusty-gcc_spkg, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie-gcc_spkg, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17-gcc_spkg, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, centos-7-gcc_spkg, centos-8, gentoo, archlinux-latest, opensuse-15, opensuse-15.3, opensuse-tumbleweed, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-gcc_spkg] tox_packages_factor: [maximal] targets_pattern: [0-g, h-o, p, q-z] env: diff --git a/.github/workflows/tox-optional.yml b/.github/workflows/tox-optional.yml index 0b5ee0c3b23..81beea8a8e2 100644 --- a/.github/workflows/tox-optional.yml +++ b/.github/workflows/tox-optional.yml @@ -38,7 +38,9 @@ jobs: fail-fast: false max-parallel: 6 matrix: - tox_system_factor: [ubuntu-trusty, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, centos-7, centos-8, gentoo, archlinux-latest, opensuse-15, opensuse-15.3, opensuse-tumbleweed, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386] + # This list is different from the one in tox.yml: + # Trac #31526 switches gcc 4.x-based distributions to using the gcc_spkg configuration factor + tox_system_factor: [ubuntu-trusty-gcc_spkg, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie-gcc_spkg, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17-gcc_spkg, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, centos-7-gcc_spkg, centos-8, gentoo, archlinux-latest, opensuse-15, opensuse-15.3, opensuse-tumbleweed, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-gcc_spkg] tox_packages_factor: [maximal] targets_pattern: [0-g, h-o, p, q-z] env: From e8540a9ece41b6469c10c13929ced3f2f31b5cf5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 12 Apr 2021 22:44:21 -0700 Subject: [PATCH 191/232] src/sage/misc/package.py: sage_conf now has a version --- src/sage/misc/package.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/misc/package.py b/src/sage/misc/package.py index 9e335a09bc0..e402293baf8 100644 --- a/src/sage/misc/package.py +++ b/src/sage/misc/package.py @@ -333,8 +333,8 @@ def installed_packages(exclude_pip=True): [...'alabaster', ...'sage_conf', ...] sage: installed_packages()['alabaster'] # optional - build, random '0.7.12' - sage: installed_packages()['sage_conf'] # optional - build - 'none' + sage: installed_packages()['sage_conf'] # optional - build, random + '9.3' .. SEEALSO:: From fea1f4ed048fceefc3e822e5be34a658f8cd2a78 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 14 Apr 2021 17:27:01 -0700 Subject: [PATCH 192/232] src/sage/misc/package.py: Better doctest for installed_packages --- src/sage/misc/package.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/misc/package.py b/src/sage/misc/package.py index e402293baf8..e77d412bc06 100644 --- a/src/sage/misc/package.py +++ b/src/sage/misc/package.py @@ -333,8 +333,8 @@ def installed_packages(exclude_pip=True): [...'alabaster', ...'sage_conf', ...] sage: installed_packages()['alabaster'] # optional - build, random '0.7.12' - sage: installed_packages()['sage_conf'] # optional - build, random - '9.3' + sage: installed_packages()['sage_conf'] == sage.env.SAGE_VERSION # optional - build + True .. SEEALSO:: From bdde34100078a26b4619816e6de85cf8307aa3d5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 19 May 2021 08:14:56 -0700 Subject: [PATCH 193/232] Revert "Partially revert #30008" This reverts commit 721ede18042cfee9ca044823599346e1ee7420f1. --- build/bin/sage-bootstrap-python | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/build/bin/sage-bootstrap-python b/build/bin/sage-bootstrap-python index c3afc1261af..faee444e8dc 100755 --- a/build/bin/sage-bootstrap-python +++ b/build/bin/sage-bootstrap-python @@ -30,6 +30,15 @@ fi # is accessible by this python; this is to guard on Cygwin against Pythons # installed somewhere else in Windows. +# Trac #30008: Make it work even if the environment tries to sabotage UTF-8 +# operation in Python 3.0.x-3.6.x by setting LC_ALL=C or similar. + +if [ "$LC_ALL" = "C" -o "$LANG" = "C" -o "$LC_CTYPE" = "C" ]; then + LC_ALL=$(locale -a | grep -E -i '^(c|en_us)[-.]utf-?8$' | head -n 1) + LANG=$LC_ALL + export LC_ALL + export LANG +fi PYTHONS="python python3 python3.8 python3.7 python2.7 python3.6 python2" for PY in $PYTHONS; do From 03cee8203ed5852e71eb01e1c4a071a01fa98b22 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix <20100.delecroix@gmail.com> Date: Thu, 27 May 2021 23:53:06 +0200 Subject: [PATCH 194/232] 31848: fix a random failure in is_edge_transitive --- src/sage/graphs/graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 7fea475c191..0f1216047a5 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -2727,7 +2727,7 @@ def is_edge_transitive(self): A = self.automorphism_group() e = next(self.edge_iterator(labels=False)) e = [A._domain_to_gap[e[0]], A._domain_to_gap[e[1]]] - + e.sort() return libgap(A).OrbitLength(e, libgap.OnSets) == self.size() @doc_index("Graph properties") From d5a33dea423e784bab20ee811367ce12d248057c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 3 Jun 2021 15:43:07 -0700 Subject: [PATCH 195/232] src/sage/libs/linkages/padics/relaxed/__init__.py: New --- src/sage/libs/linkages/padics/relaxed/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/sage/libs/linkages/padics/relaxed/__init__.py diff --git a/src/sage/libs/linkages/padics/relaxed/__init__.py b/src/sage/libs/linkages/padics/relaxed/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From e568e3b2885a96b2008b43bcf1986501ede3447d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 3 Jun 2021 16:02:02 -0700 Subject: [PATCH 196/232] .gitignore: Add exception for a non-generated .c file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 70e0765fb73..40cb54fa701 100644 --- a/.gitignore +++ b/.gitignore @@ -114,6 +114,7 @@ src/sage/modular/arithgroup/farey_symbol.h !src/sage/graphs/cliquer/cl.c !src/sage/graphs/graph_decompositions/sage_tdlib.cpp !src/sage/libs/eclib/wrap.cpp +!src/sage/libs/linkages/padics/relaxed/flint_helper.c !src/sage/misc/inherit_comparison_impl.c !src/sage/modular/arithgroup/farey.cpp !src/sage/modular/arithgroup/sl2z.cpp From 30ee8d6505eb6ec224e1aa1a20a6a9dc64ec2ea9 Mon Sep 17 00:00:00 2001 From: Release Manager Date: Sun, 6 Jun 2021 16:56:44 +0200 Subject: [PATCH 197/232] Updated SageMath version to 9.4.beta1 --- .zenodo.json | 8 ++++---- VERSION.txt | 2 +- build/pkgs/configure/checksums.ini | 6 +++--- build/pkgs/configure/package-version.txt | 2 +- build/pkgs/sagelib/package-version.txt | 2 +- src/VERSION.txt | 2 +- src/bin/sage-version.sh | 6 +++--- src/sage/version.py | 6 +++--- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.zenodo.json b/.zenodo.json index f57eda9b908..e567adc1a12 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,10 +1,10 @@ { "description": "Mirror of the Sage https://sagemath.org/ source tree", "license": "other-open", - "title": "sagemath/sage: 9.4.beta0", - "version": "9.4.beta0", + "title": "sagemath/sage: 9.4.beta1", + "version": "9.4.beta1", "upload_type": "software", - "publication_date": "2021-05-25", + "publication_date": "2021-06-06", "creators": [ { "affiliation": "SageMath.org", @@ -15,7 +15,7 @@ "related_identifiers": [ { "scheme": "url", - "identifier": "https://github.com/sagemath/sage/tree/9.4.beta0", + "identifier": "https://github.com/sagemath/sage/tree/9.4.beta1", "relation": "isSupplementTo" }, { diff --git a/VERSION.txt b/VERSION.txt index 32443a2a4c1..032560869f7 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 9.4.beta0, Release Date: 2021-05-25 +SageMath version 9.4.beta1, Release Date: 2021-06-06 diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 5b81d62ba6e..355f5cef649 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=fc049bba332acf1d0e40275f20cdbcb734ad5bb3 -md5=971a6e2f0f92060a85aefa4130ddc287 -cksum=2143691469 +sha1=4b1b427c4e1cc5e3b7226a5c84d2312ada851086 +md5=ad2e1c54a9421a63d975f95dc771d11b +cksum=3168609300 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 0720b669a7c..ca5eb2e6c93 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -9e7b0bd3124630ebf368909dbcea7c2480ea1f27 +7690208434ed1d4aab0ea40b760669494d502c42 diff --git a/build/pkgs/sagelib/package-version.txt b/build/pkgs/sagelib/package-version.txt index 9f62018ee76..dd1b37884f3 100644 --- a/build/pkgs/sagelib/package-version.txt +++ b/build/pkgs/sagelib/package-version.txt @@ -1 +1 @@ -9.4.beta0 +9.4.beta1 diff --git a/src/VERSION.txt b/src/VERSION.txt index 9f62018ee76..dd1b37884f3 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -9.4.beta0 +9.4.beta1 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index c1b4451e263..8b31e9a4d7a 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -1,5 +1,5 @@ # Sage version information for shell scripts # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='9.4.beta0' -SAGE_RELEASE_DATE='2021-05-25' -SAGE_VERSION_BANNER='SageMath version 9.4.beta0, Release Date: 2021-05-25' +SAGE_VERSION='9.4.beta1' +SAGE_RELEASE_DATE='2021-06-06' +SAGE_VERSION_BANNER='SageMath version 9.4.beta1, Release Date: 2021-06-06' diff --git a/src/sage/version.py b/src/sage/version.py index 3289093f8e6..bf5f5872b81 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,5 +1,5 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '9.4.beta0' -date = '2021-05-25' -banner = 'SageMath version 9.4.beta0, Release Date: 2021-05-25' +version = '9.4.beta1' +date = '2021-06-06' +banner = 'SageMath version 9.4.beta1, Release Date: 2021-06-06' From 669a161dfb16a5e725183df86b30619a452f3e6e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 6 Jun 2021 15:36:10 -0700 Subject: [PATCH 198/232] ConvexSet_{base,closed,relatively_open}: New; make Polyhedron_base, LatticePolytopeClass, ConvexRationalPolyhedralCone subclasses --- src/sage/geometry/cone.py | 3 +- src/sage/geometry/convex_set.py | 157 ++++++++++++++++++++++++++ src/sage/geometry/lattice_polytope.py | 5 +- src/sage/geometry/polyhedron/base.py | 3 +- 4 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 src/sage/geometry/convex_set.py diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index 79c75ad7841..243314c173c 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -213,6 +213,7 @@ from sage.structure.all import SageObject, parent from sage.structure.richcmp import richcmp_method, richcmp from sage.geometry.integral_points import parallelotope_points +from sage.geometry.convex_set import ConvexSet_closed from sage.misc.lazy_import import lazy_import from sage.features import PythonModule @@ -1375,7 +1376,7 @@ def classify_cone_2d(ray0, ray1, check=True): # and ``ambient_ray_indices`` keyword parameters. See ``intersection`` method # for an example why this is needed. @richcmp_method -class ConvexRationalPolyhedralCone(IntegralRayCollection, Container): +class ConvexRationalPolyhedralCone(IntegralRayCollection, Container, ConvexSet_closed): r""" Create a convex rational polyhedral cone. diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py new file mode 100644 index 00000000000..ed8a72ca225 --- /dev/null +++ b/src/sage/geometry/convex_set.py @@ -0,0 +1,157 @@ +r""" +Convex Sets +""" + +# **************************************************************************** +# Copyright (C) 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 +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.structure.sage_object import SageObject +from sage.misc.abstract_method import abstract_method + +class ConvexSet_base(SageObject): + + """ + Abstract base class for convex sets. + """ + + @abstract_method + def is_empty(self): + r""" + Test whether ``self`` is the empty set + + OUTPUT: + + Boolean. + """ + + @abstract_method + def is_universe(self): + r""" + Test whether ``self`` is the whole ambient space + + OUTPUT: + + Boolean. + """ + + @abstract_method + def is_full_dimensional(self): + r""" + Return whether ``self`` is full dimensional. + + OUTPUT: + + Boolean. Whether the polyhedron is not contained in any strict + affine subspace. + + """ + + @abstract_method + def is_open(self): + r""" + Return whether ``self`` is open. + + OUTPUT: + + Boolean. + + """ + + def is_relatively_open(self): + r""" + Return whether ``self`` is open. + + OUTPUT: + + Boolean. + + """ + if self.is_open(): + return True + raise NotImplementedError + + @abstract_method + def is_closed(self): + r""" + Return whether ``self`` is closed. + + OUTPUT: + + Boolean. + + """ + + def closure(self): + r""" + Return the topological closure of ``self``. + """ + if self.is_closed(): + return self + raise NotImplementedError + + def interior(self): + r""" + Return the topological interior of ``self``. + """ + if self.is_closed(): + return self + raise NotImplementedError + + @abstract_method(optional=True) + def affine_hull(self): + r""" + Return the affine hull of ``self``. + """ + + +class ConvexSet_closed(ConvexSet_base): + + r""" + Abstract base class for closed convex sets. + """ + + def is_closed(self): + r""" + Return whether ``self`` is closed. + + OUTPUT: + + Boolean. + """ + return True + + def is_open(self): + r""" + Return whether ``self`` is open. + + OUTPUT: + + Boolean. + + """ + return self.is_empty() or self.is_universe() + + +class ConvexSet_relatively_open(ConvexSet_base): + + r""" + Abstract base class for relatively open sets. + """ + + def is_relatively_open(self): + r""" + Return whether ``self`` is open. + + OUTPUT: + + Boolean. + + """ + return True diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index cc5d4303897..0456f5cc14b 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -135,6 +135,7 @@ from sage.structure.all import Sequence from sage.structure.sage_object import SageObject from sage.structure.richcmp import richcmp_method, richcmp +from sage.geometry.convex_set import ConvexSet_closed from copy import copy from collections.abc import Hashable @@ -464,7 +465,7 @@ def is_LatticePolytope(x): return isinstance(x, LatticePolytopeClass) @richcmp_method -class LatticePolytopeClass(SageObject, Hashable): +class LatticePolytopeClass(ConvexSet_closed, Hashable): r""" Create a lattice polytope. @@ -517,6 +518,8 @@ def __init__(self, points=None, compute_vertices=None, sage: LatticePolytope([(1,2,3), (4,5,6)]) # indirect test 1-d lattice polytope in 3-d lattice M + sage: TestSuite(_).run() + """ if ambient is None: self._ambient = self diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 23ffed867da..5cfb0278aef 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -51,6 +51,7 @@ from sage.functions.other import sqrt, floor, ceil from sage.groups.matrix_gps.finitely_generated import MatrixGroup from sage.graphs.graph import Graph +from sage.geometry.convex_set import ConvexSet_closed from .constructor import Polyhedron from sage.categories.sets_cat import EmptySetError @@ -98,7 +99,7 @@ def is_Polyhedron(X): ######################################################################### -class Polyhedron_base(Element): +class Polyhedron_base(Element, ConvexSet_closed): """ Base class for Polyhedron objects From e67f75377b140110ccd28fc9fa54b6e090b077cd Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 6 Jun 2021 20:33:52 -0700 Subject: [PATCH 199/232] ConvexRationalPolyhedralCone: Add method is_empty and aliases is_full_dimensional, is_universe --- src/sage/geometry/cone.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index 243314c173c..8838d58b654 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -3191,6 +3191,14 @@ def is_smooth(self): return False return self.rays().matrix().elementary_divisors() == [1] * self.nrays() + def is_empty(self): + """ + Return whether ``self`` is the empty set. + + Because a cone always contains the origin, this method returns ``False``. + """ + return False + def is_trivial(self): """ Checks if the cone has no rays. @@ -4448,6 +4456,8 @@ def is_solid(self): A cone is said to be solid if it has nonempty interior. That is, if its extreme rays span the entire ambient space. + An alias is :meth:`is_full_dimensional`. + OUTPUT: ``True`` if this cone is solid, and ``False`` otherwise. @@ -4487,6 +4497,8 @@ def is_solid(self): """ return (self.dim() == self.lattice_dim()) + is_full_dimensional = is_solid + def is_proper(self): r""" Check if this cone is proper. @@ -4537,6 +4549,8 @@ def is_full_space(self): r""" Check if this cone is equal to its ambient vector space. + An alias is :meth:`is_universe`. + OUTPUT: ``True`` if this cone equals its entire ambient vector @@ -4574,6 +4588,8 @@ def is_full_space(self): """ return self.linear_subspace() == self.lattice().vector_space() + is_universe = is_full_space + def lineality(self): r""" Return the lineality of this cone. From 770fdcd9125c8bb78dd45573613e6489220664a0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 6 Jun 2021 22:52:29 -0700 Subject: [PATCH 200/232] LatticePolytopeClass, IntegralRayCollection: Make ambient_dim an alias for lattice_dim --- src/sage/geometry/cone.py | 4 ++++ src/sage/geometry/convex_set.py | 5 +++++ src/sage/geometry/lattice_polytope.py | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index 8838d58b654..07df0fc575e 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -1010,6 +1010,8 @@ def lattice_dim(self): r""" Return the dimension of the ambient lattice of ``self``. + An alias is :meth:`ambient_dim`. + OUTPUT: - integer. @@ -1024,6 +1026,8 @@ def lattice_dim(self): """ return self.lattice().dimension() + ambient_dim = lattice_dim + def nrays(self): r""" Return the number of rays of ``self``. diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index ed8a72ca225..197aecac9be 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -42,6 +42,11 @@ def is_universe(self): """ @abstract_method + def ambient_dim(self): + r""" + Return the dimension of the ambient space. + """ + def is_full_dimensional(self): r""" Return whether ``self`` is full dimensional. diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index 0456f5cc14b..10866bc8c31 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -2599,6 +2599,8 @@ def lattice_dim(self): r""" Return the dimension of the ambient lattice of ``self``. + An alias is :meth:`ambient_dim`. + OUTPUT: - integer. @@ -2613,6 +2615,8 @@ def lattice_dim(self): """ return self.lattice().dimension() + ambient_dim = lattice_dim + def linearly_independent_vertices(self): r""" Return a maximal set of linearly independent vertices. From b2ac6396c075f8ad4d71a379de367fd9cbaa3561 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 6 Jun 2021 22:54:22 -0700 Subject: [PATCH 201/232] ConvexSet_base.dim: New --- src/sage/geometry/convex_set.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 197aecac9be..56189f2518c 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -21,7 +21,6 @@ class ConvexSet_base(SageObject): Abstract base class for convex sets. """ - @abstract_method def is_empty(self): r""" Test whether ``self`` is the empty set @@ -30,8 +29,8 @@ def is_empty(self): Boolean. """ + return self.dim() < 0 - @abstract_method def is_universe(self): r""" Test whether ``self`` is the whole ambient space @@ -40,6 +39,15 @@ def is_universe(self): Boolean. """ + if not self.is_full_dimensional(): + return False + raise NotImplementedError + + @abstract_method + def dim(self): + r""" + Return the dimension of ``self``. + """ @abstract_method def ambient_dim(self): @@ -55,8 +63,8 @@ def is_full_dimensional(self): Boolean. Whether the polyhedron is not contained in any strict affine subspace. - """ + return self.dim() == self.ambient_dim() @abstract_method def is_open(self): @@ -105,7 +113,7 @@ def interior(self): r""" Return the topological interior of ``self``. """ - if self.is_closed(): + if self.is_open(): return self raise NotImplementedError From ccc84213906ff6bf34d2e5c6b50d146a2901ec9c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 6 Jun 2021 22:54:52 -0700 Subject: [PATCH 202/232] ConvexSet_base.is_compact, ConvexSet_compact: New --- src/sage/geometry/convex_set.py | 43 +++++++++++++++++++++++++++ src/sage/geometry/lattice_polytope.py | 4 +-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 56189f2518c..37ef89ae39e 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -101,6 +101,21 @@ def is_closed(self): """ + def is_compact(self): + r""" + Return whether ``self`` is compact. + + OUTPUT: + + Boolean. + + """ + if not self.is_closed(): + return False + if self.dimension() < 1: + return True + raise NotImplementedError + def closure(self): r""" Return the topological closure of ``self``. @@ -152,6 +167,34 @@ def is_open(self): return self.is_empty() or self.is_universe() +class ConvexSet_compact(ConvexSet_closed): + + r""" + Abstract base class for compact convex sets. + """ + + def is_universe(self): + r""" + Return whether ``self`` is the whole ambient space + + OUTPUT: + + Boolean. + """ + return self.ambient_dim() == 0 and not self.is_empty() + + def is_compact(self): + r""" + Return whether ``self`` is compact. + + OUTPUT: + + Boolean. + + """ + return True + + class ConvexSet_relatively_open(ConvexSet_base): r""" diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index 10866bc8c31..a063657e30a 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -135,7 +135,7 @@ from sage.structure.all import Sequence from sage.structure.sage_object import SageObject from sage.structure.richcmp import richcmp_method, richcmp -from sage.geometry.convex_set import ConvexSet_closed +from sage.geometry.convex_set import ConvexSet_compact from copy import copy from collections.abc import Hashable @@ -465,7 +465,7 @@ def is_LatticePolytope(x): return isinstance(x, LatticePolytopeClass) @richcmp_method -class LatticePolytopeClass(ConvexSet_closed, Hashable): +class LatticePolytopeClass(ConvexSet_compact, Hashable): r""" Create a lattice polytope. From 4bac2fe2db4519478d6e5633e86b29098d9aa0c4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 7 Jun 2021 12:53:33 -0700 Subject: [PATCH 203/232] RelativeInterior: Subclass ConvexSet_relatively_open, add missing methods --- src/sage/geometry/convex_set.py | 13 +++++- src/sage/geometry/relative_interior.py | 59 +++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 37ef89ae39e..5a531dd9c44 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -203,7 +203,7 @@ class ConvexSet_relatively_open(ConvexSet_base): def is_relatively_open(self): r""" - Return whether ``self`` is open. + Return whether ``self`` is relatively open. OUTPUT: @@ -211,3 +211,14 @@ def is_relatively_open(self): """ return True + + def is_open(self): + r""" + Return whether ``self`` is open. + + OUTPUT: + + Boolean. + + """ + return self.is_full_dimensional() diff --git a/src/sage/geometry/relative_interior.py b/src/sage/geometry/relative_interior.py index 73e65d4361e..c41012835eb 100644 --- a/src/sage/geometry/relative_interior.py +++ b/src/sage/geometry/relative_interior.py @@ -12,9 +12,9 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.structure.sage_object import SageObject +from sage.geometry.convex_set import ConvexSet_relatively_open -class RelativeInterior(SageObject): +class RelativeInterior(ConvexSet_relatively_open): r""" The relative interior of a polyhedron or cone @@ -71,6 +71,40 @@ def __contains__(self, point): """ return self._polyhedron.relative_interior_contains(point) + def ambient_dim(self): + r""" + Return the dimension of the ambient space. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: segment.ambient_dim() + 2 + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.ambient_dim() + 2 + + """ + return self._polyhedron.ambient_dim() + + def dim(self): + r""" + Return the dimension of ``self``. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: segment.dim() + 1 + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.dim() + 1 + + """ + return self._polyhedron.dim() + def interior(self): r""" Return the interior of ``self``. @@ -125,6 +159,27 @@ def closure(self): """ return self._polyhedron + def is_closed(self): + r""" + Return whether ``self`` is closed. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.is_closed() + False + """ + # Relies on ``self`` not set up for polyhedra that are already + # relatively open themselves. + assert not self._polyhedron.is_relatively_open() + return False + def _repr_(self): r""" Return a description of ``self``. From dede9de1a8e01114b289149bccb51c466223d027 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 7 Jun 2021 16:47:05 -0700 Subject: [PATCH 204/232] src/doc/en/reference/discrete_geometry/index.rst: Add sage/geometry/convex_set --- src/doc/en/reference/discrete_geometry/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/src/doc/en/reference/discrete_geometry/index.rst b/src/doc/en/reference/discrete_geometry/index.rst index 88dbc812caf..6975fa144a7 100644 --- a/src/doc/en/reference/discrete_geometry/index.rst +++ b/src/doc/en/reference/discrete_geometry/index.rst @@ -105,6 +105,7 @@ Miscellaneous .. toctree:: :maxdepth: 1 + sage/geometry/convex_set sage/geometry/linear_expression sage/geometry/newton_polygon sage/geometry/relative_interior From 007e6fda9fb5dfccf109b06d15f8799f7f03ac5f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 8 Jun 2021 13:33:20 -0700 Subject: [PATCH 205/232] ConvexRationalPolyhedralCone.is_empty: Add doctest --- src/sage/geometry/cone.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index dc43db1a6a0..aa1acf765ac 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -3283,6 +3283,13 @@ def is_empty(self): Return whether ``self`` is the empty set. Because a cone always contains the origin, this method returns ``False``. + + EXAMPLES:: + + sage: trivial_cone = cones.trivial(3) + sage: trivial_cone.is_empty() + False + """ return False From 73e39ce9c9121c34da6d3ced36b36262ae4e9026 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 10 Jun 2021 11:43:40 -0700 Subject: [PATCH 206/232] ConvexRationalPolyhedralCone.is_compact: Define --- src/sage/geometry/cone.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index aa1acf765ac..8e42648aef5 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -3311,6 +3311,8 @@ def is_trivial(self): """ return self.nrays() == 0 + is_compact = is_trivial + def is_strictly_convex(self): r""" Check if ``self`` is strictly convex. From 92f0610e33b15cba97247b787bbcc0a9593d9d34 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 10 Jun 2021 11:45:44 -0700 Subject: [PATCH 207/232] ConvexSet_open: New --- src/sage/geometry/convex_set.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 5a531dd9c44..f77498b4fb1 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -196,9 +196,8 @@ def is_compact(self): class ConvexSet_relatively_open(ConvexSet_base): - r""" - Abstract base class for relatively open sets. + Abstract base class for relatively open convex sets. """ def is_relatively_open(self): @@ -222,3 +221,31 @@ def is_open(self): """ return self.is_full_dimensional() + + +class ConvexSet_open(ConvexSet_relatively_open): + r""" + Abstract base class for open convex sets. + """ + + def is_open(self): + r""" + Return whether ``self`` is open. + + OUTPUT: + + Boolean. + + """ + return True + + def is_closed(self): + r""" + Return whether ``self`` is closed. + + OUTPUT: + + Boolean. + + """ + return self.is_empty() or self.is_universe() From 03a31efc560cffebe2cf270ae7d460302307ac95 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 10 Jun 2021 11:48:50 -0700 Subject: [PATCH 208/232] Polyhedron_base.is_full_dimensional: Merge into ConvexSet_base.is_full_dimensional --- src/sage/geometry/convex_set.py | 11 +++++++++++ src/sage/geometry/polyhedron/base.py | 18 ------------------ 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index f77498b4fb1..cd8dd1d9911 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -63,6 +63,17 @@ def is_full_dimensional(self): Boolean. Whether the polyhedron is not contained in any strict affine subspace. + + EXAMPLES:: + + sage: c = Cone([(1,0)]) + sage: c.is_full_dimensional() + False + + sage: polytopes.hypercube(3).is_full_dimensional() + True + sage: Polyhedron(vertices=[(1,2,3)], rays=[(1,0,0)]).is_full_dimensional() + False """ return self.dim() == self.ambient_dim() diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 03ee54f8077..8e242132313 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -9814,24 +9814,6 @@ def edge_label(i, j, c_ij): else: return MatrixGroup(matrices) - def is_full_dimensional(self): - """ - Return whether the polyhedron is full dimensional. - - OUTPUT: - - Boolean. Whether the polyhedron is not contained in any strict - affine subspace. - - EXAMPLES:: - - sage: polytopes.hypercube(3).is_full_dimensional() - True - sage: Polyhedron(vertices=[(1,2,3)], rays=[(1,0,0)]).is_full_dimensional() - False - """ - return self.dim() == self.ambient_dim() - def is_combinatorially_isomorphic(self, other, algorithm='bipartite_graph'): r""" Return whether the polyhedron is combinatorially isomorphic to another polyhedron. From a71507e11c33cbc2c61600b76098a814e6132f1d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 10 Jun 2021 11:49:54 -0700 Subject: [PATCH 209/232] ConvexSet_base: Add some doctests --- src/sage/geometry/convex_set.py | 41 +++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index cd8dd1d9911..53caea0b12b 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -16,7 +16,6 @@ from sage.misc.abstract_method import abstract_method class ConvexSet_base(SageObject): - """ Abstract base class for convex sets. """ @@ -28,6 +27,14 @@ def is_empty(self): OUTPUT: Boolean. + + EXAMPLES:: + + sage: p = LatticePolytope([], lattice=ToricLattice(3).dual()); p + -1-d lattice polytope in 3-d lattice M + sage: p.is_empty() + True + """ return self.dim() < 0 @@ -90,12 +97,24 @@ def is_open(self): def is_relatively_open(self): r""" - Return whether ``self`` is open. + Return whether ``self`` is relatively open. + + The default implementation of this method only knows that open + sets are also relatively open. OUTPUT: Boolean. + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def is_open(self): + ....: return True + sage: ExampleSet().is_relatively_open() + True + """ if self.is_open(): return True @@ -123,13 +142,20 @@ def is_compact(self): """ if not self.is_closed(): return False - if self.dimension() < 1: + if self.dim() < 1: return True raise NotImplementedError def closure(self): r""" Return the topological closure of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_closed + sage: C = ConvexSet_closed() + sage: C.closure() is C + True """ if self.is_closed(): return self @@ -138,6 +164,13 @@ def closure(self): def interior(self): r""" Return the topological interior of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_open + sage: C = ConvexSet_open() + sage: C.interior() is C + True """ if self.is_open(): return self @@ -151,7 +184,6 @@ def affine_hull(self): class ConvexSet_closed(ConvexSet_base): - r""" Abstract base class for closed convex sets. """ @@ -179,7 +211,6 @@ def is_open(self): class ConvexSet_compact(ConvexSet_closed): - r""" Abstract base class for compact convex sets. """ From 3a831826846c776c95a51133e1b4e216d248acc3 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 10 Jun 2021 11:50:23 -0700 Subject: [PATCH 210/232] ConvexSet_base.relative_interior: New --- src/sage/geometry/convex_set.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 53caea0b12b..114018dc629 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -176,6 +176,21 @@ def interior(self): return self raise NotImplementedError + def relative_interior(self): + r""" + Return the relative interior of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_relatively_open + sage: C = ConvexSet_relatively_open() + sage: C.relative_interior() is C + True + """ + if self.is_relatively_open(): + return self + raise NotImplementedError + @abstract_method(optional=True) def affine_hull(self): r""" From 6a0baac5f679d90a1acd3696f3613350af5af9f1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 10 Jun 2021 11:50:51 -0700 Subject: [PATCH 211/232] ConvexSet_base._test_convex_set: New --- src/sage/geometry/convex_set.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 114018dc629..8c5a0019c16 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -197,6 +197,37 @@ def affine_hull(self): Return the affine hull of ``self``. """ + def _test_convex_set(self, tester=None, **options): + """ + Run some tests on the methods of :class:`ConvexSet_base`. + """ + if tester is None: + tester = self._tester(**options) + dim = self.dim() + tester.assertTrue(dim <= self.ambient_dim()) + if self.is_empty(): + tester.assertTrue(dim == -1) + if self.is_universe(): + tester.assertTrue(self.is_full_dimensional()) + cl_self = self.closure() + try: + int_self = self.interior() + except NotImplementedError: + int_self = None + try: + relint_self = self.relative_interior() + except NotImplementedError: + relint_self = None + if self.is_full_dimensional(): + tester.assertTrue(int_self == relint_self) + if self.is_relatively_open(): + tester.assertTrue(self == relint_self) + if self.is_open(): + tester.assertTrue(self == int_self) + if self.is_closed(): + tester.assertTrue(self == cl_self) + if self.is_compact(): + tester.assertTrue(self.is_closed()) class ConvexSet_closed(ConvexSet_base): r""" From 0495bb05c37f55257b892c6790234d9fb5dd77f5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 10 Jun 2021 16:34:03 -0700 Subject: [PATCH 212/232] RelativeInterior: Fix up doctest --- src/sage/geometry/relative_interior.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/geometry/relative_interior.py b/src/sage/geometry/relative_interior.py index e0dfc9ce6b9..795ef408692 100644 --- a/src/sage/geometry/relative_interior.py +++ b/src/sage/geometry/relative_interior.py @@ -47,7 +47,7 @@ def __init__(self, polyhedron): TESTS:: - sage: P = Polyhedron() + sage: P = Polyhedron([[1, 2], [3, 4]]) sage: from sage.geometry.relative_interior import RelativeInterior sage: TestSuite(RelativeInterior(P)).run() """ From 9a7ce3a53d63ace6ed56f4addc92f61b85549b32 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 10 Jun 2021 17:22:27 -0700 Subject: [PATCH 213/232] src/sage/geometry/convex_set.py: More examples and tests --- src/sage/geometry/convex_set.py | 141 ++++++++++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 8 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 8c5a0019c16..9ea892c99b9 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -22,7 +22,7 @@ class ConvexSet_base(SageObject): def is_empty(self): r""" - Test whether ``self`` is the empty set + Test whether ``self`` is the empty set. OUTPUT: @@ -34,13 +34,12 @@ def is_empty(self): -1-d lattice polytope in 3-d lattice M sage: p.is_empty() True - """ return self.dim() < 0 def is_universe(self): r""" - Test whether ``self`` is the whole ambient space + Test whether ``self`` is the whole ambient space. OUTPUT: @@ -84,23 +83,39 @@ def is_full_dimensional(self): """ return self.dim() == self.ambient_dim() - @abstract_method def is_open(self): r""" Return whether ``self`` is open. + The default implementation of this method only knows that the + empty set and the ambient space are open. + OUTPUT: Boolean. + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def is_empty(self): + ....: return False + ....: def is_universe(self): + ....: return True + sage: ExampleSet().is_open() + True """ + if self.is_empty() or self.is_universe(): + return True + raise NotImplementedError def is_relatively_open(self): r""" Return whether ``self`` is relatively open. The default implementation of this method only knows that open - sets are also relatively open. + sets are also relatively open, and in addition singletons are + relatively open. OUTPUT: @@ -114,31 +129,55 @@ def is_relatively_open(self): ....: return True sage: ExampleSet().is_relatively_open() True - """ if self.is_open(): return True + if self.dim() == 0: + return True raise NotImplementedError - @abstract_method def is_closed(self): r""" Return whether ``self`` is closed. + The default implementation of this method only knows that the + empty set, a singleton set, and the ambient space are closed. + OUTPUT: Boolean. + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def dim(self): + ....: return 0 + sage: ExampleSet().is_closed() + True """ + if self.is_empty() or self.dim() == 0 or self.is_universe(): + return True + raise NotImplementedError def is_compact(self): r""" Return whether ``self`` is compact. + The default implementation of this method only knows that a + non-closed set cannot be compact, and that the empty set and + a singleton set are compact. + OUTPUT: Boolean. + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def dim(self): + ....: return 0 + sage: ExampleSet().is_compact() + True """ if not self.is_closed(): return False @@ -200,6 +239,32 @@ def affine_hull(self): def _test_convex_set(self, tester=None, **options): """ Run some tests on the methods of :class:`ConvexSet_base`. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_open + sage: class FaultyConvexSet(ConvexSet_open): + ....: def is_universe(self): + ....: return True + ....: def dim(self): + ....: return 42 + ....: def ambient_dim(self): + ....: return 91 + sage: TestSuite(FaultyConvexSet()).run(skip='_test_pickling') + Traceback (most recent call last): + ... + The following tests failed: _test_convex_set + + sage: class BiggerOnTheInside(ConvexSet_open): + ....: def dim(self): + ....: return 100000 + ....: def ambient_dim(self): + ....: return 3 + sage: TestSuite(BiggerOnTheInside()).run(skip='_test_pickling') + Traceback (most recent call last): + ... + The following tests failed: _test_convex_set + """ if tester is None: tester = self._tester(**options) @@ -241,6 +306,12 @@ def is_closed(self): OUTPUT: Boolean. + + EXAMPLES:: + + sage: hcube = polytopes.hypercube(5) + sage: hcube.is_closed() + True """ return True @@ -252,6 +323,15 @@ def is_open(self): Boolean. + EXAMPLES:: + + sage: hcube = polytopes.hypercube(5) + sage: hcube.is_open() + False + + sage: zerocube = polytopes.hypercube(0) + sage: zerocube.is_open() + True """ return self.is_empty() or self.is_universe() @@ -268,6 +348,16 @@ def is_universe(self): OUTPUT: Boolean. + + EXAMPLES:: + + sage: cross3 = lattice_polytope.cross_polytope(3) + sage: cross3.is_universe() + False + sage: point0 = LatticePolytope([[]]); point0 + 0-d reflexive polytope in 0-d lattice M + sage: point0.is_universe() + True """ return self.ambient_dim() == 0 and not self.is_empty() @@ -279,9 +369,16 @@ def is_compact(self): Boolean. + EXAMPLES:: + + sage: cross3 = lattice_polytope.cross_polytope(3) + sage: cross3.is_compact() + True """ return True + is_relatively_open = ConvexSet_closed.is_open + class ConvexSet_relatively_open(ConvexSet_base): r""" @@ -296,6 +393,12 @@ def is_relatively_open(self): Boolean. + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior() + sage: ri_segment.is_relatively_open() + True """ return True @@ -307,8 +410,14 @@ def is_open(self): Boolean. + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior() + sage: ri_segment.is_open() + False """ - return self.is_full_dimensional() + return self.is_empty() or self.is_full_dimensional() class ConvexSet_open(ConvexSet_relatively_open): @@ -324,6 +433,12 @@ def is_open(self): Boolean. + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_open + sage: b = ConvexSet_open() + sage: b.is_open() + True """ return True @@ -335,5 +450,15 @@ def is_closed(self): Boolean. + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_open + sage: class OpenBall(ConvexSet_open): + ....: def dim(self): + ....: return 3 + ....: def is_universe(self): + ....: return False + sage: OpenBall().is_closed() + False """ return self.is_empty() or self.is_universe() From e2b0ef7390426c7d8f6f7f0a1382b326d5c9bc6e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 10 Jun 2021 18:39:17 -0700 Subject: [PATCH 214/232] ConvexSet_base._test_convex_set: Fix doctest output --- src/sage/geometry/convex_set.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 9ea892c99b9..4372d90a1cd 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -251,7 +251,7 @@ def _test_convex_set(self, tester=None, **options): ....: def ambient_dim(self): ....: return 91 sage: TestSuite(FaultyConvexSet()).run(skip='_test_pickling') - Traceback (most recent call last): + Failure in _test_convex_set: ... The following tests failed: _test_convex_set @@ -261,7 +261,7 @@ def _test_convex_set(self, tester=None, **options): ....: def ambient_dim(self): ....: return 3 sage: TestSuite(BiggerOnTheInside()).run(skip='_test_pickling') - Traceback (most recent call last): + Failure in _test_convex_set: ... The following tests failed: _test_convex_set From 45c840a98ea222b30847b6ae8411d52f7cd778ee Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 10 Jun 2021 21:41:58 -0700 Subject: [PATCH 215/232] ConvexSet_base.codim, codimension: New --- src/sage/geometry/cone.py | 3 +++ src/sage/geometry/convex_set.py | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index 8e42648aef5..eab23498e85 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -1225,8 +1225,11 @@ def codim(self): sage: K.codim() == K.dual().lineality() True """ + # same as ConvexSet_base.codim; the main point is the much more detailed + # docstring. return (self.lattice_dim() - self.dim()) + codimension = codim def span(self, base_ring=None): r""" diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 4372d90a1cd..dab6e61b25e 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -61,6 +61,21 @@ def ambient_dim(self): Return the dimension of the ambient space. """ + def codimension(self): + r""" + Return the codimension of ``self``. + + An alias is :meth:`codim`. + + EXAMPLES:: + + sage: Polyhedron(vertices=[(1,2,3)], rays=[(1,0,0)]).codimension() + 2 + """ + return self.ambient_dim() - self.dim() + + codim = codimension + def is_full_dimensional(self): r""" Return whether ``self`` is full dimensional. @@ -269,7 +284,10 @@ def _test_convex_set(self, tester=None, **options): if tester is None: tester = self._tester(**options) dim = self.dim() + codim = self.codim() tester.assertTrue(dim <= self.ambient_dim()) + if dim >= 0: + tester.assertTrue(dim + codim == self.ambient_dim()) if self.is_empty(): tester.assertTrue(dim == -1) if self.is_universe(): From 17467c498978ca9f165b0619b30d5ab2fc84272b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 10 Jun 2021 21:43:20 -0700 Subject: [PATCH 216/232] ConvexSet_base: Make dimension, ambient_dimension aliases for dim, ambient_dim --- src/sage/geometry/convex_set.py | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index dab6e61b25e..d2ae244dd3c 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -55,12 +55,46 @@ def dim(self): Return the dimension of ``self``. """ + def dimension(self): + r""" + Return the dimension of ``self``. + + This is the same as :meth:`dim`. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def dim(self): + ....: return 42 + sage: ExampleSet().dimension() + 42 + """ + return self.dim() + @abstract_method def ambient_dim(self): r""" Return the dimension of the ambient space. """ + def ambient_dimension(self): + r""" + Return the dimension of ``self``. + + This is the same as :meth:`ambient_dim`. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def ambient_dim(self): + ....: return 91 + sage: ExampleSet().ambient_dimension() + 91 + """ + return self.ambient_dim() + def codimension(self): r""" Return the codimension of ``self``. From fa5dc6eb2c696c0094261c76b72a16cb0bc92846 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 10 Jun 2021 21:47:03 -0700 Subject: [PATCH 217/232] ConvexSet_base.cartesian_product: New --- src/sage/geometry/convex_set.py | 29 ++++++++++++++++++++++------ src/sage/geometry/polyhedron/base.py | 2 ++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index d2ae244dd3c..465e975c15c 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -279,12 +279,6 @@ def relative_interior(self): return self raise NotImplementedError - @abstract_method(optional=True) - def affine_hull(self): - r""" - Return the affine hull of ``self``. - """ - def _test_convex_set(self, tester=None, **options): """ Run some tests on the methods of :class:`ConvexSet_base`. @@ -346,6 +340,29 @@ def _test_convex_set(self, tester=None, **options): if self.is_compact(): tester.assertTrue(self.is_closed()) + # Optional methods + + @abstract_method(optional=True) + def affine_hull(self): + r""" + Return the affine hull of ``self``. + """ + + @abstract_method(optional=True) + def cartesian_product(self, other): + """ + Return the Cartesian product. + + INPUT: + + - ``other`` -- another convex set + + OUTPUT: + + The Cartesian product of ``self`` and ``other``. + """ + + class ConvexSet_closed(ConvexSet_base): r""" Abstract base class for closed convex sets. diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 8e242132313..1d17d27df09 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -4817,6 +4817,8 @@ def product(self, other): _mul_ = product + cartesian_product = product + def _test_product(self, tester=None, **options): """ Run tests on the method :meth:`.product`. From f4bdffda473c608289d6b8c90a3687484d24f3be Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 10 Jun 2021 21:58:07 -0700 Subject: [PATCH 218/232] ConvexSet_base.contains, intersection: New --- src/sage/geometry/convex_set.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 465e975c15c..6827b0a10d8 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -362,6 +362,30 @@ def cartesian_product(self, other): The Cartesian product of ``self`` and ``other``. """ + @abstract_method(optional=True) + def contains(self, point): + """ + Test whether ``self`` contains the given ``point``. + + INPUT: + + - ``point`` -- a point or its coordinates + """ + + @abstract_method(optional=True) + def intersection(self, other): + r""" + Return the intersection of ``self`` and ``other``. + + INPUT: + + - ``other`` -- another convex set + + OUTPUT: + + The intersection. + """ + class ConvexSet_closed(ConvexSet_base): r""" From ee00642b382baf7b5ee7ed3b42e5bf6dcaa9fb64 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 11 Jun 2021 14:12:19 -0700 Subject: [PATCH 219/232] ConvexSet_base.ambient: New; clarify ambient_dim, ambient_dimension, codimension --- src/sage/geometry/convex_set.py | 16 +++++++++++++--- src/sage/geometry/polyhedron/base.py | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 6827b0a10d8..f34bcb78493 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -72,15 +72,21 @@ def dimension(self): """ return self.dim() + @abstract_method + def ambient(self): + r""" + Return the ambient convex set or space. + """ + @abstract_method def ambient_dim(self): r""" - Return the dimension of the ambient space. + Return the dimension of the ambient convex set or space. """ def ambient_dimension(self): r""" - Return the dimension of ``self``. + Return the dimension of the ambient convex set or space. This is the same as :meth:`ambient_dim`. @@ -97,7 +103,7 @@ def ambient_dimension(self): def codimension(self): r""" - Return the codimension of ``self``. + Return the codimension of ``self`` in `self.ambient()``. An alias is :meth:`codim`. @@ -287,6 +293,8 @@ def _test_convex_set(self, tester=None, **options): sage: from sage.geometry.convex_set import ConvexSet_open sage: class FaultyConvexSet(ConvexSet_open): + ....: def ambient(self): + ....: return QQ^55 ....: def is_universe(self): ....: return True ....: def dim(self): @@ -301,6 +309,8 @@ def _test_convex_set(self, tester=None, **options): sage: class BiggerOnTheInside(ConvexSet_open): ....: def dim(self): ....: return 100000 + ....: def ambient(self): + ....: return QQ^3 ....: def ambient_dim(self): ....: return 3 sage: TestSuite(BiggerOnTheInside()).run(skip='_test_pickling') diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 1d17d27df09..0f9d65c4594 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -2478,7 +2478,7 @@ def Vrepresentation_space(self): """ return self.parent().Vrepresentation_space() - ambient_space = Vrepresentation_space + ambient = ambient_space = Vrepresentation_space def Hrepresentation_space(self): r""" From 2c756d43f92db60d12be732c515fee2a0928ea99 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 11 Jun 2021 14:12:41 -0700 Subject: [PATCH 220/232] PolyhedronFace: Make it a subclass of ConvexSet_closed --- src/sage/geometry/polyhedron/face.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/sage/geometry/polyhedron/face.py b/src/sage/geometry/polyhedron/face.py index 7a00f46ab19..12baa5425b8 100644 --- a/src/sage/geometry/polyhedron/face.py +++ b/src/sage/geometry/polyhedron/face.py @@ -78,12 +78,12 @@ from sage.misc.all import cached_method from sage.modules.free_module_element import vector from sage.matrix.constructor import matrix - +from sage.geometry.convex_set import ConvexSet_closed ######################################################################### @richcmp_method -class PolyhedronFace(SageObject): +class PolyhedronFace(ConvexSet_closed): r""" A face of a polyhedron. @@ -121,6 +121,11 @@ class PolyhedronFace(SageObject): (An inequality (1, 1, 1) x + 1 >= 0,) sage: face.ambient_Vrepresentation() (A vertex at (-1, 0, 0), A vertex at (0, -1, 0), A vertex at (0, 0, -1)) + + TESTS:: + + sage: TestSuite(face).run() + """ def __init__(self, polyhedron, V_indices, H_indices): @@ -147,6 +152,7 @@ def __init__(self, polyhedron, V_indices, H_indices): sage: from sage.geometry.polyhedron.face import PolyhedronFace sage: PolyhedronFace(Polyhedron(), [], []) # indirect doctest A -1-dimensional face of a Polyhedron in ZZ^0 + sage: TestSuite(_).run() """ self._polyhedron = polyhedron self._ambient_Vrepresentation_indices = tuple(V_indices) @@ -649,6 +655,8 @@ def polyhedron(self): """ return self._polyhedron + ambient = polyhedron + @cached_method def as_polyhedron(self): """ From 8d77b3e67c27e24ccae8e529eddb4beece0e8a30 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 11 Jun 2021 14:47:17 -0700 Subject: [PATCH 221/232] PolyhedronFace.is_relatively_open, is_compact: New, add doctests --- src/sage/geometry/polyhedron/face.py | 61 +++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/src/sage/geometry/polyhedron/face.py b/src/sage/geometry/polyhedron/face.py index 12baa5425b8..31f0de091e1 100644 --- a/src/sage/geometry/polyhedron/face.py +++ b/src/sage/geometry/polyhedron/face.py @@ -124,7 +124,7 @@ class PolyhedronFace(ConvexSet_closed): TESTS:: - sage: TestSuite(face).run() + sage: TestSuite(face).run(skip='_test_pickling') """ @@ -152,7 +152,7 @@ def __init__(self, polyhedron, V_indices, H_indices): sage: from sage.geometry.polyhedron.face import PolyhedronFace sage: PolyhedronFace(Polyhedron(), [], []) # indirect doctest A -1-dimensional face of a Polyhedron in ZZ^0 - sage: TestSuite(_).run() + sage: TestSuite(_).run(skip='_test_pickling') """ self._polyhedron = polyhedron self._ambient_Vrepresentation_indices = tuple(V_indices) @@ -186,6 +186,10 @@ def vertex_generator(self): A vertex at (1, 1) sage: type(face.vertex_generator()) <... 'generator'> + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ for V in self.ambient_Vrepresentation(): if V.is_vertex(): @@ -206,6 +210,10 @@ def vertices(self): sage: face = triangle.faces(1)[2] sage: face.vertices() (A vertex at (0, 1), A vertex at (1, 0)) + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ return tuple(self.vertex_generator()) @@ -224,6 +232,10 @@ def n_vertices(self): sage: face = Q.faces(2)[0] sage: face.n_vertices() 3 + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ return len(self.vertices()) @@ -237,6 +249,10 @@ def ray_generator(self): sage: face = pi.faces(1)[1] sage: next(face.ray_generator()) A ray in the direction (1, 0) + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ for V in self.ambient_Vrepresentation(): if V.is_ray(): @@ -288,6 +304,10 @@ def line_generator(self): sage: face = pr.faces(1)[0] sage: next(face.line_generator()) A line in the direction (1, 0) + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ for V in self.ambient_Vrepresentation(): if V.is_line(): @@ -657,6 +677,43 @@ def polyhedron(self): ambient = polyhedron + def is_relatively_open(self): + r""" + Return whether ``self`` is relatively open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) + sage: line = half_plane.faces(1)[0]; line + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: line.is_relatively_open() + True + """ + return self.as_polyhedron().is_relatively_open() + + def is_compact(self): + r""" + Return whether ``self`` is compact. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) + sage: line = half_plane.faces(1)[0]; line + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: line.is_compact() + False + """ + return not any(V.is_ray() or V.is_line() + for V in self.ambient_Vrepresentation()) + @cached_method def as_polyhedron(self): """ From 6ba5d9c2bd5dca867d632b9f569407ac5f758e6f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 11 Jun 2021 16:23:51 -0700 Subject: [PATCH 222/232] ConvexSet_base.ambient_vector_space: New --- src/sage/geometry/cone.py | 20 ++++++++++++++++ src/sage/geometry/convex_set.py | 10 ++++++++ src/sage/geometry/lattice_polytope.py | 20 ++++++++++++++++ src/sage/geometry/polyhedron/base.py | 34 +++++++++++++++++++++++++-- src/sage/geometry/polyhedron/face.py | 23 ++++++++++++++++++ 5 files changed, 105 insertions(+), 2 deletions(-) diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index eab23498e85..fd2074676c7 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -981,6 +981,26 @@ def lattice(self): """ return self._lattice + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + It is the ambient lattice (:meth:`lattice`) tensored with a field. + + INPUT:: + + - ``base_field`` -- (default: the rationals) a field. + + EXAMPLES:: + + sage: c = Cone([(1,0)]) + sage: c.ambient_vector_space() + Vector space of dimension 2 over Rational Field + sage: c.ambient_vector_space(AA) + Vector space of dimension 2 over Algebraic Real Field + """ + return self.lattice().vector_space(base_field=base_field) + @cached_method def dual_lattice(self): r""" diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index f34bcb78493..fbb297d4ae7 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -72,6 +72,12 @@ def dimension(self): """ return self.dim() + @abstract_method + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + """ + @abstract_method def ambient(self): r""" @@ -295,6 +301,8 @@ def _test_convex_set(self, tester=None, **options): sage: class FaultyConvexSet(ConvexSet_open): ....: def ambient(self): ....: return QQ^55 + ....: def ambient_vector_space(self, base_field=None): + ....: return QQ^16 ....: def is_universe(self): ....: return True ....: def dim(self): @@ -309,6 +317,8 @@ def _test_convex_set(self, tester=None, **options): sage: class BiggerOnTheInside(ConvexSet_open): ....: def dim(self): ....: return 100000 + ....: def ambient_vector_space(self): + ....: return QQ^3 ....: def ambient(self): ....: return QQ^3 ....: def ambient_dim(self): diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index a063657e30a..bde2f0a6be7 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -2617,6 +2617,26 @@ def lattice_dim(self): ambient_dim = lattice_dim + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + It is the ambient lattice (:meth:`lattice`) tensored with a field. + + INPUT:: + + - ``base_field`` -- (default: the rationals) a field. + + EXAMPLES:: + + sage: p = LatticePolytope([(1,0)]) + sage: p.ambient_vector_space() + Vector space of dimension 2 over Rational Field + sage: p.ambient_vector_space(AA) + Vector space of dimension 2 over Algebraic Real Field + """ + return self.lattice().vector_space(base_field=base_field) + def linearly_independent_vertices(self): r""" Return a maximal set of linearly independent vertices. diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 0f9d65c4594..67208a3b5d1 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -2462,7 +2462,7 @@ def bounded_edges(self): def Vrepresentation_space(self): r""" - Return the ambient vector space. + Return the ambient free module. OUTPUT: @@ -2478,7 +2478,37 @@ def Vrepresentation_space(self): """ return self.parent().Vrepresentation_space() - ambient = ambient_space = Vrepresentation_space + ambient_space = Vrepresentation_space + + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + It is the ambient free module (:meth:`Vrepresentation_space`) tensored + with a field. + + INPUT:: + + - ``base_field`` -- (default: the fraction field of the base ring) a field. + + EXAMPLES:: + + sage: poly_test = Polyhedron(vertices = [[1,0,0,0],[0,1,0,0]]) + sage: poly_test.ambient_vector_space() + Vector space of dimension 4 over Rational Field + sage: poly_test.ambient_vector_space() is poly_test.ambient() + True + + sage: poly_test.ambient_vector_space(AA) + Vector space of dimension 4 over Algebraic Real Field + sage: poly_test.ambient_vector_space(RR) + Vector space of dimension 4 over Real Field with 53 bits of precision + sage: poly_test.ambient_vector_space(SR) + Vector space of dimension 4 over Symbolic Ring + """ + return self.Vrepresentation_space().vector_space(base_field=base_field) + + ambient = ambient_vector_space def Hrepresentation_space(self): r""" diff --git a/src/sage/geometry/polyhedron/face.py b/src/sage/geometry/polyhedron/face.py index 31f0de091e1..af317977e5a 100644 --- a/src/sage/geometry/polyhedron/face.py +++ b/src/sage/geometry/polyhedron/face.py @@ -677,6 +677,29 @@ def polyhedron(self): ambient = polyhedron + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + It is the ambient free module of the containing polyhedron tensored + with a field. + + INPUT:: + + - ``base_field`` -- (default: the fraction field of the base ring) a field. + + EXAMPLES:: + + sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) + sage: line = half_plane.faces(1)[0]; line + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: line.ambient_vector_space() + Vector space of dimension 2 over Rational Field + sage: line.ambient_vector_space(AA) + Vector space of dimension 2 over Algebraic Real Field + """ + return self.polyhedron().ambient_vector_space(base_field=base_field) + def is_relatively_open(self): r""" Return whether ``self`` is relatively open. From e66448ea0b3de1286595ef4af19fe95c5d9ef18e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 11 Jun 2021 16:24:30 -0700 Subject: [PATCH 223/232] PolyhedronFace.contains, ConvexSet_base._test_contains: New --- src/sage/geometry/convex_set.py | 16 +++++++++-- src/sage/geometry/polyhedron/face.py | 43 ++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index fbb297d4ae7..518ed2bc51f 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -309,7 +309,7 @@ def _test_convex_set(self, tester=None, **options): ....: return 42 ....: def ambient_dim(self): ....: return 91 - sage: TestSuite(FaultyConvexSet()).run(skip='_test_pickling') + sage: TestSuite(FaultyConvexSet()).run(skip=('_test_pickling', '_test_contains')) Failure in _test_convex_set: ... The following tests failed: _test_convex_set @@ -323,7 +323,7 @@ def _test_convex_set(self, tester=None, **options): ....: return QQ^3 ....: def ambient_dim(self): ....: return 3 - sage: TestSuite(BiggerOnTheInside()).run(skip='_test_pickling') + sage: TestSuite(BiggerOnTheInside()).run(skip=('_test_pickling', '_test_contains')) Failure in _test_convex_set: ... The following tests failed: _test_convex_set @@ -392,6 +392,18 @@ def contains(self, point): - ``point`` -- a point or its coordinates """ + def _test_contains(self, tester=None, **options): + """ + Test the ``contains`` method. + """ + if tester is None: + tester = self._tester(**options) + space = self.ambient_vector_space() + point = space.an_element() + coords = space.coordinates(point) + if self.contains != NotImplemented: + tester.assertEqual(self.contains(point), self.contains(coords)) + @abstract_method(optional=True) def intersection(self, other): r""" diff --git a/src/sage/geometry/polyhedron/face.py b/src/sage/geometry/polyhedron/face.py index af317977e5a..56305d035e8 100644 --- a/src/sage/geometry/polyhedron/face.py +++ b/src/sage/geometry/polyhedron/face.py @@ -764,6 +764,49 @@ def as_polyhedron(self): Vrep = (self.vertices(), self.rays(), self.lines()) return P.__class__(parent, Vrep, None) + def contains(self, point): + """ + Test whether the polyhedron contains the given ``point``. + + INPUT: + + - ``point`` -- a point or its coordinates + + EXAMPLES:: + + sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) + sage: line = half_plane.faces(1)[0]; line + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: line.contains([0, 1]) + True + + As a shorthand, one may use the usual ``in`` operator:: + + sage: [5, 7] in line + False + """ + # preprocess in the same way as Polyhedron_base.contains + try: + p = vector(point) + except TypeError: # point not iterable or no common ring for elements + if len(point) > 0: + return False + else: + p = vector(self.polyhedron().base_ring(), []) + + if len(p) != self.ambient_dim(): + return False + + if not self.polyhedron().contains(p): + return False + + for H in self.ambient_Hrepresentation(): + if H.eval(p) != 0: + return False + return True + + __contains__ = contains + @cached_method def normal_cone(self, direction='outer'): """ From 1f7826d757f80ba7bae0aa64fb34c57206b2ad8f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 12 Jun 2021 08:42:08 -0700 Subject: [PATCH 224/232] ConvexSet_base._test_contains: Expand and add doctests --- src/sage/geometry/convex_set.py | 53 +++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 518ed2bc51f..56ff0d75bc4 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -395,14 +395,61 @@ def contains(self, point): def _test_contains(self, tester=None, **options): """ Test the ``contains`` method. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_closed + sage: class FaultyConvexSet(ConvexSet_closed): + ....: def ambient_vector_space(self, base_field=QQ): + ....: return base_field^2 + ....: ambient = ambient_vector_space + ....: def contains(self, point): + ....: if isinstance(point, (tuple, list)): + ....: return all(x in ZZ for x in point) + ....: return point.parent() == ZZ^2 + sage: FaultyConvexSet()._test_contains() + Traceback (most recent call last): + ... + AssertionError: False != True + + sage: class AlsoFaultyConvexSet(ConvexSet_closed): + ....: def ambient_vector_space(self, base_field=QQ): + ....: return base_field^2 + ....: def ambient(self): + ....: return ZZ^2 + ....: def contains(self, point): + ....: return point in ZZ^2 + sage: AlsoFaultyConvexSet()._test_contains() + Traceback (most recent call last): + ... + AssertionError: True != False """ if tester is None: tester = self._tester(**options) + ambient = self.ambient() space = self.ambient_vector_space() - point = space.an_element() - coords = space.coordinates(point) + try: + ambient_point = ambient.an_element() + except (AttributeError, NotImplementedError): + ambient_point = None + space_point = space.an_element() + else: + space_point = space(ambient_point) + space_coords = space.coordinates(space_point) if self.contains != NotImplemented: - tester.assertEqual(self.contains(point), self.contains(coords)) + contains_space_point = self.contains(space_point) + if ambient_point is not None: + tester.assertEqual(contains_space_point, self.contains(ambient_point)) + tester.assertEqual(contains_space_point, self.contains(space_coords)) + from sage.rings.qqbar import AA + ext_space = self.ambient_vector_space(AA) + ext_space_point = ext_space(space_point) + tester.assertEqual(contains_space_point, self.contains(ext_space_point)) + from sage.symbolic.ring import SR + symbolic_space = self.ambient_vector_space(SR) + symbolic_space_point = symbolic_space(space_point) + # Only test that it can accept SR vectors without error. + self.contains(symbolic_space_point) @abstract_method(optional=True) def intersection(self, other): From 142fb462e22a0598eb56c5b61f6b7b602fcbbaac Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 12 Jun 2021 08:45:28 -0700 Subject: [PATCH 225/232] ConvexSet_base.codimension: Add doctest for alias 'codim' --- src/sage/geometry/convex_set.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 6827b0a10d8..7945296471d 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -99,11 +99,15 @@ def codimension(self): r""" Return the codimension of ``self``. - An alias is :meth:`codim`. - EXAMPLES:: - sage: Polyhedron(vertices=[(1,2,3)], rays=[(1,0,0)]).codimension() + sage: P = Polyhedron(vertices=[(1,2,3)], rays=[(1,0,0)]) + sage: P.codimension() + 2 + + An alias is :meth:`codim`:: + + sage: P.codim() 2 """ return self.ambient_dim() - self.dim() From 30ebaf5a657040cb787bea0f5c2540ca08115284 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 12 Jun 2021 08:49:31 -0700 Subject: [PATCH 226/232] ConvexSet_base.ambient_dim, ambient_dimension: Fix doc --- src/sage/geometry/convex_set.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 7945296471d..b7793476cf8 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -75,12 +75,12 @@ def dimension(self): @abstract_method def ambient_dim(self): r""" - Return the dimension of the ambient space. + Return the dimension of the ambient convex set or space. """ def ambient_dimension(self): r""" - Return the dimension of ``self``. + Return the dimension of the ambient convex set or space. This is the same as :meth:`ambient_dim`. From fcf2a32a768097f9248cda456624784cc9f199c6 Mon Sep 17 00:00:00 2001 From: Jonathan Kliem Date: Sat, 12 Jun 2021 14:30:04 +0200 Subject: [PATCH 227/232] fix coverage --- src/sage/geometry/convex_set.py | 63 +++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index b7793476cf8..8ef66c68285 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -44,6 +44,15 @@ def is_universe(self): OUTPUT: Boolean. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.is_universe() + Traceback (most recent call last): + ... + NotImplementedError: """ if not self.is_full_dimensional(): return False @@ -53,6 +62,15 @@ def is_universe(self): def dim(self): r""" Return the dimension of ``self``. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.dim() + Traceback (most recent call last): + ... + NotImplementedError: """ def dimension(self): @@ -76,6 +94,15 @@ def dimension(self): def ambient_dim(self): r""" Return the dimension of the ambient convex set or space. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.ambient_dim() + Traceback (most recent call last): + ... + NotImplementedError: """ def ambient_dimension(self): @@ -350,6 +377,15 @@ def _test_convex_set(self, tester=None, **options): def affine_hull(self): r""" Return the affine hull of ``self``. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.affine_hull() + Traceback (most recent call last): + ... + TypeError: 'NotImplementedType' object is not callable """ @abstract_method(optional=True) @@ -364,6 +400,15 @@ def cartesian_product(self, other): OUTPUT: The Cartesian product of ``self`` and ``other``. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.cartesian_product(C) + Traceback (most recent call last): + ... + TypeError: 'NotImplementedType' object is not callable """ @abstract_method(optional=True) @@ -374,6 +419,15 @@ def contains(self, point): INPUT: - ``point`` -- a point or its coordinates + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.contains(vector([0, 0])) + Traceback (most recent call last): + ... + TypeError: 'NotImplementedType' object is not callable """ @abstract_method(optional=True) @@ -388,6 +442,15 @@ def intersection(self, other): OUTPUT: The intersection. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.intersection(C) + Traceback (most recent call last): + ... + TypeError: 'NotImplementedType' object is not callable """ From 6bef52ba121da086a07e2fe2b6e5f9b7ff3cbb51 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 12 Jun 2021 09:20:46 -0700 Subject: [PATCH 228/232] ConvexSet_base._test_convex_set: Run the testsuite of relint --- src/sage/geometry/convex_set.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 8ef66c68285..f55a4cd17b5 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -370,6 +370,12 @@ def _test_convex_set(self, tester=None, **options): tester.assertTrue(self == cl_self) if self.is_compact(): tester.assertTrue(self.is_closed()) + from sage.misc.sage_unittest import TestSuite + if relint_self is not None and relint_self is not self: + tester.info("\n Running the test suite of self.relative_interior()") + TestSuite(relint_self).run(verbose=tester._verbose, + prefix=tester._prefix + " ") + tester.info(tester._prefix + " ", newline=False) # Optional methods From 6ab5677085b94f2d7453a4b619e16806cff3b233 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 12 Jun 2021 12:44:58 -0700 Subject: [PATCH 229/232] RelativeInterior.is_universe: New --- src/sage/geometry/relative_interior.py | 28 ++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/sage/geometry/relative_interior.py b/src/sage/geometry/relative_interior.py index 795ef408692..3fd2f6c6b54 100644 --- a/src/sage/geometry/relative_interior.py +++ b/src/sage/geometry/relative_interior.py @@ -79,7 +79,8 @@ def ambient_dim(self): sage: segment.ambient_dim() 2 sage: ri_segment = segment.relative_interior(); ri_segment - Relative interior of a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices sage: ri_segment.ambient_dim() 2 """ @@ -95,7 +96,8 @@ def dim(self): sage: segment.dim() 1 sage: ri_segment = segment.relative_interior(); ri_segment - Relative interior of a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices sage: ri_segment.dim() 1 """ @@ -154,6 +156,28 @@ def closure(self): """ return self._polyhedron + def is_universe(self): + r""" + Return whether ``self`` is the whole ambient space + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.is_universe() + False + """ + # Relies on ``self`` not set up for polyhedra that are already + # relatively open themselves. + assert not self._polyhedron.is_universe() + return False + def is_closed(self): r""" Return whether ``self`` is closed. From c085d30d1c10ceacd9980520d0f3c7e2a78d531c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 12 Jun 2021 09:33:31 -0700 Subject: [PATCH 230/232] Polyhedron_base.interior: Handle the empty polyhedron correctly --- src/sage/geometry/polyhedron/base.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 1d17d27df09..50274eced76 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -8425,7 +8425,15 @@ def interior(self): sage: P_lower.interior() The empty polyhedron in ZZ^2 + TESTS:: + + sage: Empty = Polyhedron(ambient_dim=2); Empty + The empty polyhedron in ZZ^2 + sage: Empty.interior() is Empty + True """ + if self.is_open(): + return self if not self.is_full_dimensional(): return self.parent().element_class(self.parent(), None, None) return self.relative_interior() From 686d0afbeba9f5d33131ecbe20a907c20635faa5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 12 Jun 2021 09:39:53 -0700 Subject: [PATCH 231/232] Polyhedron_base.product: Add doctest for alias 'cartesian_product' --- src/sage/geometry/polyhedron/base.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 50274eced76..fe4253fe3bc 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -4767,6 +4767,11 @@ def product(self, other): sage: P1 * 2.0 A 1-dimensional polyhedron in RDF^1 defined as the convex hull of 2 vertices + An alias is :meth:`cartesian_product`:: + + sage: P1.cartesian_product(P2) == P1.product(P2) + True + TESTS: Check that :trac:`15253` is fixed:: From 7323b10d6c628c32adfeb62025879f909f707c61 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 14 Jun 2021 15:50:57 -0700 Subject: [PATCH 232/232] ConvexSet_base._test_contains: Only test extension to AA for exact base rings --- src/sage/geometry/convex_set.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 8e672a94c68..d256ae1671b 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -505,10 +505,11 @@ def _test_contains(self, tester=None, **options): if ambient_point is not None: tester.assertEqual(contains_space_point, self.contains(ambient_point)) tester.assertEqual(contains_space_point, self.contains(space_coords)) - from sage.rings.qqbar import AA - ext_space = self.ambient_vector_space(AA) - ext_space_point = ext_space(space_point) - tester.assertEqual(contains_space_point, self.contains(ext_space_point)) + if space.base_ring().is_exact(): + from sage.rings.qqbar import AA + ext_space = self.ambient_vector_space(AA) + ext_space_point = ext_space(space_point) + tester.assertEqual(contains_space_point, self.contains(ext_space_point)) from sage.symbolic.ring import SR symbolic_space = self.ambient_vector_space(SR) symbolic_space_point = symbolic_space(space_point)