From 0d32f4be61421da6f14f8b871478dd3501fe5204 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Sun, 1 Nov 2015 16:14:05 -0500 Subject: [PATCH 001/170] Implements a method to compute isometries between positive definite quadratic forms. --- src/sage/quadratic_forms/quadratic_form.py | 4 +- .../quadratic_form__automorphisms.py | 2 +- .../quadratic_form__equivalence_testing.py | 235 +++++++++++++++++- 3 files changed, 236 insertions(+), 5 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form.py b/src/sage/quadratic_forms/quadratic_form.py index e08a59e8d60..279ba5de62e 100644 --- a/src/sage/quadratic_forms/quadratic_form.py +++ b/src/sage/quadratic_forms/quadratic_form.py @@ -373,7 +373,9 @@ class QuadraticForm(SageObject): is_globally_equivalent__souvigner, \ is_globally_equivalent_to, \ is_locally_equivalent_to, \ - has_equivalent_Jordan_decomposition_at_prime + has_equivalent_Jordan_decomposition_at_prime, \ + isometry + def __init__(self, R, n=None, entries=None, unsafe_initialization=False, number_of_automorphisms=None, determinant=None): """ diff --git a/src/sage/quadratic_forms/quadratic_form__automorphisms.py b/src/sage/quadratic_forms/quadratic_form__automorphisms.py index 8138e7df591..93fb3b44e10 100644 --- a/src/sage/quadratic_forms/quadratic_form__automorphisms.py +++ b/src/sage/quadratic_forms/quadratic_form__automorphisms.py @@ -246,7 +246,7 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): len_bound_pari = 2*(len_bound - 1) # Call PARI's qfminim() - parilist = self._pari_().qfminim(len_bound_pari)[2].Vec() + parilist = self._pari_().qfminim(len_bound_pari, flag=2)[2].Vec() # List of lengths parilens = pari(r"(M,v) -> vector(#v, i, (v[i]~ * M * v[i])\2)")(self, parilist) diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index 524de9e65d0..f575d5d253a 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -1,18 +1,17 @@ """ Equivalence Testing """ - -from sage.matrix.constructor import Matrix from sage.misc.mrange import mrange from sage.rings.arith import hilbert_symbol, prime_divisors, is_prime, valuation, GCD, legendre_symbol from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ from quadratic_form import is_QuadraticForm from sage.env import SAGE_LOCAL -import tempfile, os +import tempfile, os, copy from random import random @@ -452,7 +451,237 @@ def has_equivalent_Jordan_decomposition_at_prime(self, other, p): +def isometry(self, other): + """ + Given two equivalent quadratic forms, computes an isometry from one to the other. + Note: Currently only works for positive definite quadratic forms. This is because isometry() relies on + the method short_vector_list_up_to_length() which is only defined for positive definite forms. + INPUT: + self: a positive definite QuadraticForm + other: a positive definite QuadraticForm + NOTE: This algorithm may not terminate if self is not equivalent to other! + OUTPUT: + a matrix T representing the isometry transformation, such that if QM is the gram matrix of Q and + FM is the gram matrix of F, then QM == T.transpose() * FM * T yields True. + EXAMPLES:: + sage: Q = DiagonalQuadraticForm(QQ, [1, 1, 2]) + sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) + sage: T = Q.isometry(F) + sage: T + [ 1/2 1/2 0] + [-1/2 1/2 0] + [ 0 0 1] + + sage: Q.Gram_matrix() == T.transpose() * F.Gram_matrix() * T + True + + sage: T = F.isometry(Q) + sage: T + [ 1 -1 0] + [ 1 1 0] + [ 0 0 1] + + sage: F.Gram_matrix() == T.transpose() * Q.Gram_matrix() * T + True + + sage: A = DiagonalQuadraticForm(QQ, [1,5]) + sage: B = QuadraticForm(QQ, 2, [1, 12, 81]) + sage: T = A.isometry(B) + sage: T + [ 1 -2] + [ 0 1/3] + + sage: A.Gram_matrix() == T.transpose() * B.Gram_matrix() * T + True + + sage: C = DiagonalQuadraticForm(QQ, [1, 5, 9]) + sage: D = DiagonalQuadraticForm(QQ, [6, 30, 1]) + sage: T = D.isometry(C) + sage: T + [ 1 -5 0] + [ 1 1 0] + [ 0 0 1/3] + + sage: D.Gram_matrix() == T.transpose() * C.Gram_matrix() * T + True + + sage: E = DiagonalQuadraticForm(QQ, [1, 1]) + sage: F = QuadraticForm(QQ, 2, [17, 94, 130]) + sage: T = E.isometry(F) + sage: T + [189/17 -43/17] + [ -4 1] + + sage: E.Gram_matrix() == T.transpose() * F.Gram_matrix() * T + True + """ + from sage.matrix.constructor import Matrix + ##Define a method that determines whether or not a matrix is diagonal. + def is_diagonal(matrix): + dim = matrix.dimensions()[0] + for i in range(dim): + for j in range(dim): + if (i != j) and (matrix[i][j] != 0): + return False + return True + + Q = copy.deepcopy(self) + F = copy.deepcopy(other) + + if not Q.is_positive_definite() or not F.is_positive_definite(): + raise TypeError("Both Quadratic Forms must be positive definite.") + + if not is_QuadraticForm(other): + raise TypeError("First argument must be a Quadratic Form.") + + n = Q.dim() + + ##If either form is not diagonal, diagonalize it. + q_diagonal_transform = f_diagonal_transform = Matrix.identity(n) + if not is_diagonal(Q.Gram_matrix()): + Q, q_diagonal_transform = Q.rational_diagonal_form(True) + if not is_diagonal(F.Gram_matrix()): + F, f_diagonal_transform = F.rational_diagonal_form(True) + + #This is the method that does all the work to compute the isometry. + transform = diagonal_isometry(Q,F) + + return f_diagonal_transform * transform * q_diagonal_transform.inverse() + + +##Helper method for isometry. +def diagonal_isometry(V, W): +##Computes an isometry between diagonal equivalent forms V and W. + from sage.functions.other import sqrt + from quadratic_form import DiagonalQuadraticForm + from sage.matrix.constructor import matrix, column_matrix, Matrix + from sage.modules.free_module_element import vector + #----HELPER METHODS-----# + + def diagonal_qf_pull_off_n_terms(gram_matrix, n, ring): + ##Returns the diagonal quadratic form corresponding to the given gram matrix + ##with the first n terms removed. + diagonal = gram_matrix.diagonal(); + return DiagonalQuadraticForm(ring, diagonal[n:]) + + def compute_gram_matrix_from_basis(Q, basis): + ##Computes the gram matrix by treating the columns of basis as basis vectors. + n = Q.dim() + rows = []; + for i in range(n): + rows.append([Q.bilinear_map(basis.column(i), basis.column(j)) for j in range(n)]) + return Matrix(rows) + + def modify_basis(basis, v, i): + ##Modifies basis to include v (a linear combination of the basis vectors) at column i in the basis. + b = copy.deepcopy(basis) + column = vector(QQ, b.dimensions()[0]) + for j in range(len(v)): + column += b.column(i + j) * v[j] + b.set_column(i, column) + return b + + def gram_schmidt(matrix, fixed_vector_index, gram_matrix): + ##Orthogonalizes the columns of matrix against a fixed column vector. + n = matrix.dimensions()[0] + vectors = [0] * n + fixed_vec = matrix.column(fixed_vector_index) + vectors[fixed_vector_index] = fixed_vec + + for i in range(fixed_vector_index): + vectors[i] = matrix.column(i) + for i in range(fixed_vector_index + 1, n): + vectors[i] = matrix.column(i) - (gram_matrix[i, fixed_vector_index] / gram_matrix[fixed_vector_index, fixed_vector_index]) * fixed_vec + + return column_matrix(vectors) + + def vectors_of_common_length(Q, F): + i = max(Q.Gram_matrix()[0][0], F.Gram_matrix()[0][0]) + n = 100 + while True: + q_vecs, f_vecs = Q.short_vector_list_up_to_length(n), F.short_vector_list_up_to_length(n) + while i < n: + ##Find a value represented by both forms + if q_vecs[i] and f_vecs[i]: + return (q_vecs[i][0], f_vecs[i][0]); + i += 1 + n += 100 + + def vectors_of_common_length_dev(Q, F, qb, fb, iteration): + ##Returns a pair of vectors v1 and v2, such that Q(v1) = F(v2), + ##and qb and fb are bases when their columns at index iteration are replaced with + ##v1 and v2 respectively. (i.e. qb and fb contain no all-zero rows) + def zero_row(matrix, col, i): + ##Sets the ith column of matrix to col and returns true if matrix has an all zero row. + m = modify_basis(matrix, col, i) + rows, cols = m.dimensions() + z = [0] * cols + for i in range(rows): + if m.row(i).list() == z: + return True + return False + i = max(Q.Gram_matrix()[0][0], F.Gram_matrix()[0][0]) + n = 100 + #NOTE: If the given forms are not equivalent, this will be an infinite loop. + while True: + q_vecs, f_vecs = Q.short_vector_list_up_to_length(n), F.short_vector_list_up_to_length(n) + while i < n: + ##Find a value that is represented by both forms + if q_vecs[i] and f_vecs[i]: + ##Find a pair of vectors such that if each becomes a column of qb and fb respectively, + ##neither matrix will have an all-zero row. + for j in range(len(q_vecs[i])): + for k in range(len(f_vecs[i])): + if not zero_row(qb, q_vecs[i][j], iteration) and not zero_row(fb, f_vecs[i][k], iteration): + return (q_vecs[i][j], f_vecs[i][k]) + i += 1 + n += 100 + + #--- END OF HELPER METHODS ---# + + Q, F = copy.deepcopy(V), copy.deepcopy(W) + n = Q.dim() + + q_basis, f_basis = Matrix.identity(n), Matrix.identity(n) + for i in range(n-1): + ##Find vectors v and w such that Q(v) = F(w) + v, w = vectors_of_common_length_dev(Q, F, q_basis, f_basis, i) + #print("Find vectors {0} and {1} such that Q(v) = F(w)".format(v, w)) + + ##Modify the bases to include v and w. + q_basis = modify_basis(q_basis, v, i) + f_basis = modify_basis(f_basis, w, i) + #print("Modified bases:\nQb =\n{0}, \nFb =\n{1}".format(q_basis, f_basis)) + + ##Compute the gram matrices with respect to the modified bases. + QM = compute_gram_matrix_from_basis(V, q_basis) + FM = compute_gram_matrix_from_basis(W, f_basis) + #print("Gram matrices with respect to modified bases:\nQM =\n{0}, \nFM =\n{1}".format(QM,FM)) + + ##Ensure that the bases are orthogonal, so the gram matrices will be diagonal. + q_basis = gram_schmidt(q_basis, i, QM) + f_basis = gram_schmidt(f_basis, i, FM) + #print("Orthogonal bases:\nQb =\n{0}, \nFb =\n{1}".format(q_basis, f_basis)) + + ##Compute the gram matrices with respect to the orthogonal bases. + QM = compute_gram_matrix_from_basis(V, q_basis) + FM = compute_gram_matrix_from_basis(W, f_basis) + #print("Gram matrices with respect to orthogonal bases:\nQM =\n{0}, \nFM =\n{1}".format(QM,FM)) + + ##Pull off the first term and continue + Q = diagonal_qf_pull_off_n_terms(QM, i+1, Q.base_ring()) + F = diagonal_qf_pull_off_n_terms(FM, i+1, F.base_ring()) + #print("Smaller qf's:\nQ =\n{0}, \nF = \n{1}".format(Q, F)) + + ##Compute the final term as a special case: + QM, FM = Q.Gram_matrix(), F.Gram_matrix() + if QM[0][0] != 0: + q_basis.set_col_to_multiple_of_col(n-1, n-1, sqrt(FM[0][0] / QM[0][0])) + elif FM[0][0] != 0: + f_basis.set_col_to_multiple_of_col(n-1, n-1, sqrt(QM[0][0] / FM[0][0])) + + return f_basis * q_basis.inverse() From 9e211fecd4b525ad30276d37d442a68ce9bec8f0 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Sun, 1 Nov 2015 16:36:01 -0500 Subject: [PATCH 002/170] Adds a flag parameter to short_vector_list_up_to_length to pass to PARI's qfminim that allows qfminim to work on quadratic forms with non-integral real coefficients. --- .../quadratic_form__automorphisms.py | 8 ++++++-- .../quadratic_form__equivalence_testing.py | 14 +------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form__automorphisms.py b/src/sage/quadratic_forms/quadratic_form__automorphisms.py index 93fb3b44e10..320629e8c1d 100644 --- a/src/sage/quadratic_forms/quadratic_form__automorphisms.py +++ b/src/sage/quadratic_forms/quadratic_form__automorphisms.py @@ -148,7 +148,7 @@ def basis_of_short_vectors(self, show_lengths=False, safe_flag=True): -def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): +def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False, real_entry_flag=False): """ Return a list of lists of short vectors `v`, sorted by length, with Q(`v`) < len_bound. @@ -160,6 +160,9 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): - ``up_to_sign_flag`` -- (default: ``False``) if set to True, then only one of the vectors of the pair `[v, -v]` is listed. + - "real_entry_flag" -- (default: "False") if set to True, then qfminim can + operate on forms with non-integral real entries. + OUTPUT: A list of lists of vectors such that entry `[i]` contains all @@ -246,7 +249,8 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): len_bound_pari = 2*(len_bound - 1) # Call PARI's qfminim() - parilist = self._pari_().qfminim(len_bound_pari, flag=2)[2].Vec() + real_flag = 2 if real_entry_flag else 0 + parilist = self._pari_().qfminim(len_bound_pari, flag=real_flag)[2].Vec() # List of lengths parilens = pari(r"(M,v) -> vector(#v, i, (v[i]~ * M * v[i])\2)")(self, parilist) diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index f575d5d253a..218b6efdc6c 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -598,18 +598,6 @@ def gram_schmidt(matrix, fixed_vector_index, gram_matrix): return column_matrix(vectors) - def vectors_of_common_length(Q, F): - i = max(Q.Gram_matrix()[0][0], F.Gram_matrix()[0][0]) - n = 100 - while True: - q_vecs, f_vecs = Q.short_vector_list_up_to_length(n), F.short_vector_list_up_to_length(n) - while i < n: - ##Find a value represented by both forms - if q_vecs[i] and f_vecs[i]: - return (q_vecs[i][0], f_vecs[i][0]); - i += 1 - n += 100 - def vectors_of_common_length_dev(Q, F, qb, fb, iteration): ##Returns a pair of vectors v1 and v2, such that Q(v1) = F(v2), ##and qb and fb are bases when their columns at index iteration are replaced with @@ -628,7 +616,7 @@ def zero_row(matrix, col, i): n = 100 #NOTE: If the given forms are not equivalent, this will be an infinite loop. while True: - q_vecs, f_vecs = Q.short_vector_list_up_to_length(n), F.short_vector_list_up_to_length(n) + q_vecs, f_vecs = Q.short_vector_list_up_to_length(n, real_entry_flag=True), F.short_vector_list_up_to_length(n, real_entry_flag=True) while i < n: ##Find a value that is represented by both forms if q_vecs[i] and f_vecs[i]: From 51ba9f8f06040341cade6f602254ddc3ede7cee1 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Tue, 3 Nov 2015 18:00:40 -0500 Subject: [PATCH 003/170] Adds isometry method for quadratic forms. --- src/sage/quadratic_forms/quadratic_form.py | 3 +- .../quadratic_form__automorphisms.py | 15 +- .../quadratic_form__equivalence_testing.py | 226 ++++++++++++++++++ 3 files changed, 239 insertions(+), 5 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form.py b/src/sage/quadratic_forms/quadratic_form.py index 1e39e1c6fd1..241c7e8fa71 100644 --- a/src/sage/quadratic_forms/quadratic_form.py +++ b/src/sage/quadratic_forms/quadratic_form.py @@ -374,7 +374,8 @@ class QuadraticForm(SageObject): is_globally_equivalent_to, \ is_locally_equivalent_to, \ has_equivalent_Jordan_decomposition_at_prime, \ - is_rationally_isometric + is_rationally_isometric, \ + isometry def __init__(self, R, n=None, entries=None, unsafe_initialization=False, number_of_automorphisms=None, determinant=None): """ diff --git a/src/sage/quadratic_forms/quadratic_form__automorphisms.py b/src/sage/quadratic_forms/quadratic_form__automorphisms.py index 28999a4a140..1574752dcdf 100644 --- a/src/sage/quadratic_forms/quadratic_form__automorphisms.py +++ b/src/sage/quadratic_forms/quadratic_form__automorphisms.py @@ -102,7 +102,7 @@ def basis_of_short_vectors(self, show_lengths=False, safe_flag=None): return basis -def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): +def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False, real_entry_flag=False): """ Return a list of lists of short vectors `v`, sorted by length, with Q(`v`) < len_bound. @@ -114,8 +114,13 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): - ``up_to_sign_flag`` -- (default: ``False``) if set to True, then only one of the vectors of the pair `[v, -v]` is listed. - OUTPUT: a list of lists of vectors such that entry ``[i]`` contains - all vectors of length `i`. + - "real_entry_flag" -- (default: "False") if set to True, then qfminim can + operate on forms with non-integral real entries. + + OUTPUT: + + A list of lists of vectors such that entry `[i]` contains all + vectors of length `i`. EXAMPLES:: @@ -198,7 +203,8 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): len_bound_pari = 2*(len_bound - 1) # Call PARI's qfminim() - parilist = self._pari_().qfminim(len_bound_pari)[2].Vec() + real_flag = 2 if real_entry_flag else 0 + parilist = self._pari_().qfminim(len_bound_pari, flag=real_flag)[2].Vec() # List of lengths parilens = pari(r"(M,v) -> vector(#v, i, (v[i]~ * M * v[i])\2)")(self, parilist) @@ -210,6 +216,7 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): # In certain trivial cases, PARI can sometimes return longer # vectors than requested. if length < len_bound: + v = parilist[i] sagevec = V(list(parilist[i])) vec_sorted_list[length].append(sagevec) if not up_to_sign_flag : diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index a27837f6b3a..16325515ad4 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -487,3 +487,229 @@ def is_rationally_isometric(self, other): return False return True + + +def isometry(self, other): + """ + Given two equivalent quadratic forms, computes an isometry from one to the other. + Note: Currently only works for positive definite quadratic forms. This is because isometry() relies on + the method short_vector_list_up_to_length() which is only defined for positive definite forms. + + INPUT: + self: a positive definite QuadraticForm + other: a positive definite QuadraticForm + NOTE: This algorithm may not terminate if self is not equivalent to other! + + OUTPUT: + a matrix T representing the isometry transformation, such that if QM is the gram matrix of Q and + FM is the gram matrix of F, then QM == T.transpose() * FM * T yields True. + + EXAMPLES:: + sage: Q = DiagonalQuadraticForm(QQ, [1, 1, 2]) + sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) + sage: T = Q.isometry(F) + sage: T + [ 1/2 1/2 0] + [-1/2 1/2 0] + [ 0 0 1] + + sage: Q.Gram_matrix() == T.transpose() * F.Gram_matrix() * T + True + + sage: T = F.isometry(Q) + sage: T + [ 1 -1 0] + [ 1 1 0] + [ 0 0 1] + + sage: F.Gram_matrix() == T.transpose() * Q.Gram_matrix() * T + True + + sage: A = DiagonalQuadraticForm(QQ, [1,5]) + sage: B = QuadraticForm(QQ, 2, [1, 12, 81]) + sage: T = A.isometry(B) + sage: T + [ 1 -2] + [ 0 1/3] + + sage: A.Gram_matrix() == T.transpose() * B.Gram_matrix() * T + True + + sage: C = DiagonalQuadraticForm(QQ, [1, 5, 9]) + sage: D = DiagonalQuadraticForm(QQ, [6, 30, 1]) + sage: T = D.isometry(C) + sage: T + [ 1 -5 0] + [ 1 1 0] + [ 0 0 1/3] + + sage: D.Gram_matrix() == T.transpose() * C.Gram_matrix() * T + True + + sage: E = DiagonalQuadraticForm(QQ, [1, 1]) + sage: F = QuadraticForm(QQ, 2, [17, 94, 130]) + sage: T = E.isometry(F) + sage: T + [189/17 -43/17] + [ -4 1] + + sage: E.Gram_matrix() == T.transpose() * F.Gram_matrix() * T + True + """ + import copy + from sage.matrix.constructor import Matrix + ##Define a method that determines whether or not a matrix is diagonal. + def is_diagonal(matrix): + dim = matrix.dimensions()[0] + for i in range(dim): + for j in range(dim): + if (i != j) and (matrix[i][j] != 0): + return False + return True + + Q = copy.deepcopy(self) + F = copy.deepcopy(other) + + if not Q.is_positive_definite() or not F.is_positive_definite(): + raise TypeError("Both Quadratic Forms must be positive definite.") + + if not is_QuadraticForm(other): + raise TypeError("First argument must be a Quadratic Form.") + + n = Q.dim() + + ##If either form is not diagonal, diagonalize it. + q_diagonal_transform = f_diagonal_transform = Matrix.identity(n) + if not is_diagonal(Q.Gram_matrix()): + Q, q_diagonal_transform = Q.rational_diagonal_form(True) + if not is_diagonal(F.Gram_matrix()): + F, f_diagonal_transform = F.rational_diagonal_form(True) + + #This is the method that does all the work to compute the isometry. + transform = diagonal_isometry(Q,F) + + return f_diagonal_transform * transform * q_diagonal_transform.inverse() + + +##Helper method for isometry. +def diagonal_isometry(V, W): +##Computes an isometry between diagonal equivalent forms V and W. + import copy + from sage.functions.other import sqrt + from quadratic_form import DiagonalQuadraticForm + from sage.matrix.constructor import matrix, column_matrix, Matrix + from sage.modules.free_module_element import vector + #----HELPER METHODS-----# + + def diagonal_qf_pull_off_n_terms(gram_matrix, n, ring): + ##Returns the diagonal quadratic form corresponding to the given gram matrix + ##with the first n terms removed. + diagonal = gram_matrix.diagonal(); + return DiagonalQuadraticForm(ring, diagonal[n:]) + + def compute_gram_matrix_from_basis(Q, basis): + ##Computes the gram matrix by treating the columns of basis as basis vectors. + n = Q.dim() + rows = []; + for i in range(n): + rows.append([Q.bilinear_map(basis.column(i), basis.column(j)) for j in range(n)]) + return Matrix(rows) + + def modify_basis(basis, v, i): + ##Modifies basis to include v (a linear combination of the basis vectors) at column i in the basis. + b = copy.deepcopy(basis) + column = vector(QQ, b.dimensions()[0]) + for j in range(len(v)): + column += b.column(i + j) * v[j] + b.set_column(i, column) + return b + + def gram_schmidt(matrix, fixed_vector_index, gram_matrix): + ##Orthogonalizes the columns of matrix against a fixed column vector. + n = matrix.dimensions()[0] + vectors = [0] * n + fixed_vec = matrix.column(fixed_vector_index) + vectors[fixed_vector_index] = fixed_vec + + for i in range(fixed_vector_index): + vectors[i] = matrix.column(i) + for i in range(fixed_vector_index + 1, n): + vectors[i] = matrix.column(i) - (gram_matrix[i, fixed_vector_index] / gram_matrix[fixed_vector_index, fixed_vector_index]) * fixed_vec + + return column_matrix(vectors) + + def vectors_of_common_length_dev(Q, F, qb, fb, iteration): + ##Returns a pair of vectors v1 and v2, such that Q(v1) = F(v2), + ##and qb and fb are bases when their columns at index iteration are replaced with + ##v1 and v2 respectively. (i.e. qb and fb contain no all-zero rows) + def zero_row(matrix, col, i): + ##Sets the ith column of matrix to col and returns true if matrix has an all zero row. + m = modify_basis(matrix, col, i) + rows, cols = m.dimensions() + z = [0] * cols + for i in range(rows): + if m.row(i).list() == z: + return True + return False + + i = max(Q.Gram_matrix()[0][0], F.Gram_matrix()[0][0]) + n = 100 + #NOTE: If the given forms are not equivalent, this will be an infinite loop. + while True: + q_vecs, f_vecs = Q.short_vector_list_up_to_length(n, real_entry_flag=True), F.short_vector_list_up_to_length(n, real_entry_flag=True) + while i < n: + ##Find a value that is represented by both forms + if q_vecs[i] and f_vecs[i]: + ##Find a pair of vectors such that if each becomes a column of qb and fb respectively, + ##neither matrix will have an all-zero row. + for j in range(len(q_vecs[i])): + for k in range(len(f_vecs[i])): + if not zero_row(qb, q_vecs[i][j], iteration) and not zero_row(fb, f_vecs[i][k], iteration): + return (q_vecs[i][j], f_vecs[i][k]) + i += 1 + n += 100 + + #--- END OF HELPER METHODS ---# + + Q, F = copy.deepcopy(V), copy.deepcopy(W) + n = Q.dim() + + q_basis, f_basis = Matrix.identity(n), Matrix.identity(n) + for i in range(n-1): + ##Find vectors v and w such that Q(v) = F(w) + v, w = vectors_of_common_length_dev(Q, F, q_basis, f_basis, i) + #print("Find vectors {0} and {1} such that Q(v) = F(w)".format(v, w)) + + ##Modify the bases to include v and w. + q_basis = modify_basis(q_basis, v, i) + f_basis = modify_basis(f_basis, w, i) + #print("Modified bases:\nQb =\n{0}, \nFb =\n{1}".format(q_basis, f_basis)) + + ##Compute the gram matrices with respect to the modified bases. + QM = compute_gram_matrix_from_basis(V, q_basis) + FM = compute_gram_matrix_from_basis(W, f_basis) + #print("Gram matrices with respect to modified bases:\nQM =\n{0}, \nFM =\n{1}".format(QM,FM)) + + ##Ensure that the bases are orthogonal, so the gram matrices will be diagonal. + q_basis = gram_schmidt(q_basis, i, QM) + f_basis = gram_schmidt(f_basis, i, FM) + #print("Orthogonal bases:\nQb =\n{0}, \nFb =\n{1}".format(q_basis, f_basis)) + + ##Compute the gram matrices with respect to the orthogonal bases. + QM = compute_gram_matrix_from_basis(V, q_basis) + FM = compute_gram_matrix_from_basis(W, f_basis) + #print("Gram matrices with respect to orthogonal bases:\nQM =\n{0}, \nFM =\n{1}".format(QM,FM)) + + ##Pull off the first term and continue + Q = diagonal_qf_pull_off_n_terms(QM, i+1, Q.base_ring()) + F = diagonal_qf_pull_off_n_terms(FM, i+1, F.base_ring()) + #print("Smaller qf's:\nQ =\n{0}, \nF = \n{1}".format(Q, F)) + + ##Compute the final term as a special case: + QM, FM = Q.Gram_matrix(), F.Gram_matrix() + if QM[0][0] != 0: + q_basis.set_col_to_multiple_of_col(n-1, n-1, sqrt(FM[0][0] / QM[0][0])) + elif FM[0][0] != 0: + f_basis.set_col_to_multiple_of_col(n-1, n-1, sqrt(QM[0][0] / FM[0][0])) + + return f_basis * q_basis.inverse() From 8a040f3e2bdc5b312bd14b1fe36e85dda87ead91 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Thu, 12 Nov 2015 16:14:34 -0500 Subject: [PATCH 004/170] Merge branch 'develop' into ticket/19112 --- VERSION.txt | 2 +- build/make/deps | 6 +- build/pkgs/bliss/SPKG.txt | 2 +- build/pkgs/bliss/checksums.ini | 8 +- build/pkgs/bliss/package-version.txt | 2 +- .../bliss/patches/digraph_heuristic.patch | 10 - build/pkgs/bliss/spkg-install | 2 +- build/pkgs/boehm_gc/dependencies | 5 + build/pkgs/boost_cropped/dependencies | 5 + build/pkgs/buckygen/dependencies | 5 + build/pkgs/cephes/dependencies | 5 + build/pkgs/cliquer/dependencies | 5 + build/pkgs/cliquer/spkg-install | 2 +- build/pkgs/combinatorial_designs/dependencies | 5 + build/pkgs/compilerwrapper/dependencies | 5 + build/pkgs/configure/checksums.ini | 6 +- build/pkgs/configure/package-version.txt | 2 +- build/pkgs/conway_polynomials/spkg-install | 2 +- build/pkgs/cryptominisat/dependencies | 5 + build/pkgs/cython/checksums.ini | 6 +- build/pkgs/cython/package-version.txt | 2 +- build/pkgs/d3js/dependencies | 5 + .../database_cremona_ellcurve/dependencies | 5 + .../pkgs/database_jones_numfield/dependencies | 5 + .../pkgs/database_stein_watkins/dependencies | 5 + .../database_stein_watkins_mini/dependencies | 5 + .../pkgs/database_symbolic_data/dependencies | 5 + build/pkgs/git/checksums.ini | 6 +- build/pkgs/git/package-version.txt | 2 +- build/pkgs/git/spkg-install | 9 +- build/pkgs/gp2c/SPKG.txt | 18 + build/pkgs/gp2c/checksums.ini | 4 + build/pkgs/gp2c/dependencies | 5 + build/pkgs/gp2c/package-version.txt | 1 + build/pkgs/gp2c/patches/20150326.patch | 218 + build/pkgs/gp2c/patches/global.patch | 126 + build/pkgs/gp2c/spkg-check | 3 + build/pkgs/gp2c/spkg-install | 24 + build/pkgs/gp2c/type | 1 + build/pkgs/graphs/dependencies | 5 + build/pkgs/iconv/dependencies | 5 + build/pkgs/lcalc/spkg-install | 5 + build/pkgs/libogg/dependencies | 5 + build/pkgs/lrcalc/dependencies | 5 + build/pkgs/mpir/package-version.txt | 2 +- build/pkgs/mpir/patches/fix-19280.patch | 22 + build/pkgs/mpir/patches/test-19280.patch | 161 + build/pkgs/nauty/dependencies | 5 + build/pkgs/ncurses/dependencies | 5 + build/pkgs/networkx/checksums.ini | 6 +- build/pkgs/networkx/dependencies | 2 +- build/pkgs/networkx/package-version.txt | 2 +- build/pkgs/networkx/spkg-install | 2 +- build/pkgs/openssl/dependencies | 5 + build/pkgs/pari/checksums.ini | 6 +- build/pkgs/pari/package-version.txt | 2 +- build/pkgs/pari/patches/KERNELCFLAGS.patch | 16 - build/pkgs/pari/patches/README.txt | 10 - build/pkgs/pari/patches/det_garbage.patch | 55 - build/pkgs/pari/patches/perl_regex.patch | 200 - build/pkgs/pari/spkg-install | 9 +- build/pkgs/pari_galdata/dependencies | 5 + build/pkgs/pari_jupyter/SPKG.txt | 22 + build/pkgs/pari_jupyter/checksums.ini | 4 + build/pkgs/pari_jupyter/dependencies | 1 + build/pkgs/pari_jupyter/package-version.txt | 1 + build/pkgs/pari_jupyter/spkg-install | 3 + build/pkgs/pari_jupyter/type | 1 + build/pkgs/pari_seadata_small/dependencies | 5 + build/pkgs/patch/dependencies | 5 + build/pkgs/patchbot/SPKG.txt | 26 + build/pkgs/patchbot/checksums.ini | 4 + build/pkgs/patchbot/dependencies | 1 + build/pkgs/patchbot/package-version.txt | 1 + build/pkgs/patchbot/spkg-install | 14 + build/pkgs/patchbot/type | 1 + build/pkgs/planarity/dependencies | 5 + build/pkgs/plantri/dependencies | 5 + build/pkgs/polytopes_db/dependencies | 5 + build/pkgs/qepcad/spkg-install | 2 +- build/pkgs/r/spkg-install | 19 +- build/pkgs/rubiks/dependencies | 5 + build/pkgs/rw/dependencies | 5 + build/pkgs/saclib/dependencies | 5 + build/pkgs/singular/spkg-install | 5 + build/pkgs/symmetrica/dependencies | 5 + build/pkgs/sympow/dependencies | 5 + build/pkgs/termcap/dependencies | 5 + build/pkgs/valgrind/dependencies | 5 + build/pkgs/zeromq/dependencies | 5 + build/pkgs/zlib/dependencies | 5 + build/pkgs/zn_poly/spkg-install | 1 + build/sage_bootstrap/cmdline.py | 44 +- configure.ac | 1269 +++-- src/bin/sage-banner | 5 +- src/bin/sage-env | 11 +- src/bin/sage-notebook | 16 +- src/bin/sage-unzip | 15 + src/bin/sage-version.sh | 4 +- src/doc/en/reference/algebras/index.rst | 4 + src/doc/en/reference/categories/index.rst | 14 + src/doc/en/reference/coding/index.rst | 4 +- src/doc/en/reference/combinat/module_list.rst | 8 +- .../en/reference/data_structures/index.rst | 1 + src/doc/en/reference/graphs/index.rst | 1 + src/doc/en/reference/homology/index.rst | 4 + src/doc/en/reference/index.rst | 1 + src/doc/en/reference/misc/index.rst | 1 + .../polynomial_rings_univar.rst | 1 + .../rings/asymptotic_expansions_index.rst | 50 + src/doc/en/reference/sat/index.rst | 38 +- .../coercion_and_categories.rst | 2 +- .../nf_galois_groups.rst | 3 +- .../lie/weyl_character_ring.rst | 12 +- .../tutorial-objects-and-classes.rst | 2 +- src/doc/en/tutorial/tour_coercion.rst | 1 + src/doc/fr/tutorial/tour_coercion.rst | 1 + src/doc/pt/tutorial/tour_coercion.rst | 4 +- src/module_list.py | 4 + src/sage/algebras/associated_graded.py | 341 ++ src/sage/algebras/catalog.py | 4 + src/sage/algebras/clifford_algebra.py | 90 +- .../finite_dimensional_algebra_element.py | 22 + src/sage/algebras/free_zinbiel_algebra.py | 253 + src/sage/algebras/jordan_algebra.py | 47 + .../algebras/quatalg/quaternion_algebra.py | 12 +- src/sage/algebras/schur_algebra.py | 16 +- .../algebras/steenrod/steenrod_algebra.py | 29 +- src/sage/algebras/weyl_algebra.py | 97 +- src/sage/all.py | 1 + src/sage/categories/additive_magmas.py | 8 +- src/sage/categories/additive_semigroups.py | 4 +- src/sage/categories/algebras.py | 2 + src/sage/categories/algebras_with_basis.py | 19 +- src/sage/categories/all.py | 3 +- src/sage/categories/bialgebras.py | 8 +- src/sage/categories/bialgebras_with_basis.py | 384 +- src/sage/categories/bimodules.py | 14 +- src/sage/categories/cartesian_product.py | 88 +- src/sage/categories/category.py | 28 +- src/sage/categories/category_cy_helper.pyx | 7 +- src/sage/categories/category_types.py | 43 +- src/sage/categories/category_with_axiom.py | 5 + src/sage/categories/coalgebras.py | 39 +- src/sage/categories/coalgebras_with_basis.py | 68 +- .../covariant_functorial_construction.py | 4 +- src/sage/categories/coxeter_groups.py | 3 +- src/sage/categories/cw_complexes.py | 215 + ...distributive_magmas_and_additive_magmas.py | 4 +- src/sage/categories/domains.py | 4 +- src/sage/categories/enumerated_sets.py | 154 +- src/sage/categories/euclidean_domains.py | 9 +- src/sage/categories/examples/cw_complexes.py | 164 + .../examples/filtered_algebras_with_basis.py | 178 + .../examples/filtered_modules_with_basis.py | 151 + .../examples/graded_modules_with_basis.py | 5 +- src/sage/categories/examples/graphs.py | 122 + src/sage/categories/examples/manifolds.py | 94 + src/sage/categories/filtered_algebras.py | 62 + .../filtered_algebras_with_basis.py | 541 ++ src/sage/categories/filtered_modules.py | 161 + .../categories/filtered_modules_with_basis.py | 928 ++++ .../finite_dimensional_algebras_with_basis.py | 21 + ...inite_dimensional_bialgebras_with_basis.py | 3 +- .../finite_dimensional_modules_with_basis.py | 28 +- src/sage/categories/finite_enumerated_sets.py | 4 +- src/sage/categories/finite_posets.py | 16 +- src/sage/categories/graded_algebras.py | 19 +- .../categories/graded_algebras_with_basis.py | 137 +- .../graded_bialgebras_with_basis.py | 3 +- src/sage/categories/graded_modules.py | 119 +- .../categories/graded_modules_with_basis.py | 164 +- src/sage/categories/graphs.py | 108 + src/sage/categories/groups.py | 15 + .../categories/highest_weight_crystals.py | 15 +- src/sage/categories/homset.py | 34 +- src/sage/categories/hopf_algebras.py | 9 +- .../categories/hopf_algebras_with_basis.py | 4 +- src/sage/categories/lie_groups.py | 72 + src/sage/categories/magmas.py | 8 +- src/sage/categories/manifolds.py | 349 ++ src/sage/categories/map.pyx | 8 +- src/sage/categories/metric_spaces.py | 258 + src/sage/categories/modules.py | 137 +- src/sage/categories/modules_with_basis.py | 620 ++- src/sage/categories/monoids.py | 3 +- src/sage/categories/morphism.pyx | 5 +- src/sage/categories/posets.py | 3 + src/sage/categories/primer.py | 8 +- src/sage/categories/pushout.py | 426 +- src/sage/categories/regular_crystals.py | 9 +- src/sage/categories/rings.py | 17 +- src/sage/categories/semigroups.py | 4 +- src/sage/categories/sets_cat.py | 263 +- src/sage/categories/simplicial_complexes.py | 101 + src/sage/categories/super_algebras.py | 67 + .../categories/super_algebras_with_basis.py | 61 + .../super_hopf_algebras_with_basis.py | 30 + src/sage/categories/super_modules.py | 229 + .../categories/super_modules_with_basis.py | 185 + src/sage/categories/topological_spaces.py | 108 + .../unique_factorization_domains.py | 81 + src/sage/coding/all.py | 15 +- src/sage/coding/codes_catalog.py | 9 +- src/sage/coding/encoder.py | 325 ++ src/sage/coding/encoders_catalog.py | 16 + src/sage/coding/linear_code.py | 415 +- src/sage/combinat/affine_permutation.py | 18 +- src/sage/combinat/all.py | 7 +- src/sage/combinat/alternating_sign_matrix.py | 113 +- src/sage/combinat/cartesian_product.py | 165 +- .../cluster_algebra_quiver/cluster_seed.py | 202 +- .../combinat/cluster_algebra_quiver/quiver.py | 144 +- src/sage/combinat/colored_permutations.py | 1108 +++++ src/sage/combinat/combinat.py | 45 +- src/sage/combinat/composition.py | 16 +- src/sage/combinat/composition_signed.py | 6 +- src/sage/combinat/crystals/affine.py | 114 +- src/sage/combinat/crystals/affinization.py | 43 +- src/sage/combinat/crystals/alcove_path.py | 29 +- .../combinat/crystals/elementary_crystals.py | 24 +- src/sage/combinat/crystals/fast_crystals.py | 37 +- .../combinat/crystals/monomial_crystals.py | 12 + src/sage/combinat/crystals/subcrystal.py | 110 +- src/sage/combinat/crystals/tensor_product.py | 4 +- src/sage/combinat/crystals/virtual_crystal.py | 12 + src/sage/combinat/designs/block_design.py | 23 +- .../combinat/designs/incidence_structures.py | 97 +- src/sage/combinat/diagram_algebras.py | 205 +- src/sage/combinat/enumerated_sets.py | 4 +- src/sage/combinat/finite_state_machine.py | 365 +- src/sage/combinat/free_module.py | 525 +- src/sage/combinat/fully_packed_loop.py | 33 +- src/sage/combinat/integer_list.py | 2409 +-------- src/sage/combinat/integer_lists/__init__.py | 6 + src/sage/combinat/integer_lists/base.pxd | 15 + src/sage/combinat/integer_lists/base.pyx | 702 +++ src/sage/combinat/integer_lists/invlex.pxd | 3 + src/sage/combinat/integer_lists/invlex.pyx | 1678 +++++++ src/sage/combinat/integer_lists/lists.py | 292 ++ src/sage/combinat/integer_lists/nn.py | 43 + src/sage/combinat/integer_matrices.py | 2 +- src/sage/combinat/integer_vector.py | 217 +- src/sage/combinat/k_tableau.py | 4 +- src/sage/combinat/matrices/hadamard_matrix.py | 336 +- .../combinat/ncsf_qsym/generic_basis_code.py | 5 +- src/sage/combinat/ncsf_qsym/ncsf.py | 3 +- src/sage/combinat/ncsf_qsym/tutorial.py | 10 +- src/sage/combinat/ordered_tree.py | 6 +- src/sage/combinat/partition.py | 536 +- src/sage/combinat/partition_tuple.py | 7 +- src/sage/combinat/perfect_matching.py | 29 + src/sage/combinat/posets/__init__.py | 4 + src/sage/combinat/posets/cartesian_product.py | 505 ++ src/sage/combinat/posets/elements.py | 11 + src/sage/combinat/posets/hasse_diagram.py | 2 + src/sage/combinat/posets/lattices.py | 32 + src/sage/combinat/posets/moebius_algebra.py | 732 +++ src/sage/combinat/posets/posets.py | 734 ++- src/sage/combinat/quickref.py | 2 +- .../bij_abstract_class.py | 2 +- .../rigged_configurations/bij_type_D.py | 2 +- .../rigged_configurations/kleber_tree.py | 63 +- .../rigged_configurations/kr_tableaux.py | 34 +- .../rigged_configuration_element.py | 119 + .../rigged_configurations.py | 42 +- src/sage/combinat/root_system/__init__.py | 1 + src/sage/combinat/root_system/all.py | 5 +- .../combinat/root_system/cartan_matrix.py | 248 +- src/sage/combinat/root_system/cartan_type.py | 66 +- .../combinat/root_system/coxeter_group.py | 1 - .../combinat/root_system/coxeter_matrix.py | 1183 ++++- src/sage/combinat/root_system/coxeter_type.py | 576 +++ .../combinat/root_system/dynkin_diagram.py | 11 +- .../root_system/integrable_representations.py | 200 +- src/sage/combinat/root_system/plot.py | 6 +- .../root_system/root_lattice_realizations.py | 12 +- src/sage/combinat/root_system/type_I.py | 7 +- src/sage/combinat/set_partition.py | 5 +- src/sage/combinat/sf/macdonald.py | 4 +- src/sage/combinat/sf/sfa.py | 4 +- src/sage/combinat/sidon_sets.py | 4 +- src/sage/combinat/similarity_class_type.py | 48 +- src/sage/combinat/skew_tableau.py | 109 +- .../combinat/species/composition_species.py | 26 +- src/sage/combinat/tableau.py | 2 +- src/sage/combinat/tutorial.py | 140 +- src/sage/combinat/words/word_char.pyx | 174 +- src/sage/combinat/words/words.py | 5 +- src/sage/data_structures/mutable_poset.py | 3522 +++++++++++++ src/sage/databases/findstat.py | 13 +- src/sage/dev/git_interface.py | 26 +- .../dynamics/interval_exchanges/template.py | 47 +- src/sage/functions/log.py | 44 +- src/sage/functions/other.py | 93 +- src/sage/game_theory/normal_form_game.py | 5 +- src/sage/geometry/cone.py | 252 +- .../hyperbolic_space/hyperbolic_geodesic.py | 5 +- .../hyperbolic_space/hyperbolic_interface.py | 35 +- .../hyperbolic_space/hyperbolic_model.py | 4 - .../hyperplane_arrangement/arrangement.py | 130 +- src/sage/geometry/integral_points.pyx | 7 +- src/sage/geometry/lattice_polytope.py | 19 +- src/sage/geometry/linear_expression.py | 85 +- src/sage/geometry/polyhedron/base.py | 60 +- src/sage/geometry/polyhedron/base_ZZ.py | 4 +- .../geometry/polyhedron/double_description.py | 2 +- .../double_description_inhomogeneous.py | 4 +- .../lattice_euclidean_group_element.py | 2 +- src/sage/geometry/polyhedron/palp_database.py | 10 +- .../polyhedron/ppl_lattice_polytope.py | 17 +- .../geometry/polyhedron/representation.py | 4 +- src/sage/graphs/asteroidal_triples.pyx | 2 +- src/sage/graphs/base/dense_graph.pyx | 148 +- src/sage/graphs/bliss.pyx | 1 + src/sage/graphs/digraph.py | 348 +- .../graphs/generators/classical_geometries.py | 183 +- src/sage/graphs/generators/families.py | 65 +- src/sage/graphs/generic_graph.py | 40 +- src/sage/graphs/graph.py | 808 +-- src/sage/graphs/graph_generators.py | 6 + src/sage/graphs/graph_input.py | 527 ++ src/sage/graphs/hyperbolicity.pyx | 2 +- src/sage/graphs/hypergraph_generators.py | 19 + src/sage/graphs/independent_sets.pyx | 2 +- src/sage/graphs/schnyder.py | 145 +- src/sage/graphs/strongly_regular_db.pyx | 777 ++- .../abelian_gps/abelian_group_element.py | 1 - src/sage/groups/abelian_gps/element_base.py | 10 + .../additive_abelian_group.py | 4 +- src/sage/groups/conjugacy_classes.py | 2 +- src/sage/groups/finitely_presented_named.py | 6 +- src/sage/groups/free_group.py | 11 + src/sage/groups/matrix_gps/coxeter_group.py | 123 +- src/sage/groups/matrix_gps/group_element.py | 13 +- src/sage/groups/matrix_gps/morphism.py | 15 +- .../homology/algebraic_topological_model.py | 592 +++ src/sage/homology/cell_complex.py | 399 +- src/sage/homology/chain_complex_homspace.py | 16 +- src/sage/homology/chain_complex_morphism.py | 235 +- src/sage/homology/chain_homotopy.py | 600 +++ src/sage/homology/cubical_complex.py | 68 +- src/sage/homology/delta_complex.py | 38 +- src/sage/homology/homology_morphism.py | 409 ++ .../homology_vector_space_with_basis.py | 834 ++++ src/sage/homology/simplicial_complex.py | 132 +- .../homology/simplicial_complex_homset.py | 109 +- .../homology/simplicial_complex_morphism.py | 310 +- src/sage/interfaces/chomp.py | 8 +- src/sage/interfaces/expect.py | 6 +- src/sage/interfaces/octave.py | 262 +- src/sage/interfaces/sage0.py | 16 + src/sage/interfaces/singular.py | 4 +- src/sage/lfunctions/zero_sums.pyx | 3 +- src/sage/libs/flint/fmpz.pxd | 4 + src/sage/libs/pari/paridecl.pxd | 101 +- src/sage/libs/singular/groebner_strategy.pyx | 4 +- src/sage/matrix/action.pyx | 10 +- src/sage/matrix/constructor.py | 3 - src/sage/matrix/matrix1.pyx | 14 +- src/sage/matrix/matrix_mod2_dense.pyx | 12 + src/sage/matrix/matrix_space.py | 8 +- src/sage/matrix/operation_table.py | 22 +- src/sage/matroids/lean_matrix.pyx | 16 +- src/sage/matroids/matroid.pyx | 12 +- src/sage/misc/c3_controlled.pyx | 7 +- src/sage/misc/converting_dict.py | 291 ++ src/sage/misc/cython.py | 18 +- src/sage/misc/functional.py | 3 +- src/sage/misc/misc.py | 26 + src/sage/misc/mrange.py | 107 + src/sage/misc/rest_index_of_methods.py | 181 +- src/sage/misc/sage_unittest.py | 4 +- .../modular/arithgroup/arithgroup_element.pyx | 10 +- .../modular/arithgroup/congroup_gammaH.py | 68 +- src/sage/modular/modform/element.py | 8 +- .../modform_hecketriangle/abstract_ring.py | 55 +- .../modform_hecketriangle/abstract_space.py | 58 +- .../modular/modform_hecketriangle/functors.py | 51 +- .../graded_ring_element.py | 24 + .../modular/modform_hecketriangle/readme.py | 15 +- src/sage/modular/modsym/ambient.py | 35 +- src/sage/modular/modsym/manin_symbol.pyx | 14 +- src/sage/modular/modsym/modular_symbols.py | 2 +- src/sage/modular/modsym/p1list.pyx | 4 +- src/sage/modular/modsym/space.py | 9 +- .../modular/overconvergent/weightspace.py | 14 + src/sage/modules/fg_pid/fgp_element.py | 33 +- src/sage/modules/free_module.py | 53 +- src/sage/modules/free_module_element.pyx | 54 +- src/sage/modules/free_module_homspace.py | 11 +- src/sage/modules/with_basis/morphism.py | 5 +- src/sage/monoids/free_monoid_element.py | 17 + src/sage/monoids/indexed_free_monoid.py | 27 + src/sage/plot/arc.py | 253 +- src/sage/plot/hyperbolic_arc.py | 12 +- src/sage/plot/hyperbolic_polygon.py | 14 +- src/sage/quadratic_forms/genera/genus.py | 206 +- src/sage/repl/display/fancy_repr.py | 25 + src/sage/repl/image.py | 5 +- src/sage/repl/ipython_kernel/install.py | 134 +- src/sage/repl/ipython_kernel/kernel.py | 26 +- src/sage/rings/all.py | 3 + src/sage/rings/asymptotic/all.py | 2 + src/sage/rings/asymptotic/asymptotic_ring.py | 3295 +++++++++++++ src/sage/rings/asymptotic/growth_group.py | 4356 +++++++++++++---- .../asymptotic/growth_group_cartesian.py | 1257 +++++ src/sage/rings/asymptotic/misc.py | 693 +++ src/sage/rings/asymptotic/term_monoid.py | 2791 ++++++++--- src/sage/rings/big_oh.py | 17 + src/sage/rings/cfinite_sequence.py | 12 + src/sage/rings/complex_ball_acb.pyx | 143 +- src/sage/rings/complex_double.pyx | 22 +- src/sage/rings/complex_field.py | 5 +- src/sage/rings/complex_interval.pyx | 214 +- src/sage/rings/complex_interval_field.py | 9 +- src/sage/rings/complex_number.pyx | 8 +- src/sage/rings/continued_fraction.py | 83 +- .../rings/finite_rings/element_givaro.pyx | 13 - .../rings/finite_rings/element_ntl_gf2e.pyx | 22 +- .../rings/finite_rings/element_pari_ffelt.pyx | 56 +- .../rings/finite_rings/hom_finite_field.pyx | 11 - src/sage/rings/finite_rings/integer_mod.pyx | 12 - src/sage/rings/fraction_field_FpT.pyx | 20 +- src/sage/rings/fraction_field_element.pyx | 20 +- .../function_field/function_field_element.pyx | 24 + src/sage/rings/ideal.py | 16 + src/sage/rings/integer_ring.pyx | 9 +- .../rings/laurent_series_ring_element.pyx | 24 +- src/sage/rings/morphism.pyx | 51 +- .../number_field/number_field_element.pyx | 5 +- .../number_field_element_quadratic.pyx | 21 +- src/sage/rings/padics/CA_template.pxi | 16 - src/sage/rings/padics/CR_template.pxi | 16 - src/sage/rings/padics/FM_template.pxi | 16 - src/sage/rings/padics/local_generic.py | 1 + src/sage/rings/padics/morphism.pyx | 19 +- .../rings/padics/padic_ZZ_pX_FM_element.pyx | 19 - src/sage/rings/padics/padic_base_leaves.py | 12 +- src/sage/rings/padics/padic_generic.py | 38 +- .../rings/padics/padic_generic_element.pyx | 36 + src/sage/rings/polynomial/complex_roots.py | 129 +- src/sage/rings/polynomial/ideal.py | 1 - .../polynomial/infinite_polynomial_element.py | 7 +- .../rings/polynomial/laurent_polynomial.pyx | 62 +- .../rings/polynomial/multi_polynomial.pyx | 4 +- .../polynomial/multi_polynomial_ideal.py | 132 +- .../multi_polynomial_libsingular.pyx | 14 +- .../polynomial/multi_polynomial_sequence.py | 24 +- src/sage/rings/polynomial/pbori.pyx | 34 +- src/sage/rings/polynomial/plural.pyx | 40 +- .../rings/polynomial/polynomial_element.pyx | 125 +- .../polynomial/polynomial_element_generic.py | 68 +- .../polynomial/polynomial_real_mpfr_dense.pyx | 25 +- src/sage/rings/polynomial/polynomial_ring.py | 2 +- .../rings/polynomial/polynomial_template.pxi | 10 +- src/sage/rings/polynomial/refine_root.pyx | 141 + src/sage/rings/power_series_poly.pyx | 18 - src/sage/rings/power_series_ring.py | 26 +- src/sage/rings/power_series_ring_element.pyx | 38 +- src/sage/rings/qqbar.py | 52 +- src/sage/rings/quotient_ring.py | 22 +- src/sage/rings/quotient_ring_element.py | 12 + src/sage/rings/rational.pyx | 7 +- src/sage/rings/rational_field.py | 4 +- src/sage/rings/real_arb.pyx | 341 +- src/sage/rings/real_double.pyx | 2 +- src/sage/rings/real_lazy.pyx | 91 +- src/sage/rings/real_mpfi.pxd | 15 +- src/sage/rings/real_mpfi.pyx | 300 +- src/sage/rings/real_mpfr.pxd | 14 +- src/sage/rings/real_mpfr.pyx | 174 +- src/sage/rings/ring.pyx | 13 +- .../rings/semirings/tropical_semiring.pyx | 31 +- src/sage/sandpiles/sandpile.py | 70 +- src/sage/sat/all.py | 2 + src/sage/sat/converters/polybori.py | 2 +- src/sage/sat/solvers/sat_lp.py | 145 + src/sage/sat/solvers/satsolver.pyx | 60 +- .../elliptic_curves/ell_number_field.py | 4 +- src/sage/schemes/toric/divisor.py | 24 +- src/sage/schemes/toric/points.py | 26 +- src/sage/sets/cartesian_product.py | 60 +- src/sage/sets/family.py | 2 +- src/sage/sets/finite_set_maps.py | 5 +- src/sage/sets/set.py | 33 +- src/sage/sets/set_from_iterator.py | 18 + src/sage/structure/category_object.pyx | 3 +- src/sage/structure/coerce_actions.pyx | 5 +- src/sage/structure/element.pxd | 1 + src/sage/structure/element.pyx | 38 +- src/sage/structure/indexed_generators.py | 20 +- src/sage/structure/parent.pyx | 17 +- src/sage/symbolic/function.pyx | 4 +- src/sage/symbolic/random_tests.py | 33 +- src/sage/symbolic/ring.pyx | 32 + src/sage/tensor/coordinate_patch.py | 12 +- src/sage/tensor/differential_form_element.py | 13 +- src/sage/tensor/differential_forms.py | 4 +- .../tensor/modules/finite_rank_free_module.py | 6 +- .../book_schilling_zabrocki_kschur_primer.py | 2 +- .../tests/french_book/domaines_doctest.py | 2 +- .../tests/french_book/nonlinear_doctest.py | 3 +- src/sage/version.py | 4 +- src/sage_setup/autogen/pari/parser.py | 2 +- src/setup.py | 20 +- 506 files changed, 45072 insertions(+), 11361 deletions(-) delete mode 100644 build/pkgs/bliss/patches/digraph_heuristic.patch create mode 100644 build/pkgs/boehm_gc/dependencies create mode 100644 build/pkgs/boost_cropped/dependencies create mode 100644 build/pkgs/buckygen/dependencies create mode 100644 build/pkgs/cephes/dependencies create mode 100644 build/pkgs/cliquer/dependencies create mode 100644 build/pkgs/combinatorial_designs/dependencies create mode 100644 build/pkgs/compilerwrapper/dependencies create mode 100644 build/pkgs/cryptominisat/dependencies create mode 100644 build/pkgs/d3js/dependencies create mode 100644 build/pkgs/database_cremona_ellcurve/dependencies create mode 100644 build/pkgs/database_jones_numfield/dependencies create mode 100644 build/pkgs/database_stein_watkins/dependencies create mode 100644 build/pkgs/database_stein_watkins_mini/dependencies create mode 100644 build/pkgs/database_symbolic_data/dependencies create mode 100644 build/pkgs/gp2c/SPKG.txt create mode 100644 build/pkgs/gp2c/checksums.ini create mode 100644 build/pkgs/gp2c/dependencies create mode 100644 build/pkgs/gp2c/package-version.txt create mode 100644 build/pkgs/gp2c/patches/20150326.patch create mode 100644 build/pkgs/gp2c/patches/global.patch create mode 100755 build/pkgs/gp2c/spkg-check create mode 100755 build/pkgs/gp2c/spkg-install create mode 100644 build/pkgs/gp2c/type create mode 100644 build/pkgs/graphs/dependencies create mode 100644 build/pkgs/iconv/dependencies create mode 100644 build/pkgs/libogg/dependencies create mode 100644 build/pkgs/lrcalc/dependencies create mode 100644 build/pkgs/mpir/patches/fix-19280.patch create mode 100644 build/pkgs/mpir/patches/test-19280.patch create mode 100644 build/pkgs/nauty/dependencies create mode 100644 build/pkgs/ncurses/dependencies create mode 100644 build/pkgs/openssl/dependencies delete mode 100644 build/pkgs/pari/patches/KERNELCFLAGS.patch delete mode 100644 build/pkgs/pari/patches/det_garbage.patch delete mode 100644 build/pkgs/pari/patches/perl_regex.patch create mode 100644 build/pkgs/pari_galdata/dependencies create mode 100644 build/pkgs/pari_jupyter/SPKG.txt create mode 100644 build/pkgs/pari_jupyter/checksums.ini create mode 100644 build/pkgs/pari_jupyter/dependencies create mode 100644 build/pkgs/pari_jupyter/package-version.txt create mode 100755 build/pkgs/pari_jupyter/spkg-install create mode 100644 build/pkgs/pari_jupyter/type create mode 100644 build/pkgs/pari_seadata_small/dependencies create mode 100644 build/pkgs/patch/dependencies create mode 100644 build/pkgs/patchbot/SPKG.txt create mode 100644 build/pkgs/patchbot/checksums.ini create mode 100644 build/pkgs/patchbot/dependencies create mode 100644 build/pkgs/patchbot/package-version.txt create mode 100755 build/pkgs/patchbot/spkg-install create mode 100644 build/pkgs/patchbot/type create mode 100644 build/pkgs/planarity/dependencies create mode 100644 build/pkgs/plantri/dependencies create mode 100644 build/pkgs/polytopes_db/dependencies create mode 100644 build/pkgs/rubiks/dependencies create mode 100644 build/pkgs/rw/dependencies create mode 100644 build/pkgs/saclib/dependencies create mode 100644 build/pkgs/symmetrica/dependencies create mode 100644 build/pkgs/sympow/dependencies create mode 100644 build/pkgs/termcap/dependencies create mode 100644 build/pkgs/valgrind/dependencies create mode 100644 build/pkgs/zeromq/dependencies create mode 100644 build/pkgs/zlib/dependencies create mode 100755 src/bin/sage-unzip create mode 100644 src/sage/algebras/associated_graded.py create mode 100644 src/sage/algebras/free_zinbiel_algebra.py create mode 100644 src/sage/categories/cw_complexes.py create mode 100644 src/sage/categories/examples/cw_complexes.py create mode 100644 src/sage/categories/examples/filtered_algebras_with_basis.py create mode 100644 src/sage/categories/examples/filtered_modules_with_basis.py create mode 100644 src/sage/categories/examples/graphs.py create mode 100644 src/sage/categories/examples/manifolds.py create mode 100644 src/sage/categories/filtered_algebras.py create mode 100644 src/sage/categories/filtered_algebras_with_basis.py create mode 100644 src/sage/categories/filtered_modules.py create mode 100644 src/sage/categories/filtered_modules_with_basis.py create mode 100644 src/sage/categories/graphs.py create mode 100644 src/sage/categories/lie_groups.py create mode 100644 src/sage/categories/manifolds.py create mode 100644 src/sage/categories/metric_spaces.py create mode 100644 src/sage/categories/simplicial_complexes.py create mode 100644 src/sage/categories/super_algebras.py create mode 100644 src/sage/categories/super_algebras_with_basis.py create mode 100644 src/sage/categories/super_hopf_algebras_with_basis.py create mode 100644 src/sage/categories/super_modules.py create mode 100644 src/sage/categories/super_modules_with_basis.py create mode 100644 src/sage/categories/topological_spaces.py create mode 100644 src/sage/coding/encoder.py create mode 100644 src/sage/coding/encoders_catalog.py create mode 100644 src/sage/combinat/colored_permutations.py create mode 100644 src/sage/combinat/integer_lists/__init__.py create mode 100644 src/sage/combinat/integer_lists/base.pxd create mode 100644 src/sage/combinat/integer_lists/base.pyx create mode 100644 src/sage/combinat/integer_lists/invlex.pxd create mode 100644 src/sage/combinat/integer_lists/invlex.pyx create mode 100644 src/sage/combinat/integer_lists/lists.py create mode 100644 src/sage/combinat/integer_lists/nn.py create mode 100644 src/sage/combinat/posets/cartesian_product.py create mode 100644 src/sage/combinat/posets/moebius_algebra.py create mode 100644 src/sage/combinat/root_system/coxeter_type.py create mode 100644 src/sage/data_structures/mutable_poset.py create mode 100644 src/sage/graphs/graph_input.py create mode 100644 src/sage/homology/algebraic_topological_model.py create mode 100644 src/sage/homology/chain_homotopy.py create mode 100644 src/sage/homology/homology_morphism.py create mode 100644 src/sage/homology/homology_vector_space_with_basis.py create mode 100644 src/sage/misc/converting_dict.py create mode 100644 src/sage/rings/asymptotic/asymptotic_ring.py create mode 100644 src/sage/rings/asymptotic/growth_group_cartesian.py create mode 100644 src/sage/rings/asymptotic/misc.py create mode 100644 src/sage/rings/polynomial/refine_root.pyx create mode 100644 src/sage/sat/all.py create mode 100644 src/sage/sat/solvers/sat_lp.py diff --git a/VERSION.txt b/VERSION.txt index 68733eba217..feb64723094 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -Sage version 6.9, released 2015-10-10 +Sage version 6.10.beta2, released 2015-10-28 diff --git a/build/make/deps b/build/make/deps index 3d0fb935312..512b95c7401 100644 --- a/build/make/deps +++ b/build/make/deps @@ -101,6 +101,7 @@ base: $(INST)/$(BZIP2) $(INST)/$(PATCH) $(INST)/$(PKGCONF) # dependencies for Cython files (e.g. PARI, NTL, SAGE_MP_LIBRARY). sagelib: \ $(INST)/$(ATLAS) \ + $(INST)/$(BRIAL) \ $(INST)/$(CEPHES) \ $(INST)/$(CLIQUER) \ $(INST)/$(CYTHON) \ @@ -115,6 +116,7 @@ sagelib: \ $(INST)/$(GSL) \ $(INST)/$(IML) \ $(INST)/$(JINJA2) \ + $(INST)/$(JUPYTER_CORE) \ $(INST)/$(LCALC) \ $(INST)/$(LRCALC) \ $(INST)/$(LIBGAP) \ @@ -130,7 +132,6 @@ sagelib: \ $(INST)/$(NUMPY) \ $(INST)/$(PARI) \ $(INST)/$(PLANARITY) \ - $(INST)/$(BRIAL) \ $(INST)/$(PPL) \ $(INST)/$(PYNAC) \ $(INST)/$(PYTHON) \ @@ -140,7 +141,8 @@ sagelib: \ $(INST)/$(SINGULAR) \ $(INST)/$(SIX) \ $(INST)/$(SYMMETRICA) \ - $(INST)/$(ZN_POLY) + $(INST)/$(ZN_POLY) \ + $(EXTCODE) if [ -z "$$SAGE_INSTALL_FETCH_ONLY" ]; then \ cd $(SAGE_SRC) && source bin/sage-env && \ sage-logger 'time $(MAKE) sage' '$(SAGE_LOGS)/sage-$(SAGE_VERSION).log'; \ diff --git a/build/pkgs/bliss/SPKG.txt b/build/pkgs/bliss/SPKG.txt index 73bd34d5183..84b97f0f8fc 100644 --- a/build/pkgs/bliss/SPKG.txt +++ b/build/pkgs/bliss/SPKG.txt @@ -7,7 +7,7 @@ forms of graphs. == License == -GPL +LGPL == Upstream Contact == diff --git a/build/pkgs/bliss/checksums.ini b/build/pkgs/bliss/checksums.ini index 740953e6b30..7fc59156c20 100644 --- a/build/pkgs/bliss/checksums.ini +++ b/build/pkgs/bliss/checksums.ini @@ -1,4 +1,4 @@ -tarball=bliss-VERSION.tar.bz2 -sha1=500796365fdf142dcfb34f979d678ffa456f1431 -md5=4e57118f9acad9a80baaefd93f967bdf -cksum=662785944 +tarball=bliss-VERSION.tar.gz +sha1=46322da1a03750e199e156d8967d88b89ebad5de +md5=b936d5d54b618a77ed59dfeeced3fa58 +cksum=76340303 diff --git a/build/pkgs/bliss/package-version.txt b/build/pkgs/bliss/package-version.txt index 615e437dd31..6ab5ccfd03e 100644 --- a/build/pkgs/bliss/package-version.txt +++ b/build/pkgs/bliss/package-version.txt @@ -1 +1 @@ -0.72.p1 +0.73 diff --git a/build/pkgs/bliss/patches/digraph_heuristic.patch b/build/pkgs/bliss/patches/digraph_heuristic.patch deleted file mode 100644 index 6e61458f424..00000000000 --- a/build/pkgs/bliss/patches/digraph_heuristic.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- src/graph.cc 2015-02-11 13:20:39.922021355 +0100 -+++ src-patched/graph_new.cc 2015-02-11 13:20:15.546020960 +0100 -@@ -1920,6 +1920,7 @@ - Digraph::Digraph(const unsigned int nof_vertices) - { - vertices.resize(nof_vertices); -+ sh = shs_f; - } - - diff --git a/build/pkgs/bliss/spkg-install b/build/pkgs/bliss/spkg-install index e3f9c7cb15a..746526d99b8 100755 --- a/build/pkgs/bliss/spkg-install +++ b/build/pkgs/bliss/spkg-install @@ -18,7 +18,7 @@ for patch in ../patches/*.patch; do done -$MAKE && mv libbliss.a "$SAGE_LOCAL/lib" && mv *.hh "$SAGE_LOCAL/include" +$MAKE && cp libbliss.a "$SAGE_LOCAL/lib" && cp *.hh "$SAGE_LOCAL/include" if [ $? -ne 0 ]; then echo "An error occurred whilst building bliss" diff --git a/build/pkgs/boehm_gc/dependencies b/build/pkgs/boehm_gc/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/boehm_gc/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/boost_cropped/dependencies b/build/pkgs/boost_cropped/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/boost_cropped/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/buckygen/dependencies b/build/pkgs/buckygen/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/buckygen/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/cephes/dependencies b/build/pkgs/cephes/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/cephes/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/cliquer/dependencies b/build/pkgs/cliquer/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/cliquer/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/cliquer/spkg-install b/build/pkgs/cliquer/spkg-install index 4e7d6a5c5a8..87f23bb59c7 100755 --- a/build/pkgs/cliquer/spkg-install +++ b/build/pkgs/cliquer/spkg-install @@ -24,7 +24,6 @@ fi # Flags for building a dynamically linked shared object. if [ "$UNAME" = "Darwin" ]; then - export MACOSX_DEPLOYMENT_TARGET="10.3" SAGESOFLAGS="-dynamiclib -single_module -flat_namespace -undefined dynamic_lookup" elif [ "$UNAME" = "SunOS" ]; then SAGESOFLAGS="-shared -Wl,-h,libcliquer.so -Wl,-ztext" @@ -63,6 +62,7 @@ cp *.h "$SAGE_LOCAL/include/cliquer/" if [ "$UNAME" = "Darwin" ]; then cp -f libcliquer.so "$SAGE_LOCAL/lib/libcliquer.dylib" + install_name_tool -id "${SAGE_LOCAL}"/lib/libcliquer.dylib "${SAGE_LOCAL}"/lib/libcliquer.dylib elif [ "$UNAME" = "CYGWIN" ]; then cp -f libcliquer.so "$SAGE_LOCAL/lib/libcliquer.dll" fi diff --git a/build/pkgs/combinatorial_designs/dependencies b/build/pkgs/combinatorial_designs/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/combinatorial_designs/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/compilerwrapper/dependencies b/build/pkgs/compilerwrapper/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/compilerwrapper/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/configure/checksums.ini b/build/pkgs/configure/checksums.ini index ad47e649554..c6a3fac1146 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=beb4de16a8e5a632516a6b5f63313e66c6770183 -md5=d6922f63e807c1d240582601e746dcb1 -cksum=2973216508 +sha1=d7f2ecde69b59bec3887fd0ba1f4e2784394fd6b +md5=fa6127892f72b56b96a592fcec729f25 +cksum=1455899060 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 078fa0fe576..9f54fe3133b 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -119 +122 diff --git a/build/pkgs/conway_polynomials/spkg-install b/build/pkgs/conway_polynomials/spkg-install index 0023233e122..441dc8e8381 100755 --- a/build/pkgs/conway_polynomials/spkg-install +++ b/build/pkgs/conway_polynomials/spkg-install @@ -1,7 +1,7 @@ #!/usr/bin/env python import os -from sage.all import save +from sage.structure.sage_object import save from sage.env import SAGE_SHARE install_root = os.path.join(SAGE_SHARE, 'conway_polynomials') diff --git a/build/pkgs/cryptominisat/dependencies b/build/pkgs/cryptominisat/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/cryptominisat/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/cython/checksums.ini b/build/pkgs/cython/checksums.ini index 95899805812..f3ac4410434 100644 --- a/build/pkgs/cython/checksums.ini +++ b/build/pkgs/cython/checksums.ini @@ -1,4 +1,4 @@ tarball=Cython-VERSION.tar.gz -sha1=2ff0f863d3b996d2265d0bf06e567e5dd23d004d -md5=db3c5b365e1c3f71c7cd90e96473a3ab -cksum=1672168057 +sha1=d5592dc3d529c55a5ef95346caccf11c556993bd +md5=813df20f7ce5f00e60568e0371fbd07c +cksum=365027876 diff --git a/build/pkgs/cython/package-version.txt b/build/pkgs/cython/package-version.txt index e13359b8fa3..9e40e75c5d2 100644 --- a/build/pkgs/cython/package-version.txt +++ b/build/pkgs/cython/package-version.txt @@ -1 +1 @@ -0.23.1.p0 +0.23.3 diff --git a/build/pkgs/d3js/dependencies b/build/pkgs/d3js/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/d3js/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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_cremona_ellcurve/dependencies b/build/pkgs/database_cremona_ellcurve/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_cremona_ellcurve/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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_jones_numfield/dependencies b/build/pkgs/database_jones_numfield/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_jones_numfield/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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_stein_watkins/dependencies b/build/pkgs/database_stein_watkins/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_stein_watkins/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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_stein_watkins_mini/dependencies b/build/pkgs/database_stein_watkins_mini/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_stein_watkins_mini/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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_symbolic_data/dependencies b/build/pkgs/database_symbolic_data/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_symbolic_data/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/git/checksums.ini b/build/pkgs/git/checksums.ini index 88657651061..3a6e9681738 100644 --- a/build/pkgs/git/checksums.ini +++ b/build/pkgs/git/checksums.ini @@ -1,4 +1,4 @@ tarball=git-VERSION.tar.gz -sha1=150efeb9c016cb8d3e768a408f3f407d18d69661 -md5=edf994cf34cd3354dadcdfa6b4292335 -cksum=995320395 +sha1=ff32a94936309ca3f0e3d56e479e7510a3c1c925 +md5=da293290da69f45a86a311ad3cd43dc8 +cksum=2025246710 diff --git a/build/pkgs/git/package-version.txt b/build/pkgs/git/package-version.txt index 276cbf9e285..097a15a2af3 100644 --- a/build/pkgs/git/package-version.txt +++ b/build/pkgs/git/package-version.txt @@ -1 +1 @@ -2.3.0 +2.6.2 diff --git a/build/pkgs/git/spkg-install b/build/pkgs/git/spkg-install index 6331ae29f5f..f9ba3204f16 100755 --- a/build/pkgs/git/spkg-install +++ b/build/pkgs/git/spkg-install @@ -49,13 +49,10 @@ done export NO_FINK=1 export NO_DARWIN_PORTS=1 -# Darwin 8 (Tiger) does not support common crypto -if { uname -sr | grep 'Darwin 8' ;} &>/dev/null; then - export NO_APPLE_COMMON_CRYPTO=1 -fi - # OSX Git with FSF GCC is broken, disable completely for now. See #17091 -export NO_APPLE_COMMON_CRYPTO=1 +if [ "$UNAME" = "Darwin" ]; then + export NO_OPENSSL=1 +fi # First make GIT-VERSION-FILE (we patched Makefile such that configure # no longer depends on this, so it's safer to explicitly build this). diff --git a/build/pkgs/gp2c/SPKG.txt b/build/pkgs/gp2c/SPKG.txt new file mode 100644 index 00000000000..984b63c7865 --- /dev/null +++ b/build/pkgs/gp2c/SPKG.txt @@ -0,0 +1,18 @@ += gp2c = + +== Description == + +The gp2c compiler is a package for translating GP routines into the C +programming language, so that they can be compiled and used with the PARI +system or the GP calculator. + +== License == + +GPL version 2+ + +== Upstream Contact == + * http://pari.math.u-bordeaux.fr/ + +== Dependencies == + * PARI + * Perl diff --git a/build/pkgs/gp2c/checksums.ini b/build/pkgs/gp2c/checksums.ini new file mode 100644 index 00000000000..0f114ad0f51 --- /dev/null +++ b/build/pkgs/gp2c/checksums.ini @@ -0,0 +1,4 @@ +tarball=gp2c-VERSION.tar.gz +sha1=5acb1a13e1ed8ee877ffb34baa3b817e720f3e50 +md5=cb263990e399153aca6a2540930b4600 +cksum=1931194041 diff --git a/build/pkgs/gp2c/dependencies b/build/pkgs/gp2c/dependencies new file mode 100644 index 00000000000..58cfd0bc484 --- /dev/null +++ b/build/pkgs/gp2c/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(PARI) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. diff --git a/build/pkgs/gp2c/package-version.txt b/build/pkgs/gp2c/package-version.txt new file mode 100644 index 00000000000..6eb15535643 --- /dev/null +++ b/build/pkgs/gp2c/package-version.txt @@ -0,0 +1 @@ +0.0.9pl3 diff --git a/build/pkgs/gp2c/patches/20150326.patch b/build/pkgs/gp2c/patches/20150326.patch new file mode 100644 index 00000000000..3f7651f8162 --- /dev/null +++ b/build/pkgs/gp2c/patches/20150326.patch @@ -0,0 +1,218 @@ +Various fixes between released version 0.0.9pl3 and gp2c git repo + +diff -ru gp2c-0.0.9pl3/gp2c gp2c/gp2c +--- gp2c-0.0.9pl3/gp2c 2011-10-02 22:28:23.000000000 +0200 ++++ gp2c/gp2c 2015-07-09 14:50:05.600098572 +0200 +@@ -14,4 +14,4 @@ + EOF + exit 1; + fi +-GP2C_FUNC_DSC=desc/func.dsc src/gp2c $* ++GP2C_FUNC_DSC=desc/func.dsc exec src/gp2c "$@" +diff -ru gp2c-0.0.9pl3/gp2c-dbg gp2c/gp2c-dbg +--- gp2c-0.0.9pl3/gp2c-dbg 2011-10-02 22:28:23.000000000 +0200 ++++ gp2c/gp2c-dbg 2015-07-09 14:50:05.601098584 +0200 +@@ -13,4 +13,4 @@ + EOF + exit 1; + fi +-GP2C=./gp2c scripts/gp2c-dbg $* ++GP2C=./gp2c exec scripts/gp2c-dbg "$@" +diff -ru gp2c-0.0.9pl3/gp2c-run gp2c/gp2c-run +--- gp2c-0.0.9pl3/gp2c-run 2011-10-02 22:28:23.000000000 +0200 ++++ gp2c/gp2c-run 2015-07-09 14:50:05.601098584 +0200 +@@ -13,4 +13,4 @@ + EOF + exit 1; + fi +-GP2C=./gp2c scripts/gp2c-run $* ++GP2C=./gp2c exec scripts/gp2c-run "$@" +diff -ru gp2c-0.0.9pl3/src/funcdesc.c gp2c/src/funcdesc.c +--- gp2c-0.0.9pl3/src/funcdesc.c 2014-11-17 14:30:55.000000000 +0100 ++++ gp2c/src/funcdesc.c 2015-07-09 14:50:05.603098609 +0200 +@@ -575,7 +575,8 @@ + gpfunc *gp = lfunc + findfunction(entryname(arg[0])); + if (gp->spec==GPuser && gp->user->wrapper>=0) + { +- if (funcmode(*gp)&(1<user->wrapper; ++ if ((funcmode(*gp)&(1<fout, nb-1,arg+1,FC_tovecprec,d->nerr); + else + { +diff -ru gp2c-0.0.9pl3/src/funcspec.c gp2c/src/funcspec.c +--- gp2c-0.0.9pl3/src/funcspec.c 2014-11-22 22:28:59.000000000 +0100 ++++ gp2c/src/funcspec.c 2015-07-09 14:50:05.604098621 +0200 +@@ -432,8 +432,11 @@ + arg[0] = newleaf(pred); + } + genblock(arg[0],n); +- arg[1]=geninsertvar(arg[1],ret); +- makesubblock(arg[1]); ++ if (arg[1]!=GNOARG) ++ { ++ arg[1]=geninsertvar(arg[1],ret); ++ makesubblock(arg[1]); ++ } + if(nb==3) + { + arg[2]=geninsertvar(arg[2],ret==-1?-1:newleaf(ret)); +diff -ru gp2c-0.0.9pl3/src/genfunc.c gp2c/src/genfunc.c +--- gp2c-0.0.9pl3/src/genfunc.c 2014-12-16 10:56:21.000000000 +0100 ++++ gp2c/src/genfunc.c 2015-07-09 14:50:05.605098633 +0200 +@@ -524,13 +524,15 @@ + } + if (name) + { +- if (i>=nb) +- die(err_func,"too few arguments in lambda"); + fputs(" ",fout); + if (c=='p') + fprintf(fout,"prec"); + else ++ { ++ if (i>=nb) ++ die(err_func,"too few arguments in lambda"); + gencode(fout, name[i++]); ++ } + } + } + if (name && i=nb) +- die(err_func,"too few arguments in lambda"); + if (c=='p') +- fprintf(fout,"prec"); ++ { ++ if (mode&(1<=nb) ++ die(err_func,"too few arguments in lambda"); ++ if (!firstarg) fprintf(fout,", "); ++ firstarg=0; ++ ot = tree[name[i]].t; ++ tree[name[i]].t = t; ++ gencast(fout, name[i], ot); ++ tree[name[i]].t = ot; ++ i++; ++ } + } + if (name && iuser->defnode; + int savc=s_ctx.n; +@@ -650,7 +667,6 @@ + int res; + ctxvar *vres; + context *fc=block+gp->user->bl; +- gpfunc *wr=lfunc+wrap; + gpdescarg *rule; + if (!wr->proto.code) + die(wr->node,"Wrapper not defined"); +@@ -688,7 +704,7 @@ + fprintf(fout," = "); + } + fprintf(fout,"%s(",gp->proto.cname); +- firstarg=genwrapargs(fout, wrap, nb, stack); ++ firstarg=genwrapargs(fout, wrap, nb, stack, m); + for (i=0,d=1;is.n;i++) + { + ctxvar *v=fc->c+i; +diff -ru gp2c-0.0.9pl3/src/printnode.c gp2c/src/printnode.c +--- gp2c-0.0.9pl3/src/printnode.c 2013-10-12 20:32:23.000000000 +0200 ++++ gp2c/src/printnode.c 2015-07-09 14:50:05.606098645 +0200 +@@ -231,6 +231,10 @@ + fprintf(fout,"&"); + printnode(fout,x); + break; ++ case Fvararg: ++ printnode(fout,x); ++ fprintf(fout,"[..]"); ++ break; + case Fcall: + printnode(fout,x); + fprintf(fout,"("); +diff -ru gp2c-0.0.9pl3/src/topfunc.c gp2c/src/topfunc.c +--- gp2c-0.0.9pl3/src/topfunc.c 2014-12-16 10:53:09.000000000 +0100 ++++ gp2c/src/topfunc.c 2015-07-09 14:50:05.606098645 +0200 +@@ -109,7 +109,7 @@ + int nn = newanon(); + int nf = newnode(Fdeffunc,newnode(Ffunction,nn,x),y); + int seq = newnode(Fentry,nn,-1); +- topfunc(n,p,fun,pfun,nf,wr); ++ topfunc(nf,p,fun,pfun,nf,wr); + if (fun>=0) tree[n] = tree[seq]; + } + +diff -ru gp2c-0.0.9pl3/test2/gp/apply.gp gp2c/test2/gp/apply.gp +--- gp2c-0.0.9pl3/test2/gp/apply.gp 2013-06-11 12:02:48.000000000 +0200 ++++ gp2c/test2/gp/apply.gp 2015-07-09 14:50:05.609098682 +0200 +@@ -3,6 +3,7 @@ + f3(f,v)=apply(f,v) + g(x)=x^2+1 + f4(v)=apply(g,v) ++f5(z:small)=apply(n:small->(n+z)!,[1,2,3,4,5]) + + g1(v,z)=select(x->x>z,v) + g2(v)=select(isprime,v) +diff -ru gp2c-0.0.9pl3/test2/input/apply gp2c/test2/input/apply +--- gp2c-0.0.9pl3/test2/input/apply 2013-06-11 12:02:48.000000000 +0200 ++++ gp2c/test2/input/apply 2015-07-09 14:50:05.609098682 +0200 +@@ -2,6 +2,7 @@ + f2([1,2,3,4]) + f3(sqr,[1,2,3,4]) + f4([1,2,3,4]) ++f5(-1) + + g1([1,2,3,4],2) + g2([1,2,3,4]) +diff -ru gp2c-0.0.9pl3/test2/res/apply.res gp2c/test2/res/apply.res +--- gp2c-0.0.9pl3/test2/res/apply.res 2013-06-11 12:02:48.000000000 +0200 ++++ gp2c/test2/res/apply.res 2015-07-09 14:50:05.610098694 +0200 +@@ -2,6 +2,7 @@ + [1, 4, 9, 16] + [1, 4, 9, 16] + [2, 5, 10, 17] ++[1, 1, 2, 6, 24] + [3, 4] + [2, 3] + [2, 3] diff --git a/build/pkgs/gp2c/patches/global.patch b/build/pkgs/gp2c/patches/global.patch new file mode 100644 index 00000000000..dd0c24d0472 --- /dev/null +++ b/build/pkgs/gp2c/patches/global.patch @@ -0,0 +1,126 @@ +Patch taken from upstream to fix global() + +diff --git a/src/genblock.c b/src/genblock.c +index a3582d2..e626e0f 100644 +--- a/src/genblock.c ++++ b/src/genblock.c +@@ -251,7 +251,11 @@ int genblockdeclaration(int args, int n, int flag, int type, int *seq) + /*Make sure GEN objects are gerepilable.*/ + val = newnode(Ftag, newnode(Ftag, newsmall(0), Ggen), tv); + if (decl==global) +- fillvar(var,flag,tv,val); ++ { ++ int f=fillvar(var,flag,tv,-1); ++ if (autogc) ++ affectval(f,val,seq); ++ } + else + pushvar(var,flag,tv,val); + break; +@@ -270,10 +274,12 @@ int genblockdeclaration(int args, int n, int flag, int type, int *seq) + val=newcall("_const_quote",newstringnode(entryname(stack[i]),-1)); + affectval(fillvar(stack[i],flag,mint,-1),val,seq); + } +- else if (autogc) ++ else + { ++ int f = fillvar(stack[i],flag,mint,-1); + /*Make sure (implicitly GEN) objects are gerepilable.*/ +- fillvar(stack[i],flag,mint, newsmall(0)); ++ if (autogc) ++ affectval(f, newsmall(0), seq); + } + break; + case function: +diff --git a/src/topfunc.c b/src/topfunc.c +index 872830e..bff787f 100644 +--- a/src/topfunc.c ++++ b/src/topfunc.c +@@ -154,13 +154,12 @@ static int topfuncproto(int n, int fun, int pfun, int nf) + break; + case 'I': + case 'E': +- if (wr>=-1) +- { +- case 'J': +- seq = newnode(Flambda,var,newleaf(a)); +- tree[a] = tree[seq]; +- topfunclambda(a, n, fun, pfun, wr); +- } ++ if (wr<-1) ++ break; ++ case 'J': /* Fall through */ ++ seq = newnode(Flambda,var,newleaf(a)); ++ tree[a] = tree[seq]; ++ topfunclambda(a, n, fun, pfun, wr); + break; + } + break; +diff --git a/src/toplevel.c b/src/toplevel.c +index f5999c4..23c92a8 100644 +--- a/src/toplevel.c ++++ b/src/toplevel.c +@@ -120,7 +120,7 @@ void gentoplevel(int n) + } + else + gentoplevel(x); +- if (y>=0 && tree[y].f!=Fdeffunc) ++ if (y>=0 && tree[y].f!=Fdeffunc && tree[y].f!=Fseq) + { + int dy = detag(y); + if (isfunc(dy,"global")) +diff --git a/test/gp/initfunc.gp b/test/gp/initfunc.gp +index 03c1dd3..ed0b87d 100644 +--- a/test/gp/initfunc.gp ++++ b/test/gp/initfunc.gp +@@ -1,4 +1,4 @@ +-global(y:var,n:small=0,m:small,k=2,L:vec,T2) ++global(y:var='y,n:small=0,m:small,k=2,L:vec,T2) + T=clone([4,3,2,2,1,1,1,1,0,0,0,0,0,0,0,0]) + T2=clone([4,3,2,2,1,1,1,1,0,0,0,0,0,0,0,0]) + L=[]~; +@@ -15,3 +15,10 @@ aff()=print(n," ",m); + M=[1,2;3,4] + print(x^4+k); + pow_M(k)=M^k ++global(x1:int,x2:int); ++global(y1,y2):int; ++global(z1,z2); ++f1(x)=my(z=x^2+1);if(z==1,x1=x,x2=x);z ++f2(x)=my(z=x^2+1);if(z==1,y1=x,y2=x);z ++f3(x)=my(z=x^2+1);if(z==1,z1=x,z2=x);z ++global(t1);t1=0 +diff --git a/test/input/initfunc b/test/input/initfunc +index 8506d08..46a126d 100644 +--- a/test/input/initfunc ++++ b/test/input/initfunc +@@ -2,3 +2,9 @@ init_initfunc(); print(pow_M(2)) + vector(100,i,mybfffo(i)) + vector(31,i,mybfffo(1<<(i-1))) + for(i=1,5,count();aff()) ++init_initfunc();f1(0) ++init_initfunc();f1(1) ++init_initfunc();f2(0) ++init_initfunc();f2(1) ++init_initfunc();f3(0) ++init_initfunc();f3(1) +diff --git a/test/res/initfunc.res b/test/res/initfunc.res +index 391d09d..a1e538c 100644 +--- a/test/res/initfunc.res ++++ b/test/res/initfunc.res +@@ -13,3 +13,15 @@ x^4 + 2 + 3 30 + 4 30 + 5 29 ++x^4 + 2 ++1 ++x^4 + 2 ++2 ++x^4 + 2 ++1 ++x^4 + 2 ++2 ++x^4 + 2 ++1 ++x^4 + 2 ++2 diff --git a/build/pkgs/gp2c/spkg-check b/build/pkgs/gp2c/spkg-check new file mode 100755 index 00000000000..9adcda86f90 --- /dev/null +++ b/build/pkgs/gp2c/spkg-check @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && $MAKE check diff --git a/build/pkgs/gp2c/spkg-install b/build/pkgs/gp2c/spkg-install new file mode 100755 index 00000000000..0346d362ada --- /dev/null +++ b/build/pkgs/gp2c/spkg-install @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +if [ -z "$SAGE_LOCAL" ]; then + echo "SAGE_LOCAL undefined ... exiting" + echo "Maybe run 'sage -sh'?" + exit 1 +fi + +cd src + +for patch in ../patches/*.patch; do + [ -r "$patch" ] || continue # Skip non-existing or non-readable patches + patch -p1 <"$patch" + if [ $? -ne 0 ]; then + echo >&2 "Error applying '$patch'" + exit 1 + fi +done + +set -e + +./configure --prefix="$SAGE_LOCAL" --with-paricfg="$SAGE_LOCAL/lib/pari/pari.cfg" + +$MAKE install diff --git a/build/pkgs/gp2c/type b/build/pkgs/gp2c/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/gp2c/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/graphs/dependencies b/build/pkgs/graphs/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/graphs/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/iconv/dependencies b/build/pkgs/iconv/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/iconv/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/lcalc/spkg-install b/build/pkgs/lcalc/spkg-install index d1ff0c5c5e4..50bd1f97752 100755 --- a/build/pkgs/lcalc/spkg-install +++ b/build/pkgs/lcalc/spkg-install @@ -78,4 +78,9 @@ success "$MAKE" echo "Now installing lcalc binary, library and header files..." rm -fr "$SAGE_LOCAL"/include/libLfunction $MAKE install + +if [ "$UNAME" = "Darwin" ]; then + install_name_tool -id ${SAGE_LOCAL}/lib/libLfunction.dylib "${SAGE_LOCAL}"/lib/libLfunction.dylib +fi + success "$MAKE install" diff --git a/build/pkgs/libogg/dependencies b/build/pkgs/libogg/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/libogg/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/lrcalc/dependencies b/build/pkgs/lrcalc/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/lrcalc/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/mpir/package-version.txt b/build/pkgs/mpir/package-version.txt index 24ba9a38de6..9c8d6791e82 100644 --- a/build/pkgs/mpir/package-version.txt +++ b/build/pkgs/mpir/package-version.txt @@ -1 +1 @@ -2.7.0 +2.7.0.p1 diff --git a/build/pkgs/mpir/patches/fix-19280.patch b/build/pkgs/mpir/patches/fix-19280.patch new file mode 100644 index 00000000000..ced078c66ee --- /dev/null +++ b/build/pkgs/mpir/patches/fix-19280.patch @@ -0,0 +1,22 @@ +Apply the fix proposed in http://trac.sagemath.org/ticket/18546#comment:23 +to the bug reported in http://trac.sagemath.org/ticket/19280 . + +diff -ru mpir-2.7.0/gmp-impl.h mpir-2.7.0-fixed/gmp-impl.h +Index: gmp-impl.h +=================================================================== +--- mpir-2.7.0/gmp-impl.h 2015-06-10 23:02:35.000000000 +0200 ++++ mpir-2.7.0-fixed/gmp-impl.h 2015-09-29 17:04:17.746919331 +0200 +@@ -2040,11 +2040,11 @@ + #endif + + #ifndef SB_DIVAPPR_Q_SMALL_THRESHOLD +-#define SB_DIVAPPR_Q_SMALL_THRESHOLD 11 ++#define SB_DIVAPPR_Q_SMALL_THRESHOLD 0 + #endif + + #ifndef SB_DIV_QR_SMALL_THRESHOLD +-#define SB_DIV_QR_SMALL_THRESHOLD 11 ++#define SB_DIV_QR_SMALL_THRESHOLD 0 + #endif + + #ifndef DC_DIVAPPR_Q_THRESHOLD diff --git a/build/pkgs/mpir/patches/test-19280.patch b/build/pkgs/mpir/patches/test-19280.patch new file mode 100644 index 00000000000..cef193c8bbf --- /dev/null +++ b/build/pkgs/mpir/patches/test-19280.patch @@ -0,0 +1,161 @@ +Include a test for the bug reported in http://trac.sagemath.org/ticket/19280 . +diff -ruN mpir-2.7.0/tests/mpz/Makefile.am mpir-2.7.0-patched/tests/mpz/Makefile.am +--- mpir-2.7.0/tests/mpz/Makefile.am 2015-06-09 19:18:27.000000000 +0200 ++++ mpir-2.7.0-patched/tests/mpz/Makefile.am 2015-09-24 18:39:33.093974089 +0200 +@@ -26,7 +26,7 @@ + AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/tests + LDADD = $(top_builddir)/tests/libtests.la $(top_builddir)/libmpir.la + +-check_PROGRAMS = bit convert dive dive_ui io logic reuse t-addsub t-aorsmul t-bin t-cdiv_ui t-cmp t-cmp_d t-cmp_si t-cong t-cong_2exp t-div_2exp t-divis t-divis_2exp t-export t-fac_ui t-fdiv t-fdiv_ui t-fib_ui t-fits t-gcd t-gcd_ui t-get_d t-get_d_2exp t-get_si t-get_sx t-get_ux t-hamdist t-import t-inp_str t-io_raw t-jac t-lcm t-likely_prime_p t-lucnum_ui t-mfac_uiui t-mul t-mul_i t-next_prime_candidate t-oddeven t-perfpow t-perfsqr t-popcount t-pow t-powm t-powm_ui t-pprime_p t-primorial_ui t-root t-scan t-set_d t-set_f t-set_si t-set_str t-set_sx t-set_ux t-sizeinbase t-sqrtrem t-tdiv t-tdiv_ui t-trial_division ++check_PROGRAMS = bit convert dive dive_ui io logic reuse t-addsub t-aorsmul t-bin t-cdiv_ui t-cmp t-cmp_d t-cmp_si t-cong t-cong_2exp t-div_2exp t-divis t-divis_2exp t-export t-fac_ui t-fdiv t-fdiv_ui t-fib_ui t-fits t-gcd t-gcd_ui t-get_d t-get_d_2exp t-get_si t-get_sx t-get_ux t-hamdist t-import t-inp_str t-io_raw t-jac t-lcm t-likely_prime_p t-lucnum_ui t-mfac_uiui t-mul t-mul_i t-next_prime_candidate t-oddeven t-perfpow t-perfsqr t-popcount t-pow t-powm t-powm_ui t-pprime_p t-primorial_ui t-root t-scan t-set_d t-set_f t-set_si t-set_str t-set_sx t-set_ux t-sizeinbase t-sqrtrem t-tdiv t-tdiv_ui t-trial_division t-19280 + + if ENABLE_STATIC + if ENABLE_SHARED +diff -ruN mpir-2.7.0/tests/mpz/Makefile.in mpir-2.7.0-patched/tests/mpz/Makefile.in +--- mpir-2.7.0/tests/mpz/Makefile.in 2015-06-16 12:40:00.000000000 +0200 ++++ mpir-2.7.0-patched/tests/mpz/Makefile.in 2015-09-24 18:42:37.485967609 +0200 +@@ -121,7 +121,7 @@ + t-set_si$(EXEEXT) t-set_str$(EXEEXT) t-set_sx$(EXEEXT) \ + t-set_ux$(EXEEXT) t-sizeinbase$(EXEEXT) t-sqrtrem$(EXEEXT) \ + t-tdiv$(EXEEXT) t-tdiv_ui$(EXEEXT) t-trial_division$(EXEEXT) \ +- $(am__EXEEXT_1) ++ t-19280$(EXEEXT) $(am__EXEEXT_1) + @ENABLE_SHARED_TRUE@@ENABLE_STATIC_TRUE@am__append_1 = st_hamdist st_popcount + subdir = tests/mpz + DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ +@@ -197,6 +197,11 @@ + st_popcount_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(st_popcount_LDFLAGS) $(LDFLAGS) -o $@ ++t_19280_SOURCES = t-19280.c ++t_19280_OBJECTS = t-19280.$(OBJEXT) ++t_19280_LDADD = $(LDADD) ++t_19280_DEPENDENCIES = $(top_builddir)/tests/libtests.la \ ++ $(top_builddir)/libmpir.la + t_addsub_SOURCES = t-addsub.c + t_addsub_OBJECTS = t-addsub.$(OBJEXT) + t_addsub_LDADD = $(LDADD) +@@ -526,21 +531,7 @@ + am__v_CCLD_0 = @echo " CCLD " $@; + am__v_CCLD_1 = + SOURCES = bit.c convert.c dive.c dive_ui.c io.c logic.c reuse.c \ +- $(st_hamdist_SOURCES) $(st_popcount_SOURCES) t-addsub.c \ +- t-aorsmul.c t-bin.c t-cdiv_ui.c t-cmp.c t-cmp_d.c t-cmp_si.c \ +- t-cong.c t-cong_2exp.c t-div_2exp.c t-divis.c t-divis_2exp.c \ +- t-export.c t-fac_ui.c t-fdiv.c t-fdiv_ui.c t-fib_ui.c t-fits.c \ +- t-gcd.c t-gcd_ui.c t-get_d.c t-get_d_2exp.c t-get_si.c \ +- t-get_sx.c t-get_ux.c t-hamdist.c t-import.c t-inp_str.c \ +- t-io_raw.c t-jac.c t-lcm.c t-likely_prime_p.c t-lucnum_ui.c \ +- t-mfac_uiui.c t-mul.c t-mul_i.c t-next_prime_candidate.c \ +- t-oddeven.c t-perfpow.c t-perfsqr.c t-popcount.c t-pow.c \ +- t-powm.c t-powm_ui.c t-pprime_p.c t-primorial_ui.c t-root.c \ +- t-scan.c t-set_d.c t-set_f.c t-set_si.c t-set_str.c t-set_sx.c \ +- t-set_ux.c t-sizeinbase.c t-sqrtrem.c t-tdiv.c t-tdiv_ui.c \ +- t-trial_division.c +-DIST_SOURCES = bit.c convert.c dive.c dive_ui.c io.c logic.c reuse.c \ +- $(am__st_hamdist_SOURCES_DIST) $(am__st_popcount_SOURCES_DIST) \ ++ $(st_hamdist_SOURCES) $(st_popcount_SOURCES) t-19280.c \ + t-addsub.c t-aorsmul.c t-bin.c t-cdiv_ui.c t-cmp.c t-cmp_d.c \ + t-cmp_si.c t-cong.c t-cong_2exp.c t-div_2exp.c t-divis.c \ + t-divis_2exp.c t-export.c t-fac_ui.c t-fdiv.c t-fdiv_ui.c \ +@@ -554,6 +545,21 @@ + t-set_f.c t-set_si.c t-set_str.c t-set_sx.c t-set_ux.c \ + t-sizeinbase.c t-sqrtrem.c t-tdiv.c t-tdiv_ui.c \ + t-trial_division.c ++DIST_SOURCES = bit.c convert.c dive.c dive_ui.c io.c logic.c reuse.c \ ++ $(am__st_hamdist_SOURCES_DIST) $(am__st_popcount_SOURCES_DIST) \ ++ t-19280.c t-addsub.c t-aorsmul.c t-bin.c t-cdiv_ui.c t-cmp.c \ ++ t-cmp_d.c t-cmp_si.c t-cong.c t-cong_2exp.c t-div_2exp.c \ ++ t-divis.c t-divis_2exp.c t-export.c t-fac_ui.c t-fdiv.c \ ++ t-fdiv_ui.c t-fib_ui.c t-fits.c t-gcd.c t-gcd_ui.c t-get_d.c \ ++ t-get_d_2exp.c t-get_si.c t-get_sx.c t-get_ux.c t-hamdist.c \ ++ t-import.c t-inp_str.c t-io_raw.c t-jac.c t-lcm.c \ ++ t-likely_prime_p.c t-lucnum_ui.c t-mfac_uiui.c t-mul.c \ ++ t-mul_i.c t-next_prime_candidate.c t-oddeven.c t-perfpow.c \ ++ t-perfsqr.c t-popcount.c t-pow.c t-powm.c t-powm_ui.c \ ++ t-pprime_p.c t-primorial_ui.c t-root.c t-scan.c t-set_d.c \ ++ t-set_f.c t-set_si.c t-set_str.c t-set_sx.c t-set_ux.c \ ++ t-sizeinbase.c t-sqrtrem.c t-tdiv.c t-tdiv_ui.c \ ++ t-trial_division.c + am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ +@@ -1020,6 +1026,10 @@ + @rm -f st_popcount$(EXEEXT) + $(AM_V_CCLD)$(st_popcount_LINK) $(st_popcount_OBJECTS) $(st_popcount_LDADD) $(LIBS) + ++t-19280$(EXEEXT): $(t_19280_OBJECTS) $(t_19280_DEPENDENCIES) $(EXTRA_t_19280_DEPENDENCIES) ++ @rm -f t-19280$(EXEEXT) ++ $(AM_V_CCLD)$(LINK) $(t_19280_OBJECTS) $(t_19280_LDADD) $(LIBS) ++ + t-addsub$(EXEEXT): $(t_addsub_OBJECTS) $(t_addsub_DEPENDENCIES) $(EXTRA_t_addsub_DEPENDENCIES) + @rm -f t-addsub$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(t_addsub_OBJECTS) $(t_addsub_LDADD) $(LIBS) +@@ -1931,6 +1941,13 @@ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ ++ "$$tst" $(AM_TESTS_FD_REDIRECT) ++t-19280.log: t-19280$(EXEEXT) ++ @p='t-19280$(EXEEXT)'; \ ++ b='t-19280'; \ ++ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ ++ --log-file $$b.log --trs-file $$b.trs \ ++ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) + st_hamdist.log: st_hamdist$(EXEEXT) + @p='st_hamdist$(EXEEXT)'; \ +diff -ruN mpir-2.7.0/tests/mpz/t-19280.c mpir-2.7.0-patched/tests/mpz/t-19280.c +--- mpir-2.7.0/tests/mpz/t-19280.c 1970-01-01 01:00:00.000000000 +0100 ++++ mpir-2.7.0-patched/tests/mpz/t-19280.c 2015-09-24 18:57:03.005937190 +0200 +@@ -0,0 +1,50 @@ ++/* Test t-19280. ++ ++*/ ++ ++#include ++#include ++ ++#include "mpir.h" ++#include "gmp-impl.h" ++#include "tests.h" ++ ++#define printf gmp_printf ++ ++/* Check mpz_tdif_q gives a correct answer on 32-bit, ++ see http://trac.sagemath.org/ticket/19280. ++ This was wrong in sage 6.9.beta7. */ ++static void ++check_19280 (void) ++{ ++ ++ mpz_t one, x, w, correct; ++ mpz_init(one); ++ mpz_init(x); ++ mpz_init(w); ++ mpz_init(correct); ++ mpz_set_str(one, "62165404551223330269422781018352605012557018849668464680057997111644937126566671941632", 10); ++ mpz_set_str(x, "39623752663112484341451587580", 10); ++ mpz_set_str(correct, "1568892403497558507879962225846103176600476845510570267609", 10); ++ ++ mpz_tdiv_q(w, one, x); ++ if (mpz_cmp(w, correct)!=0) { ++ printf("mpz_tdiv_q returned %Zd instead of %Zd.\n", w, correct); ++ abort(); ++ } ++ mpz_clear(one); ++ mpz_clear(x); ++ mpz_clear(w); ++ mpz_clear(correct); ++} ++ ++int ++main (void) ++{ ++ tests_start (); ++ ++ check_19280 (); ++ ++ tests_end (); ++ exit (0); ++} diff --git a/build/pkgs/nauty/dependencies b/build/pkgs/nauty/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/nauty/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/ncurses/dependencies b/build/pkgs/ncurses/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/ncurses/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/networkx/checksums.ini b/build/pkgs/networkx/checksums.ini index 50122f00a66..9aaf119a3b8 100644 --- a/build/pkgs/networkx/checksums.ini +++ b/build/pkgs/networkx/checksums.ini @@ -1,4 +1,4 @@ tarball=networkx-VERSION.tar.gz -sha1=d6c1524724d3e47f7621bb2072863463924bfb99 -md5=b4a9e68ecd1b0164446ee432d2e20bd0 -cksum=3256827710 +sha1=99292e464c25be5e96de295752880bf5e5f1848a +md5=eb7a065e37250a4cc009919dacfe7a9d +cksum=2520536431 diff --git a/build/pkgs/networkx/dependencies b/build/pkgs/networkx/dependencies index edf27112135..d4db0ff5eae 100644 --- a/build/pkgs/networkx/dependencies +++ b/build/pkgs/networkx/dependencies @@ -1,4 +1,4 @@ -$(INST)/$(PYTHON) +$(INST)/$(PYTHON) $(INST)/$(DECORATOR) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/networkx/package-version.txt b/build/pkgs/networkx/package-version.txt index a8fdfda1c78..c044b1a3269 100644 --- a/build/pkgs/networkx/package-version.txt +++ b/build/pkgs/networkx/package-version.txt @@ -1 +1 @@ -1.8.1 +1.10 diff --git a/build/pkgs/networkx/spkg-install b/build/pkgs/networkx/spkg-install index 6769f79adac..5cd75ee5791 100755 --- a/build/pkgs/networkx/spkg-install +++ b/build/pkgs/networkx/spkg-install @@ -15,4 +15,4 @@ rm -rf "$SAGE_LOCAL"/spkg/network* cd src -python setup.py install --home="$SAGE_LOCAL" --force +python setup.py install diff --git a/build/pkgs/openssl/dependencies b/build/pkgs/openssl/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/openssl/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/pari/checksums.ini b/build/pkgs/pari/checksums.ini index 8870cbb86ee..c62c530a888 100644 --- a/build/pkgs/pari/checksums.ini +++ b/build/pkgs/pari/checksums.ini @@ -1,4 +1,4 @@ tarball=pari-VERSION.tar.gz -sha1=307409c3917f6df71d2e10640c119e7d31c1f2e6 -md5=41936ce2dce6bd00a662bf43a772685f -cksum=855809013 +sha1=fa23e0c8b6e38a356048d19224dc9b9658d77724 +md5=c753faaa4780de5ad8d461f0ffd70ecf +cksum=1220765312 diff --git a/build/pkgs/pari/package-version.txt b/build/pkgs/pari/package-version.txt index 8db184f072c..2b25bd18c62 100644 --- a/build/pkgs/pari/package-version.txt +++ b/build/pkgs/pari/package-version.txt @@ -1 +1 @@ -2.8-1637-g489005a.p1 +2.8-1813-g6157df4.p0 diff --git a/build/pkgs/pari/patches/KERNELCFLAGS.patch b/build/pkgs/pari/patches/KERNELCFLAGS.patch deleted file mode 100644 index 537dbb52d1e..00000000000 --- a/build/pkgs/pari/patches/KERNELCFLAGS.patch +++ /dev/null @@ -1,16 +0,0 @@ -diff -ru src/config/get_cc b/config/get_cc ---- src/config/get_cc 2014-02-01 21:41:54.534348273 +0100 -+++ b/config/get_cc 2014-02-01 21:42:50.850930971 +0100 -@@ -94,7 +94,11 @@ - OPTFLAGS="$OPTFLAGS -fno-strict-aliasing" - fi - rm -f $exe $exe$exe_suff -- KERNELCFLAGS=-funroll-loops -+ if [ "$SAGE_DEBUG" = yes ]; then -+ KERNELCFLAGS=-O1 -+ else -+ KERNELCFLAGS=-funroll-loops -+ fi - - DBGFLAGS=${DBGFLAGS:-"-g $warn"} - # Specific optimisations for some architectures diff --git a/build/pkgs/pari/patches/README.txt b/build/pkgs/pari/patches/README.txt index 93ac577bd55..d9aef73659a 100644 --- a/build/pkgs/pari/patches/README.txt +++ b/build/pkgs/pari/patches/README.txt @@ -11,17 +11,7 @@ Patches to configuration files: Darwin. Submitted upstream, but upstream only applied it for PowerPC. Since this doesn't break anything and only improves performance, add the flag unconditionally. -* KERNELCFLAGS.patch (Jeroen Demeyer): when SAGE_DEBUG=yes, compile - kernel files with -O1 instead of -funroll-loops; -O0 gives a - segmentation fault on some OS X systems when doing - factor(10356613*10694706299664611221) - See #13921, also reported upstream: - - http://pari.math.u-bordeaux.fr/archives/pari-dev-1301/msg00000.html C files: -* det_garbage.patch (Jeroen Demeyer, #15654): When computing a - determinant(), only collect garbage once per outer loop iteration. - Better increase PARI stack size instead of collecting garbage too - often. * public_memory_functions.patch (Jeroen Demeyer, #16997): Make some of PARI's private memory functions public to improve interface with Sage. diff --git a/build/pkgs/pari/patches/det_garbage.patch b/build/pkgs/pari/patches/det_garbage.patch deleted file mode 100644 index ab7af6643af..00000000000 --- a/build/pkgs/pari/patches/det_garbage.patch +++ /dev/null @@ -1,55 +0,0 @@ -diff --git a/src/basemath/alglin1.c b/src/basemath/alglin1.c -index cada9eb..515eba9 100644 ---- a/src/basemath/alglin1.c -+++ b/src/basemath/alglin1.c -@@ -248,6 +248,7 @@ gen_det(GEN a, void *E, const struct bb_field *ff) - a = RgM_shallowcopy(a); - for (i=1; ired(E,gcoeff(a,k,i)); -@@ -272,7 +273,7 @@ gen_det(GEN a, void *E, const struct bb_field *ff) - for (j=i+1; j<=nbco; j++) - { - gcoeff(a,j,k) = ff->add(E, gcoeff(a,j,k), ff->mul(E,m,gcoeff(a,j,i))); -- if (gc_needed(av,1)) -+ if (gc_needed(av,1) && (garbage++ == 0)) - { - if(DEBUGMEM>1) pari_warn(warnmem,"det. col = %ld",i); - gerepileall(av,4, &a,&x,&q,&m); -@@ -3722,6 +3723,7 @@ det_simple_gauss(GEN a, GEN data, pivot_fun pivot) - a = RgM_shallowcopy(a); - for (i=1; i nbco) return gerepilecopy(av, gcoeff(a,i,i)); - if (k != i) -@@ -3741,7 +3743,7 @@ det_simple_gauss(GEN a, GEN data, pivot_fun pivot) - for (j=i+1; j<=nbco; j++) - { - gcoeff(a,j,k) = gsub(gcoeff(a,j,k), gmul(m,gcoeff(a,j,i))); -- if (gc_needed(av,3)) -+ if (gc_needed(av,3) && (garbage++ == 0)) - { - if(DEBUGMEM>1) pari_warn(warnmem,"det. col = %ld",i); - gerepileall(av,2, &a,&x); -@@ -3792,6 +3794,7 @@ det_bareiss(GEN a) - { - GEN ci, ck, m; - int diveuc = (gequal1(pprec)==0); -+ int garbage = 0; /* Only gerepile() once per loop iteration */ - - p = gcoeff(a,i,i); - if (gequal0(p)) -@@ -3828,7 +3831,7 @@ det_bareiss(GEN a) - GEN p1 = gsub(gmul(p,gel(ck,j)), gmul(m,gel(ci,j))); - if (diveuc) p1 = mydiv(p1,pprec); - gel(ck,j) = gerepileupto(av2, p1); -- if (gc_needed(av,2)) -+ if (gc_needed(av,2) && (garbage++ == 0)) - { - if(DEBUGMEM>1) pari_warn(warnmem,"det. col = %ld",i); - gerepileall(av,2, &a,&pprec); diff --git a/build/pkgs/pari/patches/perl_regex.patch b/build/pkgs/pari/patches/perl_regex.patch deleted file mode 100644 index 038f4d604e8..00000000000 --- a/build/pkgs/pari/patches/perl_regex.patch +++ /dev/null @@ -1,200 +0,0 @@ -commit 257750686ae1fe928a2b4b489844c1b57a108bd3 -Author: Karim Belabas -Date: Tue Jul 14 15:23:42 2015 +0200 - - doc_make: escape all {} in regexps [ perl-5.22 warns on these => fatal - -diff --git a/src/desc/doc_make b/src/desc/doc_make -index bb41bc9..8521a9d 100755 ---- a/src/desc/doc_make -+++ b/src/desc/doc_make -@@ -38,13 +38,13 @@ while () - $v =~ s/\[\]/[\\,]/g; - $v =~ s/(\w\w+)/\\var{$1}/g; - $v =~ s/\^([a-z])/\\hbox{\\kbd{\\pow}}$1/g; -- $v =~ s/\\var{flag}/\\fl/g; -- $v =~ s/\\var{(\d+)}/{$1}/g; -+ $v =~ s/\\var\{flag\}/\\fl/g; -+ $v =~ s/\\var\{(\d+)\}/{$1}/g; - $v =~ s/_/\\_/g; # don't merge with first subst: \var{} rule kills it - - $v = "\$($v)\$"; - } -- if ($doc !~ /\\syn\w*{/ && $sec !~ /programming\/control/) { -+ if ($doc !~ /\\syn\w*\{/ && $sec !~ /programming\/control/) { - $doc .= library_syntax($fun, $args); - } - s/_def_//; -commit 742c70e505a7e75128720f619d63e882c03e9346 -Author: Karim Belabas -Date: Tue Jul 14 13:20:07 2015 +0200 - - gphelp: escape all {} in regexps [ perl-5.22 warns on these => fatal ] - -diff --git a/doc/gphelp.in b/doc/gphelp.in -index 00ff6bd..89f2768 100755 ---- a/doc/gphelp.in -+++ b/doc/gphelp.in -@@ -298,7 +298,7 @@ sub treat { - if ($pat =~ /[a-zA-Z0-9]$/) { $pat .= '\b'; } else { $pat .= '}'; } - while () - { -- if (/\\(subsubsec[a-z]*|subsec[a-z]*|section|chapter|label){$pat/) -+ if (/\\(subsubsec[a-z]*|subsec[a-z]*|section|chapter|label)\{$pat/) - { $first = $_; last; } - } - if (eof(DOC)) -@@ -380,7 +380,7 @@ sub apropos_check { - return if ($line !~ /$help/i); - - local($_) = $current; -- s/\\b{(.)}/\\$1/; -+ s/\\b\{(.)\}/\\$1/; - s/\{\}//g; - s/\\pow/^/; - s/\\%/%/; -@@ -400,7 +400,7 @@ sub apropos { - @sentence_list = @list = ""; - while () - { -- if (/^\\(subsubsec[a-z]*|subsec[a-z]*|section|chapter){/) -+ if (/^\\(subsubsec[a-z]*|subsec[a-z]*|section|chapter)\{/) - { - $new = &get_match($_,'{','}'); - &apropos_check($line, $current); -@@ -748,7 +748,7 @@ sub basic_subst { - s/\\fun\s*\{([^{}]*)\}\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*\{((?:[^{}]|\{[^{}]*\})*)\}/\\kbd{$1 \\key{$2}($3)}\\sidx{$2}/g; - - s/\\\\(?=[a-zA-Z])/\\bs /g; -- s/\\b{}\\b{}/\\bs\\bs /g; -+ s/\\b\{\}\\b\{\}/\\bs\\bs /g; - s/\\\\/\\bs/g; - s/(\'\'|\`\`)/"/g unless $to_pod; # (english) double quotes - # asymptotic or isomorphic (~) [beware of ties] -@@ -760,16 +760,16 @@ sub basic_subst { - s/\\(~|tilde)/~/g; - - s/\\(equiv)(?![a-zA-Z])/ = /g; -- s/\\`a/$tr{agrave}/; s/\\`{a}/$tr{agrave}/; -- s/\\"o/$tr{ouml}/; s/\\"{o}/$tr{ouml}/; -- s/\\"u/$tr{uuml}/; s/\\"{u}/$tr{uuml}/; -- s/\\'e/$tr{eacute}/; s/\\'{e}/$tr{eacute}/; -+ s/\\`a/$tr{agrave}/; s/\\`\{a\}/$tr{agrave}/; -+ s/\\"o/$tr{ouml}/; s/\\"\{o\}/$tr{ouml}/; -+ s/\\"u/$tr{uuml}/; s/\\"\{u\}/$tr{uuml}/; -+ s/\\'e/$tr{eacute}/; s/\\'\{e\}/$tr{eacute}/; - - s/(^|[^\\])%.*/$1/g; # comments - s/\\vadjust\s*\{\s*\\penalty\s*\d+\s*\}//g; - - # We do not strip %\n, thus: -- s/\\kbd{\n\s*/\\kbd{/g; -+ s/\\kbd\{\n\s*/\\kbd{/g; - s/\$\\bf(\b|(?=[\d_]))\s*([^\$]+)\$/\$$tr{startbcode}$1$tr{endbcode}\$/g; - s/\$/$tr{dollar}/g; # math mode - s/\t/ /g; s/\\,//g; s/\\[ ;]/ /g; # various spaces -@@ -779,7 +779,7 @@ sub basic_subst { - s/\\TeX\{\}/TeX/g; - s/\\TeX(\W)/TeX$1/g; - s/ *\\circ\b */ o /g; -- s/\\d?frac{\s*((?:[^{}]|\{[^{}]*\})*)}{\s*((?:[^{}]|\{[^{}]*\})*)}/($1)\/($2)/g; -+ s/\\d?frac\{\s*((?:[^{}]|\{[^{}]*\})*)\}\{\s*((?:[^{}]|\{[^{}]*\})*)\}/($1)\/($2)/g; - s(\\d?frac\s*(\d)\s*(\d))(($1/$2))g; - s[{\s*(\w)\s*\\over(?![a-zA-Z])\s*(\w)\s*}]{($1/$2)}g; - s[{\s*((?:[^{}]|\{[^{}]*\})*)\\over(?![a-zA-Z])\s*((?:[^{}]|\{[^{}]*\})*)}][($1)/($2)]g; -@@ -796,7 +796,7 @@ sub basic_subst { - - s/(\\string)?\\_/_/g; - s/\\([#\$&%|])/$1/g; -- s/\\(hat(?![a-zA-Z])|\^)({\\?\s*})?/^/g; -+ s/\\(hat(?![a-zA-Z])|\^)(\{\\?\s*\})?/^/g; - s/^(\@\[podleader\]head\d *)\\pow(?![a-zA-z])( *)/$1^$2/gm; - s/ *\\pow(?![a-zA-z]) */^/g; - -@@ -896,21 +896,21 @@ sub basic_subst { - s/\\(floor|ceil|round|binom)\{/$1\{/g; - s/\\(var|emph)\{([^\}]*)\}/$tr{startit}$2$tr{endit}/g; - s/\\fl(?![a-zA-Z])/$tr{startit}flag$tr{endit}/g; -- s/\\b{([^}]*)}/$tr{startcode}\\$1$tr{endcode}/g; -+ s/\\b\{([^}]*)\}/$tr{startcode}\\$1$tr{endcode}/g; - s/\\kbdsidx/\\sidx/g; - s/\\sidx\{[^\}]*\}//g unless $to_pod; - s/\\[a-zA-Z]*idx\{([^\}]*)\}/$1/g unless $to_pod; -- s/{\\text{(st|nd|th)}}/\\text{$1}/g; -- s/\^\\text{th}/-th/g; -- s/1\^\\text{st}/1st/g; -- s/2\^\\text{nd}/2nd/g; -+ s/\{\\text\{(st|nd|th)\}\}/\\text{$1}/g; -+ s/\^\\text\{th\}/-th/g; -+ s/1\^\\text\{st\}/1st/g; -+ s/2\^\\text\{nd\}/2nd/g; - - s/\\(text|hbox|Big)//g; - s/^([ \t]+)\{ *\\(it|sl|bf|tt)\b/S<$1>{\\$2/gm; - s/\{ *\\(it|sl) *(([^{}]+(?=[{}])|\{[^{}]*\})*)\}/$tr{startit}$2$tr{endit}/g; - s/\{ *\\bf *(([^{}]+(?=[{}])|\{[^{}]*\})*)\}/$tr{startbold}$1$tr{endbold}/g; - s/\{ *\\tt *(([^{}]+(?=[{}])|\{[^{}]*\})*)\}/$tr{startpodcode}$1$tr{endpodcode}/g; -- $seek=1 if (s/\\emph{ */$tr{startit}/g); -+ $seek=1 if (s/\\emph\{ */$tr{startit}/g); - if ($seek) { $seek=0 if (s/\}/$tr{endit}/) } - s/\\(backslash|bs)\{(\w)\}/\\$2/g; - s/\\(backslash|bs)(?![a-zA-Z]) */\\/g; -@@ -1028,21 +1028,21 @@ sub TeXprint_topod { - # Try to guard \label/\sidx (removing possible '.') - # This somehow breaks index... - # s/(\\(?:section|subsec(?:ref|idx|op)?(unix)?)\s*{(?:(?:[^{}]+(?=[{}])|{[^{}]+})+)})\.?\s*\\(label|sidx)/$1\n\\$2/; -- s/(\\(?:section|subsec(?:ref|idx|op)?)\s*{(?:(?:[^{}]+(?=[{}])|{[^{}]+})+)})\.?\s*\\(label|sidx)/$1\n\\$2/; -+ s/(\\(?:section|subsec(?:ref|idx|op)?)\s*\{(?:(?:[^{}]+(?=[{}])|{[^{}]+})+)\})\.?\s*\\(label|sidx)/$1\n\\$2/; - - # last if /\\subsec[\\{}ref]*[\\\${]$help[}\\\$]/o; -- s/\\chapter\s*{((?:[^{}]|\{[^{}]*\})*)}\s*/\n\n$tr{podleader}head1 NAME\n\nlibPARI - $1\n\n/; -- s/\\appendix\s*{((?:[^{}]|\{[^{}]*\})*)}\s*/\n\n$tr{podleader}head1 NAME\n\nAppendix - $1\n\n/; -- s/\\section\s*{((?:[^{}]|\{[^{}]*\})*)}\s*/"\n\n$tr{podleader}head1 " . indexify($1) . "\n\n"/e; -+ s/\\chapter\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*/\n\n$tr{podleader}head1 NAME\n\nlibPARI - $1\n\n/; -+ s/\\appendix\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*/\n\n$tr{podleader}head1 NAME\n\nAppendix - $1\n\n/; -+ s/\\section\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*/"\n\n$tr{podleader}head1 " . indexify($1) . "\n\n"/e; - - # Try to delimit by : -- s/\\subsec(?:ref)?(?:unix)?\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}([^\n]*):[\n ]/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; -- s/\\subsubsec(?:ref)?(?:unix)?\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}([^:]*):\s*/"\n\n$tr{podleader}head3 " . indexify("$1$3") . "\n\n"/e; -+ s/\\subsec(?:ref)?(?:unix)?\s*\{(([^{}]+(?=[{}])|{[^{}]+})+)\}([^\n]*):[\n ]/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; -+ s/\\subsubsec(?:ref)?(?:unix)?\s*\{(([^{}]+(?=[{}])|{[^{}]+})+)\}([^:]*):\s*/"\n\n$tr{podleader}head3 " . indexify("$1$3") . "\n\n"/e; - s/\\subsubsec\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}(.*)$/"\n\n$tr{podleader}head3 " . indexify("$1") . "$3\n\n"/me; - s/\\subseckbd\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}([^:]*):\s*/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; - # Try to delimit by ' ' -- s/\\subsec(?:ref)?(?:unix)?\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}(\S*)\s+/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; -- s/\\subsec(?:title)?(?:unix)?\s*{(([^{}]+(?=[{}])|{[^{}]*})+)}:?\s*/"\n\n$tr{podleader}head2 " . indexify("$1") . "\n\n"/e; -+ s/\\subsec(?:ref)?(?:unix)?\s*\{(([^{}]+(?=[{}])|{[^{}]+})+)\}(\S*)\s+/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; -+ s/\\subsec(?:title)?(?:unix)?\s*\{(([^{}]+(?=[{}])|{[^{}]*})+)\}:?\s*/"\n\n$tr{podleader}head2 " . indexify("$1") . "\n\n"/e; - - # This is to skip preface in refcard: - /\Q$tr{podleader}\Ehead1|\\title(?![a-zA-Z])\s*\{/o and $seen_start = 1 -@@ -1097,18 +1097,18 @@ sub TeXprint_topod { - s/\$\$(.*?)\$\$\s*/\n\nS< >$tr{startcode}$1$tr{endcode}\n\n/gs; - s/\$([^\$]+)\$/$tr{startcode}$1$tr{endcode}/g; - -- s/\\s(?:ref|idx){\s*([^{}]*)}/"X<" . for_index($1) . ">"/ge; # -- s/\\(?:ref|idx){\s*([^{}]*)}/"X<" . for_index($1) . ">$1"/ge; -+ s/\\s(?:ref|idx)\{\s*([^{}]*)\}/"X<" . for_index($1) . ">"/ge; # -+ s/\\(?:ref|idx)\{\s*([^{}]*)\}/"X<" . for_index($1) . ">$1"/ge; - - # Conflict between different versions of PARI and refcard: --# s/\\(?:key|li)\s*{(.*)}\s*{(.+)}[ \t]*\n/\n\n=item C<$2>\n\n$1\n\n/msg; --# s/\\(?:key|li)\s*{(.*)}\s*{}[ \t]*\n/\n\n=back\n\n$1\n\n=over\n\n/mgs; --# s/\\(key|var)(?![a-zA-Z])\s*{(\w+)}/C<$2>/mg; -- s/\\var\s*{X<(\w+)>(\w+)}/X<$1>$tr{startcode}$2$tr{endcode}/mg; -- s/\\var\s*{f{}lag}/$tr{startcode}flag$tr{endcode}/mg; -- -- s/\\metax(?![a-zA-Z])\s*{(.*)}\s*{\s*(\w+)(?=C\<)(.*)}[ \t]*\n/\n\n=item C$3>\n\n$1\n\n/mg; -- s/\\metax(?![a-zA-Z])\s*{(.*)}\s*{(.*)}[ \t]*\n/\n\n=item C<$2>\n\n$1\n\n/mg; -+# s/\\(?:key|li)\s*\{(.*)\}\s*\{(.+)\}[ \t]*\n/\n\n=item C<$2>\n\n$1\n\n/msg; -+# s/\\(?:key|li)\s*\{(.*)\}\s*\{\}[ \t]*\n/\n\n=back\n\n$1\n\n=over\n\n/mgs; -+# s/\\(key|var)(?![a-zA-Z])\s*\{(\w+)\}/C<$2>/mg; -+ s/\\var\s*\{X<(\w+)>(\w+)\}/X<$1>$tr{startcode}$2$tr{endcode}/mg; -+ s/\\var\s*\{f\{\}lag\}/$tr{startcode}flag$tr{endcode}/mg; -+ -+ s/\\metax(?![a-zA-Z])\s*\{(.*)\}\s*\{\s*(\w+)(?=C\<)(.*)\}[ \t]*\n/\n\n=item C$3>\n\n$1\n\n/mg; -+ s/\\metax(?![a-zA-Z])\s*\{(.*)\}\s*\{(.*)\}[ \t]*\n/\n\n=item C<$2>\n\n$1\n\n/mg; - s/C\<\{\}=/C\<=/g; - s/\\fl(?![a-zA-Z])/I/g; - s/\\file(?![a-zA-Z])/F/g; diff --git a/build/pkgs/pari/spkg-install b/build/pkgs/pari/spkg-install index 6a7b1ebc9d3..55a64aec9a8 100755 --- a/build/pkgs/pari/spkg-install +++ b/build/pkgs/pari/spkg-install @@ -206,7 +206,14 @@ set_environment # Set CFLAGS if [ "$SAGE_DEBUG" = yes ]; then # Disable optimisation, add debug symbols. - CFLAGS="$CFLAGS -O0 -g" + CFLAGS="-O0 -g $CFLAGS" + + # Compile kernel files with -O1 instead of -funroll-loops; -O0 gives + # a segmentation fault on some OS X systems when doing + # factor(10356613*10694706299664611221) + # See #13921, also reported upstream: + # - http://pari.math.u-bordeaux.fr/archives/pari-dev-1301/msg00000.html + PARI_MAKEFLAGS="KERNELCFLAGS=-O1 $PARI_MAKEFLAGS" else # Use PARI's default CFLAGS (with -g added). # PARI's Configure adds -O3 to the CFLAGS, so we don't need to add diff --git a/build/pkgs/pari_galdata/dependencies b/build/pkgs/pari_galdata/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/pari_galdata/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/pari_jupyter/SPKG.txt b/build/pkgs/pari_jupyter/SPKG.txt new file mode 100644 index 00000000000..681cdc57f23 --- /dev/null +++ b/build/pkgs/pari_jupyter/SPKG.txt @@ -0,0 +1,22 @@ += pari_jupyter = + +== Description == + +A Jupyter kernel for PARI/GP + +== License == + +GPL version 3 or later + +== Upstream Contact == + +* https://github.com/jdemeyer/pari_jupyter +* Jeroen Demeyer + +== Dependencies == + +* Jupyter 4 +* Python (tested with 2.7.9) +* Cython (git master) +* PARI (git master) +* GMP or MPIR (any version which works with PARI) diff --git a/build/pkgs/pari_jupyter/checksums.ini b/build/pkgs/pari_jupyter/checksums.ini new file mode 100644 index 00000000000..d3b36f6aebb --- /dev/null +++ b/build/pkgs/pari_jupyter/checksums.ini @@ -0,0 +1,4 @@ +tarball=pari_jupyter-VERSION.tar.gz +sha1=404df06171e68056d9efe8a29804204138488885 +md5=902b290a997128e6be949c0bec44ca6e +cksum=3922118226 diff --git a/build/pkgs/pari_jupyter/dependencies b/build/pkgs/pari_jupyter/dependencies new file mode 100644 index 00000000000..5cf512f3f56 --- /dev/null +++ b/build/pkgs/pari_jupyter/dependencies @@ -0,0 +1 @@ +$(INST)/$(PARI) $(INST)/$(JUPYTER_CORE) | $(INST)/$(CYTHON) diff --git a/build/pkgs/pari_jupyter/package-version.txt b/build/pkgs/pari_jupyter/package-version.txt new file mode 100644 index 00000000000..3eefcb9dd5b --- /dev/null +++ b/build/pkgs/pari_jupyter/package-version.txt @@ -0,0 +1 @@ +1.0.0 diff --git a/build/pkgs/pari_jupyter/spkg-install b/build/pkgs/pari_jupyter/spkg-install new file mode 100755 index 00000000000..94ac1fed30e --- /dev/null +++ b/build/pkgs/pari_jupyter/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && ./setup.py install diff --git a/build/pkgs/pari_jupyter/type b/build/pkgs/pari_jupyter/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/pari_jupyter/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/pari_seadata_small/dependencies b/build/pkgs/pari_seadata_small/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/pari_seadata_small/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/patch/dependencies b/build/pkgs/patch/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/patch/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/patchbot/SPKG.txt b/build/pkgs/patchbot/SPKG.txt new file mode 100644 index 00000000000..c34994b3818 --- /dev/null +++ b/build/pkgs/patchbot/SPKG.txt @@ -0,0 +1,26 @@ += SageMath patchbot = + +== Description == + +Apply branches and run tests on open Sage tickets. + +The patchbot is used to automate the testing of git branches. It has two +different aspects: a server side and a client side. + +Instructions for using the client side can be found at + +http://wiki.sagemath.org/buildbot/details + +== License == + +GPLv2+ + +== Upstream Contact == + +Robert Bradshaw +Frédéric Chapoton +https://github.com/robertwb/sage-patchbot/ + +== Dependencies == + +python, python-dateutil, sage-scripts diff --git a/build/pkgs/patchbot/checksums.ini b/build/pkgs/patchbot/checksums.ini new file mode 100644 index 00000000000..4a35077fa89 --- /dev/null +++ b/build/pkgs/patchbot/checksums.ini @@ -0,0 +1,4 @@ +tarball=patchbot-VERSION.tar.bz2 +sha1=1850ce7004fe49b669be0b53102d32e9095cc307 +md5=a84f244c2f6e6c715676a09028750b36 +cksum=1356602931 diff --git a/build/pkgs/patchbot/dependencies b/build/pkgs/patchbot/dependencies new file mode 100644 index 00000000000..4e1e0144211 --- /dev/null +++ b/build/pkgs/patchbot/dependencies @@ -0,0 +1 @@ +# No dependencies diff --git a/build/pkgs/patchbot/package-version.txt b/build/pkgs/patchbot/package-version.txt new file mode 100644 index 00000000000..f225a78adf0 --- /dev/null +++ b/build/pkgs/patchbot/package-version.txt @@ -0,0 +1 @@ +2.5.2 diff --git a/build/pkgs/patchbot/spkg-install b/build/pkgs/patchbot/spkg-install new file mode 100755 index 00000000000..5dcc96f4629 --- /dev/null +++ b/build/pkgs/patchbot/spkg-install @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +if [ "$SAGE_LOCAL" = "" ]; then + echo "SAGE_LOCAL undefined ... exiting"; + echo "Maybe run 'sage -sh'?" + exit 1 +fi + +# Delete any currently existing patchbot +rm -rf "$SAGE_LOCAL/bin/patchbot" + +# Copy into final location. +# The sage-patchbot script knows how to call this... +cp -Rv src "$SAGE_LOCAL/bin/patchbot" diff --git a/build/pkgs/patchbot/type b/build/pkgs/patchbot/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/patchbot/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/planarity/dependencies b/build/pkgs/planarity/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/planarity/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/plantri/dependencies b/build/pkgs/plantri/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/plantri/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/polytopes_db/dependencies b/build/pkgs/polytopes_db/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/polytopes_db/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/qepcad/spkg-install b/build/pkgs/qepcad/spkg-install index 1f7566b703c..6da4a409be9 100755 --- a/build/pkgs/qepcad/spkg-install +++ b/build/pkgs/qepcad/spkg-install @@ -33,7 +33,7 @@ saclib="$SAGE_LOCAL/lib/saclib" export saclib qe=$(pwd -P) export qe -$MAKE opt +$MAKE -j1 opt if [ $? -ne 0 ]; then echo >&2 "Error building qepcad." diff --git a/build/pkgs/r/spkg-install b/build/pkgs/r/spkg-install index 0e5f4f2c2a6..679d39e1b82 100755 --- a/build/pkgs/r/spkg-install +++ b/build/pkgs/r/spkg-install @@ -82,15 +82,14 @@ fi if [ "$UNAME" = "Darwin" ]; then # We don't want to install R as a library framework on OSX R_CONFIGURE="--enable-R-framework=no $R_CONFIGURE" + # OS X 10.10 and/or Xcode 6.3 and over broke the R installation. See + # http://trac.sagemath.org/ticket/18254. + if [ $MACOSX_VERSION -ge 14 ]; then + echo "OS X 10.$[$MACOSX_VERSION-4] Configuring R without aqua support." + R_CONFIGURE="--with-aqua=no $R_CONFIGURE" + fi fi -# OS X 10.10 and/or Xcode 6.3 broke the R installation. See -# http://trac.sagemath.org/ticket/18254. -if { uname -sr | grep 'Darwin 14\.' ;} &>/dev/null; then - echo "OS X 10.10: Configuring R without aqua support." - R_CONFIGURE="--with-aqua=no $R_CONFIGURE" -fi - if [ "$UNAME" = "CYGWIN" ]; then # Cygwin libm does not provide "long double" functions # and we do not install Cephes on Cygwin at the moment @@ -126,6 +125,12 @@ for patch in ../patches/*.patch; do fi done +if [ "$UNAME" = "Darwin" ]; then + # Fixing install_name(s) + sed -i -e 's:\"-install_name :\"-install_name ${libdir}/R/lib/:' configure + sed -i -e "/SHLIB_EXT/s/\.so/.dylib/" configure +fi + # Don't override R_HOME_DIR in local/bin/R while building R. # See patches/R.sh.patch export SAGE_BUILDING_R=yes diff --git a/build/pkgs/rubiks/dependencies b/build/pkgs/rubiks/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/rubiks/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/rw/dependencies b/build/pkgs/rw/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/rw/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/saclib/dependencies b/build/pkgs/saclib/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/saclib/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/singular/spkg-install b/build/pkgs/singular/spkg-install index d8ff399a465..4d7f0c318be 100755 --- a/build/pkgs/singular/spkg-install +++ b/build/pkgs/singular/spkg-install @@ -218,6 +218,11 @@ build_libsingular() return 1 fi + if [ "$UNAME" = "Darwin" ]; then + # on darwin we need to adjust the install name of the library + install_name_tool -id "${SAGE_LOCAL}"/lib/libsingular.dylib "${SAGE_LOCAL}"/lib/libsingular.dylib + fi + # singular does not install this header cp Singular/sing_dbm.h $SAGE_LOCAL/include/singular/ diff --git a/build/pkgs/symmetrica/dependencies b/build/pkgs/symmetrica/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/symmetrica/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/sympow/dependencies b/build/pkgs/sympow/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/sympow/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/termcap/dependencies b/build/pkgs/termcap/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/termcap/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/valgrind/dependencies b/build/pkgs/valgrind/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/valgrind/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/zeromq/dependencies b/build/pkgs/zeromq/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/zeromq/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/zlib/dependencies b/build/pkgs/zlib/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/zlib/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +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/zn_poly/spkg-install b/build/pkgs/zn_poly/spkg-install index 00067eab437..25161e48a1c 100755 --- a/build/pkgs/zn_poly/spkg-install +++ b/build/pkgs/zn_poly/spkg-install @@ -192,6 +192,7 @@ else echo >&2 "Error copying 'libzn_poly.dylib'." exit 1 fi + install_name_tool -id ${SAGE_LOCAL}/lib/libzn_poly.dylib "${SAGE_LOCAL}"/lib/libzn_poly.dylib fi ############################################################################### diff --git a/build/sage_bootstrap/cmdline.py b/build/sage_bootstrap/cmdline.py index c1dfe89c8d8..c08d958e4c4 100644 --- a/build/sage_bootstrap/cmdline.py +++ b/build/sage_bootstrap/cmdline.py @@ -167,11 +167,13 @@ class SageDownloadFileApplication(object): def run(self): progress = True url = None + print_fastest_mirror = None destination = None for arg in sys.argv[1:]: if arg.startswith('--print-fastest-mirror'): - print(MirrorList().fastest) - sys.exit(0) + url = "" + print_fastest_mirror = True + continue if arg.startswith('--quiet'): progress = False continue @@ -184,13 +186,31 @@ def run(self): raise ValueError('too many arguments') if url is None: print(dedent(self.__doc__.format(SAGE_DISTFILES=SAGE_DISTFILES))) - sys.exit(1) - if url.startswith('http://') or url.startswith('https://') or url.startswith('ftp://'): - Download(url, destination, progress=progress, ignore_errors=True).run() - else: - # url is a tarball name - tarball = Tarball(url) - tarball.download() - if destination is not None: - tarball.save_as(destination) - + sys.exit(2) + + try: + if url.startswith('http://') or url.startswith('https://') or url.startswith('ftp://'): + Download(url, destination, progress=progress, ignore_errors=True).run() + elif print_fastest_mirror: + url = "fastest mirror" # For error message + print(MirrorList().fastest) + else: + # url is a tarball name + tarball = Tarball(url) + tarball.download() + if destination is not None: + tarball.save_as(destination) + except BaseException: + try: + stars = '*' * 72 + '\n' + sys.stderr.write(stars) + try: + import traceback + traceback.print_exc(file=sys.stderr) + sys.stderr.write(stars) + except: + pass + sys.stderr.write("Error downloading %s\n"%(url,)) + sys.stderr.write(stars) + finally: + sys.exit(1) diff --git a/configure.ac b/configure.ac index 5e320f74a41..dec7721c73f 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,14 @@ -dnl Very loosely based on configure.ac in prereq-0.3 written by William Stein. -dnl Version 0.7 written by David Kirkby, released under the GPL version 2. -dnl in January 2010 +#***************************************************************************** +# Copyright (C) 2005-2007 William Stein +# Copyright (C) 2009-2011 David Kirkby +# Copyright (C) 2012-2015 Jeroen Demeyer +# +# 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/ +#***************************************************************************** dnl If you are going to update this, please stick the recommended layout dnl in the autoconf manual - i.e. @@ -14,9 +22,8 @@ dnl Next check compiler characteristics dnl Next check for library functions dnl Next check for system services -dnl Older versions give "undefined macro: _m4_divert_diversion", see -dnl http://trac.sagemath.org/ticket/15606#comment:19 -AC_PREREQ([2.64]) +dnl Older versions do not support $GFC +AC_PREREQ([2.69]) AC_DEFUN([SAGE_VERSION], m4_esyscmd_s([. src/bin/sage-version.sh && echo $SAGE_VERSION])) AC_INIT([Sage], SAGE_VERSION, [sage-devel@googlegroups.com]) @@ -80,8 +87,6 @@ SAGE_SHARE="$SAGE_LOCAL/share" SAGE_EXTCODE="$SAGE_SHARE/sage/ext" SAGE_SPKG_INST="$SAGE_LOCAL/var/lib/sage/installed" -export PATH="$SAGE_ROOT/build/bin:$SAGE_SRC/bin:$SAGE_LOCAL/bin:$PATH" - ############################################################################### # Determine whether to use MPIR (default standard pkg) or GMP (optional pkg). ############################################################################### @@ -115,171 +120,583 @@ else ;; esac fi +] + + +#--------------------------------------------------------- +AX_CHECK_ROOT([AC_MSG_ERROR([You cannot build Sage as root, switch to an unpriviledged user])], []) + +# Check whether we are on a supported platform +AC_CANONICAL_BUILD() +AC_CANONICAL_HOST() + +case $host in +*-*-sunos*|*-*-solaris2.[1-9]) +AC_MSG_ERROR([[ +Sage is not supported on any version of Solaris earlier than 10. +Sage has been tested on the first release of Solaris 10 +(03/2005) and works on that. Sage may or may not work with +your version of Solaris. + +More information can be found about Sage on Solaris +on the Wiki at http://wiki.sagemath.org/solaris]]);; + +*-*-darwin[1-7].*) +AC_MSG_ERROR([[ +Sage has never been built on OS X 10.3 (Panther) +or earlier. The oldest version of OS X successfully used +is OS X version 10.4 (Tiger). You might consider updating +your version of OS X if your hardware permits this, but +Apple charges for upgrades of OS X]]);; + +*-*-hpux*) +AC_MSG_ERROR([[ +You are attempting to build Sage on HP's HP-UX operating system, +which is not a supported platform for Sage yet though +some work has been done on HP-UX. A port does not look to +be particularly difficult. Some information can be +found on the Sage Wiki at http://wiki.sagemath.org/HP-UX + +If you would like to help port Sage to HP-UX, +please join the sage-devel discussion list - see +http://groups.google.com/group/sage-devel +The Sage community would also appreciate any patches you submit]]);; + +*-*-aix*) +AC_MSG_ERROR([[ +You are attempting to build Sage on IBM's AIX operating system, +which is not a supported platform for Sage yet. Things may or +may not work. If you would like to help port Sage to AIX, +please join the sage-devel discussion list - see +http://groups.google.com/group/sage-devel +The Sage community would also appreciate any patches you submit]]);; + +*-*-irix*) +AC_MSG_ERROR([[ +You are attempting to build Sage on SGI's IRIX operating system, +which is not a supported platform for Sage yet. Things may or +may not work. If you would like to help port Sage to IRIX, +please join the sage-devel discussion list - see +http://groups.google.com/group/sage-devel +The Sage community would also appreciate any patches you submit]]);; + +*-*-osf*) +AC_MSG_ERROR([[ +You are attempting to build Sage on HP's Tru64 operating system, +which is not a supported platform for Sage yet. Things may or +may not work. If you would like to help port Sage to Tru64, +please join the sage-devel discussion list - see +http://groups.google.com/group/sage-devel +The Sage community would also appreciate any patches you submit]]);; + +*-*-freebsd*) +AC_MSG_ERROR([[ +You are attempting to build Sage on the FreeBSD operating system, +which is not a supported platform for Sage yet, though +developers are working on adding FreeBSD support. Things may or +may not work. If you would like to help port Sage to FreeBSD, +please join the sage-devel discussion list - see +http://groups.google.com/group/sage-devel +The Sage community would also appreciate any patches you submit]]);; + +# The following are all supported platforms. +*-*-linux*);; +*-*-darwin*);; +*-*-solaris*);; +*-*-cygwin*);; + +# Wildcard for other unsupported platforms +*) +AC_MSG_ERROR([[ +You are attempting to build Sage on $host, +which is not a supported platform for Sage yet. Things may or +may not work. If you would like to help port Sage to $host, +please join the sage-devel discussion list - see +http://groups.google.com/group/sage-devel +The Sage community would also appreciate any patches you submit]]);; +esac + ############################################################################### -# Determine whether to install GCC (gcc, g++, gfortran). +# Check general programs ############################################################################### -# Determine various compilers. These variables should not be exported, -# they are only used in this build/make/install script to determine whether to -# install GCC. The "real" $CC, $CXX,... variables for building Sage are -# set in sage-env. +AC_CHECK_PROG(found_ar, ar, yes, no) +if test x$found_ar != xyes +then + AC_MSG_NOTICE([Sorry, the 'ar' command must be in the path to build AC_PACKAGE_NAME]) + AC_MSG_NOTICE([On some systems it can be found in /usr/ccs/bin ]) + AC_MSG_NOTICE(['ar' is also part of the GNU 'binutils' package.]) + AC_MSG_ERROR([Exiting, as the archiver 'ar' can not be found.]) +fi + +AC_CHECK_PROG(found_m4, m4, yes, no) +if test x$found_m4 != xyes +then + AC_MSG_NOTICE([Sorry, the 'm4' command must be in the path to build AC_PACKAGE_NAME]) + AC_MSG_NOTICE([On some systems it can be found in /usr/ccs/bin]) + AC_MSG_NOTICE([See also http://www.gnu.org/software/m4/]) + AC_MSG_ERROR([Exiting, as the macro processor 'm4' can not be found.]) +fi + +AC_CHECK_PROG(found_ranlib, ranlib, yes, no) +if test x$found_ranlib != xyes +then + AC_MSG_NOTICE([Sorry, the 'ranlib' command must be in the path to build AC_PACKAGE_NAME]) + AC_MSG_NOTICE([On some systems it can be found in /usr/ccs/bin ]) + AC_MSG_NOTICE(['ranlib' is also part of the GNU 'binutils' package.]) + AC_MSG_ERROR([Exiting, as 'ranlib' can not be found.]) +fi -if [ -z "$CXX" ]; then - CXX=g++ +AC_CHECK_PROG(found_strip, strip, yes, no) +if test x$found_strip != xyes +then + AC_MSG_NOTICE([Sorry, the 'strip' command must be in the path to build AC_PACKAGE_NAME]) + AC_MSG_NOTICE([On some systems 'strip' can be found in /usr/ccs/bin ]) + AC_MSG_NOTICE(['strip' is also part of the GNU 'binutils' package.]) + AC_MSG_ERROR([Exiting, as 'strip' can not be found.]) fi -if [ -z "$CC" ]; then - if command -v gcc >/dev/null 2>/dev/null; then - CC=gcc +# Check tar +AC_CACHE_CHECK([for GNU or BSD tar], [ac_cv_path_TAR], [ +AC_PATH_PROGS_FEATURE_CHECK(TAR, [tar gtar], [[ +ac_version_TAR=`$ac_path_TAR --version 2>&1` +if echo "$ac_version_TAR" | grep >/dev/null GNU; then + ac_cv_path_TAR=$ac_path_TAR + if test $ac_prog = tar; then + ac_path_TAR_found=: + fi +fi +if echo "$ac_version_TAR" | grep >/dev/null bsdtar; then + ac_cv_path_TAR=$ac_path_TAR + if test $ac_prog = tar; then + ac_path_TAR_found=: fi fi +]], +[AC_MSG_ERROR([could not find either a GNU or BSD version of tar])], +[$PATH:/usr/sfw/bin]) +]) + +command_TAR=`command -v tar 2>/dev/null` +AS_IF([test x$command_TAR != x$ac_cv_path_TAR], + [AC_MSG_ERROR([[found a good version of tar in $ac_cv_path_TAR, but it's not the first "tar" program in your PATH]])] +) -if [ -z "$FC" ]; then - if command -v gfortran >/dev/null 2>/dev/null; then - FC=gfortran - elif command -v g95 >/dev/null 2>/dev/null; then - FC=g95 - elif command -v g77 >/dev/null 2>/dev/null; then - FC=g77 +# Check make (unless MAKE is set) +if test -z "$MAKE"; then + AC_CACHE_CHECK([for GNU make], [ac_cv_path_MAKE], [ + AC_PATH_PROGS_FEATURE_CHECK(MAKE, [make gmake], [[ + ac_version_MAKE=`$ac_path_MAKE --version 2>&1` + if echo "$ac_version_MAKE" | grep >/dev/null GNU; then + ac_cv_path_MAKE=$ac_path_MAKE + if test $ac_prog = make; then + ac_path_MAKE_found=: + fi fi + ]], + [AC_MSG_ERROR([could not find a GNU version of make])], + [$PATH:/usr/sfw/bin]) + ]) + + command_MAKE=`command -v make 2>/dev/null` + AS_IF([test x$command_MAKE != x$ac_cv_path_MAKE], + [AC_MSG_ERROR([[found GNU make in $ac_cv_path_MAKE, but it's not the first "make" program in your PATH]])]) +fi + +# Check for Latex, the use of which is less important in Sage than +# it used to be, as it was at one time required to build any documentation +# but this is no longer so. +AC_CHECK_PROG(found_latex, latex, yes, no) +if test x$found_latex != xyes +then + AC_MSG_WARN([You do not have 'latex', which is recommended, but not]) + AC_MSG_WARN([required. Latex is only really used for building pdf]) + AC_MSG_WARN([documents and for %latex mode in the AC_PACKAGE_NAME notebook.]) fi -if [ -f "$SAGE_LOCAL/bin/gcc" ]; then - # Ignore SAGE_INSTALL_GCC if GCC is already installed - SAGE_INSTALL_GCC="" +# Check that perl is available, with version 5.8.0 or later. +# Some packages need perl, however it is not clear whether Sage really +# requires version >= 5.8.0. The R package *used* to require it, but +# not anymore. -- Jeroen Demeyer +AC_PATH_PROG([PERL],[perl]) +AX_PROG_PERL_VERSION([5.8.0],[],[ + AC_MSG_ERROR([Exiting, since AC_PACKAGE_NAME requires perl-5.8.0 or later]) +]) + +# To build Python on multi-arch Debian-based systems, we need +# dpkg-architecture. Since we need dpkg-architecture to determine +# whether we're on a multi-arch system and require dpkg-architecture, +# we simply require it always on Debian-based systems. +AC_CHECK_PROG(found_dpkg, dpkg, yes, no) +AC_CHECK_PROG(found_dpkg_arch, dpkg-architecture, yes, no) +if test x$found_dpkg = xyes && test x$found_dpkg_arch = xno +then + AC_MSG_NOTICE([You do not have 'dpkg-architecture', which is required to build]) + AC_MSG_NOTICE([Python on multi-arch Debian-based systems. This includes all recent]) + AC_MSG_NOTICE([Debian and Ubuntu systems. You can install this with:]) + AC_MSG_NOTICE([ sudo apt-get install dpkg-dev]) + AC_MSG_ERROR([Exiting, since AC_PACKAGE_NAME requires dpkg-architecture on Debian]) fi -if [ -n "$SAGE_INSTALL_GCC" ]; then + +############################################################################### +# Check C/C++/Fortran compilers +############################################################################### + +dnl Usage: SAGE_SHOULD_INSTALL_GCC(reason) +dnl +dnl Use this macro to indicate that we SHOULD install GCC. +dnl In this case, GCC will be installed unless SAGE_INSTALL_GCC=no. +dnl In the latter case, a warning is given. +AC_DEFUN([SAGE_SHOULD_INSTALL_GCC], [ + if test x$SAGE_INSTALL_GCC = xexists; then + true # Do nothing if already installed + elif test x$SAGE_INSTALL_GCC = xno; then + AC_MSG_WARN([$1]) + else + AC_MSG_NOTICE([Installing GCC because $1]) + need_to_install_gcc=yes + fi +]) + +dnl Usage: SAGE_MUST_INSTALL_GCC(reason) +dnl +dnl Use this macro to indicate that we MUST install GCC. +dnl In this case, it is an error if SAGE_INSTALL_GCC=no. +AC_DEFUN([SAGE_MUST_INSTALL_GCC], [ + if test x$SAGE_INSTALL_GCC = xexists; then + true # Do nothing if already installed + elif test x$SAGE_INSTALL_GCC = xno; then + AC_MSG_ERROR([SAGE_INSTALL_GCC is set to 'no', but $1]) + else + AC_MSG_NOTICE([Installing GCC because $1]) + need_to_install_gcc=yes + fi +]) + + +# By default, do not install GCC +need_to_install_gcc=no + +if test -f "$SAGE_LOCAL/bin/gcc"; then + # Special value for SAGE_INSTALL_GCC if GCC is already installed + SAGE_INSTALL_GCC=exists +elif test -n "$SAGE_INSTALL_GCC"; then # Check the value of the environment variable SAGE_INSTALL_GCC case "$SAGE_INSTALL_GCC" in yes) - echo >&2 "Installing GCC because SAGE_INSTALL_GCC is set to 'yes'." - need_to_install_gcc=yes;; + SAGE_MUST_INSTALL_GCC([SAGE_INSTALL_GCC is set to 'yes']);; no) - need_to_install_gcc=no;; + true;; *) - echo >&2 "Error: SAGE_INSTALL_GCC should be set to 'yes' or 'no'." - echo >&2 "You can also leave it unset to install GCC when needed." - exit 2;; + AC_MSG_ERROR([SAGE_INSTALL_GCC should be set to 'yes' or 'no'. You can also leave it unset to install GCC when needed]);; esac -else - # SAGE_INSTALL_GCC is not set, install GCC when needed. - need_to_install_gcc=no - - # Check whether $CXX is some version of GCC. If it's a different - # compiler, install GCC. - CXXtype=`source sage-env; testcxx.sh $CXX` - if [ "$CXXtype" != GCC ]; then - echo >&2 "Installing GCC because your '$CXX' isn't GCC (GNU C++)." - need_to_install_gcc=yes - else - # $CXX points to some version of GCC, find out which version. - GCCVERSION=`$CXX -dumpversion` - # Add the .0 because Debian/Ubuntu gives version numbers like - # 4.6 instead of 4.6.4 (Trac #18885) - case "$GCCVERSION.0" in - [0-3].*|4.[0-3].*) - # Install our own GCC if the system-provided one is older than gcc-4.4. - # * gcc-4.2.4 compiles a slow IML: - # https://groups.google.com/forum/?fromgroups#!topic/sage-devel/Ux3t0dW2FSI - # * gcc-4.3 might have trouble building ATLAS: - # https://groups.google.com/forum/?fromgroups#!topic/sage-devel/KCeFqQ_w2FE - echo >&2 "Installing GCC because you have $CXX version $GCCVERSION, which is quite old." - need_to_install_gcc=yes;; - 4.4.*|4.5.*) - # GCC 4.4.x and GCC 4.5.x fail to compile PARI/GP on ia64: - # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46044 - if [ x`uname -m` = xia64 ]; then - echo >&2 "Installing GCC because you have $CXX version $GCCVERSION on ia64." - echo >&2 "gcc <= 4.5 fails to compile PARI/GP on ia64." - need_to_install_gcc=yes - fi;; - 4.6.*) - # Also install GCC if we have version 4.6.* which is - # known to give trouble within Sage: - # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48702 - # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48774 - # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52061 - # * https://groups.google.com/d/msg/sage-release/xgmJ3nAcUOY/jH8OZjftYRsJ - echo >&2 "Installing GCC because you have $CXX version $GCCVERSION." - echo >&2 "gcc-4.6.* has known bugs affecting Sage." - need_to_install_gcc=yes;; - 4.7.0) - # GCC 4.7.0 is very broken on ia64, see - # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48496 - # This is fixed in GCC 4.7.1. - if [ x`uname -m` = xia64 ]; then - echo >&2 "Installing GCC because you have $CXX version $GCCVERSION on ia64." - echo >&2 "gcc-4.7.0 has a serious bug on ia64." - need_to_install_gcc=yes - fi;; - esac - fi +fi - # Check C, C++ and Fortran compilers. - if [ -z "$CC" ]; then - echo >&2 "Installing GCC because a C compiler is missing." - need_to_install_gcc=yes - fi - if [ -z "$FC" ]; then - echo >&2 "Installing GCC because a Fortran compiler is missing." - need_to_install_gcc=yes - fi -fi +AC_LANG(C) +AC_PROG_CC() +AC_PROG_CPP() -# If we are not installing GCC: check that the assembler and linker -# used by $CXX match $AS and $LD. -# See http://trac.sagemath.org/sage_trac/ticket/14296 -if [ $need_to_install_gcc != yes ]; then - if [ "$AS" != "" ]; then - CXX_as=`$CXX -print-file-name=as 2>/dev/null` - CXX_as=`command -v $CXX_as 2>/dev/null` - cmd_AS=`command -v $AS` - - if [ "$CXX_as" != "" -a "$CXX_as" != "$cmd_AS" ]; then - echo >&2 "Error: Mismatch of assemblers between" - echo >&2 " * $CXX using $CXX_as" - echo >&2 " * \$AS equal to $AS" - if [ "$SAGE_PORT" = "" ]; then - echo >&2 "Aborting, either change or unset AS or set SAGE_PORT=yes or set" - echo >&2 "SAGE_INSTALL_GCC=yes (this GCC would use assembler $AS)" - exit 1 - else - echo >&2 "Continuing since SAGE_PORT is set." - fi - fi - fi - if [ "$LD" != "" ]; then - CXX_ld=`$CXX -print-file-name=ld 2>/dev/null` - CXX_ld=`command -v $CXX_ld 2>/dev/null` - cmd_LD=`command -v $LD` - - if [ "$CXX_ld" != "" -a "$CXX_ld" != "$cmd_LD" ]; then - echo >&2 "Error: Mismatch of linkers between" - echo >&2 " * $CXX using $CXX_ld" - echo >&2 " * \$LD equal to $LD" - if [ "$SAGE_PORT" = "" ]; then - echo >&2 "Aborting, either change or unset LD or set SAGE_PORT=yes or set" - echo >&2 "SAGE_INSTALL_GCC=yes (this GCC would use linker $LD)" - exit 1 - else - echo >&2 "Continuing since SAGE_PORT is set." - fi - fi - fi +AC_LANG(C++) +AC_PROG_CXX() + +AC_LANG(Fortran) +AC_PROG_FC() + +if test "x$CXX" = x +then + AC_MSG_ERROR([a C++ compiler is missing]) fi ############################################################################### -# Create $SAGE_ROOT/build/make/Makefile starting from build/make/deps +# Check header files ############################################################################### -# Use file descriptor 7 since make uses 3 and 4 and configure uses 5 and 6 -exec 7>build/make/Makefile +# complex.h is one that might not exist on older systems. +AC_LANG(C++) +AC_CHECK_HEADER([complex.h],[],[ + AC_MSG_ERROR([Exiting, since you do not have the 'complex.h' header file.]) +]) -cat >&7 </dev/null` + CXX_as=`command -v $CXX_as 2>/dev/null` + cmd_AS=`command -v $AS` + + if test "$CXX_as" != "" -a "$CXX_as" != "$cmd_AS"; then + SAGE_SHOULD_INSTALL_GCC([there is a mismatch of assemblers]) + AC_MSG_NOTICE([ $CXX uses $CXX_as]) + AC_MSG_NOTICE([ \$AS equal to $AS]) + fi +fi +if test -n "$LD"; then + CXX_ld=`$CXX -print-file-name=ld 2>/dev/null` + CXX_ld=`command -v $CXX_ld 2>/dev/null` + cmd_LD=`command -v $LD` + + if test "$CXX_ld" != "" -a "$CXX_ld" != "$cmd_LD"; then + SAGE_SHOULD_INSTALL_GCC([there is a mismatch of linkers]) + AC_MSG_NOTICE([ $CXX uses $CXX_ld]) + AC_MSG_NOTICE([ \$LD equal to $LD]) + fi +fi + + +############################################################################### +# Check libraries +############################################################################### + +# First check for something that should be in any maths library (sqrt). +AC_LANG(C++) +AC_CHECK_LIB(m,sqrt,[],[ + AC_MSG_NOTICE([This system has no maths library installed.]) + # On AIX this is not installed by default - strange as that might seem. + # but is in a fileset bos.adt.libm. However, the fileset bos.adt + # includes other things that are probably useful. + if test "x`uname`" = 'xAIX' + then + AC_MSG_NOTICE([On AIX, libm is contained in the bos.adt.libm fileset.]) + AC_MSG_NOTICE([Actually, we recommend to install the complete bos.adt fileset.]) + AC_MSG_NOTICE([This needs to be performed by a system administrator.]) + fi + AC_MSG_ERROR([Exiting, since a maths library was not found.]) + ]) + +# Check for system services + +# Check that we are not building in a directory containing spaces +AS_IF([echo "$ac_pwd" |grep " " >/dev/null], + AC_MSG_ERROR([the path to the Sage root directory ($ac_pwd) contains a space. Sage will not build correctly in this case]) +) + + +if test x`uname` = xDarwin; then +[ + # Warning: xcodebuild does not seem to be maintained in Xcode 4.3 + # or later, so do not rely on the variable XCODE_VERS with OS X + # 10.7 or later. + XCODE_VERS=`xcodebuild -version 2> /dev/null | grep Xcode | sed -e 's/[A-Za-z ]//g'` + if [ -z $XCODE_VERS ]; then + XCODE_VERS="2" + fi + XCODE_VERS_MAJOR=`echo $XCODE_VERS | cut '-d.' -f1` + DARWIN_VERSION=`uname -r | cut '-d.' -f1` + echo "***************************************************" + echo "***************************************************" + if [ $DARWIN_VERSION -gt 10 ]; then + echo "You are using OS X Lion (or later)." + echo "You are strongly advised to install Apple's latest Xcode" + echo "unless you already have it. You can install this using" + echo "the App Store. Also, make sure you install Xcode's" + echo "Command Line Tools -- see Sage's README.txt." + elif [ $XCODE_VERS_MAJOR -gt 2 ]; then + echo "You are using Xcode version $XCODE_VERS." + echo "You are strongly advised to install Apple's latest Xcode" + echo "unless you already have it. You can download this from" + echo "http://developer.apple.com/downloads/." + echo "If using Xcode 4.3 or later, make sure you install Xcode's" + echo "Command Line Tools -- see Sage's README.txt." + else + echo "You are using Xcode version 1 or 2" + echo "WARNING: You are strongly advised to install the" + echo "latest version of Apple's Xcode for your platform," + echo "unless you already have it." + if [ $DARWIN_VERSION -eq 10 ]; then + echo "Probably you need Xcode 3.2.6" + elif [ $DARWIN_VERSION -eq 9 ]; then + echo "Probably you need Xcode 3.1.4" + elif [ $DARWIN_VERSION -lt 9 ]; then + echo "Probably you need Xcode 2.5" + fi + fi +] + +########################################################################### +# (OS X only) +# Sage will probably not build at all if either Fink or MacPorts can be +# found, and the error messages can be extremely confusing. Even if it does +# build, the product will probably be wrong. This runs a basic check to +# find them. Once the Sage build process is perfected, this won't be necessary. +# dphilp 15/9/2008 +########################################################################### + PORTS_PATH=`which port` + if test -f "$PORTS_PATH"; then +AC_MSG_ERROR([["found MacPorts in $PORTS_PATH. Either: +(1) rename /opt/local and /sw, or +(2) change PATH and DYLD_LIBRARY_PATH +(Once Sage is built, you can restore them.)]]) + fi + + FINK_PATH=`which fink` + if test -f "$FINK_PATH"; then +AC_MSG_ERROR([["found Fink in $FINK_PATH. Either: +(1) rename /opt/local and /sw, or +(2) change PATH and DYLD_LIBRARY_PATH +(Once Sage is built, you can restore them.)]]) + fi +fi + + +############################################################################### +# Create $SAGE_ROOT/build/make/Makefile starting from build/make/deps +############################################################################### + +# Use file descriptor 7 since make uses 3 and 4 and configure uses 5 and 6 +exec 7>build/make/Makefile + +cat >&7 <&7 "SHELL = `command -v bash`" echo >&7 -] AC_MSG_CHECKING([package versions]) AC_MSG_RESULT([]) @@ -511,573 +927,6 @@ done exec 7>&- ] -#--------------------------------------------------------- -AX_CHECK_ROOT([AC_MSG_ERROR([You cannot build Sage as root, switch to an unpriviledged user])], []) - -# Check whether we are on a supported platform -AC_CANONICAL_BUILD() -AC_CANONICAL_HOST() - -case $host in -*-*-sunos*|*-*-solaris2.[1-9]) -AC_MSG_ERROR([[ -Sage is not supported on any version of Solaris earlier than 10. -Sage has been tested on the first release of Solaris 10 -(03/2005) and works on that. Sage may or may not work with -your version of Solaris. - -More information can be found about Sage on Solaris -on the Wiki at http://wiki.sagemath.org/solaris]]);; - -*-*-darwin[1-7].*) -AC_MSG_ERROR([[ -Sage has never been built on OS X 10.3 (Panther) -or earlier. The oldest version of OS X successfully used -is OS X version 10.4 (Tiger). You might consider updating -your version of OS X if your hardware permits this, but -Apple charges for upgrades of OS X]]);; - -*-*-hpux*) -AC_MSG_ERROR([[ -You are attempting to build Sage on HP's HP-UX operating system, -which is not a supported platform for Sage yet though -some work has been done on HP-UX. A port does not look to -be particularly difficult. Some information can be -found on the Sage Wiki at http://wiki.sagemath.org/HP-UX - -If you would like to help port Sage to HP-UX, -please join the sage-devel discussion list - see -http://groups.google.com/group/sage-devel -The Sage community would also appreciate any patches you submit]]);; - -*-*-aix*) -AC_MSG_ERROR([[ -You are attempting to build Sage on IBM's AIX operating system, -which is not a supported platform for Sage yet. Things may or -may not work. If you would like to help port Sage to AIX, -please join the sage-devel discussion list - see -http://groups.google.com/group/sage-devel -The Sage community would also appreciate any patches you submit]]);; - -*-*-irix*) -AC_MSG_ERROR([[ -You are attempting to build Sage on SGI's IRIX operating system, -which is not a supported platform for Sage yet. Things may or -may not work. If you would like to help port Sage to IRIX, -please join the sage-devel discussion list - see -http://groups.google.com/group/sage-devel -The Sage community would also appreciate any patches you submit]]);; - -*-*-osf*) -AC_MSG_ERROR([[ -You are attempting to build Sage on HP's Tru64 operating system, -which is not a supported platform for Sage yet. Things may or -may not work. If you would like to help port Sage to Tru64, -please join the sage-devel discussion list - see -http://groups.google.com/group/sage-devel -The Sage community would also appreciate any patches you submit]]);; - -*-*-freebsd*) -AC_MSG_ERROR([[ -You are attempting to build Sage on the FreeBSD operating system, -which is not a supported platform for Sage yet, though -developers are working on adding FreeBSD support. Things may or -may not work. If you would like to help port Sage to FreeBSD, -please join the sage-devel discussion list - see -http://groups.google.com/group/sage-devel -The Sage community would also appreciate any patches you submit]]);; - -# The following are all supported platforms. -*-*-linux*);; -*-*-darwin*);; -*-*-solaris*);; -*-*-cygwin*);; - -# Wildcard for other unsupported platforms -*) -AC_MSG_ERROR([[ -You are attempting to build Sage on $host, -which is not a supported platform for Sage yet. Things may or -may not work. If you would like to help port Sage to $host, -please join the sage-devel discussion list - see -http://groups.google.com/group/sage-devel -The Sage community would also appreciate any patches you submit]]);; -esac - - -dnl Check compiler versions -buggy_gcc_version1="4.0.0" -minimum_gcc_version_for_no_hassle="4.0.1" -minimum_gcc_version_for_debugging_purposes="3.4.0" - -#--------------------------------------------------------- -# Process options and environment variables - -# Check --enable-compiler-checks (default depends on whether we are -# installing GCC) -AC_ARG_ENABLE([compiler-checks], [Check versions and presence of C, C++ and Fortran compilers (default: yes unless installing GCC)], - [enable_compiler_checks=$enableval], - [# Default - if test "$need_to_install_gcc" = yes; then - enable_compiler_checks=no; - else - enable_compiler_checks=yes; - fi - ] - ) - -# Import environment variables. -source src/bin/sage-env || AC_MSG_ERROR([failed to source sage-env]) - -#--------------------------------------------------------- -# Check some programs needed actually exist. -AC_CHECK_PROG(found_ar, ar, yes, no) -if test x$found_ar != xyes -then - AC_MSG_NOTICE([Sorry, the 'ar' command must be in the path to build AC_PACKAGE_NAME]) - AC_MSG_NOTICE([On some systems it can be found in /usr/ccs/bin ]) - AC_MSG_NOTICE(['ar' is also part of the GNU 'binutils' package.]) - AC_MSG_ERROR([Exiting, as the archiver 'ar' can not be found.]) -fi - -AC_CHECK_PROG(found_m4, m4, yes, no) -if test x$found_m4 != xyes -then - AC_MSG_NOTICE([Sorry, the 'm4' command must be in the path to build AC_PACKAGE_NAME]) - AC_MSG_NOTICE([On some systems it can be found in /usr/ccs/bin]) - AC_MSG_NOTICE([See also http://www.gnu.org/software/m4/]) - AC_MSG_ERROR([Exiting, as the macro processor 'm4' can not be found.]) -fi - -AC_CHECK_PROG(found_ranlib, ranlib, yes, no) -if test x$found_ranlib != xyes -then - AC_MSG_NOTICE([Sorry, the 'ranlib' command must be in the path to build AC_PACKAGE_NAME]) - AC_MSG_NOTICE([On some systems it can be found in /usr/ccs/bin ]) - AC_MSG_NOTICE(['ranlib' is also part of the GNU 'binutils' package.]) - AC_MSG_ERROR([Exiting, as 'ranlib' can not be found.]) -fi - -AC_CHECK_PROG(found_strip, strip, yes, no) -if test x$found_strip != xyes -then - AC_MSG_NOTICE([Sorry, the 'strip' command must be in the path to build AC_PACKAGE_NAME]) - AC_MSG_NOTICE([On some systems 'strip' can be found in /usr/ccs/bin ]) - AC_MSG_NOTICE(['strip' is also part of the GNU 'binutils' package.]) - AC_MSG_ERROR([Exiting, as 'strip' can not be found.]) -fi - -# Check tar -AC_CACHE_CHECK([for GNU or BSD tar], [ac_cv_path_TAR], [ -AC_PATH_PROGS_FEATURE_CHECK(TAR, [tar gtar], [[ -ac_version_TAR=`$ac_path_TAR --version 2>&1` -if echo "$ac_version_TAR" | grep >/dev/null GNU; then - ac_cv_path_TAR=$ac_path_TAR - if test $ac_prog = tar; then - ac_path_TAR_found=: - fi -fi -if echo "$ac_version_TAR" | grep >/dev/null bsdtar; then - ac_cv_path_TAR=$ac_path_TAR - if test $ac_prog = tar; then - ac_path_TAR_found=: - fi -fi -]], -[AC_MSG_ERROR([could not find either a GNU or BSD version of tar])], -[$PATH:/usr/sfw/bin]) -]) - -command_TAR=`command -v tar 2>/dev/null` -AS_IF([test x$command_TAR != x$ac_cv_path_TAR], - [AC_MSG_ERROR([[found a good version of tar in $ac_cv_path_TAR, but it's not the first "tar" program in your PATH]])] -) - -# Check make (unless MAKE is set) -if test -z "$MAKE"; then - AC_CACHE_CHECK([for GNU make], [ac_cv_path_MAKE], [ - AC_PATH_PROGS_FEATURE_CHECK(MAKE, [make gmake], [[ - ac_version_MAKE=`$ac_path_MAKE --version 2>&1` - if echo "$ac_version_MAKE" | grep >/dev/null GNU; then - ac_cv_path_MAKE=$ac_path_MAKE - if test $ac_prog = make; then - ac_path_MAKE_found=: - fi - fi - ]], - [AC_MSG_ERROR([could not find a GNU version of make])], - [$PATH:/usr/sfw/bin]) - ]) - - command_MAKE=`command -v make 2>/dev/null` - AS_IF([test x$command_MAKE != x$ac_cv_path_MAKE], - [AC_MSG_ERROR([[found GNU make in $ac_cv_path_MAKE, but it's not the first "make" program in your PATH]])]) -fi - -# Check for Latex, the use of which is less important in Sage than -# it used to be, as it was at one time required to build any documentation -# but this is no longer so. -AC_CHECK_PROG(found_latex, latex, yes, no) -if test x$found_latex != xyes -then - AC_MSG_WARN([You do not have 'latex', which is recommended, but not]) - AC_MSG_WARN([required. Latex is only really used for building pdf]) - AC_MSG_WARN([documents and for %latex mode in the AC_PACKAGE_NAME notebook.]) -fi - -# Check that perl is available, with version 5.8.0 or later. -# Some packages need perl, however it is not clear whether Sage really -# requires version >= 5.8.0. The R package *used* to require it, but -# not anymore. -- Jeroen Demeyer -AC_PATH_PROG([PERL],[perl]) -AX_PROG_PERL_VERSION([5.8.0],[],[ - AC_MSG_ERROR([Exiting, since AC_PACKAGE_NAME requires perl-5.8.0 or later]) -]) - -# To build Python on multi-arch Debian-based systems, we need -# dpkg-architecture. Since we need dpkg-architecture to determine -# whether we're on a multi-arch system and require dpkg-architecture, -# we simply require it always on Debian-based systems. -AC_CHECK_PROG(found_dpkg, dpkg, yes, no) -AC_CHECK_PROG(found_dpkg_arch, dpkg-architecture, yes, no) -if test x$found_dpkg = xyes && test x$found_dpkg_arch = xno -then - AC_MSG_NOTICE([You do not have 'dpkg-architecture', which is required to build]) - AC_MSG_NOTICE([Python on multi-arch Debian-based systems. This includes all recent]) - AC_MSG_NOTICE([Debian and Ubuntu systems. You can install this with:]) - AC_MSG_NOTICE([ sudo apt-get install dpkg-dev]) - AC_MSG_ERROR([Exiting, since AC_PACKAGE_NAME requires dpkg-architecture on Debian]) -fi - -#--------------------------------------------------------- -# C/C++/Fortran compilers - -# First check for programs we need. -AC_LANG(C) -AC_PROG_CC() -AC_PROG_CPP() - -AC_LANG(C++) -AC_PROG_CXX() - -AC_LANG(Fortran) -AC_PROG_FC() - -if test "x$CXX" = x -then - AC_MSG_ERROR([Exiting, since a C++ compiler was not found.]) -fi - -if test $enable_compiler_checks = yes -then - if test "x$CC" = x - then - AC_MSG_ERROR([Exiting, since a C compiler was not found.]) - fi - if test "x$FC" = x - then - AC_MSG_ERROR([Exiting, since a Fortran compiler was not found.]) - fi -fi - -# Next one should check for header files. -# complex.h is one that might not exist on older systems. -AC_LANG(C++) -AC_CHECK_HEADER([complex.h],[],[ - AC_MSG_ERROR([Exiting, since you do not have the 'complex.h' header file.]) -]) - -# Next one should check for types. -# None needed - -# Next one should check for structures. -# None needed - -# Next one should check for compiler characterists. - -# Check that we can compile C99 code -AC_LANG(C) -AC_PROG_CC_C99() -if test $enable_compiler_checks = yes -then - if test "x$ac_cv_prog_cc_c99" = xno - then - AC_MSG_ERROR([Exiting, as your C compiler cannot compile C99 code]) - fi -fi - -# Check that the Fortran compiler accepts free-format source code -# (as opposed to the older fixed-format style from Fortran 77). -# This helps verify the compiler works too, so if some idiot -# sets FC to /usr/bin/ls, we will at least know it's -# not a working Fortran compiler. -AC_LANG(Fortran) -if test $enable_compiler_checks = yes -then - # see http://www.gnu.org/software/hello/manual/autoconf/Fortran-Compiler.html - AC_FC_FREEFORM([], - [ - AC_MSG_NOTICE([Your Fortran compiler does not accept free-format source code]) - AC_MSG_NOTICE([which means the compiler is either seriously broken, or]) - AC_MSG_NOTICE([is too old to build Sage.]) - AC_MSG_ERROR([Exiting, as the Fortran compiler is not suitable]) - ]) -fi - -if test $enable_compiler_checks = yes -then - # Check that all compilers (C, C++, Fortan) are either all GNU - # compiler or all non-GNU compilers. If not, there is a problem, as - # mixing GNU and non-GNU compilers is likely to cause problems. - if test x$GCC = xyes && test x$GXX != xyes - then - AC_MSG_NOTICE([You are trying to use gcc but not g++]) - AC_MSG_ERROR([The mixing of GNU and non-GNU compilers is not permitted]) - fi - if test x$GXX = xyes && test x$ac_cv_fc_compiler_gnu != xyes - then - AC_MSG_NOTICE([You are trying to use g++ but not gfortran]) - AC_MSG_ERROR([The mixing of GNU and non-GNU compilers is not permitted]) - fi - if test x$ac_cv_fc_compiler_gnu = xyes && test x$GCC != xyes - then - AC_MSG_NOTICE([You are trying to use gfortran but not gcc]) - AC_MSG_ERROR([The mixing of GNU and non-GNU compilers is not permitted]) - fi -fi - -# The following tests check the version of the compilers (if GNU) -# are all the same. If non-GNU compilers are used, then no such -# checks are performed. -if test $enable_compiler_checks = yes -then -if test x$GCC = xyes -then - # Thank you to Andrew W. Nosenko andrew.w.nosenko@gmail.com - # who answered my query about testing of gcc versions on - # the autoconf@gnu.org mailing list. - # AS_VERSION_COMPARE(ver-1, ver-2, [action-if-less], [action-if-eq], [action-if-greater]) - AX_GCC_VERSION - AX_GXX_VERSION - AS_VERSION_COMPARE([$GCC_VERSION], [$GXX_VERSION], - [ - AC_MSG_NOTICE([gcc ($GCC_VERSION) and g++ ($GXX_VERSION) are not the same version]) - AC_MSG_NOTICE([which they must be. Check your setting of CC and CXX]) - AC_MSG_ERROR([Exiting, since the C and C++ compilers have different versions]) - ],[],[ - AC_MSG_NOTICE([gcc ($GCC_VERSION) and g++ ($GXX_VERSION) are not the same version]) - AC_MSG_NOTICE([which they must be. Check your setting of CC and CXX]) - AC_MSG_ERROR([Exiting, since the C and C++ compilers have different versions])]) - - # In the paragraph below, 'gfortran' is used to indicate the GNU Fortran - # compiler, though it might be called something else. - - # It's not easily possible to determine the Fortran version, as - # gfortran -dumpversion did not until GCC 4.5 return just the - # the version number, but the same as gfortran --version - # for example: - - # drkirkby@hawk:~$ gcc -dumpversion - # 4.3.4 - - # drkirkby@hawk:~$ g++ -dumpversion - # 4.3.4 - - # drkirkby@hawk:~$ gfortran -dumpversion - # GNU Fortran (GCC) 4.3.4 - # Copyright (C) 2008 Free Software Foundation, Inc. - # GNU Fortran comes with NO WARRANTY, to the extent permitted by law. - # You may redistribute copies of GNU Fortran - # under the terms of the GNU General Public License. - # For more information about these matters, see the file named COPYING - - # This behaviour is fixed in the gcc 4.5 branch. Since we need to - # support older versions of the compiler, we can't change this. - - # But I would expect that the version will be on the output - # of the compiler followed by -dumpversion (e.g. fortran -dumpversion) - - # So we grep for the known gcc version on the output of gfortran -dumpversion. - # and hope we find the same string. If so, they are almost certainly - # the same version. - fortran_version_string="`$FC -dumpversion | grep $GCC_VERSION 2>&1`" - - if test "x$fortran_version_string" = x - then - AC_MSG_NOTICE([Although gcc and g++ are both version $GCC_VERSION]) - AC_MSG_NOTICE([the Fortran compiler $FC is some other version.]) - AC_MSG_NOTICE([The output from $FC --version is below.]) - echo "" - $FC --version 2>&1 - echo "" - AC_MSG_ERROR([Exiting, since the Fortran compiler is not the same version as the C and C++ compilers]) - else - AC_MSG_NOTICE([Excellent, the C, C++ and Fortran compilers are all GCC $GCC_VERSION]) - fi - - # Exit if the version of GCC is known to be too old, and so old - # we have no intension whatsoever of trying to make Sage work with it. - AS_VERSION_COMPARE([$GCC_VERSION], [$minimum_gcc_version_for_debugging_purposes],[ - AC_MSG_NOTICE([GCC $GCC_VERSION is too old and can not build AC_PACKAGE_NAME. ]) - AC_MSG_NOTICE([Please use a GCC of at least $minimum_gcc_version_for_no_hassle ]) - AC_MSG_NOTICE([There are no plans whatsoever to support GCC $GCC_VERSION]) - AC_MSG_ERROR([Exiting, due to the use of a version of GCC that is too old]) - ], [],[]) - - # Exit if Sage is *precisely* version $buggy_gcc_version1, as that is very buggy. - # If any later versions of GCC are found to be buggy (which would never surprise me) - # We can easily add a buggy_gcc_version2 and repeat the next 5 lines. - # At the time of writing (28th September 2009) that is version 4.0.0, - # but rather than hard-code that, it is set as a variable. - AS_VERSION_COMPARE([$GCC_VERSION], [$buggy_gcc_version1],[],[ - AC_MSG_NOTICE([GCC $buggy_gcc_version1 is very buggy and can not build AC_PACKAGE_NAME.]) - AC_MSG_NOTICE([Please use a gcc of at least $minimum_gcc_version_for_no_hassle.]) - AC_MSG_ERROR([Exiting, due to the use of a version of GCC that is too buggy]) - ],[]) - - # Issue a warning if gcc is too old to work with all packages, but - # someone wants to try getting one or more packages work with - # an earlier gcc. At the time of writing, (28th Sept 2009), ratpoints - # has such an issue, requiring version 4.0.1, but we would like to - # get it to work with version 3.4.x - AS_VERSION_COMPARE([$GCC_VERSION], [$minimum_gcc_version_for_no_hassle],[ - AC_MSG_NOTICE([******************************************************]) - AC_MSG_NOTICE([******************************************************]) - AC_MSG_NOTICE([******************************************************]) - AC_MSG_NOTICE([GCC $GCC_VERSION is too old and can not build AC_PACKAGE_NAME.]) - AC_MSG_NOTICE([Please use a gcc of at least $minimum_gcc_version_for_no_hassle]) - AC_MSG_NOTICE([if you just want AC_PACKAGE_NAME to build without problems.]) - AC_MSG_NOTICE([]) - if test "${SAGE_USE_OLD_GCC+set}" = set; then - AC_MSG_NOTICE([Since the variable SAGE_USE_OLD_GCC was set, the]) - AC_MSG_NOTICE([build will continue, but it will fail without changes]) - AC_MSG_NOTICE([to the Sage source code. You can be 100% sure of that.]) - else - AC_MSG_NOTICE([If you want to try building Sage with a GCC 3.4.x ]) - AC_MSG_NOTICE([with a view to debugging the problems which stop it ]) - AC_MSG_NOTICE([working on a gcc 3.4 series compiler, set the ]) - AC_MSG_NOTICE([environment variable SAGE_USE_OLD_GCC to something non]) - AC_MSG_NOTICE([empty. But if you just want Sage to work, upgrade GCC ]) - AC_MSG_NOTICE([******************************************************]) - AC_MSG_NOTICE([******************************************************]) - AC_MSG_ERROR([Exiting, due to the use of a version of GCC that is too old]) - fi - ], -[ -AC_MSG_NOTICE([Good, gcc and g++ are are just new enough as GCC $minimum_gcc_version_for_no_hassle]) -AC_MSG_NOTICE([is the minimum version needed needed to build AC_PACKAGE_NAME]) -] -, -[ -AC_MSG_NOTICE([Excellent, GCC $GCC_VERSION is later than the minimum]) -AC_MSG_NOTICE([needed to build AC_PACKAGE_NAME, which is GCC version $minimum_gcc_version_for_no_hassle]) -]) - - # AS_VERSION_COMPARE(ver-1, ver-2, [action-if-less], [action-if-eq], [action-if-greater]) -else - AC_MSG_WARN([You have a non-GNU compiler, but AC_PACKAGE_NAME has never been built]) - AC_MSG_WARN([successfully with any compiler other than GCC, despite]) - AC_MSG_WARN([some attempts made on Solaris to use Sun's compiler, which]) - AC_MSG_WARN([produces faster code than GCC. However, the AC_PACKAGE_NAME developers]) - AC_MSG_WARN([want AC_PACKAGE_NAME to work with other compilers, so please try.]) - AC_MSG_WARN([The AC_PACKAGE_NAME developers would welcome any feedback you can give.]) - AC_MSG_WARN([Please visit http://groups.google.com/group/sage-devel]) - AC_MSG_WARN([If you just want to use AC_PACKAGE_NAME, we suggest the use of]) - AC_MSG_WARN([GCC of at least version $minimum_gcc_version_for_no_hassle]) -fi -fi # test $enable_compiler_checks = yes - -# Testing for library functions -# First check for something that should be in any maths library (sqrt). -AC_LANG(C++) -AC_CHECK_LIB(m,sqrt,[],[ - AC_MSG_NOTICE([This system has no maths library installed.]) - # On AIX this is not installed by default - strange as that might seem. - # but is in a fileset bos.adt.libm. However, the fileset bos.adt - # includes other things that are probably useful. - if test "x`uname`" = 'xAIX' - then - AC_MSG_NOTICE([On AIX, libm is contained in the bos.adt.libm fileset.]) - AC_MSG_NOTICE([Actually, we recommend to install the complete bos.adt fileset.]) - AC_MSG_NOTICE([This needs to be performed by a system administrator.]) - fi - AC_MSG_ERROR([Exiting, since a maths library was not found.]) - ]) - -# Check for system services - -# Check that we are not building in a directory containing spaces -AS_IF([echo "$ac_pwd" |grep " " >/dev/null], - AC_MSG_ERROR([the path to the Sage root directory ($ac_pwd) contains a space. Sage will not build correctly in this case]) -) - - -if test x`uname` = xDarwin; then -[ - # Warning: xcodebuild does not seem to be maintained in Xcode 4.3 - # or later, so do not rely on the variable XCODE_VERS with OS X - # 10.7 or later. - XCODE_VERS=`xcodebuild -version 2> /dev/null | grep Xcode | sed -e 's/[A-Za-z ]//g'` - if [ -z $XCODE_VERS ]; then - XCODE_VERS="2" - fi - XCODE_VERS_MAJOR=`echo $XCODE_VERS | cut '-d.' -f1` - DARWIN_VERSION=`uname -r | cut '-d.' -f1` - echo "***************************************************" - echo "***************************************************" - if [ $DARWIN_VERSION -gt 10 ]; then - echo "You are using OS X Lion (or later)." - echo "You are strongly advised to install Apple's latest Xcode" - echo "unless you already have it. You can install this using" - echo "the App Store. Also, make sure you install Xcode's" - echo "Command Line Tools -- see Sage's README.txt." - elif [ $XCODE_VERS_MAJOR -gt 2 ]; then - echo "You are using Xcode version $XCODE_VERS." - echo "You are strongly advised to install Apple's latest Xcode" - echo "unless you already have it. You can download this from" - echo "http://developer.apple.com/downloads/." - echo "If using Xcode 4.3 or later, make sure you install Xcode's" - echo "Command Line Tools -- see Sage's README.txt." - else - echo "You are using Xcode version 1 or 2" - echo "WARNING: You are strongly advised to install the" - echo "latest version of Apple's Xcode for your platform," - echo "unless you already have it." - if [ $DARWIN_VERSION -eq 10 ]; then - echo "Probably you need Xcode 3.2.6" - elif [ $DARWIN_VERSION -eq 9 ]; then - echo "Probably you need Xcode 3.1.4" - elif [ $DARWIN_VERSION -lt 9 ]; then - echo "Probably you need Xcode 2.5" - fi - fi -] - -########################################################################### -# (OS X only) -# Sage will probably not build at all if either Fink or MacPorts can be -# found, and the error messages can be extremely confusing. Even if it does -# build, the product will probably be wrong. This runs a basic check to -# find them. Once the Sage build process is perfected, this won't be necessary. -# dphilp 15/9/2008 -########################################################################### - PORTS_PATH=`which port` - if test -f "$PORTS_PATH"; then -AC_MSG_ERROR([["found MacPorts in $PORTS_PATH. Either: -(1) rename /opt/local and /sw, or -(2) change PATH and DYLD_LIBRARY_PATH -(Once Sage is built, you can restore them.)]]) - fi - - FINK_PATH=`which fink` - if test -f "$FINK_PATH"; then -AC_MSG_ERROR([["found Fink in $FINK_PATH. Either: -(1) rename /opt/local and /sw, or -(2) change PATH and DYLD_LIBRARY_PATH -(Once Sage is built, you can restore them.)]]) - fi -fi - dnl AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([build/make/Makefile-auto]) diff --git a/src/bin/sage-banner b/src/bin/sage-banner index 955061b92e5..f2a44eff412 100644 --- a/src/bin/sage-banner +++ b/src/bin/sage-banner @@ -1,5 +1,8 @@ ┌────────────────────────────────────────────────────────────────────┐ -│ SageMath Version 6.9, Release Date: 2015-10-10 │ +│ SageMath Version 6.10.beta2, Release Date: 2015-10-28 │ │ Type "notebook()" for the browser-based notebook interface. │ │ Type "help()" for help. │ └────────────────────────────────────────────────────────────────────┘ +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Warning: this is a prerelease version, and it may be unstable. ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/src/bin/sage-env b/src/bin/sage-env index 53246b12513..cac4860c415 100644 --- a/src/bin/sage-env +++ b/src/bin/sage-env @@ -279,13 +279,14 @@ if [ "$UNAME" = "Darwin" ]; then # cause lots of random "Abort trap" issues on OSX. see trac # #7095 for an example. MACOSX_VERSION=`uname -r | awk -F. '{print $1}'` - MACOSX_DEPLOYMENT_TARGET=10.$[$MACOSX_VERSION-4] - if [ $MACOSX_DEPLOYMENT_TARGET = '10.10' ]; then - # Workaround for the libtool version detection bug - # See http://trac.sagemath.org/ticket/17204 + if [ $MACOSX_VERSION -ge 14 ]; then + # various packages have still have issues with + # two digit OS X versions MACOSX_DEPLOYMENT_TARGET=10.9 + else + MACOSX_DEPLOYMENT_TARGET=10.$[$MACOSX_VERSION-4] fi - export MACOSX_DEPLOYMENT_TARGET + export MACOSX_DEPLOYMENT_TARGET MACOSX_VERSION fi # Compile-time path for libraries. This is the equivalent of diff --git a/src/bin/sage-notebook b/src/bin/sage-notebook index 3766a840d53..cbe3e56c6f0 100755 --- a/src/bin/sage-notebook +++ b/src/bin/sage-notebook @@ -65,18 +65,16 @@ class NotebookSageNB(object): notebook(*self.args, **self.kwds) -class NotebookIPython(object): +class NotebookJupyter(object): PREREQUISITE_ERROR = textwrap.dedent(""" - The IPython notebook requires ssl, even if you do not use + The Jupyter notebook requires ssl, even if you do not use https. Install the openssl development packages in your system and then rebuild Python (sage -f python2). """) def __init__(self, argv): - from sage.repl.ipython_kernel.install import \ - SageKernelSpec, have_prerequisites - SageKernelSpec.update() + from sage.repl.ipython_kernel.install import have_prerequisites if not have_prerequisites(): print(self.PREREQUISITE_ERROR) raise SystemExit(1) @@ -118,8 +116,8 @@ EXAMPLES: notebook_launcher = { 'default': NotebookSageNB, # change this to change the default 'sagenb': NotebookSageNB, - 'ipython': NotebookIPython, - 'jupyter': NotebookIPython, + 'ipython': NotebookJupyter, + 'jupyter': NotebookJupyter, } notebook_names = ', '.join(notebook_launcher.keys()) @@ -182,8 +180,8 @@ if __name__ == '__main__': parser.print_help() elif launcher == NotebookSageNB: NotebookSageNB([], help=True) - elif launcher == NotebookIPython: - NotebookIPython(['help']) + elif launcher == NotebookJupyter: + NotebookJupyter(['help']) else: parser.print_help() sys.exit(0) diff --git a/src/bin/sage-unzip b/src/bin/sage-unzip new file mode 100755 index 00000000000..ca0fdf834a8 --- /dev/null +++ b/src/bin/sage-unzip @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# +# This file is a replacement for the 'unzip' command. +# +# We obtain its main feature (extraction) through the 'zipfile' python module. + +import argparse + +parser = argparse.ArgumentParser(description='Extract the contents of a .zip archive') +parser.add_argument('filename', help="the .zip archive", type=argparse.FileType('r')) +parser.add_argument('-d',help='An optional directory to which to extract files. Set to "." by default.',action='store',default='.') + +args = parser.parse_args() +from zipfile import ZipFile +ZipFile(args.filename).extractall(args.d) diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 7b35690365e..9330d0a3dbb 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -1,4 +1,4 @@ # Sage version information for shell scripts # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='6.9' -SAGE_RELEASE_DATE='2015-10-10' +SAGE_VERSION='6.10.beta2' +SAGE_RELEASE_DATE='2015-10-28' diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst index f80229432e5..f07d5a3f78e 100644 --- a/src/doc/en/reference/algebras/index.rst +++ b/src/doc/en/reference/algebras/index.rst @@ -4,6 +4,8 @@ Algebras .. toctree:: :maxdepth: 2 + sage/algebras/associated_graded + sage/algebras/catalog sage/algebras/clifford_algebra @@ -25,6 +27,8 @@ Algebras sage/algebras/free_algebra_quotient sage/algebras/free_algebra_quotient_element + sage/algebras/free_zinbiel_algebra + sage/algebras/group_algebra sage/algebras/iwahori_hecke_algebra diff --git a/src/doc/en/reference/categories/index.rst b/src/doc/en/reference/categories/index.rst index 6ee9d20e54d..34cf4165a73 100644 --- a/src/doc/en/reference/categories/index.rst +++ b/src/doc/en/reference/categories/index.rst @@ -56,6 +56,7 @@ Individual Categories sage/categories/coxeter_group_algebras sage/categories/coxeter_groups sage/categories/crystals + sage/categories/cw_complexes sage/categories/discrete_valuation sage/categories/distributive_magmas_and_additive_magmas sage/categories/division_rings @@ -63,6 +64,10 @@ Individual Categories sage/categories/enumerated_sets sage/categories/euclidean_domains sage/categories/fields + sage/categories/filtered_algebras + sage/categories/filtered_algebras_with_basis + sage/categories/filtered_modules + sage/categories/filtered_modules_with_basis sage/categories/finite_coxeter_groups sage/categories/finite_crystals sage/categories/finite_dimensional_algebras_with_basis @@ -94,6 +99,7 @@ Individual Categories sage/categories/graded_hopf_algebras_with_basis sage/categories/graded_modules sage/categories/graded_modules_with_basis + sage/categories/graphs sage/categories/group_algebras sage/categories/groupoid sage/categories/groups @@ -105,10 +111,13 @@ Individual Categories sage/categories/integral_domains sage/categories/lattice_posets sage/categories/left_modules + sage/categories/lie_groups sage/categories/magmas sage/categories/magmas_and_additive_magmas sage/categories/magmatic_algebras + sage/categories/manifolds sage/categories/matrix_algebras + sage/categories/metric_spaces sage/categories/modular_abelian_varieties sage/categories/modules sage/categories/modules_with_basis @@ -135,6 +144,8 @@ Individual Categories sage/categories/sets_cat sage/categories/sets_with_grading sage/categories/sets_with_partial_maps + sage/categories/simplicial_complexes + sage/categories/topological_spaces sage/categories/unique_factorization_domains sage/categories/unital_algebras sage/categories/vector_spaces @@ -183,6 +194,7 @@ Examples of parents using categories sage/categories/examples/commutative_additive_semigroups sage/categories/examples/coxeter_groups sage/categories/examples/crystals + sage/categories/examples/cw_complexes sage/categories/examples/facade_sets sage/categories/examples/finite_coxeter_groups sage/categories/examples/finite_dimensional_algebras_with_basis @@ -192,8 +204,10 @@ Examples of parents using categories sage/categories/examples/finite_weyl_groups sage/categories/examples/graded_connected_hopf_algebras_with_basis sage/categories/examples/graded_modules_with_basis + sage/categories/examples/graphs sage/categories/examples/hopf_algebras_with_basis sage/categories/examples/infinite_enumerated_sets + sage/categories/examples/manifolds sage/categories/examples/monoids sage/categories/examples/posets sage/categories/examples/semigroups_cython diff --git a/src/doc/en/reference/coding/index.rst b/src/doc/en/reference/coding/index.rst index 004458eba3f..6311e17f696 100644 --- a/src/doc/en/reference/coding/index.rst +++ b/src/doc/en/reference/coding/index.rst @@ -6,8 +6,10 @@ Coding Theory .. toctree:: :maxdepth: 1 - sage/coding/channels_catalog + sage/coding/encoder + sage/coding/encoders_catalog sage/coding/channel_constructions + sage/coding/channels_catalog sage/coding/codes_catalog sage/coding/linear_code sage/coding/code_constructions diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index 4abd0e8f295..c2c594d9fd1 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -35,6 +35,7 @@ Comprehensive Module list sage/combinat/cluster_algebra_quiver/mutation_type sage/combinat/cluster_algebra_quiver/quiver sage/combinat/cluster_algebra_quiver/quiver_mutation_type + sage/combinat/colored_permutations sage/combinat/combinat sage/combinat/combinat_cython sage/combinat/combination @@ -117,7 +118,9 @@ Comprehensive Module list sage/combinat/graph_path sage/combinat/gray_codes sage/combinat/hall_polynomial - sage/combinat/integer_list + sage/combinat/integer_lists/base + sage/combinat/integer_lists/lists + sage/combinat/integer_lists/invlex sage/combinat/integer_matrices sage/combinat/integer_vector sage/combinat/integer_vector_weighted @@ -162,11 +165,13 @@ Comprehensive Module list sage/combinat/permutation_nk sage/combinat/posets/__init__ sage/combinat/posets/all + sage/combinat/posets/cartesian_product sage/combinat/posets/elements sage/combinat/posets/hasse_diagram sage/combinat/posets/incidence_algebras sage/combinat/posets/lattices sage/combinat/posets/linear_extensions + sage/combinat/posets/moebius_algebra sage/combinat/posets/poset_examples sage/combinat/posets/posets sage/combinat/q_analogues @@ -208,6 +213,7 @@ Comprehensive Module list sage/combinat/root_system/cartan_type sage/combinat/root_system/coxeter_group sage/combinat/root_system/coxeter_matrix + sage/combinat/root_system/coxeter_type sage/combinat/root_system/dynkin_diagram sage/combinat/root_system/hecke_algebra_representation sage/combinat/root_system/integrable_representations diff --git a/src/doc/en/reference/data_structures/index.rst b/src/doc/en/reference/data_structures/index.rst index 52e3280b17d..90bf85efdf2 100644 --- a/src/doc/en/reference/data_structures/index.rst +++ b/src/doc/en/reference/data_structures/index.rst @@ -7,5 +7,6 @@ Data Structures sage/misc/binary_tree sage/data_structures/bitset sage/data_structures/bounded_integer_sequences + sage/data_structures/mutable_poset .. include:: ../footer.txt diff --git a/src/doc/en/reference/graphs/index.rst b/src/doc/en/reference/graphs/index.rst index f71db685e66..a27c03f556a 100644 --- a/src/doc/en/reference/graphs/index.rst +++ b/src/doc/en/reference/graphs/index.rst @@ -91,6 +91,7 @@ Libraries of algorithms sage/graphs/graph_latex sage/graphs/graph_editor sage/graphs/graph_list + sage/graphs/graph_input sage/graphs/hyperbolicity sage/graphs/tutte_polynomial sage/graphs/generic_graph_pyx diff --git a/src/doc/en/reference/homology/index.rst b/src/doc/en/reference/homology/index.rst index 7834bd7d79d..0524030d31c 100644 --- a/src/doc/en/reference/homology/index.rst +++ b/src/doc/en/reference/homology/index.rst @@ -13,6 +13,7 @@ cell complexes. sage/homology/chain_complex sage/homology/chain_complex_morphism + sage/homology/chain_homotopy sage/homology/chain_complex_homspace sage/homology/simplicial_complex sage/homology/simplicial_complex_morphism @@ -23,6 +24,9 @@ cell complexes. sage/homology/cell_complex sage/homology/koszul_complex sage/homology/homology_group + sage/homology/homology_vector_space_with_basis + sage/homology/algebraic_topological_model + sage/homology/homology_morphism sage/homology/matrix_utils sage/interfaces/chomp diff --git a/src/doc/en/reference/index.rst b/src/doc/en/reference/index.rst index 5972217c39d..85c52019900 100644 --- a/src/doc/en/reference/index.rst +++ b/src/doc/en/reference/index.rst @@ -78,6 +78,7 @@ Calculus * :doc:`Symbolic Calculus ` * :doc:`Mathematical Constants ` * :doc:`Elementary and Special Functions ` +* :doc:`Asymptotic Expansions ` (experimental) Geometry and Topology --------------------- diff --git a/src/doc/en/reference/misc/index.rst b/src/doc/en/reference/misc/index.rst index 9d389c091aa..ab999774fd0 100644 --- a/src/doc/en/reference/misc/index.rst +++ b/src/doc/en/reference/misc/index.rst @@ -48,6 +48,7 @@ Lists and Iteration, etc. :maxdepth: 1 sage/misc/callable_dict + sage/misc/converting_dict sage/misc/flatten sage/misc/search sage/misc/sage_itertools diff --git a/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst b/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst index d84fd747804..bd55ed8b014 100644 --- a/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst +++ b/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst @@ -35,6 +35,7 @@ whereas others have multiple bases. sage/rings/polynomial/real_roots sage/rings/polynomial/complex_roots + sage/rings/polynomial/refine_root sage/rings/polynomial/ideal sage/rings/polynomial/polynomial_quotient_ring diff --git a/src/doc/en/reference/rings/asymptotic_expansions_index.rst b/src/doc/en/reference/rings/asymptotic_expansions_index.rst index f6268e61f00..3a15f21a3d1 100644 --- a/src/doc/en/reference/rings/asymptotic_expansions_index.rst +++ b/src/doc/en/reference/rings/asymptotic_expansions_index.rst @@ -1,9 +1,59 @@ Asymptotic Expansions ===================== + +The Asymptotic Ring +------------------- + +The asymptotic ring, as well as its main documentation is contained in +the module + +- :doc:`sage/rings/asymptotic/asymptotic_ring`. + + +Supplements +----------- + +Behind the scenes of working with asymptotic expressions a couple of +additional classes and tools turn up. For instance the growth of each +summand is managed in growth groups, see below. + + +Growth Groups +^^^^^^^^^^^^^ + +The growth of a summand of an asymptotic expression is managed in + +- :doc:`sage/rings/asymptotic/growth_group` and + +- :doc:`sage/rings/asymptotic/growth_group_cartesian`. + + +Term Monoids +^^^^^^^^^^^^ + +A summand of an asymptotic expression is basically a term out of the following monoid: + +- :doc:`sage/rings/asymptotic/term_monoid`. + + +Miscellaneous +^^^^^^^^^^^^^ + +Various useful functions and tools are collected in + +- :doc:`sage/rings/asymptotic/misc`. + + +Asymptotic Expansions --- Table of Contents +------------------------------------------- + .. toctree:: + sage/rings/asymptotic/asymptotic_ring sage/rings/asymptotic/growth_group + sage/rings/asymptotic/growth_group_cartesian sage/rings/asymptotic/term_monoid + sage/rings/asymptotic/misc .. include:: ../footer.txt diff --git a/src/doc/en/reference/sat/index.rst b/src/doc/en/reference/sat/index.rst index 3c86ef5cac2..7f6314ac5d8 100644 --- a/src/doc/en/reference/sat/index.rst +++ b/src/doc/en/reference/sat/index.rst @@ -18,33 +18,36 @@ should be true, we write:: Solvers ------- -Any SAT solver supporting the DIMACS input format is easily interfaced using the -:class:`sage.sat.solvers.dimacs.DIMACS` blueprint. Sage ships with pre-written interfaces for *RSat* -[RS]_ and *Glucose* [GL]_. Furthermore, Sage provides a C++ interface to the *CryptoMiniSat* [CMS]_ SAT -solver which can be used interchangably with DIMACS-based solvers, but also provides advanced -features. For this, the optional CryptoMiniSat package must be -installed, this can be accomplished by typing the following in the +By default, Sage solves SAT instances as an Integer Linear Program (see +:mod:`sage.numerical.mip`), but any SAT solver supporting the DIMACS input +format is easily interfaced using the :class:`sage.sat.solvers.dimacs.DIMACS` +blueprint. Sage ships with pre-written interfaces for *RSat* [RS]_ and *Glucose* +[GL]_. Furthermore, Sage provides a C++ interface to the *CryptoMiniSat* [CMS]_ +SAT solver which can be used interchangably with DIMACS-based solvers, but also +provides advanced features. For this last solver, the optional CryptoMiniSat +package must be installed, this can be accomplished by typing the following in the shell:: sage -i cryptominisat sagelib -Since by default Sage does not include any SAT solver, we demonstrate key features by instantiating -a fake DIMACS-based solver. We start with a trivial example:: +We now show how to solve a simple SAT problem. :: (x1 OR x2 OR x3) AND (x1 OR x2 OR (NOT x3)) In Sage's notation:: - sage: from sage.sat.solvers.dimacs import DIMACS - sage: solver = DIMACS(command="sat-solver") + sage: solver = SAT() sage: solver.add_clause( ( 1, 2, 3) ) sage: solver.add_clause( ( 1, 2, -3) ) + sage: solver() # random + (None, True, True, False) .. NOTE:: - :meth:`sage.sat.solvers.dimacs.DIMACS.add_clause` creates new variables when necessary. In - particular, it creates *all* variables up to the given index. Hence, adding a literal involving - the variable 1000 creates up to 1000 internal variables. + :meth:`~sage.sat.solvers.dimacs.DIMACS.add_clause` creates new variables + when necessary. When using CryptoMiniSat, it creates *all* variables up to + the given index. Hence, adding a literal involving the variable 1000 creates + up to 1000 internal variables. DIMACS-base solvers can also be used to write DIMACS files:: @@ -76,14 +79,6 @@ Alternatively, there is :meth:`sage.sat.solvers.dimacs.DIMACS.clauses`:: These files can then be passed external SAT solvers. -We demonstrate solving using CryptoMiniSat:: - - sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat - sage: cms = CryptoMiniSat() # optional - cryptominisat - sage: cms.add_clause((1,2,-3)) # optional - cryptominisat - sage: cms() # optional - cryptominisat - (None, True, True, False) - Details on Specific Solvers ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -92,6 +87,7 @@ Details on Specific Solvers sage/sat/solvers/satsolver sage/sat/solvers/dimacs + sage/sat/solvers/sat_lp .. optional - cryptominisat .. sage/sat/solvers/cryptominisat/cryptominisat .. sage/sat/solvers/cryptominisat/solverconf diff --git a/src/doc/en/thematic_tutorials/coercion_and_categories.rst b/src/doc/en/thematic_tutorials/coercion_and_categories.rst index 058b9f41f6b..b6f8e8bb015 100644 --- a/src/doc/en/thematic_tutorials/coercion_and_categories.rst +++ b/src/doc/en/thematic_tutorials/coercion_and_categories.rst @@ -878,7 +878,7 @@ The four axioms requested for coercions rational field is a homomorphism of euclidean domains:: sage: QQ.coerce_map_from(ZZ).category_for() - Category of euclidean domains + Join of Category of euclidean domains and Category of metric spaces .. end of output 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 e4de374dda9..4756963f818 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 @@ -89,8 +89,7 @@ groups in the GAP transitive groups database. :: sage: K. = NumberField(x^3 - 2) - sage: K.galois_group(type="gap", algorithm='magma') # optional - magma - verbose... + sage: K.galois_group(type="gap", algorithm='magma') # optional - magma database_gap Galois group Transitive group number 2 of degree 3 of the Number Field in a with defining polynomial x^3 - 2 diff --git a/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst b/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst index 3caeb368547..af5ed9821dd 100644 --- a/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst +++ b/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst @@ -164,13 +164,13 @@ coefficients) through the usual free module accessors:: [((0, 0, 0), 1), ((1, 0, 0), 1), ((1, 1, 0), 1), ((1, 1, 1), 1)] sage: pprint(dict(chi)) {(0, 0, 0): 1, (1, 0, 0): 1, (1, 1, 0): 1, (1, 1, 1): 1} - sage: chi.monomials() + sage: M = sorted(chi.monomials(), key=lambda x: x.support()); M [B3(0,0,0), B3(1,0,0), B3(1,1,0), B3(1,1,1)] - sage: chi.support() + sage: sorted(chi.support()) [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1)] sage: chi.coefficients() [1, 1, 1, 1] - sage: [r.degree() for r in chi.monomials()] + sage: [r.degree() for r in M] [1, 7, 21, 35] sage: sum(r.degree() for r in chi.monomials()) 64 @@ -484,10 +484,10 @@ itself, that is, the integral of `|tr(g)|^{10}`:: sage: tr^5 5*A2(2,2,1) + 6*A2(3,1,1) + 5*A2(3,2,0) + 4*A2(4,1,0) + A2(5,0,0) - sage: (tr^5).monomials() + sage: sorted((tr^5).monomials(), key=lambda x: x.support()) [A2(2,2,1), A2(3,1,1), A2(3,2,0), A2(4,1,0), A2(5,0,0)] - sage: (tr^5).coefficients() - [5, 6, 5, 4, 1] + sage: sorted((tr^5).coefficients()) + [1, 4, 5, 5, 6] sage: sum(x^2 for x in (tr^5).coefficients()) 103 diff --git a/src/doc/en/thematic_tutorials/tutorial-objects-and-classes.rst b/src/doc/en/thematic_tutorials/tutorial-objects-and-classes.rst index 388d7d1d473..118e03c69c2 100644 --- a/src/doc/en/thematic_tutorials/tutorial-objects-and-classes.rst +++ b/src/doc/en/thematic_tutorials/tutorial-objects-and-classes.rst @@ -246,7 +246,7 @@ As an element of a vector space, ``el`` has a particular behavior:: sage: 2*el 2*B[[1, 2, 3]] + 6*B[[1, 3, 2]] + B[[3, 2, 1]] - sage: el.support() + sage: sorted(el.support()) [[1, 2, 3], [1, 3, 2], [3, 2, 1]] sage: el.coefficient([1, 2, 3]) 1 diff --git a/src/doc/en/tutorial/tour_coercion.rst b/src/doc/en/tutorial/tour_coercion.rst index 2bbb25d352b..7f94a0c84fd 100644 --- a/src/doc/en/tutorial/tour_coercion.rst +++ b/src/doc/en/tutorial/tour_coercion.rst @@ -118,6 +118,7 @@ implemented in Sage as well: sage: ZZ.category() Join of Category of euclidean domains and Category of infinite enumerated sets + and Category of metric spaces sage: ZZ.category().is_subcategory(Rings()) True sage: ZZ in Rings() diff --git a/src/doc/fr/tutorial/tour_coercion.rst b/src/doc/fr/tutorial/tour_coercion.rst index a032be7968c..395493ba3a6 100644 --- a/src/doc/fr/tutorial/tour_coercion.rst +++ b/src/doc/fr/tutorial/tour_coercion.rst @@ -119,6 +119,7 @@ par ailleurs les catégories en tant que telles : sage: ZZ.category() Join of Category of euclidean domains and Category of infinite enumerated sets + and Category of metric spaces sage: ZZ.category().is_subcategory(Rings()) True sage: ZZ in Rings() diff --git a/src/doc/pt/tutorial/tour_coercion.rst b/src/doc/pt/tutorial/tour_coercion.rst index 58ca4301876..569caee10fa 100644 --- a/src/doc/pt/tutorial/tour_coercion.rst +++ b/src/doc/pt/tutorial/tour_coercion.rst @@ -121,7 +121,9 @@ categorias matemáticas também são implementadas no Sage: sage: Rings() Category of rings sage: ZZ.category() - Join of Category of euclidean domains and Category of infinite enumerated sets + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces sage: ZZ.category().is_subcategory(Rings()) True diff --git a/src/module_list.py b/src/module_list.py index 7108872b586..d0a48218689 100644 --- a/src/module_list.py +++ b/src/module_list.py @@ -1570,6 +1570,10 @@ def uname_specific(name, value, alternative): sources = ['sage/rings/polynomial/real_roots.pyx'], libraries=['mpfr']), + Extension('sage.rings.polynomial.refine_root', + sources = ['sage/rings/polynomial/refine_root.pyx'], + libraries=['gmp', 'mpfr', 'mpfi']), + Extension('sage.rings.polynomial.symmetric_reduction', sources = ['sage/rings/polynomial/symmetric_reduction.pyx']), diff --git a/src/sage/algebras/associated_graded.py b/src/sage/algebras/associated_graded.py new file mode 100644 index 00000000000..750cbe33df1 --- /dev/null +++ b/src/sage/algebras/associated_graded.py @@ -0,0 +1,341 @@ +r""" +Associated Graded Algebras To Filtered Algebras + +AUTHORS: + +- Travis Scrimshaw (2014-10-08): Initial version +""" + +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from copy import copy + +from sage.categories.modules_with_basis import ModulesWithBasis +from sage.sets.family import Family +from sage.combinat.free_module import CombinatorialFreeModule + +class AssociatedGradedAlgebra(CombinatorialFreeModule): + r""" + The associated graded algebra/module `\operatorname{gr} A` + of a filtered algebra/module with basis `A`. + + Let `A` be a filtered module over a commutative ring `R`. + Let `(F_i)_{i \in I}` be the filtration of `A`, with `I` being + a totally ordered set. Define + + .. MATH:: + + G_i = F_i / \sum_{j < i} F_j + + for every `i \in I`, and then + + .. MATH:: + + \operatorname{gr} A = \bigoplus_{i \in I} G_i. + + There are canonical projections `p_i : F_i \to G_i` for + every `i \in I`. Moreover `\operatorname{gr} A` is naturally a + graded `R`-module with `G_i` being the `i`-th graded component. + This graded `R`-module is known as the *associated graded module* + (or, for short, just *graded module*) of `A`. + + Now, assume that `A` (endowed with the filtration + `(F_i)_{i \in I}`) is not just a filtered `R`-module, but also + a filtered `R`-algebra. + Let `u \in G_i` and `v \in G_j`, and let `u' \in F_i` and + `v' \in F_j` be lifts of `u` and `v`, respectively (so that + `u = p_i(u')` and `v = p_j(v')`). Then, we define a + multiplication `*` on `\operatorname{gr} A` (not to be mistaken + for the multiplication of the original algebra `A`) by + + .. MATH:: + + u * v = p_{i+j} (u' v'). + + The *associated graded algebra* (or, for short, just + *graded algebra*) of `A` is the graded algebra + `\operatorname{gr} A` (endowed with this multiplication). + + Now, assume that `A` is a filtered `R`-algebra with basis. + Let `(b_x)_{x \in X}` be the basis of `A`, + and consider the partition `X = \bigsqcup_{i \in I} X_i` of + the set `X`, which is part of the data of a filtered + algebra with basis. We know (see + :class:`~sage.categories.filtered_modules_with_basis.FilteredModulesWithBasis`) + that `A` (being a filtered `R`-module with basis) is canonically + (when the basis is considered to be part of the data) + isomorphic to `\operatorname{gr} A` as an `R`-module. Therefore + the `k`-th graded component `G_k` can be identified with + the span of `(b_x)_{x \in X_k}`, or equivalently the + `k`-th homogeneous component of `A`. Suppose + that `u' v' = \sum_{k \leq i+j} m_k` where `m_k \in G_k` (which + has been identified with the `k`-th homogeneous component of `A`). + Then `u * v = m_{i+j}`. We also note that the choice of + identification of `G_k` with the `k`-th homogeneous component + of `A` depends on the given basis. + + The basis `(b_x)_{x \in X}` of `A` gives rise to a basis + of `\operatorname{gr} A`. This latter basis is still indexed + by the elements of `X`, and consists of the images of the + `b_x` under the `R`-module isomorphism from `A` to + `\operatorname{gr} A`. It makes `\operatorname{gr} A` into + a graded `R`-algebra with basis. + + In this class, the `R`-module isomorphism from `A` to + `\operatorname{gr} A` is implemented as + :meth:`to_graded_conversion` and also as the default + conversion from `A` to `\operatorname{gr} A`. Its + inverse map is implemented as + :meth:`from_graded_conversion`. + The projection `p_i : F_i \to G_i` is implemented as + :meth:`projection` ``(i)``. + + INPUT: + + - ``A`` -- a filtered module (or algebra) with basis + + OUTPUT: + + The associated graded module of `A`, if `A` is just a filtered + `R`-module. + The associated graded algebra of `A`, if `A` is a filtered + `R`-algebra. + + EXAMPLES: + + Associated graded module of a filtered module:: + + sage: A = Modules(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.category() + Category of graded modules with basis over Rational Field + sage: x = A.basis()[Partition([3,2,1])] + sage: grA(x) + Bbar[[3, 2, 1]] + + Associated graded algebra of a filtered algebra:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.category() + Category of graded algebras with basis over Rational Field + sage: x,y,z = map(lambda s: grA.algebra_generators()[s], ['x','y','z']) + sage: x + bar(U['x']) + sage: y * x + z + bar(U['x']*U['y']) + bar(U['z']) + sage: A(y) * A(x) + A(z) + U['x']*U['y'] + + We note that the conversion between ``A`` and ``grA`` is + the canonical ``QQ``-module isomorphism stemming from the + fact that the underlying ``QQ``-modules of ``A`` and + ``grA`` are isomorphic:: + + sage: grA(A.an_element()) + bar(U['x']^2*U['y']^2*U['z']^3) + sage: elt = A.an_element() + A.algebra_generators()['x'] + 2 + sage: grelt = grA(elt); grelt + bar(U['x']^2*U['y']^2*U['z']^3) + bar(U['x']) + 2*bar(1) + sage: A(grelt) == elt + True + + .. TODO:: + + The algebra ``A`` must currently be an instance of (a subclass of) + :class:`CombinatorialFreeModule`. This should work with any + filtered algebra with a basis. + + .. TODO:: + + Implement a version of associated graded algebra for + filtered algebras without a distinguished basis. + + REFERENCES: + + - :wikipedia:`Filtered_algebra#Associated_graded_algebra` + """ + def __init__(self, A, category=None): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: TestSuite(grA).run(elements=[prod(grA.algebra_generators())]) + """ + if A not in ModulesWithBasis(A.base_ring().category()).Filtered(): + raise ValueError("the base algebra must be filtered and with basis") + self._A = A + + base_ring = A.base_ring() + base_one = base_ring.one() + + category = A.category().Graded().or_subcategory(category) + try: + opts = copy(A.print_options()) + if not opts['prefix'] and not opts['bracket']: + opts['bracket'] = '(' + opts['prefix'] = opts['prefix'] + 'bar' + except AttributeError: + opts = {'prefix': 'Abar'} + + CombinatorialFreeModule.__init__(self, base_ring, A.basis().keys(), + category=category, **opts) + + # Setup the conversion back + phi = self.module_morphism(diagonal=lambda x: base_one, codomain=A) + self._A.register_conversion(phi) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: A.graded_algebra() + Graded Algebra of An example of a filtered algebra with basis: + the universal enveloping algebra of Lie algebra of RR^3 + with cross product over Rational Field + """ + from sage.categories.algebras_with_basis import AlgebrasWithBasis + if self in AlgebrasWithBasis: + return "Graded Algebra of {}".format(self._A) + return "Graded Module of {}".format(self._A) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: latex(A.graded_algebra()) + \operatorname{gr} ... + """ + from sage.misc.latex import latex + return "\\operatorname{gr} " + latex(self._A) + + def _element_constructor_(self, x): + r""" + Construct an element of ``self`` from ``x``. + + If ``self`` `= \operatorname{gr} A` for a filtered algebra + `A` with basis, and if ``x`` is an element of `A`, then + this returns the image of `x` under the canonical `R`-module + isomorphism `A \to \operatorname{gr} A`. (In this case, + this is equivalent to calling + ``self.to_graded_conversion()(x)``.) + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA(A.an_element()) + bar(U['x']^2*U['y']^2*U['z']^3) + sage: grA(A.an_element() + A.algebra_generators()['x'] + 2) + bar(U['x']^2*U['y']^2*U['z']^3) + bar(U['x']) + 2*bar(1) + """ + if isinstance(x, CombinatorialFreeModule.Element): + if x.parent() is self._A: + return self._from_dict(dict(x)) + return super(AssociatedGradedAlgebra, self)._element_constructor_(x) + + def gen(self, *args, **kwds): + """ + Return a generator of ``self``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.gen('x') + bar(U['x']) + """ + try: + x = self._A.gen(*args, **kwds) + except AttributeError: + x = self._A.algebra_generators()[args[0]] + return self(x) + + @cached_method + def algebra_generators(self): + """ + Return the algebra generators of ``self``. + + This assumes that the algebra generators of `A` provided by + its ``algebra_generators`` method are homogeneous. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.algebra_generators() + Finite family {'y': bar(U['y']), 'x': bar(U['x']), 'z': bar(U['z'])} + """ + G = self._A.algebra_generators() + return Family(G.keys(), lambda x: self(G[x]), name="generator") + + def degree_on_basis(self, x): + """ + Return the degree of the basis element indexed by ``x``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: all(A.degree_on_basis(x) == grA.degree_on_basis(x) + ....: for g in grA.algebra_generators() for x in g.support()) + True + """ + return self._A.degree_on_basis(x) + + @cached_method + def one_basis(self): + """ + Return the basis index of the element `1` of + `\operatorname{gr} A`. + + This assumes that the unity `1` of `A` belongs to `F_0`. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.one_basis() + 1 + """ + return self._A.one_basis() + + def product_on_basis(self, x, y): + """ + Return the product on basis elements given by the + indices ``x`` and ``y``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: G = grA.algebra_generators() + sage: x,y,z = G['x'], G['y'], G['z'] + sage: x * y # indirect doctest + bar(U['x']*U['y']) + sage: y * x + bar(U['x']*U['y']) + sage: z * y * x + bar(U['x']*U['y']*U['z']) + """ + ret = self._A.product_on_basis(x, y) + deg = self._A.degree_on_basis(x) + self._A.degree_on_basis(y) + return self.sum_of_terms([(i,c) for i,c in ret + if self._A.degree_on_basis(i) == deg], + distinct=True) + diff --git a/src/sage/algebras/catalog.py b/src/sage/algebras/catalog.py index da062022b5d..06910534633 100644 --- a/src/sage/algebras/catalog.py +++ b/src/sage/algebras/catalog.py @@ -16,6 +16,7 @@ - :class:`algebras.FiniteDimensional ` - :class:`algebras.Free ` +- :class:`algebras.FreeZinbiel ` - :class:`algebras.PreLieAlgebra ` - :func:`algebras.GradedCommutative ` @@ -24,6 +25,7 @@ - :class:`algebras.Incidence ` - :class:`algebras.IwahoriHecke ` +- :class:`algebras.Mobius ` - :class:`algebras.Jordan ` - :class:`algebras.NilCoxeter @@ -48,12 +50,14 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.algebras.nil_coxeter_algebra', 'NilCoxeterAlgebra', 'NilCoxeter') +lazy_import('sage.algebras.free_zinbiel_algebra', 'FreeZinbielAlgebra', 'FreeZinbiel') lazy_import('sage.algebras.hall_algebra', 'HallAlgebra', 'Hall') lazy_import('sage.algebras.jordan_algebra', 'JordanAlgebra', 'Jordan') lazy_import('sage.algebras.shuffle_algebra', 'ShuffleAlgebra', 'Shuffle') lazy_import('sage.algebras.schur_algebra', 'SchurAlgebra', 'Schur') lazy_import('sage.algebras.commutative_dga', 'GradedCommutativeAlgebra', 'GradedCommutative') lazy_import('sage.combinat.posets.incidence_algebras', 'IncidenceAlgebra', 'Incidence') +lazy_import('sage.combinat.posets.mobius_algebra', 'MobiusAlgebra', 'Mobius') lazy_import('sage.combinat.free_prelie_algebra', 'FreePreLieAlgebra', 'FreePreLie') del lazy_import # We remove the object from here so it doesn't appear under tab completion diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index b419d985be3..0e570434890 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -18,8 +18,7 @@ from copy import copy from sage.categories.algebras_with_basis import AlgebrasWithBasis -from sage.categories.graded_algebras_with_basis import GradedAlgebrasWithBasis -from sage.categories.graded_hopf_algebras_with_basis import GradedHopfAlgebrasWithBasis +from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis from sage.modules.with_basis.morphism import ModuleMorphismByLinearity from sage.categories.poor_man_map import PoorManMap from sage.rings.all import ZZ @@ -423,6 +422,14 @@ class CliffordAlgebra(CombinatorialFreeModule): canonical isomorphism. The inclusion `i` is commonly used to identify `V` with a vector subspace of `Cl(V)`. + The Clifford algebra `Cl(V, Q)` is a `\ZZ_2`-graded algebra + (where `\ZZ_2 = \ZZ / 2 \ZZ`); this grading is determined by + placing all elements of `V` in degree `1`. It is also an + `\NN`-filtered algebra, with the filtration too being defined + by placing all elements of `V` in degree `1`. The :meth:`degree` gives + the `\NN`-*filtration* degree, and to get the super degree use instead + :meth:`~sage.categories.super_modules.SuperModules.ElementMethods.is_even_odd`. + The Clifford algebra also can be considered as a covariant functor from the category of vector spaces equipped with quadratic forms to the category of algebras. In fact, if `(V, Q)` and `(W, R)` @@ -465,7 +472,8 @@ class CliffordAlgebra(CombinatorialFreeModule): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: Cl = CliffordAlgebra(Q) sage: Cl - The Clifford algebra of the Quadratic form in 3 variables over Integer Ring with coefficients: + The Clifford algebra of the Quadratic form in 3 variables + over Integer Ring with coefficients: [ 1 2 3 ] [ * 4 5 ] [ * * 6 ] @@ -480,11 +488,6 @@ class CliffordAlgebra(CombinatorialFreeModule): a*d sage: d*c*b*a + a + 4*b*c a*b*c*d + 4*b*c + a - - .. WARNING:: - - The Clifford algebra is not graded, but instead filtered. This - will be changed once :trac:`17096` is finished. """ @staticmethod def __classcall_private__(cls, Q, names=None): @@ -520,6 +523,9 @@ def __init__(self, Q, names, category=None): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: Cl = CliffordAlgebra(Q) + sage: Cl.category() + Category of finite dimensional super algebras with basis over + (euclidean domains and infinite enumerated sets and metric spaces) sage: TestSuite(Cl).run() TESTS: @@ -536,8 +542,7 @@ def __init__(self, Q, names, category=None): """ self._quadratic_form = Q R = Q.base_ring() - if category is None: - category = GradedAlgebrasWithBasis(R) + category = AlgebrasWithBasis(R.category()).Super().Filtered().or_subcategory(category) indices = SubsetsSorted(range(Q.dim())) CombinatorialFreeModule.__init__(self, R, indices, category=category) self._assign_names(names) @@ -550,7 +555,8 @@ def _repr_(self): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: CliffordAlgebra(Q) - The Clifford algebra of the Quadratic form in 3 variables over Integer Ring with coefficients: + The Clifford algebra of the Quadratic form in 3 variables + over Integer Ring with coefficients: [ 1 2 3 ] [ * 4 5 ] [ * * 6 ] @@ -826,7 +832,7 @@ def quadratic_form(self): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: Cl. = CliffordAlgebra(Q) sage: Cl.quadratic_form() - Quadratic form in 3 variables over Integer Ring with coefficients: + Quadratic form in 3 variables over Integer Ring with coefficients: [ 1 2 3 ] [ * 4 5 ] [ * * 6 ] @@ -837,19 +843,8 @@ def degree_on_basis(self, m): r""" Return the degree of the monomial indexed by ``m``. - This degree is a nonnegative integer, and should be interpreted - as a residue class modulo `2`, since we consider ``self`` to be - `\ZZ_2`-graded (not `\ZZ`-graded, although there is a natural - *filtration* by the length of ``m``). The degree of the monomial - ``m`` in this `\ZZ_2`-grading is defined to be the length of ``m`` - taken mod `2`. - - .. WARNING: - - On the :class:`ExteriorAlgebra` class (which inherits from - :class:`CliffordAlgebra`), the :meth:`degree_on_basis` - method is overridden to return an actual `\NN`-degree. So - don't count on this method always returning `0` or `1` !! + We are considering the Clifford algebra to be `\NN`-filtered, + and the degree of the monomial ``m`` is the length of ``m``. EXAMPLES:: @@ -858,9 +853,22 @@ def degree_on_basis(self, m): sage: Cl.degree_on_basis((0,)) 1 sage: Cl.degree_on_basis((0,1)) - 0 + 2 """ - return len(m) % ZZ(2) + return ZZ(len(m)) + + def graded_algebra(self): + """ + Return the associated graded algebra of ``self``. + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: Cl.graded_algebra() + The exterior algebra of rank 3 over Integer Ring + """ + return ExteriorAlgebra(self.base_ring(), self.variable_names()) @cached_method def free_module(self): @@ -1051,7 +1059,7 @@ def lift_module_morphism(self, m, names=None): remove_zeros=True ) for i in x) return Cl.module_morphism(on_basis=f, codomain=self, - category=GradedAlgebrasWithBasis(self.base_ring())) + category=AlgebrasWithBasis(self.base_ring()).Super()) def lift_isometry(self, m, names=None): r""" @@ -1116,7 +1124,7 @@ def lift_isometry(self, m, names=None): remove_zeros=True ) for i in x) return self.module_morphism(on_basis=f, codomain=Cl, - category=GradedAlgebrasWithBasis(self.base_ring())) + category=AlgebrasWithBasis(self.base_ring()).Super()) # This is a general method for finite dimensional algebras with bases # and should be moved to the corresponding category once there is @@ -1339,7 +1347,7 @@ class ExteriorAlgebra(CliffordAlgebra): `Q(v) = 0` for all vectors `v \in V`. See :class:`CliffordAlgebra` for the notion of a Clifford algebra. - The exterior algebra of an `R`-module `V` is a `\ZZ`-graded connected + The exterior algebra of an `R`-module `V` is a connected `\ZZ`-graded Hopf superalgebra. It is commutative in the super sense (i.e., the odd elements anticommute and square to `0`). @@ -1353,10 +1361,6 @@ class ExteriorAlgebra(CliffordAlgebra): Hopf superalgebra with the odd-degree components forming the odd part. So use Hopf-algebraic methods with care! - .. TODO:: - - Add a category for Hopf superalgebras (perhaps part of :trac:`16513`). - INPUT: - ``R`` -- the base ring, *or* the free module whose exterior algebra @@ -1417,9 +1421,13 @@ def __init__(self, R, names): EXAMPLES:: sage: E. = ExteriorAlgebra(QQ) + sage: E.category() + Category of finite dimensional super hopf algebras with basis + over Rational Field sage: TestSuite(E).run() """ - CliffordAlgebra.__init__(self, QuadraticForm(R, len(names)), names, GradedHopfAlgebrasWithBasis(R)) + cat = HopfAlgebrasWithBasis(R).Super() + CliffordAlgebra.__init__(self, QuadraticForm(R, len(names)), names, cat) # TestSuite will fail if the HopfAlgebra classes will ever have tests for # the coproduct being an algebra morphism -- since this is really a # Hopf superalgebra, not a Hopf algebra. @@ -1563,7 +1571,7 @@ def lift_morphism(self, phi, names=None): f = lambda x: E.prod(E._from_dict( {(j,): phi[j,i] for j in range(n)}, remove_zeros=True ) for i in x) - return self.module_morphism(on_basis=f, codomain=E, category=GradedAlgebrasWithBasis(R)) + return self.module_morphism(on_basis=f, codomain=E, category=AlgebrasWithBasis(R).Super()) def volume_form(self): """ @@ -1830,9 +1838,9 @@ def lifted_bilinear_form(self, M): sage: M = Matrix(QQ, [[1, 2, 3], [2, 3, 4], [3, 4, 5]]) sage: Eform = E.lifted_bilinear_form(M) sage: Eform - Bilinear Form from Cartesian product of The exterior algebra of rank 3 over - Rational Field, The exterior algebra of rank 3 over Rational Field to - Rational Field + Bilinear Form from The exterior algebra of rank 3 over Rational + Field (+) The exterior algebra of rank 3 over Rational Field to + Rational Field sage: Eform(x*y, y*z) -1 sage: Eform(x*y, y) @@ -1903,8 +1911,8 @@ def lifted_form(x, y): # typing (:trac:`17124`). result += cx * cy * matr.determinant() return result - from sage.combinat.cartesian_product import CartesianProduct - return PoorManMap(lifted_form, domain=CartesianProduct(self, self), + from sage.categories.cartesian_product import cartesian_product + return PoorManMap(lifted_form, domain=cartesian_product([self, self]), codomain=self.base_ring(), name="Bilinear Form") diff --git a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.py b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.py index 898ceabe337..c17d4be8f4c 100644 --- a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.py +++ b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.py @@ -134,6 +134,28 @@ def matrix(self): """ return self._matrix + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements in + the support of ``self`` and whose values are the corresponding + coefficients. + + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + + EXAMPLES:: + + sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0], [0,1]]), Matrix([[0,1], [-1,0]])]) + sage: elt = B(Matrix([[1,1], [-1,1]])) + sage: elt.monomial_coefficients() + {0: 1, 1: 1} + """ + return self._vector.dict(copy) + def left_matrix(self): """ Return the matrix for multiplication by ``self`` from the left. diff --git a/src/sage/algebras/free_zinbiel_algebra.py b/src/sage/algebras/free_zinbiel_algebra.py new file mode 100644 index 00000000000..9c00304eea7 --- /dev/null +++ b/src/sage/algebras/free_zinbiel_algebra.py @@ -0,0 +1,253 @@ +""" +Free Zinbiel Algebras + +AUTHORS: + +- Travis Scrimshaw (2015-09): initial version +""" + +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.categories.magmatic_algebras import MagmaticAlgebras +from sage.categories.rings import Rings +from sage.combinat.free_module import CombinatorialFreeModule +from sage.combinat.words.words import Words +from sage.combinat.words.alphabet import Alphabet +from sage.sets.family import Family + +class FreeZinbielAlgebra(CombinatorialFreeModule): + r""" + The free Zinbiel algebra on `n` generators. + + Let `R` be a ring. A *Zinbiel algebra* is a non-associative + algebra with multiplication `\circ` that satisfies + + .. MATH:: + + a \circ (b \circ c) = a \circ (b \circ c) + a \circ (c \circ b). + + Zinbiel algebras were first introduced by Loday as the Koszul + dual to Leibniz algebras (hence the name coined by Lemaire). + + Zinbiel algebras are divided power algebras, in that for + + .. MATH:: + + x^{\circ n} = \bigl(x \circ (x \circ \cdots \circ( x \circ x) \cdots + ) \bigr) + + we have + + .. MATH:: + + x^{\circ m} \circ x^{\circ n} = \binom{n+m-1}{m} x^{n+m} + + and + + .. MATH:: + + \underbrace{\bigl( ( x \circ \cdots \circ x \circ (x \circ x) \cdots + ) \bigr)}_{n+1 \text{ times}} = n! x^n. + + .. NOTE:: + + This implies that Zinbiel algebras are not power associative. + + To every Zinbiel algebra, we can construct a corresponding commutative + associative algebra by using the symmetrized product: + + .. MATH:: + + a * b = a \circ b + b \circ a. + + The free Zinbiel algebra on `n` generators is isomorphic as `R`-modules + to the reduced tensor algebra `\bar{T}(R^n)` with the product + + .. MATH:: + + (x_0 x_1 \cdots x_p) \circ (x_{p+1} x_{p+2} \cdots x_{p+q}) + = \sum_{\sigma \in S_{p,q}} x_0 (x_{\sigma(1)} x_{\sigma(2)} + \cdots x_{\sigma(p+q)}, + + where `S_{p,q}` is the set of `(p,q)`-shuffles. + + The free Zinbiel algebra is free as a divided power algebra. Moreover, + the corresponding commutative algebra is isomorphic to the (non-unital) + shuffle algebra. + + INPUT: + + - ``R`` -- a ring + - ``n`` -- (optional) the number of generators + - ``names`` -- the generator names + + .. WARNING:: + + Currently the basis is indexed by all words over the variables, + incuding the empty word. This is a slight abuse as it is suppose + to be the indexed by all non-empty words. + + EXAMPLES: + + We create the free Zinbiel algebra and check the defining relation:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: (x*y)*z + Z[xyz] + Z[xzy] + sage: x*(y*z) + x*(z*y) + Z[xyz] + Z[xzy] + + We see that the Zinbiel algebra is not associative, nor even + power associative:: + + sage: x*(y*z) + Z[xyz] + sage: x*(x*x) + Z[xxx] + sage: (x*x)*x + 2*Z[xxx] + + We verify that it is a divided powers algebra:: + + sage: (x*(x*x)) * (x*(x*(x*x))) + 15*Z[xxxxxxx] + sage: binomial(3+4-1,4) + 15 + sage: (x*(x*(x*x))) * (x*(x*x)) + 20*Z[xxxxxxx] + sage: binomial(3+4-1,3) + 20 + sage: ((x*x)*x)*x + 6*Z[xxxx] + sage: (((x*x)*x)*x)*x + 24*Z[xxxxx] + + REFERENCES: + + - :wikipedia:`Zinbiel_algebra` + + .. [Loday95] Jean-Louis Loday. + *Cup-product for Leibniz cohomology and dual Leibniz algebras*. + Math. Scand., pp. 189--196 (1995). + http://www.math.uiuc.edu/K-theory/0015/cup_product.pdf + .. [LV12] Jean-Louis Loday and Bruno Vallette. *Algebraic Operads*. + Springer-Verlag Berlin Heidelberg (2012). + :doi:`10.1007/978-3-642-30362-3`. + """ + @staticmethod + def __classcall_private__(cls, R, n=None, names=None): + """ + Standardize input to ensure a unqiue representation. + + TESTS:: + + sage: Z1. = algebras.FreeZinbiel(QQ) + sage: Z2. = algebras.FreeZinbiel(QQ, 3) + sage: Z3 = algebras.FreeZinbiel(QQ, 3, 'x,y,z') + sage: Z4. = algebras.FreeZinbiel(QQ, 'x,y,z') + sage: Z1 is Z2 and Z1 is Z3 and Z1 is Z4 + True + """ + if isinstance(n, (list,tuple)): + names = n + n = len(names) + elif isinstance(n, str): + names = n.split(',') + n = len(names) + elif isinstance(names, str): + names = names.split(',') + elif n is None: + n = len(names) + return super(FreeZinbielAlgebra, cls).__classcall__(cls, R, n, tuple(names)) + + def __init__(self, R, n, names): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: TestSuite(Z).run() + """ + if R not in Rings: + raise TypeError("argument R must be a ring") + indices = Words(Alphabet(n, names=names)) + cat = MagmaticAlgebras(R).WithBasis() + self._n = n + CombinatorialFreeModule.__init__(self, R, indices, prefix='Z', + category=cat) + self._assign_names(names) + + def _repr_term(self, t): + """ + Return a string representation of the basis element indexed by ``t``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: Z._repr_term(Z._indices('xyzxxy')) + 'Z[xyzxxy]' + """ + return "{!s}[{!s}]".format(self._print_options['prefix'], repr(t)[6:]) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: Z + Free Zinbiel algebra on generators (Z[x], Z[y], Z[z]) over Rational Field + """ + return "Free Zinbiel algebra on generators {} over {}".format( + self.gens(), self.base_ring()) + + @cached_method + def algebra_generators(self): + """ + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: list(Z.algebra_generators()) + [Z[x], Z[y], Z[z]] + """ + A = self.variable_names() + return Family( A, lambda g: self.monomial(self._indices(g)) ) + + @cached_method + def gens(self): + """ + Return the generators of ``self``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: Z.gens() + (Z[x], Z[y], Z[z]) + """ + return tuple(self.algebra_generators()) + + def product_on_basis(self, x, y): + """ + Return the product of the basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: (x*y)*z # indirect doctest + Z[xyz] + Z[xzy] + """ + if not x: + return self.monomial(y) + x0 = self._indices(x[0]) + return self.sum_of_monomials(x0 + sh for sh in x[1:].shuffle(y)) + diff --git a/src/sage/algebras/jordan_algebra.py b/src/sage/algebras/jordan_algebra.py index d434996437b..0f024e61113 100644 --- a/src/sage/algebras/jordan_algebra.py +++ b/src/sage/algebras/jordan_algebra.py @@ -556,6 +556,30 @@ def _rmul_(self, other): """ return self.__class__(self.parent(), other * self._x) + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements in + the support of ``self`` and whose values are the corresponding + coefficients. + + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + + EXAMPLES:: + + sage: F. = FreeAlgebra(QQ) + sage: J = JordanAlgebra(F) + sage: a,b,c = map(J, F.gens()) + sage: elt = a + 2*b - c + sage: elt.monomial_coefficients() + {x: 1, y: 2, z: -1} + """ + return self._x.monomial_coefficients(copy) + class JordanAlgebraSymmetricBilinear(JordanAlgebra): r""" A Jordan algebra given by a symmetric bilinear form `m`. @@ -935,6 +959,29 @@ def _rmul_(self, other): """ return self.__class__(self.parent(), other * self._s, other * self._v) + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements in + the support of ``self`` and whose values are the corresponding + coefficients. + + INPUT: + + - ``copy`` -- ignored + + EXAMPLES:: + + sage: m = matrix([[0,1],[1,1]]) + sage: J. = JordanAlgebra(m) + sage: elt = a + 2*b - c + sage: elt.monomial_coefficients() + {0: 1, 1: 2, 2: -1} + """ + d = {0: self._s} + for i,c in enumerate(self._v): + d[i+1] = c + return d + def trace(self): r""" Return the trace of ``self``. diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index e42bee8ae05..b591d5a18ee 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1735,7 +1735,17 @@ def ternary_quadratic_form(self, include_basis=False): return Q class QuaternionFractionalIdeal(Ideal_fractional): - pass + def __hash__(self): + r""" + Stupid constant hash function! + + TESTS:: + + sage: R = QuaternionAlgebra(-11,-1).maximal_order() + sage: hash(R.right_ideal(R.basis())) + 0 + """ + return 0 class QuaternionFractionalIdeal_rational(QuaternionFractionalIdeal): """ diff --git a/src/sage/algebras/schur_algebra.py b/src/sage/algebras/schur_algebra.py index 7ef9cfabbde..f3092e9cf3f 100644 --- a/src/sage/algebras/schur_algebra.py +++ b/src/sage/algebras/schur_algebra.py @@ -21,20 +21,24 @@ .. [GreenPoly] J. Green, Polynomial representations of `GL_n`, Springer Verlag. """ + #***************************************************************************** -# Copyright (C) 2010 Eric Webster -# Copyright (C) 2011 Hugh Thomas (hugh.ross.thomas@gmail.com) +# Copyright (C) 2010 Eric Webster +# Copyright (C) 2011 Hugh Thomas # -# Distributed under the terms of the GNU General Public License (GPL) +# 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 itertools from sage.categories.all import AlgebrasWithBasis from sage.categories.rings import Rings from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModule_Tensor -from sage.combinat.cartesian_product import CartesianProduct -from sage.combinat.integer_list import IntegerListsLex +from sage.combinat.integer_lists import IntegerListsLex from sage.combinat.partition import Partitions, Partition from sage.combinat.permutation import Permutations from sage.combinat.sf.sf import SymmetricFunctions @@ -465,7 +469,7 @@ def _monomial_product(self, xi, v): B[1] # B[1] # B[2] + B[1] # B[2] # B[1] + B[2] # B[1] # B[1] """ ret = [] - for i in CartesianProduct(*[range(1, self._n + 1)] * self._r): + for i in itertools.product(range(1, self._n + 1), repeat=self._r): if schur_representative_from_index(i, v) == xi: ret.append(tuple(i)) return self.sum_of_monomials(ret) diff --git a/src/sage/algebras/steenrod/steenrod_algebra.py b/src/sage/algebras/steenrod/steenrod_algebra.py index de25ebb1564..e98c6d2474e 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra.py +++ b/src/sage/algebras/steenrod/steenrod_algebra.py @@ -404,12 +404,13 @@ 1 (2, 1) sage: c.monomial_coefficients() {(2, 1): 1, (5,): 1} - sage: c.monomials() + sage: sorted(c.monomials(), key=lambda x: x.support()) [Sq(2,1), Sq(5)] - sage: c.support() + sage: sorted(c.support()) [(2, 1), (5,)] sage: Adem = SteenrodAlgebra(basis='adem') - sage: (Adem.Sq(10) + Adem.Sq(9) * Adem.Sq(1)).monomials() + sage: elt = Adem.Sq(10) + Adem.Sq(9) * Adem.Sq(1) + sage: sorted(elt.monomials(), key=lambda x: x.support()) [Sq^9 Sq^1, Sq^10] sage: A7 = SteenrodAlgebra(p=7) @@ -1567,13 +1568,13 @@ def counit_on_basis(self, t): return self.base_ring().one() def _milnor_on_basis(self, t): - """ - Convert the tuple t in the current basis to an element in the + r""" + Convert the tuple ``t`` in the current basis to an element in the Milnor basis. INPUT: - - t - tuple, representing basis element in the current basis. + - ``t`` - tuple, representing basis element in the current basis. OUTPUT: element of the Steenrod algebra with the Milnor basis @@ -3115,9 +3116,9 @@ class Element(CombinatorialFreeModuleElement): 1 (2, 1) sage: c.monomial_coefficients() {(2, 1): 1, (5,): 1} - sage: c.monomials() + sage: sorted(c.monomials(), key=lambda x: x.support()) [Sq(2,1), Sq(5)] - sage: c.support() + sage: sorted(c.support()) [(2, 1), (5,)] See the documentation for this module (type @@ -3451,8 +3452,8 @@ def excess(self): OUTPUT: ``excess`` - non-negative integer The excess of a Milnor basis element `\text{Sq}(a,b,c,...)` is - `a + b + c + ...`. When `p` is odd, the excess of `Q_{0}^{e_0} - Q_{1}^{e_1} ... P(r_1, r_2, ...)` is `\sum e_i + 2 \sum r_i`. + `a + b + c + \cdots`. When `p` is odd, the excess of `Q_{0}^{e_0} + Q_{1}^{e_1} \cdots P(r_1, r_2, ...)` is `\sum e_i + 2 \sum r_i`. The excess of a linear combination of Milnor basis elements is the minimum of the excesses of those basis elements. @@ -3470,9 +3471,11 @@ def excess(self): 6 sage: (Sq(0,0,1) + Sq(4,1) + Sq(7)).excess() 1 - sage: [m.excess() for m in (Sq(0,0,1) + Sq(4,1) + Sq(7)).monomials()] + sage: elt = Sq(0,0,1) + Sq(4,1) + Sq(7) + sage: M = sorted(elt.monomials(), key=lambda x: x.support()) + sage: [m.excess() for m in M] [1, 5, 7] - sage: [m for m in (Sq(0,0,1) + Sq(4,1) + Sq(7)).monomials()] + sage: [m for m in M] [Sq(0,0,1), Sq(4,1), Sq(7)] sage: B = SteenrodAlgebra(7) sage: a = B.Q(1,2,5) @@ -3495,7 +3498,7 @@ def excess_odd(mono): of factors, plus twice the sum of the terms in the second component. """ - if len(mono) == 0: + if not mono: return 0 else: return len(mono[0]) + 2 * sum(mono[1]) diff --git a/src/sage/algebras/weyl_algebra.py b/src/sage/algebras/weyl_algebra.py index 366b6bd99f2..af3f004c2aa 100644 --- a/src/sage/algebras/weyl_algebra.py +++ b/src/sage/algebras/weyl_algebra.py @@ -402,10 +402,39 @@ def _lmul_(self, other): M = self.__monomials return self.__class__(self.parent(), {t: M[t]*other for t in M}) + def monomial_coefficients(self, copy=True): + """ + Return a dictionary which has the basis keys in the support + of ``self`` as keys and their corresponding coefficients + as values. + + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + + EXAMPLES:: + + sage: W. = DifferentialWeylAlgebra(QQ) + sage: dx,dy,dz = W.differentials() + sage: elt = (dy - (3*x - z)*dx) + sage: sorted(elt.monomial_coefficients().items()) + [(((0, 0, 0), (0, 1, 0)), 1), + (((0, 0, 1), (1, 0, 0)), 1), + (((1, 0, 0), (1, 0, 0)), -3)] + """ + if copy: + return dict(self.__monomials) + return self.__monomials + def __iter__(self): """ Return an iterator of ``self``. + This is the iterator of ``self.list()``. + EXAMPLES:: sage: W. = DifferentialWeylAlgebra(QQ) @@ -421,6 +450,11 @@ def list(self): """ Return ``self`` as a list. + This list consists of pairs `(m, c)`, where `m` is a pair of + tuples indexing a basis element of ``self``, and `c` is the + coordinate of ``self`` corresponding to this basis element. + (Only nonzero coordinates are shown.) + EXAMPLES:: sage: W. = DifferentialWeylAlgebra(QQ) @@ -434,6 +468,23 @@ def list(self): return sorted(self.__monomials.items(), key=lambda x: (-sum(x[0][1]), x[0][1], -sum(x[0][0]), x[0][0]) ) + def support(self): + """ + Return the support of ``self``. + + EXAMPLES:: + + sage: W. = DifferentialWeylAlgebra(QQ) + sage: dx,dy,dz = W.differentials() + sage: elt = dy - (3*x - z)*dx + 1 + sage: elt.support() + [((0, 0, 0), (0, 1, 0)), + ((1, 0, 0), (1, 0, 0)), + ((0, 0, 0), (0, 0, 0)), + ((0, 0, 1), (1, 0, 0))] + """ + return self.__monomials.keys() + # This is essentially copied from # sage.combinat.free_module.CombinatorialFreeModuleElement def __div__(self, x, self_on_left=False): @@ -527,6 +578,11 @@ class DifferentialWeylAlgebra(Algebra, UniqueRepresentation): sage: W. = DifferentialWeylAlgebra(QQ); W Differential Weyl algebra of polynomials in a, b over Rational Field + + .. TODO:: + + Implement the :meth:`graded_algebra` as a polynomial ring once + they are considered to be graded rings (algebras). """ @staticmethod def __classcall__(cls, R, names=None): @@ -567,7 +623,12 @@ def __init__(self, R, names=None): raise ValueError("variable names cannot differ by a leading 'd'") # TODO: Make this into a filtered algebra under the natural grading of # x_i and dx_i have degree 1 - Algebra.__init__(self, R, names, category=AlgebrasWithBasis(R).NoZeroDivisors()) + # Filtered is not included because it is a supercategory of super + if R.is_field(): + cat = AlgebrasWithBasis(R).NoZeroDivisors().Super() + else: + cat = AlgebrasWithBasis(R).Super() + Algebra.__init__(self, R, names, category=cat) def _repr_(self): r""" @@ -661,6 +722,24 @@ def _coerce_map_from_(self, R): and self.base_ring().has_coerce_map_from(R.base_ring()) ) return super(DifferentialWeylAlgebra, self)._coerce_map_from_(R) + def degree_on_basis(self, i): + """ + Return the degree of the basis element indexed by ``i``. + + EXAMPLES:: + + sage: W. = DifferentialWeylAlgebra(QQ) + sage: W.degree_on_basis( ((1, 3, 2), (0, 1, 3)) ) + 10 + + sage: W. = DifferentialWeylAlgebra(QQ) + sage: dx,dy,dz = W.differentials() + sage: elt = y*dy - (3*x - z)*dx + sage: elt.degree() + 2 + """ + return sum(i[0]) + sum(i[1]) + def polynomial_ring(self): """ Return the associated polynomial ring of ``self``. @@ -693,13 +772,21 @@ def basis(self): sage: [next(it) for i in range(20)] [1, x, y, dx, dy, x^2, x*y, x*dx, x*dy, y^2, y*dx, y*dy, dx^2, dx*dy, dy^2, x^3, x^2*y, x^2*dx, x^2*dy, x*y^2] + sage: dx, dy = W.differentials() + sage: (dx*x).monomials() + [1, x*dx] + sage: B[(x*y).support()[0]] + x*y + sage: sorted((dx*x).monomial_coefficients().items()) + [(((0, 0), (0, 0)), 1), (((1, 0), (1, 0)), 1)] """ n = self._n - # TODO in #17927: use IntegerVectors(length=2*n) - from sage.combinat.integer_list import IntegerListsNN - I = IntegerListsNN(length=n*2) + from sage.combinat.integer_lists.nn import IntegerListsNN + from sage.categories.cartesian_product import cartesian_product + elt_map = lambda u : (tuple(u[:n]), tuple(u[n:])) + I = IntegerListsNN(length=2*n, element_constructor=elt_map) one = self.base_ring().one() - f = lambda x: self.element_class(self, {(tuple(x[:n]),tuple(x[n:])): one}) + f = lambda x: self.element_class(self, {(x[0], x[1]): one}) return Family(I, f, name="basis map") @cached_method diff --git a/src/sage/all.py b/src/sage/all.py index 95e0c36e8b5..3ddfe089bff 100644 --- a/src/sage/all.py +++ b/src/sage/all.py @@ -106,6 +106,7 @@ from sage.monoids.all import * from sage.algebras.all import * from sage.modular.all import * +from sage.sat.all import * from sage.schemes.all import * from sage.graphs.all import * from sage.groups.all import * diff --git a/src/sage/categories/additive_magmas.py b/src/sage/categories/additive_magmas.py index e84ede20193..3f990e84a0a 100644 --- a/src/sage/categories/additive_magmas.py +++ b/src/sage/categories/additive_magmas.py @@ -711,9 +711,11 @@ def _test_zero(self, **options): tester.assert_(self.is_parent_of(zero)) for x in tester.some_elements(): tester.assert_(x + zero == x) - # Check that zero is immutable by asking its hash: - tester.assertEqual(type(zero.__hash__()), int) - tester.assertEqual(zero.__hash__(), zero.__hash__()) + # Check that zero is immutable if it looks like we can: + if hasattr(zero,"is_immutable"): + tester.assertEqual(zero.is_immutable(),True) + if hasattr(zero,"is_mutable"): + tester.assertEqual(zero.is_mutable(),False) # Check that bool behave consistently on zero tester.assertFalse(bool(self.zero())) diff --git a/src/sage/categories/additive_semigroups.py b/src/sage/categories/additive_semigroups.py index 7f9c2de9b5c..bb45bd19e8b 100644 --- a/src/sage/categories/additive_semigroups.py +++ b/src/sage/categories/additive_semigroups.py @@ -80,8 +80,8 @@ def _test_additive_associativity(self, **options): """ tester = self._tester(**options) S = tester.some_elements() - from sage.combinat.cartesian_product import CartesianProduct - for x,y,z in tester.some_elements(CartesianProduct(S,S,S)): + from sage.misc.misc import some_tuples + for x,y,z in some_tuples(S, 3, tester._max_runs): tester.assert_((x + y) + z == x + (y + z)) class Homsets(HomsetsCategory): diff --git a/src/sage/categories/algebras.py b/src/sage/categories/algebras.py index 5e81cee137a..8e1c4f9a1f2 100644 --- a/src/sage/categories/algebras.py +++ b/src/sage/categories/algebras.py @@ -109,7 +109,9 @@ def Semisimple(self): return self & SemisimpleAlgebras(self.base_ring()) Commutative = LazyImport('sage.categories.commutative_algebras', 'CommutativeAlgebras', at_startup=True) + Filtered = LazyImport('sage.categories.filtered_algebras', 'FilteredAlgebras') Graded = LazyImport('sage.categories.graded_algebras', 'GradedAlgebras') + Super = LazyImport('sage.categories.super_algebras', 'SuperAlgebras') WithBasis = LazyImport('sage.categories.algebras_with_basis', 'AlgebrasWithBasis') #if/when Semisimple becomes an axiom Semisimple = LazyImport('sage.categories.semisimple_algebras', 'SemisimpleAlgebras') diff --git a/src/sage/categories/algebras_with_basis.py b/src/sage/categories/algebras_with_basis.py index d4b63827895..ee0ad3865ae 100644 --- a/src/sage/categories/algebras_with_basis.py +++ b/src/sage/categories/algebras_with_basis.py @@ -117,8 +117,10 @@ def example(self, alphabet = ('a','b','c')): from sage.categories.examples.algebras_with_basis import Example return Example(self.base_ring(), alphabet) + Filtered = LazyImport('sage.categories.filtered_algebras_with_basis', 'FilteredAlgebrasWithBasis') FiniteDimensional = LazyImport('sage.categories.finite_dimensional_algebras_with_basis', 'FiniteDimensionalAlgebrasWithBasis') Graded = LazyImport('sage.categories.graded_algebras_with_basis', 'GradedAlgebrasWithBasis') + Super = LazyImport('sage.categories.super_algebras_with_basis', 'SuperAlgebrasWithBasis') class ParentMethods: @@ -198,7 +200,7 @@ class ElementMethods: def __invert__(self): """ - Returns the inverse of self if self is a multiple of one, + Return the inverse of ``self`` if ``self`` is a multiple of one, and one is in the basis of this algebra. Otherwise throws an error. @@ -207,6 +209,14 @@ def __invert__(self): inversed this way. It is correct though for graded connected algebras with basis. + .. WARNING:: + + This might produce a result which does not belong to + the parent of ``self``, yet believes to do so. For + instance, inverting 2 times the unity will produce 1/2 + times the unity, even if 1/2 is not in the base ring. + Handle with care. + EXAMPLES:: sage: C = AlgebrasWithBasis(QQ).example() @@ -222,17 +232,18 @@ def __invert__(self): ValueError: cannot invert self (= B[word: a]) """ # FIXME: make this generic - mcs = self._monomial_coefficients + mcs = self.monomial_coefficients(copy=False) one = self.parent().one_basis() if len(mcs) == 1 and one in mcs: - return self.parent()( ~mcs[ one ] ) + return self.parent().term(one, ~mcs[one]) else: raise ValueError("cannot invert self (= %s)"%self) class CartesianProducts(CartesianProductsCategory): """ - The category of algebras with basis, constructed as cartesian products of algebras with basis + The category of algebras with basis, constructed as cartesian + products of algebras with basis. Note: this construction give the direct products of algebras with basis. See comment in :class:`Algebras.CartesianProducts diff --git a/src/sage/categories/all.py b/src/sage/categories/all.py index cae2f9dec34..279174d751a 100644 --- a/src/sage/categories/all.py +++ b/src/sage/categories/all.py @@ -2,10 +2,11 @@ from category_types import( Elements, - SimplicialComplexes, ChainComplexes, ) +from sage.categories.simplicial_complexes import SimplicialComplexes + from tensor import tensor from cartesian_product import cartesian_product diff --git a/src/sage/categories/bialgebras.py b/src/sage/categories/bialgebras.py index e78f080ef18..fd7ff083689 100644 --- a/src/sage/categories/bialgebras.py +++ b/src/sage/categories/bialgebras.py @@ -11,6 +11,8 @@ from sage.categories.category_types import Category_over_base_ring from sage.categories.all import Algebras, Coalgebras +from sage.categories.super_modules import SuperModulesCategory +from sage.misc.lazy_import import LazyImport class Bialgebras(Category_over_base_ring): """ @@ -56,8 +58,8 @@ def additional_structure(self): """ return None - class ParentMethods: + class Super(SuperModulesCategory): pass - class ElementMethods: - pass + WithBasis = LazyImport('sage.categories.bialgebras_with_basis', 'BialgebrasWithBasis') + diff --git a/src/sage/categories/bialgebras_with_basis.py b/src/sage/categories/bialgebras_with_basis.py index 0487c0db3a3..d0fd23e870f 100644 --- a/src/sage/categories/bialgebras_with_basis.py +++ b/src/sage/categories/bialgebras_with_basis.py @@ -9,8 +9,12 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -def BialgebrasWithBasis(base_ring): - """ +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.modules_with_basis import ModulesWithBasis +from sage.categories.tensor import tensor + +class BialgebrasWithBasis(CategoryWithAxiom_over_base_ring): + r""" The category of bialgebras with a distinguished basis. EXAMPLES:: @@ -27,6 +31,378 @@ def BialgebrasWithBasis(base_ring): sage: TestSuite(BialgebrasWithBasis(ZZ)).run() """ - from sage.categories.all import Bialgebras - return Bialgebras(base_ring).WithBasis() + class ParentMethods: + + def convolution_product(self, *maps): + r""" + Return the convolution product (a map) of the given maps. + + Let `A` and `B` be bialgebras over a commutative ring `R`. + Given maps `f_i : A \to B` for `1 \leq i < n`, define the + convolution product + + .. MATH:: + + (f_1 * f_2 * \cdots * f_n) := \mu^{(n-1)} \circ (f_1 \otimes + f_2 \otimes \cdots \otimes f_n) \circ \Delta^{(n-1)}, + + where `\Delta^{(k)} := \bigl(\Delta \otimes + \mathrm{Id}^{\otimes(k-1)}\bigr) \circ \Delta^{(k-1)}`, + with `\Delta^{(1)} = \Delta` (the ordinary coproduct in `A`) and + `\Delta^{(0)} = \mathrm{Id}`; and with `\mu^{(k)} := \mu \circ + \bigl(\mu^{(k-1)} \otimes \mathrm{Id})` and `\mu^{(1)} = \mu` + (the ordinary product in `B`). See [Sw1969]_. + + (In the literature, one finds, e.g., `\Delta^{(2)}` for what we + denote above as `\Delta^{(1)}`. See [KMN2012]_.) + + INPUT: + + - ``maps`` -- any number `n \geq 0` of linear maps `f_1, f_2, + \ldots, f_n` on ``self``; or a single ``list`` or ``tuple`` + of such maps + + OUTPUT: + + - the new map `f_1 * f_2 * \cdots * f_2` representing their + convolution product + + .. SEEALSO:: + + :meth:`sage.categories.bialgebras.ElementMethods.convolution_product` + + AUTHORS: + + - Aaron Lauve - 12 June 2015 - Sage Days 65 + + .. TODO:: + + Remove dependency on ``modules_with_basis`` methods. + + EXAMPLES: + + We construct some maps: the identity, the antipode and + projection onto the homogeneous componente of degree 2:: + + sage: Id = lambda x: x + sage: Antipode = lambda x: x.antipode() + sage: Proj2 = lambda x: x.parent().sum_of_terms([(m, c) for (m, c) in x if m.size() == 2]) + + Compute the convolution product of the identity with itself and + with the projection ``Proj2`` on the Hopf algebra of + non-commutative symmetric functions:: + + sage: R = NonCommutativeSymmetricFunctions(QQ).ribbon() + sage: T = R.convolution_product([Id, Id]) + sage: [T(R(comp)) for comp in Compositions(3)] + [4*R[1, 1, 1] + R[1, 2] + R[2, 1], + 2*R[1, 1, 1] + 4*R[1, 2] + 2*R[2, 1] + 2*R[3], + 2*R[1, 1, 1] + 2*R[1, 2] + 4*R[2, 1] + 2*R[3], + R[1, 2] + R[2, 1] + 4*R[3]] + sage: T = R.convolution_product(Proj2, Id) + sage: [T(R([i])) for i in range(1, 5)] + [0, R[2], R[2, 1] + R[3], R[2, 2] + R[4]] + + Compute the convolution product of no maps on the Hopf algebra of + symmetric functions in non-commuting variables. This is the + composition of the counit with the unit:: + + sage: m = SymmetricFunctionsNonCommutingVariables(QQ).m() + sage: T = m.convolution_product() + sage: [T(m(lam)) for lam in SetPartitions(0).list() + SetPartitions(2).list()] + [m{}, 0, 0] + + Compute the convolution product of the projection ``Proj2`` with + the identity on the Hopf algebra of symmetric functions in + non-commuting variables:: + + sage: T = m.convolution_product(Proj2, Id) + sage: [T(m(lam)) for lam in SetPartitions(3)] + [0, + m{{1, 2}, {3}} + m{{1, 2, 3}}, + m{{1, 2}, {3}} + m{{1, 2, 3}}, + m{{1, 2}, {3}} + m{{1, 2, 3}}, + 3*m{{1}, {2}, {3}} + 3*m{{1}, {2, 3}} + 3*m{{1, 3}, {2}}] + + Compute the convolution product of the antipode with itself and the + identity map on group algebra of the symmetric group:: + + sage: G = SymmetricGroup(3) + sage: QG = GroupAlgebra(G, QQ) + sage: x = QG.sum_of_terms([(p,p.number_of_peaks() + p.number_of_inversions()) for p in Permutations(3)]); x + 2*[1, 3, 2] + [2, 1, 3] + 3*[2, 3, 1] + 2*[3, 1, 2] + 3*[3, 2, 1] + sage: T = QG.convolution_product(Antipode, Antipode, Id) + sage: T(x) + 2*[1, 3, 2] + [2, 1, 3] + 2*[2, 3, 1] + 3*[3, 1, 2] + 3*[3, 2, 1] + """ + onbasis = lambda x: self.term(x).convolution_product(*maps) + return self.module_morphism(on_basis=onbasis, codomain=self) + + class ElementMethods: + + def adams_operator(self, n): + r""" + Compute the `n`-th convolution power of the identity morphism + `\mathrm{Id}` on ``self``. + + INPUT: + + - ``n`` -- a nonnegative integer + + OUTPUT: + + - the image of ``self`` under the convolution power `\mathrm{Id}^{*n}` + + .. NOTE:: + + In the literature, this is also called a Hopf power or + Sweedler power, cf. [AL2015]_. + + .. SEEALSO:: + + :meth:`sage.categories.bialgebras.ElementMethods.convolution_product` + + REFERENCES: + + .. [AL2015] *The characteristic polynomial of the Adams operators + on graded connected Hopf algebras*. + Marcelo Aguiar and Aaron Lauve. + Algebra Number Theory, v.9, 2015, n.3, 2015. + + .. TODO:: + + Remove dependency on ``modules_with_basis`` methods. + + EXAMPLES:: + + sage: h = SymmetricFunctions(QQ).h() + sage: h[5].adams_operator(2) + 2*h[3, 2] + 2*h[4, 1] + 2*h[5] + sage: h[5].plethysm(2*h[1]) + 2*h[3, 2] + 2*h[4, 1] + 2*h[5] + sage: h([]).adams_operator(0) + h[] + sage: h([]).adams_operator(1) + h[] + sage: h[3,2].adams_operator(0) + 0 + sage: h[3,2].adams_operator(1) + h[3, 2] + + :: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: S[4].adams_operator(5) + 5*S[1, 1, 1, 1] + 10*S[1, 1, 2] + 10*S[1, 2, 1] + 10*S[1, 3] + 10*S[2, 1, 1] + 10*S[2, 2] + 10*S[3, 1] + 5*S[4] + + + :: + + sage: m = SymmetricFunctionsNonCommutingVariables(QQ).m() + sage: m[[1,3],[2]].adams_operator(-2) + 3*m{{1}, {2, 3}} + 3*m{{1, 2}, {3}} + 6*m{{1, 2, 3}} - 2*m{{1, 3}, {2}} + """ + if n < 0: + if hasattr(self, 'antipode'): + T = lambda x: x.antipode() + n = abs(n) + else: + raise ValueError("antipode not defined; cannot take negative convolution powers: {} < 0".format(n)) + else: + T = lambda x: x + return self.convolution_product([T] * n) + + def convolution_product(self, *maps): + r""" + Return the image of ``self`` under the convolution product (map) of + the maps. + + Let `A` and `B` be bialgebras over a commutative ring `R`. + Given maps `f_i : A \to B` for `1 \leq i < n`, define the + convolution product + + .. MATH:: + + (f_1 * f_2 * \cdots * f_n) := \mu^{(n-1)} \circ (f_1 \otimes + f_2 \otimes \cdots \otimes f_n) \circ \Delta^{(n-1)}, + + where `\Delta^{(k)} := \bigl(\Delta \otimes + \mathrm{Id}^{\otimes(k-1)}\bigr) \circ \Delta^{(k-1)}`, + with `\Delta^{(1)} = \Delta` (the ordinary coproduct in `A`) and + `\Delta^{(0)} = \mathrm{Id}`; and with `\mu^{(k)} := \mu \circ + \bigl(\mu^{(k-1)} \otimes \mathrm{Id})` and `\mu^{(1)} = \mu` + (the ordinary product in `B`). See [Sw1969]_. + + (In the literature, one finds, e.g., `\Delta^{(2)}` for what we + denote above as `\Delta^{(1)}`. See [KMN2012]_.) + + INPUT: + + - ``maps`` -- any number `n \geq 0` of linear maps `f_1, f_2, + \ldots, f_n` on ``self.parent()``; or a single ``list`` or + ``tuple`` of such maps + + OUTPUT: + + - the convolution product of ``maps`` applied to ``self`` + + REFERENCES: + + .. [KMN2012] On the trace of the antipode and higher indicators. + Yevgenia Kashina and Susan Montgomery and Richard Ng. + Israel J. Math., v.188, 2012. + + .. [Sw1969] Hopf algebras. + Moss Sweedler. + W.A. Benjamin, Math Lec Note Ser., 1969. + + AUTHORS: + + - Amy Pang - 12 June 2015 - Sage Days 65 + + .. TODO:: + + Remove dependency on ``modules_with_basis`` methods. + + EXAMPLES: + + We compute convolution products of the identity and antipode maps + on Schur functions:: + + sage: Id = lambda x: x + sage: Antipode = lambda x: x.antipode() + sage: s = SymmetricFunctions(QQ).schur() + sage: s[3].convolution_product(Id, Id) + 2*s[2, 1] + 4*s[3] + sage: s[3,2].convolution_product(Id) == s[3,2] + True + + The method accepts multiple arguments, or a single argument + consisting of a list of maps:: + + sage: s[3,2].convolution_product(Id, Id) + 2*s[2, 1, 1, 1] + 6*s[2, 2, 1] + 6*s[3, 1, 1] + 12*s[3, 2] + 6*s[4, 1] + 2*s[5] + sage: s[3,2].convolution_product([Id, Id]) + 2*s[2, 1, 1, 1] + 6*s[2, 2, 1] + 6*s[3, 1, 1] + 12*s[3, 2] + 6*s[4, 1] + 2*s[5] + + We test the defining property of the antipode morphism; namely, + that the antipode is the inverse of the identity map in the + convolution algebra whose identity element is the composition of + the counit and unit:: + + sage: s[3,2].convolution_product() == s[3,2].convolution_product(Antipode, Id) == s[3,2].convolution_product(Id, Antipode) + True + + :: + + sage: Psi = NonCommutativeSymmetricFunctions(QQ).Psi() + sage: Psi[2,1].convolution_product(Id, Id, Id) + 3*Psi[1, 2] + 6*Psi[2, 1] + sage: (Psi[5,1] - Psi[1,5]).convolution_product(Id, Id, Id) + -3*Psi[1, 5] + 3*Psi[5, 1] + + :: + + sage: G = SymmetricGroup(3) + sage: QG = GroupAlgebra(G,QQ) + sage: x = QG.sum_of_terms([(p,p.length()) for p in Permutations(3)]); x + [1, 3, 2] + [2, 1, 3] + 2*[2, 3, 1] + 2*[3, 1, 2] + 3*[3, 2, 1] + sage: x.convolution_product(Id, Id) + 5*[1, 2, 3] + 2*[2, 3, 1] + 2*[3, 1, 2] + sage: x.convolution_product(Id, Id, Id) + 4*[1, 2, 3] + [1, 3, 2] + [2, 1, 3] + 3*[3, 2, 1] + sage: x.convolution_product([Id]*6) + 9*[1, 2, 3] + + TESTS:: + + sage: Id = lambda x: x + sage: Antipode = lambda x: x.antipode() + + :: + + sage: h = SymmetricFunctions(QQ).h() + sage: h[5].convolution_product([Id, Id]) + 2*h[3, 2] + 2*h[4, 1] + 2*h[5] + sage: h.one().convolution_product([Id, Antipode]) + h[] + sage: h[3,2].convolution_product([Id, Antipode]) + 0 + sage: h.one().convolution_product([Id, Antipode]) == h.one().convolution_product() + True + + :: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: S[4].convolution_product([Id]*5) + 5*S[1, 1, 1, 1] + 10*S[1, 1, 2] + 10*S[1, 2, 1] + 10*S[1, 3] + + 10*S[2, 1, 1] + 10*S[2, 2] + 10*S[3, 1] + 5*S[4] + + :: + + sage: m = SymmetricFunctionsNonCommutingVariables(QQ).m() + sage: m[[1,3],[2]].convolution_product([Antipode, Antipode]) + 3*m{{1}, {2, 3}} + 3*m{{1, 2}, {3}} + 6*m{{1, 2, 3}} - 2*m{{1, 3}, {2}} + sage: m[[]].convolution_product([]) + m{} + sage: m[[1,3],[2]].convolution_product([]) + 0 + + :: + + sage: QS = SymmetricGroupAlgebra(QQ, 5) + sage: x = QS.sum_of_terms(zip(Permutations(5)[3:6],[1,2,3])); x + [1, 2, 4, 5, 3] + 2*[1, 2, 5, 3, 4] + 3*[1, 2, 5, 4, 3] + sage: x.convolution_product([Antipode, Id]) + 6*[1, 2, 3, 4, 5] + sage: x.convolution_product(Id, Antipode, Antipode, Antipode) + 3*[1, 2, 3, 4, 5] + [1, 2, 4, 5, 3] + 2*[1, 2, 5, 3, 4] + + :: + + sage: G = SymmetricGroup(3) + sage: QG = GroupAlgebra(G,QQ) + sage: x = QG.sum_of_terms([(p,p.length()) for p in Permutations(3)]); x + [1, 3, 2] + [2, 1, 3] + 2*[2, 3, 1] + 2*[3, 1, 2] + 3*[3, 2, 1] + sage: x.convolution_product(Antipode, Id) + 9*[1, 2, 3] + sage: x.convolution_product([Id, Antipode, Antipode, Antipode]) + 5*[1, 2, 3] + 2*[2, 3, 1] + 2*[3, 1, 2] + + :: + + sage: s[3,2].counit().parent() == s[3,2].convolution_product().parent() + False + """ + # Be flexible on how the maps are entered: accept a list/tuple of + # maps as well as multiple arguments + if len(maps) == 1 and isinstance(maps[0], (list, tuple)): + T = tuple(maps[0]) + else: + T = maps + + H = self.parent() + + n = len(T) + if n == 0: + return H.one() * self.counit() + if n == 1: + return T[0](self) + + # We apply the maps T_i and products concurrently with coproducts, as this + # seems to be faster than applying a composition of maps, e.g., (H.nfold_product) * tensor(T) * (H.nfold_coproduct). + + out = tensor((H.one(),self)) + HH = tensor((H,H)) + + for mor in T[:-1]: + #ALGORITHM: + #`split_convolve` moves terms of the form x # y to x*Ti(y1) # y2 in Sweedler notation. + split_convolve = lambda (x,y): ( ((xy1,y2),c*d) + for ((y1,y2),d) in H.term(y).coproduct() + for (xy1,c) in H.term(x)*mor(H.term(y1)) ) + out = HH.module_morphism(on_basis=lambda t: HH.sum_of_terms(split_convolve(t)), codomain=HH)(out) + + #Apply final map `T_n` to last term, `y`, and multiply. + return HH.module_morphism(on_basis=lambda (x,y): H.term(x)*T[-1](H.term(y)), codomain=H)(out) diff --git a/src/sage/categories/bimodules.py b/src/sage/categories/bimodules.py index 7971135a4f3..f778479bbc8 100644 --- a/src/sage/categories/bimodules.py +++ b/src/sage/categories/bimodules.py @@ -70,15 +70,21 @@ def _make_named_class_key(self, name): EXAMPLES:: sage: Bimodules(QQ,ZZ)._make_named_class_key('parent_class') - (Category of quotient fields, - Join of Category of euclidean domains and Category of infinite enumerated sets) + (Join of Category of quotient fields and Category of metric spaces, + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces) + sage: Bimodules(Fields(), ZZ)._make_named_class_key('element_class') (Category of fields, - Join of Category of euclidean domains and Category of infinite enumerated sets) + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces) sage: Bimodules(QQ, Rings())._make_named_class_key('element_class') - (Category of quotient fields, Category of rings) + (Join of Category of quotient fields and Category of metric spaces, + Category of rings) sage: Bimodules(Fields(), Rings())._make_named_class_key('element_class') (Category of fields, Category of rings) diff --git a/src/sage/categories/cartesian_product.py b/src/sage/categories/cartesian_product.py index 3e0065247a3..6cf5370c87c 100644 --- a/src/sage/categories/cartesian_product.py +++ b/src/sage/categories/cartesian_product.py @@ -12,9 +12,13 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.misc.lazy_import import lazy_import from sage.categories.covariant_functorial_construction import CovariantFunctorialConstruction, CovariantConstructionCategory +from sage.categories.pushout import MultivariateConstructionFunctor -class CartesianProductFunctor(CovariantFunctorialConstruction): +native_python_containers = set([tuple, list, set, frozenset]) + +class CartesianProductFunctor(CovariantFunctorialConstruction, MultivariateConstructionFunctor): """ A singleton class for the Cartesian product functor. @@ -105,17 +109,69 @@ class CartesianProductFunctor(CovariantFunctorialConstruction): _functor_category = "CartesianProducts" symbol = " (+) " -cartesian_product = CartesianProductFunctor() -""" -The cartesian product functorial construction. + def __init__(self): + r""" + Constructor. See :class:`CartesianProductFunctor` for details. -See :class:`CartesianProductFunctor` for more information. + TESTS:: -EXAMPLES:: + sage: from sage.categories.cartesian_product import CartesianProductFunctor + sage: CartesianProductFunctor() + The cartesian_product functorial construction + """ + CovariantFunctorialConstruction.__init__(self) + from sage.categories.sets_cat import Sets + MultivariateConstructionFunctor.__init__(self, Sets(), Sets()) - sage: cartesian_product - The cartesian_product functorial construction -""" + def __call__(self, args, **kwds): + r""" + Functorial construction application. + + This specializes the generic ``__call__`` from + :class:`CovariantFunctorialConstruction` to: + + - handle the following plain Python containers as input: + :class:`frozenset`, :class:`list`, :class:`set` and + :class:`tuple`. + + - handle the empty list of factors. + + See the examples below. + + EXAMPLES:: + + sage: cartesian_product([[0,1], ('a','b','c')]) + The cartesian product of ({0, 1}, {'a', 'b', 'c'}) + sage: _.category() + Category of Cartesian products of finite enumerated sets + + sage: cartesian_product([set([0,1,2]), [0,1]]) + The cartesian product of ({0, 1, 2}, {0, 1}) + sage: _.category() + Category of Cartesian products of sets + + Check that the empty product is handled correctly: + + sage: C = cartesian_product([]) + sage: C + The cartesian product of () + sage: C.cardinality() + 1 + sage: C.an_element() + () + sage: C.category() + Category of Cartesian products of sets + """ + if any(type(arg) in native_python_containers for arg in args): + from sage.categories.sets_cat import Sets + S = Sets() + args = [S(a, enumerated_set=True) for a in args] + elif not args: + from sage.categories.sets_cat import Sets + from sage.sets.cartesian_product import CartesianProduct + return CartesianProduct((), Sets().CartesianProducts()) + + return super(CartesianProductFunctor, self).__call__(args, **kwds) class CartesianProductsCategory(CovariantConstructionCategory): """ @@ -171,3 +227,17 @@ def base_ring(self): Integer Ring """ return self.base_category().base_ring() + +# Moved to avoid circular imports +lazy_import('sage.categories.sets_cat', 'cartesian_product') +""" +The cartesian product functorial construction + +See :class:`CartesianProductFunctor` for more information + +EXAMPLES:: + + sage: cartesian_product + The cartesian_product functorial construction +""" + diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index a43973e47f9..2d50eac827c 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -35,7 +35,7 @@ sage: V = VectorSpace(RationalField(), 3) sage: V.category() - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis over (quotient fields and metric spaces) sage: G = SymmetricGroup(9) sage: G.category() Join of Category of finite permutation groups and Category of finite weyl groups @@ -323,7 +323,8 @@ class inheritance from ``C.parent_class``. sage: Algebras(GF(5)).parent_class is Algebras(GF(7)).parent_class True - sage: Coalgebras(QQ).parent_class is Coalgebras(FractionField(QQ['x'])).parent_class + sage: F = FractionField(ZZ['t']) + sage: Coalgebras(F).parent_class is Coalgebras(FractionField(F['x'])).parent_class True We now construct a parent in the usual way:: @@ -2755,9 +2756,9 @@ def _make_named_class(self, name, method_provider, cache = False, **options): Similarly for ``QQ`` and ``RR``:: sage: QQ.category() - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: RR.category() - Category of fields + Join of Category of fields and Category of complete metric spaces sage: Modules(QQ).parent_class is Modules(RR).parent_class False @@ -2818,14 +2819,17 @@ def _make_named_class_key(self, name): sage: Algebras(ZZ)._make_named_class_key("parent_class") Join of Category of euclidean domains - and Category of infinite enumerated sets + and Category of infinite enumerated sets + and Category of metric spaces The morphism class of a bimodule depends only on the category of the left and right base rings:: sage: Bimodules(QQ, ZZ)._make_named_class_key("morphism_class") - (Category of quotient fields, - Join of Category of euclidean domains and Category of infinite enumerated sets) + (Join of Category of quotient fields and Category of metric spaces, + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces) The element class of a join category depends only on the element class of its super categories:: @@ -2953,13 +2957,14 @@ def _make_named_class_key(self, name): sage: Modules(ZZ)._make_named_class_key('element_class') Join of Category of euclidean domains - and Category of infinite enumerated sets + and Category of infinite enumerated sets + and Category of metric spaces sage: Modules(QQ)._make_named_class_key('parent_class') - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: Schemes(Spec(ZZ))._make_named_class_key('parent_class') Category of schemes sage: ModularAbelianVarieties(QQ)._make_named_class_key('parent_class') - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces """ return tuple(getattr(cat, name) for cat in self._super_categories) @@ -3005,7 +3010,8 @@ def _subcategory_hook_(self, category): EXAMPLE:: - sage: QQ['x'].category().is_subcategory(Category.join([Rings(), VectorSpaces(QuotientFields())])) # indirect doctest + sage: cat = Category.join([Rings(), VectorSpaces(QuotientFields().Metric())]) + sage: QQ['x'].category().is_subcategory(cat) # indirect doctest True """ return all(category.is_subcategory(X) for X in self._super_categories) diff --git a/src/sage/categories/category_cy_helper.pyx b/src/sage/categories/category_cy_helper.pyx index 2156bf3169b..213034df6fc 100644 --- a/src/sage/categories/category_cy_helper.pyx +++ b/src/sage/categories/category_cy_helper.pyx @@ -135,17 +135,16 @@ cpdef tuple join_as_tuple(tuple categories, tuple axioms, tuple ignore_axioms): (Category of algebras over Integer Ring, Category of finite monoids, Category of coalgebras over Rational Field, - Category of simplicial complexes) + Category of finite simplicial complexes) sage: join_as_tuple(T,('WithBasis',),()) (Category of algebras with basis over Integer Ring, Category of finite monoids, Category of coalgebras with basis over Rational Field, - Category of simplicial complexes) + Category of finite simplicial complexes) sage: join_as_tuple(T,(),((Monoids(),'Finite'),)) (Category of algebras over Integer Ring, Category of coalgebras over Rational Field, - Category of finite sets, - Category of simplicial complexes) + Category of finite simplicial complexes) """ cdef set axiomsS = set(axioms) for category in categories: diff --git a/src/sage/categories/category_types.py b/src/sage/categories/category_types.py index 435e6a5f980..3241fab5b10 100644 --- a/src/sage/categories/category_types.py +++ b/src/sage/categories/category_types.py @@ -193,13 +193,15 @@ def _make_named_class_key(self, name): EXAMPLES:: sage: Modules(ZZ)._make_named_class_key('element_class') - Join of Category of euclidean domains and Category of infinite enumerated sets + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces sage: Modules(QQ)._make_named_class_key('parent_class') - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: Schemes(Spec(ZZ))._make_named_class_key('parent_class') Category of schemes sage: ModularAbelianVarieties(QQ)._make_named_class_key('parent_class') - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: Algebras(Fields())._make_named_class_key('morphism_class') Category of fields """ @@ -513,34 +515,7 @@ def __call__(self, v): return v return self.ring().ideal(v) -############################################################# -# TODO: make those two into real categories (with super_category, ...) - -# SimplicialComplex -############################################################# -class SimplicialComplexes(Category): - """ - The category of simplicial complexes. - - EXAMPLES:: - - sage: SimplicialComplexes() - Category of simplicial complexes - - TESTS:: - - sage: TestSuite(SimplicialComplexes()).run() - """ - - def super_categories(self): - """ - EXAMPLES:: - - sage: SimplicialComplexes().super_categories() - [Category of objects] - """ - return [Objects()] # anything better? - +# TODO: make this into a better category ############################################################# # ChainComplex ############################################################# @@ -566,11 +541,11 @@ def super_categories(self): EXAMPLES:: sage: ChainComplexes(Integers(9)).super_categories() - [Category of modules with basis over Ring of integers modulo 9] + [Category of modules over Ring of integers modulo 9] """ - from sage.categories.all import Fields, FreeModules, VectorSpaces + from sage.categories.all import Fields, Modules, VectorSpaces base_ring = self.base_ring() if base_ring in Fields(): return [VectorSpaces(base_ring)] - return [FreeModules(base_ring)] + return [Modules(base_ring)] diff --git a/src/sage/categories/category_with_axiom.py b/src/sage/categories/category_with_axiom.py index d338cc4e2cc..6fe7f5c42cf 100644 --- a/src/sage/categories/category_with_axiom.py +++ b/src/sage/categories/category_with_axiom.py @@ -1673,8 +1673,11 @@ class ``Sets.Finite``), or in a separate file (typically in a class all_axioms = AxiomContainer() all_axioms += ("Flying", "Blue", + "Compact", + "Differentiable", "Smooth", "Analytic", "AlmostComplex", "FinitelyGeneratedAsMagma", "Facade", "Finite", "Infinite", + "Complete", "FiniteDimensional", "Connected", "WithBasis", "Irreducible", "Commutative", "Associative", "Inverse", "Unital", "Division", "NoZeroDivisors", @@ -2257,6 +2260,8 @@ def _repr_object_names_static(category, axioms): result = result.replace(" over ", " with basis over ", 1) elif axiom == "Connected" and "graded " in result: result = result.replace("graded ", "graded connected ", 1) + elif axiom == "Connected" and "filtered " in result: + result = result.replace("filtered ", "filtered connected ", 1) elif axiom == "Endset" and "homsets" in result: # Without the space at the end to handle Homsets().Endset() result = result.replace("homsets", "endsets", 1) diff --git a/src/sage/categories/coalgebras.py b/src/sage/categories/coalgebras.py index a4ebc8fc0af..a5fdc492111 100644 --- a/src/sage/categories/coalgebras.py +++ b/src/sage/categories/coalgebras.py @@ -13,6 +13,7 @@ from sage.categories.all import Modules from sage.categories.tensor import TensorProductsCategory, tensor from sage.categories.dual import DualObjectsCategory +from sage.categories.super_modules import SuperModulesCategory from sage.categories.realizations import RealizationsCategory from sage.categories.with_realizations import WithRealizationsCategory from sage.misc.abstract_method import abstract_method @@ -50,19 +51,6 @@ class ParentMethods: # # Will declare the coproduct of self to the coercion mechanism when it exists # pass - @cached_method - def tensor_square(self): - """ - Returns the tensor square of ``self`` - - EXAMPLES:: - - sage: A = HopfAlgebrasWithBasis(QQ).example() - sage: A.tensor_square() - An example of Hopf algebra with basis: the group algebra of the Dihedral group of order 6 as a permutation group over Rational Field # An example of Hopf algebra with basis: the group algebra of the Dihedral group of order 6 as a permutation group over Rational Field - """ - return tensor([self, self]) - @abstract_method def counit(self, x): """ @@ -192,6 +180,31 @@ def extra_super_categories(self): from sage.categories.algebras import Algebras return [Algebras(self.base_category().base_ring())] + class Super(SuperModulesCategory): + def extra_super_categories(self): + """ + EXAMPLES:: + + sage: Coalgebras(ZZ).Super().extra_super_categories() + [Join of Category of graded modules over Integer Ring + and Category of coalgebras over Integer Ring] + sage: Coalgebras(ZZ).Super().super_categories() + [Category of super modules over Integer Ring, + Category of coalgebras over Integer Ring] + + Compare this with the situation for bialgebras:: + + sage: Bialgebras(ZZ).Super().extra_super_categories() + [] + sage: Bialgebras(ZZ).Super().super_categories() + [Category of super algebras over Integer Ring, + Category of super coalgebras over Integer Ring] + + The category of bialgebras does not occur in these results, + since super bialgebras are not bialgebras. + """ + return [self.base_category().Graded()] + class WithRealizations(WithRealizationsCategory): class ParentMethods: diff --git a/src/sage/categories/coalgebras_with_basis.py b/src/sage/categories/coalgebras_with_basis.py index 0b9ff24f0ec..2ea398ecabc 100644 --- a/src/sage/categories/coalgebras_with_basis.py +++ b/src/sage/categories/coalgebras_with_basis.py @@ -13,6 +13,7 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.all import ModulesWithBasis, tensor, Hom +from sage.categories.super_modules import SuperModulesCategory class CoalgebrasWithBasis(CategoryWithAxiom_over_base_ring): """ @@ -127,5 +128,70 @@ def counit(self): return self.module_morphism(self.counit_on_basis,codomain=self.base_ring()) class ElementMethods: - pass + def coproduct_iterated(self, n=1): + r""" + Apply ``n`` coproducts to ``self``. + + .. TODO:: + Remove dependency on ``modules_with_basis`` methods. + + EXAMPLES:: + + sage: Psi = NonCommutativeSymmetricFunctions(QQ).Psi() + sage: Psi[2,2].coproduct_iterated(0) + Psi[2, 2] + sage: Psi[2,2].coproduct_iterated(2) + Psi[] # Psi[] # Psi[2, 2] + 2*Psi[] # Psi[2] # Psi[2] + + Psi[] # Psi[2, 2] # Psi[] + 2*Psi[2] # Psi[] # Psi[2] + + 2*Psi[2] # Psi[2] # Psi[] + Psi[2, 2] # Psi[] # Psi[] + + TESTS:: + + sage: p = SymmetricFunctions(QQ).p() + sage: p[5,2,2].coproduct_iterated() + p[] # p[5, 2, 2] + 2*p[2] # p[5, 2] + p[2, 2] # p[5] + + p[5] # p[2, 2] + 2*p[5, 2] # p[2] + p[5, 2, 2] # p[] + sage: p([]).coproduct_iterated(3) + p[] # p[] # p[] # p[] + + :: + + sage: Psi = NonCommutativeSymmetricFunctions(QQ).Psi() + sage: Psi[2,2].coproduct_iterated(0) + Psi[2, 2] + sage: Psi[2,2].coproduct_iterated(3) + Psi[] # Psi[] # Psi[] # Psi[2, 2] + 2*Psi[] # Psi[] # Psi[2] # Psi[2] + + Psi[] # Psi[] # Psi[2, 2] # Psi[] + 2*Psi[] # Psi[2] # Psi[] # Psi[2] + + 2*Psi[] # Psi[2] # Psi[2] # Psi[] + Psi[] # Psi[2, 2] # Psi[] # Psi[] + + 2*Psi[2] # Psi[] # Psi[] # Psi[2] + 2*Psi[2] # Psi[] # Psi[2] # Psi[] + + 2*Psi[2] # Psi[2] # Psi[] # Psi[] + Psi[2, 2] # Psi[] # Psi[] # Psi[] + + :: + + sage: m = SymmetricFunctionsNonCommutingVariables(QQ).m() + sage: m[[1,3],[2]].coproduct_iterated(2) + m{} # m{} # m{{1, 3}, {2}} + m{} # m{{1}} # m{{1, 2}} + + m{} # m{{1, 2}} # m{{1}} + m{} # m{{1, 3}, {2}} # m{} + + m{{1}} # m{} # m{{1, 2}} + m{{1}} # m{{1, 2}} # m{} + + m{{1, 2}} # m{} # m{{1}} + m{{1, 2}} # m{{1}} # m{} + + m{{1, 3}, {2}} # m{} # m{} + sage: m[[]].coproduct_iterated(3), m[[1,3],[2]].coproduct_iterated(0) + (m{} # m{} # m{} # m{}, m{{1, 3}, {2}}) + """ + if n < 0: + raise ValueError("cannot take fewer than 0 coproduct iterations: %s < 0" % str(n)) + if n == 0: + return self + if n == 1: + return self.coproduct() + from sage.functions.all import floor, ceil + from sage.rings.all import Integer + + # Use coassociativity of `\Delta` to perform many coproducts simultaneously. + fn = floor(Integer(n-1)/2); cn = ceil(Integer(n-1)/2) + split = lambda a,b: tensor([a.coproduct_iterated(fn), b.coproduct_iterated(cn)]) + return self.coproduct().apply_multilinear_morphism(split) + + class Super(SuperModulesCategory): + pass diff --git a/src/sage/categories/covariant_functorial_construction.py b/src/sage/categories/covariant_functorial_construction.py index cc2ea36216e..47ed9d8e370 100644 --- a/src/sage/categories/covariant_functorial_construction.py +++ b/src/sage/categories/covariant_functorial_construction.py @@ -201,7 +201,7 @@ def _repr_(self): """ return "The %s functorial construction"%self._functor_name - def __call__(self, args): + def __call__(self, args, **kwargs): """ Functorial construction application @@ -220,7 +220,7 @@ def __call__(self, args): args = tuple(args) # a bit brute force; let's see if this becomes a bottleneck later assert(all( hasattr(arg, self._functor_name) for arg in args)) assert(len(args) > 0) - return getattr(args[0], self._functor_name)(*args[1:]) + return getattr(args[0], self._functor_name)(*args[1:], **kwargs) class FunctorialConstructionCategory(Category): # Should this be CategoryWithBase? """ diff --git a/src/sage/categories/coxeter_groups.py b/src/sage/categories/coxeter_groups.py index d57a1710fb4..a457d0aff48 100644 --- a/src/sage/categories/coxeter_groups.py +++ b/src/sage/categories/coxeter_groups.py @@ -1163,8 +1163,7 @@ def reduced_word_graph(self): if i == len(x): continue a, b = x[i], y[i] - I = P.index_set() - m = P.coxeter_matrix()[I.index(a),I.index(b)] + m = P.coxeter_matrix()[a,b] subword = [a,b] * (m // 2) subword2 = [b,a] * (m // 2) if m % 2 != 0: diff --git a/src/sage/categories/cw_complexes.py b/src/sage/categories/cw_complexes.py new file mode 100644 index 00000000000..f7f4641d5cc --- /dev/null +++ b/src/sage/categories/cw_complexes.py @@ -0,0 +1,215 @@ +r""" +CW Complexes +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_singleton import Category_singleton +from sage.categories.category_with_axiom import CategoryWithAxiom +from sage.categories.sets_cat import Sets + +class CWComplexes(Category_singleton): + r""" + The category of CW complexes. + + A CW complex is a Closure-finite cell complex in the Weak toplogy. + + REFERENCES: + + - :wikipedia:`CW_complex` + + .. NOTE:: + + The notion of "finite" is that the number of cells is finite. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: C = CWComplexes(); C + Category of CW complexes + + TESTS:: + + sage: TestSuite(C).run() + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes().super_categories() + [Category of topological spaces] + """ + return [Sets().Topological()] + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes() # indirect doctest + Category of CW complexes + """ + return "CW complexes" + + class SubcategoryMethods: + @cached_method + def Connected(self): + """ + Return the full subcategory of the connected objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes().Connected() + Category of connected CW complexes + + TESTS:: + + sage: TestSuite(CWComplexes().Connected()).run() + sage: CWComplexes().Connected.__module__ + 'sage.categories.cw_complexes' + """ + return self._with_axiom('Connected') + + @cached_method + def FiniteDimensional(self): + """ + Return the full subcategory of the finite dimensional + objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: C = CWComplexes().FiniteDimensional(); C + Category of finite dimensional CW complexes + + TESTS:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: C = CWComplexes().FiniteDimensional() + sage: TestSuite(C).run() + sage: CWComplexes().Connected().FiniteDimensional.__module__ + 'sage.categories.cw_complexes' + """ + return self._with_axiom('FiniteDimensional') + + class Connected(CategoryWithAxiom): + """ + The category of connected CW complexes. + """ + + class FiniteDimensional(CategoryWithAxiom): + """ + Category of finite dimensional CW complexes. + """ + + class Finite(CategoryWithAxiom): + """ + Category of finite CW complexes. + + A finite CW complex is a CW complex with a finite number of cells. + """ + def extra_super_categories(self): + """ + Return the extra super categories of ``self``. + + A finite CW complex is a compact finite-dimensional CW complex. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: C = CWComplexes().Finite() + sage: C.extra_super_categories() + [Category of finite dimensional CW complexes, + Category of compact topological spaces] + """ + return [CWComplexes().FiniteDimensional(), Sets().Topological().Compact()] + + class ParentMethods: + @cached_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.dimension() + 2 + """ + C = self.cells() + return max(c.dimension() for d in C.keys() for c in C[d]) + + def Compact_extra_super_categories(self): + """ + Return extraneous super categories for ``CWComplexes().Compact()``. + + A compact CW complex is finite, see Proposition A.1 in [Hat]_. + + .. TODO:: + + Fix the name of finite CW complexes. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes().Compact() # indirect doctest + Category of finite finite dimensional CW complexes + sage: CWComplexes().Compact() is CWComplexes().Finite() + True + """ + return (Sets().Finite(),) + + class ElementMethods: + @abstract_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.an_element().dimension() + 2 + """ + + class ParentMethods: + @abstract_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.dimension() + 2 + """ + + @abstract_method(optional=True) + def cells(self): + """ + Return the cells of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: C = X.cells() + sage: sorted((d, C[d]) for d in C.keys()) + [(0, (0-cell v,)), + (1, (0-cell e1, 0-cell e2)), + (2, (2-cell f,))] + """ + diff --git a/src/sage/categories/distributive_magmas_and_additive_magmas.py b/src/sage/categories/distributive_magmas_and_additive_magmas.py index ebcb996bdaf..c0c3a53bb80 100644 --- a/src/sage/categories/distributive_magmas_and_additive_magmas.py +++ b/src/sage/categories/distributive_magmas_and_additive_magmas.py @@ -73,8 +73,8 @@ def _test_distributivity(self, **options): """ tester = self._tester(**options) S = tester.some_elements() - from sage.combinat.cartesian_product import CartesianProduct - for x,y,z in tester.some_elements(CartesianProduct(S,S,S)): + from sage.misc.misc import some_tuples + for x,y,z in some_tuples(tester.some_elements(), 3, tester._max_runs): # left distributivity tester.assert_(x * (y + z) == (x * y) + (x * z)) # right distributivity diff --git a/src/sage/categories/domains.py b/src/sage/categories/domains.py index 2e9b9ec2db1..2cdbb4cd2ce 100644 --- a/src/sage/categories/domains.py +++ b/src/sage/categories/domains.py @@ -84,8 +84,8 @@ def _test_zero_divisors(self, **options): # Filter out zero S = [s for s in tester.some_elements() if not s.is_zero()] - from sage.combinat.cartesian_product import CartesianProduct - for a,b in tester.some_elements(CartesianProduct(S,S)): + from sage.misc.misc import some_tuples + for a,b in some_tuples(S, 2, tester._max_runs): p = a * b tester.assertFalse(p.is_zero()) diff --git a/src/sage/categories/enumerated_sets.py b/src/sage/categories/enumerated_sets.py index 1cbe3103953..7b01e89d48a 100644 --- a/src/sage/categories/enumerated_sets.py +++ b/src/sage/categories/enumerated_sets.py @@ -23,7 +23,7 @@ class EnumeratedSets(Category_singleton): together with a canonical enumeration of its elements; conceptually, this is very similar to an immutable list. The main difference lies in the names and the return type of the methods, - and of course the fact that the list of element is not supposed to + and of course the fact that the list of elements is not supposed to be expanded in memory. Whenever possible one should use one of the two sub-categories :class:`FiniteEnumeratedSets` or :class:`InfiniteEnumeratedSets`. @@ -39,7 +39,7 @@ class EnumeratedSets(Category_singleton): - ``S.cardinality()``: the number of elements of the set. This is the equivalent for ``len`` on a list except that the return value is specified to be a Sage :class:`Integer` or - ``infinity``, instead of a Python ``int``; + ``infinity``, instead of a Python ``int``. - ``iter(S)``: an iterator for the elements of the set; @@ -48,15 +48,15 @@ class EnumeratedSets(Category_singleton): predictably too large to be expanded in memory. - ``S.unrank(n)``: the ``n-th`` element of the set when ``n`` is a sage - ``Integer``. This is the equivanlent for ``l[n]`` on a list. + ``Integer``. This is the equivalent for ``l[n]`` on a list. - ``S.rank(e)``: the position of the element ``e`` in the set; This is equivalent to ``l.index(e)`` for a list except that the return value is specified to be a Sage :class:`Integer`, - instead of a Python ``int``; + instead of a Python ``int``. - ``S.first()``: the first object of the set; it is equivalent to - ``S.unrank(0)``; + ``S.unrank(0)``. - ``S.next(e)``: the object of the set which follows ``e``; It is equivalent to ``S.unrank(S.rank(e)+1)``. @@ -148,12 +148,12 @@ def __iter__(self): An iterator for the enumerated set. ``iter(self)`` allows the combinatorial class to be treated as an - iterable. This if the default implementation from the category - ``EnumeratedSets()`` it just goes through the iterator of the set + iterable. This is the default implementation from the category + ``EnumeratedSets()``; it just goes through the iterator of the set to count the number of objects. By decreasing order of priority, the second column of the - following array shows which methods is used to define + following array shows which method is used to define ``__iter__``, when the methods of the first column are overloaded: +------------------------+---------------------------------+ @@ -166,53 +166,53 @@ def __iter__(self): | ``list` | ``_iterator_from_next`` | +------------------------+---------------------------------+ - If non of these are provided raise a ``NotImplementedError`` + If none of these are provided, raise a ``NotImplementedError``. EXAMPLES:: We start with an example where nothing is implemented:: sage: class broken(UniqueRepresentation, Parent): - ... def __init__(self): - ... Parent.__init__(self, category = EnumeratedSets()) - ... + ....: def __init__(self): + ....: Parent.__init__(self, category = EnumeratedSets()) + ....: sage: it = iter(broken()); [next(it), next(it), next(it)] Traceback (most recent call last): ... NotImplementedError: iterator called but not implemented - Here is what happends when ``first`` and ``next`` are implemeted:: + Here is what happens when ``first`` and ``next`` are implemented:: sage: class set_first_next(UniqueRepresentation, Parent): - ... def __init__(self): - ... Parent.__init__(self, category = EnumeratedSets()) - ... def first(self): - ... return 0 - ... def next(self, elt): - ... return elt+1 - ... + ....: def __init__(self): + ....: Parent.__init__(self, category = EnumeratedSets()) + ....: def first(self): + ....: return 0 + ....: def next(self, elt): + ....: return elt+1 + ....: sage: it = iter(set_first_next()); [next(it), next(it), next(it)] [0, 1, 2] Let us try with ``unrank``:: sage: class set_unrank(UniqueRepresentation, Parent): - ... def __init__(self): - ... Parent.__init__(self, category = EnumeratedSets()) - ... def unrank(self, i): - ... return i + 5 - ... + ....: def __init__(self): + ....: Parent.__init__(self, category = EnumeratedSets()) + ....: def unrank(self, i): + ....: return i + 5 + ....: sage: it = iter(set_unrank()); [next(it), next(it), next(it)] [5, 6, 7] Let us finally try with ``list``:: sage: class set_list(UniqueRepresentation, Parent): - ... def __init__(self): - ... Parent.__init__(self, category = EnumeratedSets()) - ... def list(self): - ... return [5, 6, 7] - ... + ....: def __init__(self): + ....: Parent.__init__(self, category = EnumeratedSets()) + ....: def list(self): + ....: return [5, 6, 7] + ....: sage: it = iter(set_list()); [next(it), next(it), next(it)] [5, 6, 7] @@ -257,7 +257,7 @@ def is_empty(self): def list(self): """ - Return an error since the cardinality of self is not known. + Return an error since the cardinality of ``self`` is not known. EXAMPLES:: @@ -715,98 +715,6 @@ def rank(self): class CartesianProducts(CartesianProductsCategory): class ParentMethods: - def __iter__(self): - r""" - Return a lexicographic iterator for the elements of this cartesian product. - - EXAMPLES:: - - sage: A = FiniteEnumeratedSets()(["a", "b"]) - sage: B = FiniteEnumeratedSets().example(); B - An example of a finite enumerated set: {1,2,3} - sage: C = cartesian_product([A, B, A]); C - The cartesian product of ({'a', 'b'}, An example of a finite enumerated set: {1,2,3}, {'a', 'b'}) - sage: C in FiniteEnumeratedSets() - True - sage: list(C) - [('a', 1, 'a'), ('a', 1, 'b'), ('a', 2, 'a'), ('a', 2, 'b'), ('a', 3, 'a'), ('a', 3, 'b'), - ('b', 1, 'a'), ('b', 1, 'b'), ('b', 2, 'a'), ('b', 2, 'b'), ('b', 3, 'a'), ('b', 3, 'b')] - sage: C.__iter__.__module__ - 'sage.categories.enumerated_sets' - - sage: F22 = GF(2).cartesian_product(GF(2)) - sage: list(F22) - [(0, 0), (0, 1), (1, 0), (1, 1)] - - sage: C = cartesian_product([Permutations(10)]*4) - sage: it = iter(C) - sage: next(it) - ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - sage: next(it) - ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 10, 9]) - - .. WARNING:: - - The elements are returned in lexicographic order, - which gives a valid enumeration only if all - factors, but possibly the first one, are - finite. So the following one is fine:: - - sage: it = iter(cartesian_product([ZZ, GF(2)])) - sage: [next(it) for _ in range(10)] - [(0, 0), (0, 1), (1, 0), (1, 1), - (-1, 0), (-1, 1), (2, 0), (2, 1), - (-2, 0), (-2, 1)] - - But this one is not:: - - sage: it = iter(cartesian_product([GF(2), ZZ])) - sage: [next(it) for _ in range(10)] - doctest:...: UserWarning: Sage is not able to determine - whether the factors of this cartesian product are - finite. The lexicographic ordering might not go through - all elements. - [(0, 0), (0, 1), (0, -1), (0, 2), (0, -2), - (0, 3), (0, -3), (0, 4), (0, -4), (0, 5)] - - .. NOTE:: - - Here it would be faster to use :func:`itertools.product` for sets - of small size. But the latter expands all factor in memory! - So we can not reasonably use it in general. - - ALGORITHM: - - Recipe 19.9 in the Python Cookbook by Alex Martelli - and David Ascher. - """ - if any(f not in Sets().Finite() for f in self.cartesian_factors()[1:]): - from warnings import warn - warn("Sage is not able to determine whether the factors of " - "this cartesian product are finite. The lexicographic " - "ordering might not go through all elements.") - - # visualize an odometer, with "wheels" displaying "digits"...: - factors = list(self.cartesian_factors()) - wheels = map(iter, factors) - digits = [next(it) for it in wheels] - while True: - yield self._cartesian_product_of_elements(digits) - for i in range(len(digits)-1, -1, -1): - try: - digits[i] = next(wheels[i]) - break - except StopIteration: - wheels[i] = iter(factors[i]) - digits[i] = next(wheels[i]) - else: - break def first(self): r""" diff --git a/src/sage/categories/euclidean_domains.py b/src/sage/categories/euclidean_domains.py index dc7364599ad..b9d29a79a29 100644 --- a/src/sage/categories/euclidean_domains.py +++ b/src/sage/categories/euclidean_domains.py @@ -87,8 +87,8 @@ def _test_euclidean_degree(self, **options): tester.assertGreaterEqual(a.euclidean_degree(), min_degree) tester.assertEqual(a.euclidean_degree() == min_degree, a.is_unit()) - from sage.combinat.cartesian_product import CartesianProduct - for a,b in tester.some_elements(CartesianProduct(S,S)): + from sage.misc.misc import some_tuples + for a,b in some_tuples(S, 2, tester._max_runs): p = a * b # For rings which are not exact, we might get something that # acts like a zero divisor. @@ -114,9 +114,8 @@ def _test_quo_rem(self, **options): """ tester = self._tester(**options) S = tester.some_elements() - - from sage.combinat.cartesian_product import CartesianProduct - for a,b in tester.some_elements(CartesianProduct(S,S)): + from sage.misc.misc import some_tuples + for a,b in some_tuples(S, 2, tester._max_runs): if b.is_zero(): tester.assertRaises(ZeroDivisionError, lambda: a.quo_rem(b)) else: diff --git a/src/sage/categories/examples/cw_complexes.py b/src/sage/categories/examples/cw_complexes.py new file mode 100644 index 00000000000..8c33b303e61 --- /dev/null +++ b/src/sage/categories/examples/cw_complexes.py @@ -0,0 +1,164 @@ +""" +Examples of CW complexes +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.element import Element +from sage.categories.cw_complexes import CWComplexes +from sage.rings.integer import Integer +from sage.rings.all import QQ +from sage.sets.family import Family + +class Surface(UniqueRepresentation, Parent): + r""" + An example of a CW complex: a (2-dimensional) surface. + + This class illustrates a minimal implementation of a CW complex. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example(); X + An example of a CW complex: the surface given by the boundary map (1, 2, 1, 2) + + sage: X.category() + Category of finite finite dimensional CW complexes + + We conclude by running systematic tests on this manifold:: + + sage: TestSuite(X).run() + """ + def __init__(self, bdy=(1, 2, 1, 2)): + r""" + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example((1, 2)); X + An example of a CW complex: the surface given by the boundary map (1, 2) + + TESTS:: + + sage: TestSuite(X).run() + """ + self._bdy = bdy + self._edges = frozenset(bdy) + Parent.__init__(self, category=CWComplexes().Finite()) + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes().example() + An example of a CW complex: the surface given by the boundary map (1, 2, 1, 2) + """ + return "An example of a CW complex: the surface given by the boundary map {}".format(self._bdy) + + def cells(self): + """ + Return the cells of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: C = X.cells() + sage: sorted((d, C[d]) for d in C.keys()) + [(0, (0-cell v,)), + (1, (0-cell e1, 0-cell e2)), + (2, (2-cell f,))] + """ + d = {0: (self.element_class(self, 0, 'v'),)} + d[1] = tuple([self.element_class(self, 0, 'e'+str(e)) for e in self._edges]) + d[2] = (self.an_element(),) + return Family(d) + + def an_element(self): + r""" + Return an element of the CW complex, as per + :meth:`Sets.ParentMethods.an_element`. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.an_element() + 2-cell f + """ + return self.element_class(self, 2, 'f') + + class Element(Element): + """ + A cell in a CW complex. + """ + def __init__(self, parent, dim, name): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: f = X.an_element() + sage: TestSuite(f).run() + """ + Element.__init__(self, parent) + self._dim = dim + self._name = name + + def _repr_(self): + """ + Return a string represention of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.an_element() + 2-cell f + """ + return "{}-cell {}".format(self._dim, self._name) + + def __eq__(self, other): + """ + Check equality. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: f = X.an_element() + sage: f == X(2, 'f') + True + sage: e1 = X(1, 'e1') + sage: e1 == f + False + """ + return (isinstance(other, Surface.Element) + and self.parent() is other.parent() + and self._dim == other._dim + and self._name == other._name) + + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: f = X.an_element() + sage: f.dimension() + 2 + """ + return self._dim + +Example = Surface + diff --git a/src/sage/categories/examples/filtered_algebras_with_basis.py b/src/sage/categories/examples/filtered_algebras_with_basis.py new file mode 100644 index 00000000000..9e7765aefa8 --- /dev/null +++ b/src/sage/categories/examples/filtered_algebras_with_basis.py @@ -0,0 +1,178 @@ +r""" +Examples of filtered algebra with basis +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.categories.filtered_algebras_with_basis import FilteredAlgebrasWithBasis +from sage.combinat.free_module import CombinatorialFreeModule +from sage.monoids.indexed_free_monoid import IndexedFreeAbelianMonoid +from sage.sets.family import Family + +class PBWBasisCrossProduct(CombinatorialFreeModule): + r""" + This class illustrates an implementation of a filtered algebra + with basis: the universal enveloping algebra of the Lie algebra + of `\RR^3` under the cross product. + + The Lie algebra is generated by `x,y,z` with brackets defined by + `[x, y] = z`, `[y, z] = x`, and `[x, z] = -y`. The universal enveloping + algebra has a (PBW) basis consisting of monomials `x^i y^j z^k`. + Despite these monomials not commuting with each other, we + nevertheless label them by the elements of the free abelian monoid + on three generators. + + INPUT: + + - ``R`` -- base ring + + The implementation involves the following: + + - A set of algebra generators -- the set of generators `x,y,z`. + + - The index of the unit element -- the unit element in the monoid + of monomials. + + - A product -- this is given on basis elements by using + :meth:`product_on_basis`. + + - A degree function -- this is determined on the basis elements + by using :meth:`degree_on_basis` which returns the sum of exponents + of the monomial. + """ + def __init__(self, base_ring): + """ + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x,y,z = A.algebra_generators() + sage: TestSuite(A).run(elements=[x*y+z]) + """ + I = IndexedFreeAbelianMonoid(['x', 'y', 'z'], prefix='U') + gen_cmp = lambda x,y: cmp((-len(x), x.to_word_list()), (-len(y), y.to_word_list())) + CombinatorialFreeModule.__init__(self, base_ring, I, bracket=False, prefix='', + generator_cmp=gen_cmp, + category=FilteredAlgebrasWithBasis(base_ring)) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: AlgebrasWithBasis(QQ).Filtered().example() + An example of a filtered algebra with basis: + the universal enveloping algebra of + Lie algebra of RR^3 with cross product over Rational Field + """ + return "An example of a filtered algebra with basis: the universal enveloping algebra of Lie algebra of RR^3 with cross product over {}".format(self.base_ring()) + + def algebra_generators(self): + """ + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: list(A.algebra_generators()) + [U['x'], U['y'], U['z']] + """ + G = self._indices.monoid_generators() + I = sorted(G.keys()) + return Family(I, lambda x: self.monomial(G[x])) + + def one_basis(self): + """ + Return the index of the unit of ``self``. + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: A.one_basis() + 1 + """ + return self._indices.one() + + def degree_on_basis(self, m): + """ + The degree of the basis element of ``self`` labelled by ``m``. + + INPUT: + + - ``m`` -- an element of the free abelian monoid + + OUTPUT: an integer, the degree of the corresponding basis element + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x = A.algebra_generators()['x'] + sage: A.degree_on_basis((x^4).leading_support()) + 4 + sage: a = A.an_element(); a + U['x']^2*U['y']^2*U['z']^3 + sage: A.degree_on_basis(a.leading_support()) + 7 + """ + return len(m) + + # TODO: This is a general procedure of expanding multiplication defined + # on generators to arbitrary monomials and could likely be factored out + # and be useful elsewhere. + def product_on_basis(self, s, t): + """ + Return the product of two basis elements indexed by ``s`` and ``t``. + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: G = A.algebra_generators() + sage: x,y,z = G['x'], G['y'], G['z'] + sage: A.product_on_basis(x.leading_support(), y.leading_support()) + U['x']*U['y'] + sage: y*x + U['x']*U['y'] - U['z'] + sage: x*y*x + U['x']^2*U['y'] - U['x']*U['z'] + sage: z*y*x + U['x']*U['y']*U['z'] - U['x']^2 + U['y']^2 - U['z']^2 + """ + if len(s) == 0: + return self.monomial(t) + if len(t) == 0: + return self.monomial(s) + if s.trailing_support() <= t.leading_support(): + return self.monomial(s*t) + + if len(t) == 1: + if len(s) == 1: + # Do the product of the generators + a = s.leading_support() + b = t.leading_support() + cur = self.monomial(s*t) + if a <= b: + return cur + if a == 'z': + if b == 'y': + return cur - self.monomial(self._indices.gen('x')) + # b == 'x' + return cur + self.monomial(self._indices.gen('y')) + # a == 'y' and b == 'x' + return cur - self.monomial(self._indices.gen('z')) + + cur = self.monomial(t) + for a in reversed(s.to_word_list()): + cur = self.monomial(self._indices.gen(a)) * cur + return cur + + cur = self.monomial(s) + for a in t.to_word_list(): + cur = cur * self.monomial(self._indices.gen(a)) + return cur + +Example = PBWBasisCrossProduct + diff --git a/src/sage/categories/examples/filtered_modules_with_basis.py b/src/sage/categories/examples/filtered_modules_with_basis.py new file mode 100644 index 00000000000..9954d6a2ff5 --- /dev/null +++ b/src/sage/categories/examples/filtered_modules_with_basis.py @@ -0,0 +1,151 @@ +r""" +Examples of filtered modules with basis +""" +#***************************************************************************** +# Copyright (C) 2013 Frederic Chapoton +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.categories.filtered_modules_with_basis import FilteredModulesWithBasis +from sage.combinat.free_module import CombinatorialFreeModule +from sage.combinat.partition import Partitions + + +class FilteredPartitionModule(CombinatorialFreeModule): + r""" + This class illustrates an implementation of a filtered module + with basis: the free module on the set of all partitions. + + INPUT: + + - ``R`` -- base ring + + The implementation involves the following: + + - A choice of how to represent elements. In this case, the basis + elements are partitions. The algebra is constructed as a + :class:`CombinatorialFreeModule + ` on the + set of partitions, so it inherits all of the methods for such + objects, and has operations like addition already defined. + + :: + + sage: A = ModulesWithBasis(QQ).Filtered().example() + + - If the algebra is called ``A``, then its basis function is + stored as ``A.basis``. Thus the function can be used to + find a basis for the degree `d` piece: essentially, just call + ``A.basis(d)``. More precisely, call ``x`` for + each ``x`` in ``A.basis(d)``. + + :: + + sage: [m for m in A.basis(4)] + [P[4], P[3, 1], P[2, 2], P[2, 1, 1], P[1, 1, 1, 1]] + + - For dealing with basis elements: :meth:`degree_on_basis`, and + :meth:`_repr_term`. The first of these defines the degree of any + monomial, and then the :meth:`degree + ` method for elements -- + see the next item -- uses it to compute the degree for a linear + combination of monomials. The last of these determines the + print representation for monomials, which automatically produces + the print representation for general elements. + + :: + + sage: A.degree_on_basis(Partition([4,3])) + 7 + sage: A._repr_term(Partition([4,3])) + 'P[4, 3]' + + - There is a class for elements, which inherits from + :class:`CombinatorialFreeModuleElement + `. An + element is determined by a dictionary whose keys are partitions and whose + corresponding values are the coefficients. The class implements + two things: an :meth:`is_homogeneous + ` method and a + :meth:`degree ` method. + + :: + + sage: p = A.monomial(Partition([3,2,1])); p + P[3, 2, 1] + sage: p.is_homogeneous() + True + sage: p.degree() + 6 + """ + def __init__(self, base_ring): + """ + EXAMPLES:: + + sage: A = ModulesWithBasis(QQ).Filtered().example(); A + An example of a filtered module with basis: the free module on partitions over Rational Field + sage: TestSuite(A).run() + """ + CombinatorialFreeModule.__init__(self, base_ring, Partitions(), + category=FilteredModulesWithBasis(base_ring)) + + # FIXME: this is currently required, because the implementation of ``basis`` + # in CombinatorialFreeModule overrides that of GradedModulesWithBasis + basis = FilteredModulesWithBasis.ParentMethods.__dict__['basis'] + + # This could be a default implementation + def degree_on_basis(self, t): + """ + The degree of the basis element indexed by the partition ``t`` + in this filtered module. + + INPUT: + + - ``t`` -- the index of an element of the basis of this module, + i.e. a partition + + OUTPUT: an integer, the degree of the corresponding basis element + + EXAMPLES:: + + sage: A = ModulesWithBasis(QQ).Filtered().example() + sage: A.degree_on_basis(Partition((2,1))) + 3 + sage: A.degree_on_basis(Partition((4,2,1,1,1,1))) + 10 + sage: type(A.degree_on_basis(Partition((1,1)))) + + """ + return t.size() + + def _repr_(self): + """ + Print representation of ``self``. + + EXAMPLES:: + + sage: ModulesWithBasis(QQ).Filtered().example() # indirect doctest + An example of a filtered module with basis: the free module on partitions over Rational Field + """ + return "An example of a filtered module with basis: the free module on partitions over %s" % self.base_ring() + + def _repr_term(self, t): + """ + Print representation for the basis element represented by the + partition ``t``. + + This governs the behavior of the print representation of all elements + of the algebra. + + EXAMPLES:: + + sage: A = ModulesWithBasis(QQ).Filtered().example() + sage: A._repr_term(Partition((4,2,1))) + 'P[4, 2, 1]' + """ + return 'P' + t._repr_() + +Example = FilteredPartitionModule + diff --git a/src/sage/categories/examples/graded_modules_with_basis.py b/src/sage/categories/examples/graded_modules_with_basis.py index ebd05587f55..e0f95880199 100644 --- a/src/sage/categories/examples/graded_modules_with_basis.py +++ b/src/sage/categories/examples/graded_modules_with_basis.py @@ -9,6 +9,7 @@ #***************************************************************************** from sage.categories.graded_modules_with_basis import GradedModulesWithBasis +from sage.categories.filtered_modules_with_basis import FilteredModulesWithBasis from sage.combinat.free_module import CombinatorialFreeModule from sage.combinat.partition import Partitions @@ -20,7 +21,7 @@ class GradedPartitionModule(CombinatorialFreeModule): INPUT: - - ``R`` - base ring + - ``R`` -- base ring The implementation involves the following: @@ -106,7 +107,7 @@ def __init__(self, base_ring): # FIXME: this is currently required, because the implementation of ``basis`` # in CombinatorialFreeModule overrides that of GradedModulesWithBasis - basis = GradedModulesWithBasis.ParentMethods.__dict__['basis'] + basis = FilteredModulesWithBasis.ParentMethods.__dict__['basis'] # This could be a default implementation def degree_on_basis(self, t): diff --git a/src/sage/categories/examples/graphs.py b/src/sage/categories/examples/graphs.py new file mode 100644 index 00000000000..9c1e1eddb7e --- /dev/null +++ b/src/sage/categories/examples/graphs.py @@ -0,0 +1,122 @@ +""" +Examples of graphs +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.element_wrapper import ElementWrapper +from sage.categories.graphs import Graphs +from sage.rings.all import QQ + +class Cycle(UniqueRepresentation, Parent): + r""" + An example of a graph: the cycle of length `n`. + + This class illustrates a minimal implementation of a graph. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example(); C + An example of a graph: the 5-cycle + + sage: C.category() + Category of graphs + + We conclude by running systematic tests on this graph:: + + sage: TestSuite(C).run() + """ + def __init__(self, n=5): + r""" + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example(6); C + An example of a graph: the 6-cycle + + TESTS:: + + sage: TestSuite(C).run() + """ + self._n = n + Parent.__init__(self, category=Graphs()) + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.categories.graphs import Graphs + sage: Graphs().example() + An example of a graph: the 5-cycle + """ + return "An example of a graph: the {}-cycle".format(self._n) + + def an_element(self): + r""" + Return an element of the graph, as per + :meth:`Sets.ParentMethods.an_element`. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.an_element() + 0 + """ + return self(0) + + def vertices(self): + """ + Return the vertices of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.vertices() + [0, 1, 2, 3, 4] + """ + return [self(i) for i in range(self._n)] + + def edges(self): + """ + Return the edges of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.edges() + [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] + """ + return [self( (i, (i+1) % self._n) ) for i in range(self._n)] + + class Element(ElementWrapper): + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: e = C.edges()[0] + sage: e.dimension() + 2 + sage: v = C.vertices()[0] + sage: v.dimension() + 1 + """ + if isinstance(self.value, tuple): + return 2 + return 1 + +Example = Cycle + diff --git a/src/sage/categories/examples/manifolds.py b/src/sage/categories/examples/manifolds.py new file mode 100644 index 00000000000..a71b7cf30e3 --- /dev/null +++ b/src/sage/categories/examples/manifolds.py @@ -0,0 +1,94 @@ +""" +Examples of manifolds +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.element_wrapper import ElementWrapper +from sage.categories.manifolds import Manifolds +from sage.rings.all import QQ + +class Plane(UniqueRepresentation, Parent): + r""" + An example of a manifold: the `n`-dimensional plane. + + This class illustrates a minimal implementation of a manifold. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(QQ).example(); M + An example of a Rational Field manifold: the 3-dimensional plane + + sage: M.category() + Category of manifolds over Rational Field + + We conclude by running systematic tests on this manifold:: + + sage: TestSuite(M).run() + """ + + def __init__(self, n=3, base_ring=None): + r""" + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(QQ).example(6); M + An example of a Rational Field manifold: the 6-dimensional plane + + TESTS:: + + sage: TestSuite(M).run() + """ + self._n = n + Parent.__init__(self, base=base_ring, category=Manifolds(base_ring)) + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(QQ).example() + An example of a Rational Field manifold: the 3-dimensional plane + """ + return "An example of a {} manifold: the {}-dimensional plane".format( + self.base_ring(), self._n) + + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(QQ).example() + sage: M.dimension() + 3 + """ + return self._n + + def an_element(self): + r""" + Return an element of the manifold, as per + :meth:`Sets.ParentMethods.an_element`. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(QQ).example() + sage: M.an_element() + (0, 0, 0) + """ + zero = self.base_ring().zero() + return self(tuple([zero]*self._n)) + + Element = ElementWrapper + +Example = Plane + diff --git a/src/sage/categories/filtered_algebras.py b/src/sage/categories/filtered_algebras.py new file mode 100644 index 00000000000..321dd604ed3 --- /dev/null +++ b/src/sage/categories/filtered_algebras.py @@ -0,0 +1,62 @@ +r""" +Filtered Algebras +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.categories.filtered_modules import FilteredModulesCategory + +class FilteredAlgebras(FilteredModulesCategory): + r""" + The category of filtered algebras. + + An algebra `A` over a commutative ring `R` is *filtered* if + `A` is endowed with a structure of a filtered `R`-module + (whose underlying `R`-module structure is identical with + that of the `R`-algebra `A`) such that the indexing set `I` + (typically `I = \NN`) is also an additive abelian monoid, + the unity `1` of `A` belongs to `F_0`, and we have + `F_i \cdot F_j \subseteq F_{i+j}` for all `i, j \in I`. + + EXAMPLES:: + + sage: Algebras(ZZ).Filtered() + Category of filtered algebras over Integer Ring + sage: Algebras(ZZ).Filtered().super_categories() + [Category of algebras over Integer Ring, + Category of filtered modules over Integer Ring] + + TESTS:: + + sage: TestSuite(Algebras(ZZ).Filtered()).run() + + REFERENCES: + + - :wikipedia:`Filtered_algebra` + """ + class ParentMethods: + @abstract_method(optional=True) + def graded_algebra(self): + """ + Return the associated graded algebra to ``self``. + + .. TODO:: + + Implement a version of the associated graded algebra + which does not require ``self`` to have a + distinguished basis. + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(ZZ).Filtered().example() + sage: A.graded_algebra() + Graded Algebra of An example of a filtered algebra with basis: + the universal enveloping algebra of + Lie algebra of RR^3 with cross product over Integer Ring + """ + diff --git a/src/sage/categories/filtered_algebras_with_basis.py b/src/sage/categories/filtered_algebras_with_basis.py new file mode 100644 index 00000000000..053d119a040 --- /dev/null +++ b/src/sage/categories/filtered_algebras_with_basis.py @@ -0,0 +1,541 @@ +r""" +Filtered Algebras With Basis + +A filtered algebra with basis over a commutative ring `R` +is a filtered algebra over `R` endowed with the structure +of a filtered module with basis (with the same underlying +filtered-module structure). See +:class:`~sage.categories.filtered_algebras.FilteredAlgebras` and +:class:`~sage.categories.filtered_modules_with_basis.FilteredModulesWithBasis` +for these two notions. +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.filtered_modules import FilteredModulesCategory + +class FilteredAlgebrasWithBasis(FilteredModulesCategory): + """ + The category of filtered algebras with a distinguished + homogeneous basis. + + A filtered algebra with basis over a commutative ring `R` + is a filtered algebra over `R` endowed with the structure + of a filtered module with basis (with the same underlying + filtered-module structure). See + :class:`~sage.categories.filtered_algebras.FilteredAlgebras` and + :class:`~sage.categories.filtered_modules_with_basis.FilteredModulesWithBasis` + for these two notions. + + EXAMPLES:: + + sage: C = AlgebrasWithBasis(ZZ).Filtered(); C + Category of filtered algebras with basis over Integer Ring + sage: sorted(C.super_categories(), key=str) + [Category of algebras with basis over Integer Ring, + Category of filtered algebras over Integer Ring, + Category of filtered modules with basis over Integer Ring] + + TESTS:: + + sage: TestSuite(C).run() + """ + class ParentMethods: + def graded_algebra(self): + r""" + Return the associated graded algebra to ``self``. + + See :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra` + for the definition and the properties of this. + + If the filtered algebra ``self`` with basis is called `A`, + then this method returns `\operatorname{gr} A`. The method + :meth:`to_graded_conversion` returns the canonical + `R`-module isomorphism `A \to \operatorname{gr} A` induced + by the basis of `A`, and the method + :meth:`from_graded_conversion` returns the inverse of this + isomorphism. The method :meth:`projection` projects + elements of `A` onto `\operatorname{gr} A` according to + their place in the filtration on `A`. + + .. WARNING:: + + When not overridden, this method returns the default + implementation of an associated graded algebra -- + namely, ``AssociatedGradedAlgebra(self)``, where + ``AssociatedGradedAlgebra`` is + :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra`. + But many instances of :class:`FilteredAlgebrasWithBasis` + override this method, as the associated graded algebra + often is (isomorphic) to a simpler object (for instance, + the associated graded algebra of a graded algebra can be + identified with the graded algebra itself). Generic code + that uses associated graded algebras (such as the code + of the :meth:`induced_graded_map` method below) should + make sure to only communicate with them via the + :meth:`to_graded_conversion`, + :meth:`from_graded_conversion`, and + :meth:`projection` methods (in particular, + do not expect there to be a conversion from ``self`` + to ``self.graded_algebra()``; this currently does not + work for Clifford algebras). Similarly, when + overriding :meth:`graded_algebra`, make sure to + accordingly redefine these three methods, unless their + definitions below still apply to your case (this will + happen whenever the basis of your :meth:`graded_algebra` + has the same indexing set as ``self``, and the partition + of this indexing set according to degree is the same as + for ``self``). + + .. TODO:: + + Maybe the thing about the conversion from ``self`` + to ``self.graded_algebra()`` on the Clifford at least + could be made to work? (I would still warn the user + against ASSUMING that it must work -- as there is + probably no way to guarantee it in all cases, and + we shouldn't require users to mess with + element constructors.) + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(ZZ).Filtered().example() + sage: A.graded_algebra() + Graded Algebra of An example of a filtered algebra with basis: + the universal enveloping algebra of + Lie algebra of RR^3 with cross product over Integer Ring + """ + from sage.algebras.associated_graded import AssociatedGradedAlgebra + return AssociatedGradedAlgebra(self) + + # Maps + + def to_graded_conversion(self): + r""" + Return the canonical `R`-module isomorphism + `A \to \operatorname{gr} A` induced by the basis of `A` + (where `A = ` ``self``). + + This is an isomorphism of `R`-modules, not of algebras. See + the class documentation :class:`AssociatedGradedAlgebra`. + + .. SEEALSO:: + + :meth:`from_graded_conversion` + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: p = A.an_element() + A.algebra_generators()['x'] + 2; p + U['x']^2*U['y']^2*U['z']^3 + U['x'] + 2 + sage: q = A.to_graded_conversion()(p); q + bar(U['x']^2*U['y']^2*U['z']^3) + bar(U['x']) + 2*bar(1) + sage: q.parent() is A.graded_algebra() + True + """ + base_one = self.base_ring().one() + return self.module_morphism(diagonal=lambda x: base_one, + codomain=self.graded_algebra()) + + def from_graded_conversion(self): + r""" + Return the inverse of the canonical `R`-module isomorphism + `A \to \operatorname{gr} A` induced by the basis of `A` + (where `A = ` ``self``). This inverse is an isomorphism + `\operatorname{gr} A \to A`. + + This is an isomorphism of `R`-modules, not of algebras. See + the class documentation :class:`AssociatedGradedAlgebra`. + + .. SEEALSO:: + + :meth:`to_graded_conversion` + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: p = A.an_element() + A.algebra_generators()['x'] + 2; p + U['x']^2*U['y']^2*U['z']^3 + U['x'] + 2 + sage: q = A.to_graded_conversion()(p) + sage: A.from_graded_conversion()(q) == p + True + sage: q.parent() is A.graded_algebra() + True + """ + base_one = self.base_ring().one() + return self.graded_algebra().module_morphism(diagonal=lambda x: base_one, + codomain=self) + + def projection(self, i): + r""" + Return the `i`-th projection `p_i : F_i \to G_i` (in the + notations of the class documentation + :class:`AssociatedGradedAlgebra`, where `A = ` ``self``). + + This method actually does not return the map `p_i` itself, + but an extension of `p_i` to the whole `R`-module `A`. + This extension is the composition of the `R`-module + isomorphism `A \to \operatorname{gr} A` with the canonical + projection of the graded `R`-module `\operatorname{gr} A` + onto its `i`-th graded component `G_i`. The codomain of + this map is `\operatorname{gr} A`, although its actual + image is `G_i`. The map `p_i` is obtained from this map + by restricting its domain to `F_i` and its image to `G_i`. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: p = A.an_element() + A.algebra_generators()['x'] + 2; p + U['x']^2*U['y']^2*U['z']^3 + U['x'] + 2 + sage: q = A.projection(7)(p); q + bar(U['x']^2*U['y']^2*U['z']^3) + sage: q.parent() is A.graded_algebra() + True + sage: A.projection(8)(p) + 0 + """ + base_zero = self.base_ring().zero() + base_one = self.base_ring().one() + grA = self.graded_algebra() + proj = lambda x: (base_one if self.degree_on_basis(x) == i + else base_zero) + return self.module_morphism(diagonal=proj, codomain=grA) + + def induced_graded_map(self, other, f): + r""" + Return the graded linear map between the associated graded + algebras of ``self`` and ``other`` canonically induced by + the filtration-preserving map ``f : self -> other``. + + Let `A` and `B` be two filtered algebras with basis, and let + `(F_i)_{i \in I}` and `(G_i)_{i \in I}` be their + filtrations. Let `f : A \to B` be a linear map which + preserves the filtration (i.e., satisfies `f(F_i) \subseteq + G_i` for all `i \in I`). Then, there is a canonically + defined graded linear map + `\operatorname{gr} f : \operatorname{gr} A \to + \operatorname{gr} B` which satisfies + + .. MATH:: + + (\operatorname{gr} f) (p_i(a)) = p_i(f(a)) + \qquad \text{for all } i \in I \text{ and } a \in F_i , + + where the `p_i` on the left hand side is the canonical + projection from `F_i` onto the `i`-th graded component + of `\operatorname{gr} A`, while the `p_i` on the right + hand side is the canonical projection from `G_i` onto + the `i`-th graded component of `\operatorname{gr} B`. + + INPUT: + + - ``other`` -- a filtered algebra with basis + + - ``f`` -- a filtration-preserving linear map from ``self`` + to ``other`` (can be given as a morphism or as a function) + + OUTPUT: + + The graded linear map `\operatorname{gr} f`. + + EXAMPLES: + + **Example 1.** + + We start with the universal enveloping algebra of the + Lie algebra `\RR^3` (with the cross product serving as + Lie bracket):: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example(); A + An example of a filtered algebra with basis: the + universal enveloping algebra of Lie algebra of RR^3 + with cross product over Rational Field + sage: M = A.indices(); M + Free abelian monoid indexed by {'x', 'y', 'z'} + sage: x,y,z = [A.basis()[M.gens()[i]] for i in "xyz"] + + Let us define a stupid filtered map from ``A`` to + itself:: + + sage: def map_on_basis(m): + ....: d = m.dict() + ....: i = d.get('x', 0); j = d.get('y', 0); k = d.get('z', 0) + ....: g = (y ** (i+j)) * (z ** k) + ....: if i > 0: + ....: g += i * (x ** (i-1)) * (y ** j) * (z ** k) + ....: return g + sage: f = A.module_morphism(on_basis=map_on_basis, + ....: codomain=A) + sage: f(x) + U['y'] + 1 + sage: f(x*y*z) + U['y']^2*U['z'] + U['y']*U['z'] + sage: f(x*x*y*z) + U['y']^3*U['z'] + 2*U['x']*U['y']*U['z'] + sage: f(A.one()) + 1 + sage: f(y*z) + U['y']*U['z'] + + (There is nothing here that is peculiar to this + universal enveloping algebra; we are only using its + module structure, and we could just as well be using + a polynomial algebra in its stead.) + + We now compute `\operatorname{gr} f` :: + + sage: grA = A.graded_algebra(); grA + Graded Algebra of An example of a filtered algebra with + basis: the universal enveloping algebra of Lie algebra + of RR^3 with cross product over Rational Field + sage: xx, yy, zz = [A.to_graded_conversion()(i) for i in [x, y, z]] + sage: xx+yy*zz + bar(U['y']*U['z']) + bar(U['x']) + sage: grf = A.induced_graded_map(A, f); grf + Generic endomorphism of Graded Algebra of An example + of a filtered algebra with basis: the universal + enveloping algebra of Lie algebra of RR^3 with cross + product over Rational Field + sage: grf(xx) + bar(U['y']) + sage: grf(xx*yy*zz) + bar(U['y']^2*U['z']) + sage: grf(xx*xx*yy*zz) + bar(U['y']^3*U['z']) + sage: grf(grA.one()) + bar(1) + sage: grf(yy*zz) + bar(U['y']*U['z']) + sage: grf(yy*zz-2*yy) + bar(U['y']*U['z']) - 2*bar(U['y']) + + **Example 2.** + + We shall now construct `\operatorname{gr} f` for a + different map `f` out of the same ``A``; the new map + `f` will lead into a graded algebra already, namely into + the algebra of symmetric functions:: + + sage: h = SymmetricFunctions(QQ).h() + sage: def map_on_basis(m): # redefining map_on_basis + ....: d = m.dict() + ....: i = d.get('x', 0); j = d.get('y', 0); k = d.get('z', 0) + ....: g = (h[1] ** i) * (h[2] ** (floor(j/2))) * (h[3] ** (floor(k/3))) + ....: g += i * (h[1] ** (i+j+k)) + ....: return g + sage: f = A.module_morphism(on_basis=map_on_basis, + ....: codomain=h) # redefining f + sage: f(x) + 2*h[1] + sage: f(y) + h[] + sage: f(z) + h[] + sage: f(y**2) + h[2] + sage: f(x**2) + 3*h[1, 1] + sage: f(x*y*z) + h[1] + h[1, 1, 1] + sage: f(x*x*y*y*z) + 2*h[1, 1, 1, 1, 1] + h[2, 1, 1] + sage: f(A.one()) + h[] + + The algebra ``h`` of symmetric functions in the `h`-basis + is already graded, so its associated graded algebra is + implemented as itself:: + + sage: grh = h.graded_algebra(); grh is h + True + sage: grf = A.induced_graded_map(h, f); grf + Generic morphism: + From: Graded Algebra of An example of a filtered + algebra with basis: the universal enveloping + algebra of Lie algebra of RR^3 with cross + product over Rational Field + To: Symmetric Functions over Rational Field + in the homogeneous basis + sage: grf(xx) + 2*h[1] + sage: grf(yy) + 0 + sage: grf(zz) + 0 + sage: grf(yy**2) + h[2] + sage: grf(xx**2) + 3*h[1, 1] + sage: grf(xx*yy*zz) + h[1, 1, 1] + sage: grf(xx*xx*yy*yy*zz) + 2*h[1, 1, 1, 1, 1] + sage: grf(grA.one()) + h[] + + **Example 3.** + + After having had a graded algebra as the codomain, let us try to + have one as the domain instead. Our new ``f`` will go from ``h`` + to ``A``:: + + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return x ** (sum(lam)) + y ** (len(lam)) + sage: f = h.module_morphism(on_basis=map_on_basis, + ....: codomain=A) # redefining f + sage: f(h[1]) + U['x'] + U['y'] + sage: f(h[2]) + U['x']^2 + U['y'] + sage: f(h[1, 1]) + U['x']^2 + U['y']^2 + sage: f(h[2, 2]) + U['x']^4 + U['y']^2 + sage: f(h[3, 2, 1]) + U['x']^6 + U['y']^3 + sage: f(h.one()) + 2 + sage: grf = h.induced_graded_map(A, f); grf + Generic morphism: + From: Symmetric Functions over Rational Field + in the homogeneous basis + To: Graded Algebra of An example of a filtered + algebra with basis: the universal enveloping + algebra of Lie algebra of RR^3 with cross + product over Rational Field + sage: grf(h[1]) + bar(U['x']) + bar(U['y']) + sage: grf(h[2]) + bar(U['x']^2) + sage: grf(h[1, 1]) + bar(U['x']^2) + bar(U['y']^2) + sage: grf(h[2, 2]) + bar(U['x']^4) + sage: grf(h[3, 2, 1]) + bar(U['x']^6) + sage: grf(h.one()) + 2*bar(1) + + **Example 4.** + + The construct `\operatorname{gr} f` also makes sense when `f` + is a filtration-preserving map between graded algebras. :: + + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return h[lam] + h[len(lam)] + sage: f = h.module_morphism(on_basis=map_on_basis, + ....: codomain=h) # redefining f + sage: f(h[1]) + 2*h[1] + sage: f(h[2]) + h[1] + h[2] + sage: f(h[1, 1]) + h[1, 1] + h[2] + sage: f(h[2, 1]) + h[2] + h[2, 1] + sage: f(h.one()) + 2*h[] + sage: grf = h.induced_graded_map(h, f); grf + Generic endomorphism of Symmetric Functions over Rational + Field in the homogeneous basis + sage: grf(h[1]) + 2*h[1] + sage: grf(h[2]) + h[2] + sage: grf(h[1, 1]) + h[1, 1] + h[2] + sage: grf(h[2, 1]) + h[2, 1] + sage: grf(h.one()) + 2*h[] + + **Example 5.** + + For another example, let us compute `\operatorname{gr} f` for a + map `f` between two Clifford algebras:: + + sage: Q = QuadraticForm(ZZ, 2, [1,2,3]) + sage: B = CliffordAlgebra(Q, names=['u','v']); B + The Clifford algebra of the Quadratic form in 2 + variables over Integer Ring with coefficients: + [ 1 2 ] + [ * 3 ] + sage: m = Matrix(ZZ, [[1, 2], [1, -1]]) + sage: f = B.lift_module_morphism(m, names=['x','y']) + sage: A = f.domain(); A + The Clifford algebra of the Quadratic form in 2 + variables over Integer Ring with coefficients: + [ 6 0 ] + [ * 3 ] + sage: x, y = A.gens() + sage: f(x) + u + v + sage: f(y) + 2*u - v + sage: f(x**2) + 6 + sage: f(x*y) + -3*u*v + 3 + sage: grA = A.graded_algebra(); grA + The exterior algebra of rank 2 over Integer Ring + sage: A.to_graded_conversion()(x) + x + sage: A.to_graded_conversion()(y) + y + sage: A.to_graded_conversion()(x*y) + x^y + sage: u = A.to_graded_conversion()(x*y+1); u + x^y + 1 + sage: A.from_graded_conversion()(u) + x*y + 1 + sage: A.projection(2)(x*y+1) + x^y + sage: A.projection(1)(x+2*y-2) + x + 2*y + sage: grf = A.induced_graded_map(B, f); grf + Generic morphism: + From: The exterior algebra of rank 2 over Integer Ring + To: The exterior algebra of rank 2 over Integer Ring + sage: grf(A.to_graded_conversion()(x)) + u + v + sage: grf(A.to_graded_conversion()(y)) + 2*u - v + sage: grf(A.to_graded_conversion()(x**2)) + 6 + sage: grf(A.to_graded_conversion()(x*y)) + -3*u^v + sage: grf(grA.one()) + 1 + """ + grA = self.graded_algebra() + grB = other.graded_algebra() + from sage.categories.graded_modules_with_basis import GradedModulesWithBasis + cat = GradedModulesWithBasis(self.base_ring()) + from_gr = self.from_graded_conversion() + def on_basis(m): + i = grA.degree_on_basis(m) + lifted_img_of_m = f(from_gr(grA.monomial(m))) + return other.projection(i)(lifted_img_of_m) + return grA.module_morphism(on_basis=on_basis, + codomain=grB, category=cat) + # If we could assume that the projection of the basis + # element of ``self`` indexed by an index ``m`` is the + # basis element of ``grA`` indexed by ``m``, then this + # could go faster: + # + # def on_basis(m): + # i = grA.degree_on_basis(m) + # return grB.projection(i)(f(self.monomial(m))) + # return grA.module_morphism(on_basis=on_basis, + # codomain=grB, category=cat) + # + # But this assumption might come back to bite us in the + # ass one day. What do you think? + + class ElementMethods: + pass + diff --git a/src/sage/categories/filtered_modules.py b/src/sage/categories/filtered_modules.py new file mode 100644 index 00000000000..8a9a79d6c08 --- /dev/null +++ b/src/sage/categories/filtered_modules.py @@ -0,0 +1,161 @@ +r""" +Filtered Modules + +A *filtered module* over a ring `R` with a totally ordered +indexing set `I` (typically `I = \NN`) is an `R`-module `M` equipped +with a family `(F_i)_{i \in I}` of `R`-submodules satisfying +`F_i \subseteq F_j` for all `i,j \in I` having `i \leq j`, and +`M = \bigcup_{i \in I} F_i`. This family is called a *filtration* +of the given module `M`. + +.. TODO:: + + Implement a notion for decreasing filtrations: where `F_j \subseteq F_i` + when `i \leq j`. + +.. TODO:: + + Implement filtrations for all concrete categories. + +.. TODO:: + + Implement `\operatorname{gr}` as a functor. +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_class_attribute +from sage.categories.category_types import Category_over_base_ring +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.covariant_functorial_construction import RegressiveCovariantConstructionCategory + +class FilteredModulesCategory(RegressiveCovariantConstructionCategory, Category_over_base_ring): + def __init__(self, base_category): + """ + EXAMPLES:: + + sage: C = Algebras(QQ).Filtered() + sage: C + Category of filtered algebras over Rational Field + sage: C.base_category() + Category of algebras over Rational Field + sage: sorted(C.super_categories(), key=str) + [Category of algebras over Rational Field, + Category of filtered modules over Rational Field] + + sage: AlgebrasWithBasis(QQ).Filtered().base_ring() + Rational Field + sage: HopfAlgebrasWithBasis(QQ).Filtered().base_ring() + Rational Field + """ + super(FilteredModulesCategory, self).__init__(base_category, base_category.base_ring()) + + _functor_category = "Filtered" + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: AlgebrasWithBasis(QQ).Filtered() # indirect doctest + Category of filtered algebras with basis over Rational Field + """ + return "filtered {}".format(self.base_category()._repr_object_names()) + +class FilteredModules(FilteredModulesCategory): + r""" + The category of filtered modules over a given ring `R`. + + A *filtered module* over a ring `R` with a totally ordered + indexing set `I` (typically `I = \NN`) is an `R`-module `M` equipped + with a family `(F_i)_{i \in I}` of `R`-submodules satisfying + `F_i \subseteq F_j` for all `i,j \in I` having `i \leq j`, and + `M = \bigcup_{i \in I} F_i`. This family is called a *filtration* + of the given module `M`. + + EXAMPLES:: + + sage: Modules(ZZ).Filtered() + Category of filtered modules over Integer Ring + sage: Modules(ZZ).Filtered().super_categories() + [Category of modules over Integer Ring] + + TESTS:: + + sage: TestSuite(Modules(ZZ).Filtered()).run() + + REFERENCES: + + - :wikipedia:`Filtration_(mathematics)` + """ + def extra_super_categories(self): + r""" + Add :class:`VectorSpaces` to the super categories of ``self`` if + the base ring is a field. + + EXAMPLES:: + + sage: Modules(QQ).Filtered().extra_super_categories() + [Category of vector spaces over Rational Field] + sage: Modules(ZZ).Filtered().extra_super_categories() + [] + + This makes sure that ``Modules(QQ).Filtered()`` returns an + instance of :class:`FilteredModules` and not a join category of + an instance of this class and of ``VectorSpaces(QQ)``:: + + sage: type(Modules(QQ).Filtered()) + + + .. TODO:: + + Get rid of this workaround once there is a more systematic + approach for the alias ``Modules(QQ)`` -> ``VectorSpaces(QQ)``. + Probably the latter should be a category with axiom, and + covariant constructions should play well with axioms. + """ + from sage.categories.modules import Modules + from sage.categories.fields import Fields + base_ring = self.base_ring() + if base_ring in Fields: + return [Modules(base_ring)] + else: + return [] + + class SubcategoryMethods: + + @cached_method + def Connected(self): + r""" + Return the full subcategory of the connected objects of ``self``. + + A filtered `R`-module `M` with filtration + `(F_0, F_1, F_2, \ldots)` (indexed by `\NN`) + is said to be *connected* if `F_0` is isomorphic + to `R`. + + EXAMPLES:: + + sage: Modules(ZZ).Filtered().Connected() + Category of filtered connected modules over Integer Ring + sage: Coalgebras(QQ).Filtered().Connected() + Join of Category of filtered connected modules over Rational Field + and Category of coalgebras over Rational Field + sage: AlgebrasWithBasis(QQ).Filtered().Connected() + Category of filtered connected algebras with basis over Rational Field + + TESTS:: + + sage: TestSuite(Modules(ZZ).Filtered().Connected()).run() + sage: Coalgebras(QQ).Filtered().Connected.__module__ + 'sage.categories.filtered_modules' + """ + return self._with_axiom("Connected") + + class Connected(CategoryWithAxiom_over_base_ring): + pass + diff --git a/src/sage/categories/filtered_modules_with_basis.py b/src/sage/categories/filtered_modules_with_basis.py new file mode 100644 index 00000000000..7f2320f7f19 --- /dev/null +++ b/src/sage/categories/filtered_modules_with_basis.py @@ -0,0 +1,928 @@ +r""" +Filtered Modules With Basis + +A *filtered module with basis* over a ring `R` means +(for the purpose of this code) a filtered `R`-module `M` +with filtration `(F_i)_{i \in I}` (typically `I = \NN`) +endowed with a basis `(b_j)_{j \in J}` of `M` and a partition +`J = \bigsqcup_{i \in I} J_i` of the set `J` (it is allowed +that some `J_i` are empty) such that for every `n \in I`, +the subfamily `(b_j)_{j \in U_n}`, where +`U_n = \bigcup_{i \leq n} J_i`, is a basis of the +`R`-submodule `F_n`. + +For every `i \in I`, the `R`-submodule of `M` spanned by +`(b_j)_{j \in J_i}` is called the `i`-*th graded component* +(aka the `i`-*th homogeneous component*) of the filtered +module with basis `M`; the elements of this submodule are +referred to as *homogeneous elements of degree* `i`. + +See the class documentation +:class:`~sage.categories.filtered_modules_with_basis.FilteredModulesWithBasis` +for further details. +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.filtered_modules import FilteredModulesCategory +from sage.misc.abstract_method import abstract_method + +class FilteredModulesWithBasis(FilteredModulesCategory): + r""" + The category of filtered modules with a distinguished basis. + + A *filtered module with basis* over a ring `R` means + (for the purpose of this code) a filtered `R`-module `M` + with filtration `(F_i)_{i \in I}` (typically `I = \NN`) + endowed with a basis `(b_j)_{j \in J}` of `M` and a partition + `J = \bigsqcup_{i \in I} J_i` of the set `J` (it is allowed + that some `J_i` are empty) such that for every `n \in I`, + the subfamily `(b_j)_{j \in U_n}`, where + `U_n = \bigcup_{i \leq n} J_i`, is a basis of the + `R`-submodule `F_n`. + + For every `i \in I`, the `R`-submodule of `M` spanned by + `(b_j)_{j \in J_i}` is called the `i`-*th graded component* + (aka the `i`-*th homogeneous component*) of the filtered + module with basis `M`; the elements of this submodule are + referred to as *homogeneous elements of degree* `i`. + The `R`-module `M` is the direct sum of its `i`-th graded + components over all `i \in I`, and thus becomes a graded + `R`-module with basis. + Conversely, any graded `R`-module with basis canonically + becomes a filtered `R`-module with basis (by defining + `F_n = \bigoplus_{i \leq n} G_i` where `G_i` is the `i`-th + graded component, and defining `J_i` as the indexing set + of the basis of the `i`-th graded component). Hence, the + notion of a filtered `R`-module with basis is equivalent + to the notion of a graded `R`-module with basis. + + However, the *category* of filtered `R`-modules with basis is not + the category of graded `R`-modules with basis. Indeed, the *morphisms* + of filtered `R`-modules with basis are defined to be morphisms of + `R`-modules which send each `F_n` of the domain to the corresponding + `F_n` of the target; in contrast, the morphisms of graded `R`-modules + with basis must preserve each homogeneous component. Also, + the notion of a filtered algebra with basis differs from + that of a graded algebra with basis. + + .. NOTE:: + + Currently, to make use of the functionality of this class, + an instance of ``FilteredModulesWithBasis`` should fulfill + the contract of a :class:`CombinatorialFreeModule` (most + likely by inheriting from it). It should also have the + indexing set `J` encoded as its ``_indices`` attribute, + and ``_indices.subset(size=i)`` should yield the subset + `J_i` (as an iterable). If the latter conditions are not + satisfied, then :meth:`basis` must be overridden. + + .. NOTE:: + + One should implement a ``degree_on_basis`` method in the parent + class in order to fully utilize the methods of this category. + This might become a required abstract method in the future. + + EXAMPLES:: + + sage: C = ModulesWithBasis(ZZ).Filtered(); C + Category of filtered modules with basis over Integer Ring + sage: sorted(C.super_categories(), key=str) + [Category of filtered modules over Integer Ring, + Category of modules with basis over Integer Ring] + sage: C is ModulesWithBasis(ZZ).Filtered() + True + + TESTS:: + + sage: C = ModulesWithBasis(ZZ).Filtered() + sage: TestSuite(C).run() + sage: C = ModulesWithBasis(QQ).Filtered() + sage: TestSuite(C).run() + """ + class ParentMethods: + + # TODO: which syntax do we prefer? + # A.basis(degree = 3) + # A.basis().subset(degree=3) + + # This is related to the following design question: + # If F = (f_i)_{i\in I} is a family, should ``F.subset(degree = 3)`` + # be the elements of F of degree 3 or those whose index is of degree 3? + + def basis(self, d=None): + r""" + Return the basis for (the ``d``-th homogeneous component + of) ``self``. + + INPUT: + + - ``d`` -- (optional, default ``None``) nonnegative integer + or ``None`` + + OUTPUT: + + If ``d`` is ``None``, returns the basis of the module. + Otherwise, returns the basis of the homogeneous component + of degree ``d`` (i.e., the subfamily of the basis of the + whole module which consists only of the basis vectors + lying in `F_d \setminus \bigcup_{i = ExteriorAlgebra(QQ) + sage: E.basis() + Lazy family (Term map from Subsets of {0, 1} to + The exterior algebra of rank 2 over Rational Field(i))_{i in + Subsets of {0, 1}} + """ + from sage.sets.family import Family + if d is None: + return Family(self._indices, self.monomial) + else: + return Family(self._indices.subset(size=d), self.monomial) + + def graded_algebra(self): + r""" + Return the associated graded module to ``self``. + + See :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra` + for the definition and the properties of this. + + If the filtered module ``self`` with basis is called `A`, + then this method returns `\operatorname{gr} A`. The method + :meth:`to_graded_conversion` returns the canonical + `R`-module isomorphism `A \to \operatorname{gr} A` induced + by the basis of `A`, and the method + :meth:`from_graded_conversion` returns the inverse of this + isomorphism. The method :meth:`projection` projects + elements of `A` onto `\operatorname{gr} A` according to + their place in the filtration on `A`. + + .. WARNING:: + + When not overridden, this method returns the default + implementation of an associated graded module -- + namely, ``AssociatedGradedAlgebra(self)``, where + ``AssociatedGradedAlgebra`` is + :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra`. + But some instances of :class:`FilteredModulesWithBasis` + override this method, as the associated graded module + often is (isomorphic) to a simpler object (for instance, + the associated graded module of a graded module can be + identified with the graded module itself). Generic code + that uses associated graded modules (such as the code + of the :meth:`induced_graded_map` method below) should + make sure to only communicate with them via the + :meth:`to_graded_conversion`, + :meth:`from_graded_conversion` and + :meth:`projection` methods (in particular, + do not expect there to be a conversion from ``self`` + to ``self.graded_algebra()``; this currently does not + work for Clifford algebras). Similarly, when + overriding :meth:`graded_algebra`, make sure to + accordingly redefine these three methods, unless their + definitions below still apply to your case (this will + happen whenever the basis of your :meth:`graded_algebra` + has the same indexing set as ``self``, and the partition + of this indexing set according to degree is the same as + for ``self``). + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: A.graded_algebra() + Graded Module of An example of a filtered module with basis: + the free module on partitions over Integer Ring + """ + from sage.algebras.associated_graded import AssociatedGradedAlgebra + return AssociatedGradedAlgebra(self) + + # Maps + + def to_graded_conversion(self): + r""" + Return the canonical `R`-module isomorphism + `A \to \operatorname{gr} A` induced by the basis of `A` + (where `A = ` ``self``). + + This is an isomorphism of `R`-modules. See + the class documentation :class:`AssociatedGradedAlgebra`. + + .. SEEALSO:: + + :meth:`from_graded_conversion` + + EXAMPLES:: + + sage: A = Modules(QQ).WithBasis().Filtered().example() + sage: p = -2 * A.an_element(); p + -4*P[] - 4*P[1] - 6*P[2] + sage: q = A.to_graded_conversion()(p); q + -4*Bbar[[]] - 4*Bbar[[1]] - 6*Bbar[[2]] + sage: q.parent() is A.graded_algebra() + True + """ + base_one = self.base_ring().one() + return self.module_morphism(diagonal=lambda x: base_one, + codomain=self.graded_algebra()) + + def from_graded_conversion(self): + r""" + Return the inverse of the canonical `R`-module isomorphism + `A \to \operatorname{gr} A` induced by the basis of `A` + (where `A = ` ``self``). This inverse is an isomorphism + `\operatorname{gr} A \to A`. + + This is an isomorphism of `R`-modules. See + the class documentation :class:`AssociatedGradedAlgebra`. + + .. SEEALSO:: + + :meth:`to_graded_conversion` + + EXAMPLES:: + + sage: A = Modules(QQ).WithBasis().Filtered().example() + sage: p = -2 * A.an_element(); p + -4*P[] - 4*P[1] - 6*P[2] + sage: q = A.to_graded_conversion()(p); q + -4*Bbar[[]] - 4*Bbar[[1]] - 6*Bbar[[2]] + sage: A.from_graded_conversion()(q) == p + True + sage: q.parent() is A.graded_algebra() + True + """ + base_one = self.base_ring().one() + return self.graded_algebra().module_morphism(diagonal=lambda x: base_one, + codomain=self) + + def projection(self, i): + r""" + Return the `i`-th projection `p_i : F_i \to G_i` (in the + notations of the class documentation + :class:`AssociatedGradedAlgebra`, where `A = ` ``self``). + + This method actually does not return the map `p_i` itself, + but an extension of `p_i` to the whole `R`-module `A`. + This extension is the composition of the `R`-module + isomorphism `A \to \operatorname{gr} A` with the canonical + projection of the graded `R`-module `\operatorname{gr} A` + onto its `i`-th graded component `G_i`. The codomain of + this map is `\operatorname{gr} A`, although its actual + image is `G_i`. The map `p_i` is obtained from this map + by restricting its domain to `F_i` and its image to `G_i`. + + EXAMPLES:: + + sage: A = Modules(ZZ).WithBasis().Filtered().example() + sage: p = -2 * A.an_element(); p + -4*P[] - 4*P[1] - 6*P[2] + sage: q = A.projection(2)(p); q + -6*Bbar[[2]] + sage: q.parent() is A.graded_algebra() + True + sage: A.projection(3)(p) + 0 + """ + base_zero = self.base_ring().zero() + base_one = self.base_ring().one() + grA = self.graded_algebra() + proj = lambda x: (base_one if self.degree_on_basis(x) == i + else base_zero) + return self.module_morphism(diagonal=proj, codomain=grA) + + def induced_graded_map(self, other, f): + r""" + Return the graded linear map between the associated graded + modules of ``self`` and ``other`` canonically induced by + the filtration-preserving map ``f : self -> other``. + + Let `A` and `B` be two filtered modules with basis, and let + `(F_i)_{i \in I}` and `(G_i)_{i \in I}` be their + filtrations. Let `f : A \to B` be a linear map which + preserves the filtration (i.e., satisfies `f(F_i) \subseteq + G_i` for all `i \in I`). Then, there is a canonically + defined graded linear map + `\operatorname{gr} f : \operatorname{gr} A \to + \operatorname{gr} B` which satisfies + + .. MATH:: + + (\operatorname{gr} f) (p_i(a)) = p_i(f(a)) + \qquad \text{for all } i \in I \text{ and } a \in F_i , + + where the `p_i` on the left hand side is the canonical + projection from `F_i` onto the `i`-th graded component + of `\operatorname{gr} A`, while the `p_i` on the right + hand side is the canonical projection from `G_i` onto + the `i`-th graded component of `\operatorname{gr} B`. + + INPUT: + + - ``other`` -- a filtered algebra with basis + + - ``f`` -- a filtration-preserving linear map from ``self`` + to ``other`` (can be given as a morphism or as a function) + + OUTPUT: + + The graded linear map `\operatorname{gr} f`. + + EXAMPLES: + + **Example 1.** + + We start with the free `\QQ`-module with basis the set of all + partitions:: + + sage: A = Modules(QQ).WithBasis().Filtered().example(); A + An example of a filtered module with basis: the free module + on partitions over Rational Field + sage: M = A.indices(); M + Partitions + sage: p1, p2, p21, p321 = [A.basis()[Partition(i)] for i in [[1], [2], [2,1], [3,2,1]]] + + Let us define a map from ``A`` to itself which acts on the + basis by sending every partition `\lambda` to the sum of + the conjugates of all partitions `\mu` for which + `\lambda / \mu` is a horizontal strip:: + + sage: def map_on_basis(lam): + ....: return A.sum_of_monomials([Partition(mu).conjugate() for k in range(sum(lam) + 1) + ....: for mu in lam.remove_horizontal_border_strip(k)]) + sage: f = A.module_morphism(on_basis=map_on_basis, + ....: codomain=A) + sage: f(p1) + P[] + P[1] + sage: f(p2) + P[] + P[1] + P[1, 1] + sage: f(p21) + P[1] + P[1, 1] + P[2] + P[2, 1] + sage: f(p21 - p1) + -P[] + P[1, 1] + P[2] + P[2, 1] + sage: f(p321) + P[2, 1] + P[2, 1, 1] + P[2, 2] + P[2, 2, 1] + + P[3, 1] + P[3, 1, 1] + P[3, 2] + P[3, 2, 1] + + We now compute `\operatorname{gr} f` :: + + sage: grA = A.graded_algebra(); grA + Graded Module of An example of a filtered module with basis: + the free module on partitions over Rational Field + sage: pp1, pp2, pp21, pp321 = [A.to_graded_conversion()(i) for i in [p1, p2, p21, p321]] + sage: pp2 + 4 * pp21 + Bbar[[2]] + 4*Bbar[[2, 1]] + sage: grf = A.induced_graded_map(A, f); grf + Generic endomorphism of Graded Module of An example of a + filtered module with basis: + the free module on partitions over Rational Field + sage: grf(pp1) + Bbar[[1]] + sage: grf(pp2 + 4 * pp21) + Bbar[[1, 1]] + 4*Bbar[[2, 1]] + + **Example 2.** + + We shall now construct `\operatorname{gr} f` for a + different map `f` out of the same ``A``; the new map + `f` will lead into a graded algebra already, namely into + the algebra of symmetric functions:: + + sage: h = SymmetricFunctions(QQ).h() + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return h.sum_of_monomials([Partition(mu).conjugate() for k in range(sum(lam) + 1) + ....: for mu in lam.remove_horizontal_border_strip(k)]) + sage: f = A.module_morphism(on_basis=map_on_basis, + ....: codomain=h) # redefining f + sage: f(p1) + h[] + h[1] + sage: f(p2) + h[] + h[1] + h[1, 1] + sage: f(A.zero()) + 0 + sage: f(p2 - 3*p1) + -2*h[] - 2*h[1] + h[1, 1] + + The algebra ``h`` of symmetric functions in the `h`-basis + is already graded, so its associated graded algebra is + implemented as itself:: + + sage: grh = h.graded_algebra(); grh is h + True + sage: grf = A.induced_graded_map(h, f); grf + Generic morphism: + From: Graded Module of An example of a filtered + module with basis: the free module on partitions + over Rational Field + To: Symmetric Functions over Rational Field + in the homogeneous basis + sage: grf(pp1) + h[1] + sage: grf(pp2) + h[1, 1] + sage: grf(pp321) + h[3, 2, 1] + sage: grf(pp2 - 3*pp1) + -3*h[1] + h[1, 1] + sage: grf(pp21) + h[2, 1] + sage: grf(grA.zero()) + 0 + + **Example 3.** + + After having had a graded module as the codomain, let us try to + have one as the domain instead. Our new ``f`` will go from ``h`` + to ``A``:: + + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return A.sum_of_monomials([Partition(mu).conjugate() for k in range(sum(lam) + 1) + ....: for mu in lam.remove_horizontal_border_strip(k)]) + sage: f = h.module_morphism(on_basis=map_on_basis, + ....: codomain=A) # redefining f + sage: f(h[1]) + P[] + P[1] + sage: f(h[2]) + P[] + P[1] + P[1, 1] + sage: f(h[1, 1]) + P[1] + P[2] + sage: f(h[2, 2]) + P[1, 1] + P[2, 1] + P[2, 2] + sage: f(h[3, 2, 1]) + P[2, 1] + P[2, 1, 1] + P[2, 2] + P[2, 2, 1] + + P[3, 1] + P[3, 1, 1] + P[3, 2] + P[3, 2, 1] + sage: f(h.one()) + P[] + sage: grf = h.induced_graded_map(A, f); grf + Generic morphism: + From: Symmetric Functions over Rational Field + in the homogeneous basis + To: Graded Module of An example of a filtered + module with basis: the free module on partitions + over Rational Field + sage: grf(h[1]) + Bbar[[1]] + sage: grf(h[2]) + Bbar[[1, 1]] + sage: grf(h[1, 1]) + Bbar[[2]] + sage: grf(h[2, 2]) + Bbar[[2, 2]] + sage: grf(h[3, 2, 1]) + Bbar[[3, 2, 1]] + sage: grf(h.one()) + Bbar[[]] + + **Example 4.** + + The construct `\operatorname{gr} f` also makes sense when `f` + is a filtration-preserving map between graded modules. :: + + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return h.sum_of_monomials([Partition(mu).conjugate() for k in range(sum(lam) + 1) + ....: for mu in lam.remove_horizontal_border_strip(k)]) + sage: f = h.module_morphism(on_basis=map_on_basis, + ....: codomain=h) # redefining f + sage: f(h[1]) + h[] + h[1] + sage: f(h[2]) + h[] + h[1] + h[1, 1] + sage: f(h[1, 1]) + h[1] + h[2] + sage: f(h[2, 1]) + h[1] + h[1, 1] + h[2] + h[2, 1] + sage: f(h.one()) + h[] + sage: grf = h.induced_graded_map(h, f); grf + Generic endomorphism of Symmetric Functions over Rational + Field in the homogeneous basis + sage: grf(h[1]) + h[1] + sage: grf(h[2]) + h[1, 1] + sage: grf(h[1, 1]) + h[2] + sage: grf(h[2, 1]) + h[2, 1] + sage: grf(h.one()) + h[] + """ + grA = self.graded_algebra() + grB = other.graded_algebra() + from sage.categories.graded_modules_with_basis import GradedModulesWithBasis + cat = GradedModulesWithBasis(self.base_ring()) + from_gr = self.from_graded_conversion() + def on_basis(m): + i = grA.degree_on_basis(m) + lifted_img_of_m = f(from_gr(grA.monomial(m))) + return other.projection(i)(lifted_img_of_m) + return grA.module_morphism(on_basis=on_basis, + codomain=grB, category=cat) + # If we could assume that the projection of the basis + # element of ``self`` indexed by an index ``m`` is the + # basis element of ``grA`` indexed by ``m``, then this + # could go faster: + # + # def on_basis(m): + # i = grA.degree_on_basis(m) + # return grB.projection(i)(f(self.monomial(m))) + # return grA.module_morphism(on_basis=on_basis, + # codomain=grB, category=cat) + # + # But this assumption might come back to bite us in the + # ass one day. What do you think? + + class ElementMethods: + + def is_homogeneous(self): + r""" + Return whether the element ``self`` is homogeneous. + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x=A(Partition((3,2,1))) + sage: y=A(Partition((4,4,1))) + sage: z=A(Partition((2,2,2))) + sage: (3*x).is_homogeneous() + True + sage: (x - y).is_homogeneous() + False + sage: (x+2*z).is_homogeneous() + True + + Here is an example with a graded algebra:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: (x, y) = (S[2], S[3]) + sage: (3*x).is_homogeneous() + True + sage: (x^3 - y^2).is_homogeneous() + True + sage: ((x + y)^2).is_homogeneous() + False + + Let us now test a filtered algebra (but remember that the + notion of homogeneity now depends on the choice of a + basis, or at least on a definition of homogeneous + components):: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x,y,z = A.algebra_generators() + sage: (x*y).is_homogeneous() + True + sage: (y*x).is_homogeneous() + False + sage: A.one().is_homogeneous() + True + sage: A.zero().is_homogeneous() + True + sage: (A.one()+x).is_homogeneous() + False + """ + degree_on_basis = self.parent().degree_on_basis + degree = None + for m in self.support(): + if degree is None: + degree = degree_on_basis(m) + else: + if degree != degree_on_basis(m): + return False + return True + + @abstract_method(optional=True) + def degree_on_basis(self, m): + r""" + Return the degree of the basis element indexed by ``m`` + in ``self``. + + EXAMPLES:: + + sage: A = GradedModulesWithBasis(QQ).example() + sage: A.degree_on_basis(Partition((2,1))) + 3 + sage: A.degree_on_basis(Partition((4,2,1,1,1,1))) + 10 + """ + + def homogeneous_degree(self): + r""" + The degree of a nonzero homogeneous element ``self`` in the + filtered module. + + .. NOTE:: + + This raises an error if the element is not homogeneous. + To compute the maximum of the degrees of the homogeneous + summands of a (not necessarily homogeneous) element, use + :meth:`maximal_degree` instead. + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A(Partition((3,2,1))) + sage: y = A(Partition((4,4,1))) + sage: z = A(Partition((2,2,2))) + sage: x.degree() + 6 + sage: (x + 2*z).degree() + 6 + sage: (y - x).degree() + Traceback (most recent call last): + ... + ValueError: element is not homogeneous + + An example in a graded algebra:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: (x, y) = (S[2], S[3]) + sage: x.homogeneous_degree() + 2 + sage: (x^3 + 4*y^2).homogeneous_degree() + 6 + sage: ((1 + x)^3).homogeneous_degree() + Traceback (most recent call last): + ... + ValueError: element is not homogeneous + + Let us now test a filtered algebra (but remember that the + notion of homogeneity now depends on the choice of a + basis):: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x,y,z = A.algebra_generators() + sage: (x*y).homogeneous_degree() + 2 + sage: (y*x).homogeneous_degree() + Traceback (most recent call last): + ... + ValueError: element is not homogeneous + sage: A.one().homogeneous_degree() + 0 + + TESTS:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: S.zero().degree() + Traceback (most recent call last): + ... + ValueError: the zero element does not have a well-defined degree + """ + if not self.support(): + raise ValueError("the zero element does not have a well-defined degree") + if not self.is_homogeneous(): + raise ValueError("element is not homogeneous") + return self.parent().degree_on_basis(self.leading_support()) + + # default choice for degree; will be overridden as necessary + degree = homogeneous_degree + + def maximal_degree(self): + """ + The maximum of the degrees of the homogeneous components + of ``self``. + + This is also the smallest `i` such that ``self`` belongs + to `F_i`. Hence, it does not depend on the basis of the + parent of ``self``. + + .. SEEALSO:: :meth:`homogeneous_degree` + + EXAMPLES: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A(Partition((3,2,1))) + sage: y = A(Partition((4,4,1))) + sage: z = A(Partition((2,2,2))) + sage: x.maximal_degree() + 6 + sage: (x + 2*z).maximal_degree() + 6 + sage: (y - x).maximal_degree() + 9 + sage: (3*z).maximal_degree() + 6 + + Now, we test this on a graded algebra:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: (x, y) = (S[2], S[3]) + sage: x.maximal_degree() + 2 + sage: (x^3 + 4*y^2).maximal_degree() + 6 + sage: ((1 + x)^3).maximal_degree() + 6 + + Let us now test a filtered algebra:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x,y,z = A.algebra_generators() + sage: (x*y).maximal_degree() + 2 + sage: (y*x).maximal_degree() + 2 + sage: A.one().maximal_degree() + 0 + sage: A.zero().maximal_degree() + Traceback (most recent call last): + ... + ValueError: the zero element does not have a well-defined degree + sage: (A.one()+x).maximal_degree() + 1 + + TESTS:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: S.zero().degree() + Traceback (most recent call last): + ... + ValueError: the zero element does not have a well-defined degree + """ + if self.is_zero(): + raise ValueError("the zero element does not have a well-defined degree") + degree_on_basis = self.parent().degree_on_basis + return max(degree_on_basis(m) for m in self.support()) + + def homogeneous_component(self, n): + """ + Return the homogeneous component of degree ``n`` of the + element ``self``. + + Let `m` be an element of a filtered `R`-module `M` with + basis. Then, `m` can be uniquely written in the form + `m = \sum_{i \in I} m_i`, where each `m_i` is a + homogeneous element of degree `i`. For `n \in I`, we + define the homogeneous component of degree `n` of the + element `m` to be `m_n`. + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A.an_element(); x + 2*P[] + 2*P[1] + 3*P[2] + sage: x.homogeneous_component(-1) + 0 + sage: x.homogeneous_component(0) + 2*P[] + sage: x.homogeneous_component(1) + 2*P[1] + sage: x.homogeneous_component(2) + 3*P[2] + sage: x.homogeneous_component(3) + 0 + + sage: A = ModulesWithBasis(ZZ).Graded().example() + sage: x = A.an_element(); x + 2*P[] + 2*P[1] + 3*P[2] + sage: x.homogeneous_component(-1) + 0 + sage: x.homogeneous_component(0) + 2*P[] + sage: x.homogeneous_component(1) + 2*P[1] + sage: x.homogeneous_component(2) + 3*P[2] + sage: x.homogeneous_component(3) + 0 + + sage: A = AlgebrasWithBasis(ZZ).Filtered().example() + sage: g = A.an_element() - 2 * A.algebra_generators()['x'] * A.algebra_generators()['y']; g + U['x']^2*U['y']^2*U['z']^3 - 2*U['x']*U['y'] + sage: g.homogeneous_component(-1) + 0 + sage: g.homogeneous_component(0) + 0 + sage: g.homogeneous_component(2) + -2*U['x']*U['y'] + sage: g.homogeneous_component(5) + 0 + sage: g.homogeneous_component(7) + U['x']^2*U['y']^2*U['z']^3 + sage: g.homogeneous_component(8) + 0 + + TESTS: + + Check that this really returns ``A.zero()`` and not a plain ``0``:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A.an_element() + sage: x.homogeneous_component(3).parent() is A + True + """ + degree_on_basis = self.parent().degree_on_basis + return self.parent().sum_of_terms((i, c) + for (i, c) in self + if degree_on_basis(i) == n) + + def truncate(self, n): + """ + Return the sum of the homogeneous components of degree + strictly less than ``n`` of ``self``. + + See :meth:`homogeneous_component` for the notion of a + homogeneous component. + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A.an_element(); x + 2*P[] + 2*P[1] + 3*P[2] + sage: x.truncate(0) + 0 + sage: x.truncate(1) + 2*P[] + sage: x.truncate(2) + 2*P[] + 2*P[1] + sage: x.truncate(3) + 2*P[] + 2*P[1] + 3*P[2] + + sage: A = ModulesWithBasis(ZZ).Graded().example() + sage: x = A.an_element(); x + 2*P[] + 2*P[1] + 3*P[2] + sage: x.truncate(0) + 0 + sage: x.truncate(1) + 2*P[] + sage: x.truncate(2) + 2*P[] + 2*P[1] + sage: x.truncate(3) + 2*P[] + 2*P[1] + 3*P[2] + + sage: A = AlgebrasWithBasis(ZZ).Filtered().example() + sage: g = A.an_element() - 2 * A.algebra_generators()['x'] * A.algebra_generators()['y']; g + U['x']^2*U['y']^2*U['z']^3 - 2*U['x']*U['y'] + sage: g.truncate(-1) + 0 + sage: g.truncate(0) + 0 + sage: g.truncate(2) + 0 + sage: g.truncate(3) + -2*U['x']*U['y'] + sage: g.truncate(5) + -2*U['x']*U['y'] + sage: g.truncate(7) + -2*U['x']*U['y'] + sage: g.truncate(8) + U['x']^2*U['y']^2*U['z']^3 - 2*U['x']*U['y'] + + TESTS: + + Check that this really return ``A.zero()`` and not a plain ``0``:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A.an_element() + sage: x.truncate(0).parent() is A + True + """ + degree_on_basis = self.parent().degree_on_basis + return self.parent().sum_of_terms((i, c) for (i, c) in self + if degree_on_basis(i) < n) + diff --git a/src/sage/categories/finite_dimensional_algebras_with_basis.py b/src/sage/categories/finite_dimensional_algebras_with_basis.py index 862540e3bc3..b4378371526 100644 --- a/src/sage/categories/finite_dimensional_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_algebras_with_basis.py @@ -948,6 +948,27 @@ def is_identity_decomposition_into_orthogonal_idempotents(self, l): and all(e*e == e for e in l) and all(e*f == 0 for e in l for f in l if f != e)) + @cached_method + def is_commutative(self): + """ + Return whether ``self`` is a commutative algebra. + + EXAMPLES:: + + sage: S4 = SymmetricGroupAlgebra(QQ, 4) + sage: S4.is_commutative() + False + sage: S2 = SymmetricGroupAlgebra(QQ, 2) + sage: S2.is_commutative() + True + """ + B = list(self.basis()) + try: # See if 1 is a basis element, if so, remove it + B.remove(self.one()) + except ValueError: + pass + return all(b*bp == bp*b for i,b in enumerate(B) for bp in B[i+1:]) + class ElementMethods: def to_matrix(self, base_ring=None, action=operator.mul, side='left'): diff --git a/src/sage/categories/finite_dimensional_bialgebras_with_basis.py b/src/sage/categories/finite_dimensional_bialgebras_with_basis.py index f276371d25f..086892237e6 100644 --- a/src/sage/categories/finite_dimensional_bialgebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_bialgebras_with_basis.py @@ -18,8 +18,7 @@ def FiniteDimensionalBialgebrasWithBasis(base_ring): sage: C = FiniteDimensionalBialgebrasWithBasis(QQ); C Category of finite dimensional bialgebras with basis over Rational Field sage: sorted(C.super_categories(), key=str) - [Category of bialgebras over Rational Field, - Category of coalgebras with basis over Rational Field, + [Category of bialgebras with basis over Rational Field, Category of finite dimensional algebras with basis over Rational Field] sage: C is Bialgebras(QQ).WithBasis().FiniteDimensional() True diff --git a/src/sage/categories/finite_dimensional_modules_with_basis.py b/src/sage/categories/finite_dimensional_modules_with_basis.py index 9a3264a4bc6..5dccfac10ee 100644 --- a/src/sage/categories/finite_dimensional_modules_with_basis.py +++ b/src/sage/categories/finite_dimensional_modules_with_basis.py @@ -265,7 +265,33 @@ def quotient_module(self, submodule, check=True, already_echelonized=False, cate return QuotientModuleWithBasis(submodule, category=category) class ElementMethods: - pass + def dense_coefficient_list(self, order=None): + """ + Return a list of *all* coefficients of ``self``. + + By default, this list is ordered in the same way as the + indexing set of the basis of the parent of ``self``. + + INPUT: + + - ``order`` -- (optional) an ordering of the basis indexing set + + EXAMPLES:: + + sage: v = vector([0, -1, -3]) + sage: v.dense_coefficient_list() + [0, -1, -3] + sage: v.dense_coefficient_list([2,1,0]) + [-3, -1, 0] + sage: sorted(v.coefficients()) + [-3, -1] + """ + if order is None: + try: + order = sorted(self.parent().basis().keys()) + except AttributeError: # Not a family, assume it is list-like + order = range(self.parent().dimension()) + return [self[i] for i in order] class MorphismMethods: def matrix(self, base_ring=None, side="left"): diff --git a/src/sage/categories/finite_enumerated_sets.py b/src/sage/categories/finite_enumerated_sets.py index 1cce141f25e..1c795543d62 100644 --- a/src/sage/categories/finite_enumerated_sets.py +++ b/src/sage/categories/finite_enumerated_sets.py @@ -496,14 +496,14 @@ class ParentMethods: 'sage.categories.sets_cat' sage: C.__iter__.__module__ - 'sage.categories.enumerated_sets' + 'sage.categories.sets_cat' """ # Ambiguity resolution between methods inherited from # Sets.CartesianProducts and from EnumeratedSets.Finite. random_element = Sets.CartesianProducts.ParentMethods.random_element.__func__ cardinality = Sets.CartesianProducts.ParentMethods.cardinality.__func__ - __iter__ = EnumeratedSets.CartesianProducts.ParentMethods.__iter__.__func__ + __iter__ = Sets.CartesianProducts.ParentMethods.__iter__.__func__ def last(self): r""" diff --git a/src/sage/categories/finite_posets.py b/src/sage/categories/finite_posets.py index 06ff1fa25f6..06e741dd0fa 100644 --- a/src/sage/categories/finite_posets.py +++ b/src/sage/categories/finite_posets.py @@ -1678,7 +1678,7 @@ def toggling_orbit_iter(self, vs, oideal, element_constructor=set, stop=True, ch next = self.order_ideal_toggles(next, vs) yield element_constructor(next) - def order_ideals_lattice(self, as_ideals=True, facade=False): + def order_ideals_lattice(self, as_ideals=True, facade=None): r""" Return the lattice of order ideals of a poset ``self``, ordered by inclusion. @@ -1700,10 +1700,13 @@ def order_ideals_lattice(self, as_ideals=True, facade=False): - ``as_ideals`` -- Boolean, if ``True`` (default) returns a poset on the set of order ideals, otherwise on the set of antichains + - ``facade`` -- Boolean or ``None`` (default). Whether to + return a facade lattice or not. By default return facade + lattice if the poset is a facade poset. EXAMPLES:: - sage: P = Posets.PentagonPoset(facade = True) + sage: P = Posets.PentagonPoset() sage: P.cover_relations() [[0, 1], [0, 2], [1, 4], [2, 3], [3, 4]] sage: J = P.order_ideals_lattice(); J @@ -1727,10 +1730,15 @@ def order_ideals_lattice(self, as_ideals=True, facade=False): sage: J.cover_relations() [[{}, {0}], [{0}, {0, 2}], [{0}, {0, 1}], [{0, 2}, {0, 1, 2}], [{0, 1}, {0, 1, 2}], [{0, 1, 2}, {0, 1, 2, 3}]] - .. NOTE:: we use facade posets in the examples above just - to ensure a nicer ordering in the output. + sage: P = Poset({1:[2]}) + sage: J_facade = P.order_ideals_lattice() + sage: J_nonfacade = P.order_ideals_lattice(facade=False) + sage: type(J_facade[0]) == type(J_nonfacade[0]) + False """ from sage.combinat.posets.lattices import LatticePoset + if facade is None: + facade = self._is_facade if as_ideals: from sage.misc.misc import attrcall from sage.sets.set import Set diff --git a/src/sage/categories/graded_algebras.py b/src/sage/categories/graded_algebras.py index 98e89f81993..d03f648d499 100644 --- a/src/sage/categories/graded_algebras.py +++ b/src/sage/categories/graded_algebras.py @@ -20,16 +20,29 @@ class GradedAlgebras(GradedModulesCategory): sage: GradedAlgebras(ZZ) Category of graded algebras over Integer Ring sage: GradedAlgebras(ZZ).super_categories() - [Category of algebras over Integer Ring, + [Category of filtered algebras over Integer Ring, Category of graded modules over Integer Ring] TESTS:: sage: TestSuite(GradedAlgebras(ZZ)).run() """ - class ParentMethods: - pass + def graded_algebra(self): + """ + Return the associated graded algebra to ``self``. + + Since ``self`` is already graded, this just returns + ``self``. + + EXAMPLES:: + + sage: m = SymmetricFunctions(QQ).m() + sage: m.graded_algebra() is m + True + """ + return self class ElementMethods: pass + diff --git a/src/sage/categories/graded_algebras_with_basis.py b/src/sage/categories/graded_algebras_with_basis.py index 66a608fad2e..949fceddfb3 100644 --- a/src/sage/categories/graded_algebras_with_basis.py +++ b/src/sage/categories/graded_algebras_with_basis.py @@ -20,7 +20,7 @@ class GradedAlgebrasWithBasis(GradedModulesCategory): sage: C = GradedAlgebrasWithBasis(ZZ); C Category of graded algebras with basis over Integer Ring sage: sorted(C.super_categories(), key=str) - [Category of algebras with basis over Integer Ring, + [Category of filtered algebras with basis over Integer Ring, Category of graded algebras over Integer Ring, Category of graded modules with basis over Integer Ring] @@ -28,107 +28,60 @@ class GradedAlgebrasWithBasis(GradedModulesCategory): sage: TestSuite(C).run() """ - class ParentMethods: - pass - - class ElementMethods: - def is_homogeneous(self): - """ - Return whether this element is homogeneous. - - EXAMPLES:: - - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: (x, y) = (S[2], S[3]) - sage: (3*x).is_homogeneous() - True - sage: (x^3 - y^2).is_homogeneous() - True - sage: ((x + y)^2).is_homogeneous() - False + # This needs to be copied in GradedAlgebras because we need to have + # FilteredAlgebrasWithBasis as an extra super category + def graded_algebra(self): """ - degree_on_basis = self.parent().degree_on_basis - degree = None - for m in self.support(): - if degree is None: - degree = degree_on_basis(m) - else: - if degree != degree_on_basis(m): - return False - return True - - def homogeneous_degree(self): - """ - The degree of this element. - - .. note:: - - This raises an error if the element is not homogeneous. - To obtain the maximum of the degrees of the homogeneous - summands, use :meth:`maximal_degree` + Return the associated graded algebra to ``self``. - .. seealso: :meth:`maximal_degree` + This is ``self``, because ``self`` is already graded. + See :meth:`~sage.categories.filtered_algebras_with_basis.FilteredAlgebrasWithBasis.graded_algebra` + for the general behavior of this method, and see + :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra` + for the definition and properties of associated graded + algebras. EXAMPLES:: - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: (x, y) = (S[2], S[3]) - sage: x.homogeneous_degree() - 2 - sage: (x^3 + 4*y^2).homogeneous_degree() - 6 - sage: ((1 + x)^3).homogeneous_degree() - Traceback (most recent call last): - ... - ValueError: Element is not homogeneous. - - TESTS:: + sage: m = SymmetricFunctions(QQ).m() + sage: m.graded_algebra() is m + True - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: S.zero().degree() - Traceback (most recent call last): - ... - ValueError: The zero element does not have a well-defined degree. - """ - if self.is_zero(): - raise ValueError("The zero element does not have a well-defined degree.") - try: - assert self.is_homogeneous() - return self.parent().degree_on_basis(self.leading_support()) - except AssertionError: - raise ValueError("Element is not homogeneous.") + TESTS: - # default choice for degree; will be overridden as necessary - degree = homogeneous_degree + Let us check that the three methods + :meth:`to_graded_conversion`, :meth:`from_graded_conversion` + and :meth:`projection` (which form the interface of the + associated graded algebra) work correctly here:: - def maximal_degree(self): + sage: to_gr = m.to_graded_conversion() + sage: from_gr = m.from_graded_conversion() + sage: m[2] == to_gr(m[2]) == from_gr(m[2]) + True + sage: u = 3*m[1] - (1/2)*m[3] + sage: u == to_gr(u) == from_gr(u) + True + sage: m.zero() == to_gr(m.zero()) == from_gr(m.zero()) + True + sage: p2 = m.projection(2) + sage: p2(m[2] - 4*m[1,1] + 3*m[1] - 2*m[[]]) + -4*m[1, 1] + m[2] + sage: p2(4*m[1]) + 0 + sage: p2(m.zero()) == m.zero() + True """ - The maximum of the degrees of the homogeneous summands. + return self - .. seealso: :meth:`homogeneous_degree` + # .. TODO:: + # Possibly override ``to_graded_conversion`` and + # ``from_graded_conversion`` with identity morphisms? + # I have to admit I don't know the right way to construct + # identity morphisms other than using the identity matrix. + # Also, ``projection`` could be overridden by, well, a + # projection. - EXAMPLES:: - - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: (x, y) = (S[2], S[3]) - sage: x.maximal_degree() - 2 - sage: (x^3 + 4*y^2).maximal_degree() - 6 - sage: ((1 + x)^3).maximal_degree() - 6 - - TESTS:: + class ElementMethods: + pass - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: S.zero().degree() - Traceback (most recent call last): - ... - ValueError: The zero element does not have a well-defined degree. - """ - if self.is_zero(): - raise ValueError("The zero element does not have a well-defined degree.") - else: - degree_on_basis = self.parent().degree_on_basis - return max(degree_on_basis(m) for m in self.support()) diff --git a/src/sage/categories/graded_bialgebras_with_basis.py b/src/sage/categories/graded_bialgebras_with_basis.py index 171de5b99e7..8b9ff21acdf 100644 --- a/src/sage/categories/graded_bialgebras_with_basis.py +++ b/src/sage/categories/graded_bialgebras_with_basis.py @@ -18,8 +18,7 @@ def GradedBialgebrasWithBasis(base_ring): sage: C = GradedBialgebrasWithBasis(QQ); C Join of Category of ... sage: sorted(C.super_categories(), key=str) - [Category of bialgebras over Rational Field, - Category of coalgebras with basis over Rational Field, + [Category of bialgebras with basis over Rational Field, Category of graded algebras with basis over Rational Field] TESTS:: diff --git a/src/sage/categories/graded_modules.py b/src/sage/categories/graded_modules.py index 65f8a6ba7bb..ca31efe005b 100644 --- a/src/sage/categories/graded_modules.py +++ b/src/sage/categories/graded_modules.py @@ -11,6 +11,7 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_class_attribute +from sage.categories.category import Category from sage.categories.category_types import Category_over_base_ring from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.covariant_functorial_construction import RegressiveCovariantConstructionCategory @@ -26,7 +27,7 @@ def __init__(self, base_category): sage: C.base_category() Category of algebras over Rational Field sage: sorted(C.super_categories(), key=str) - [Category of algebras over Rational Field, + [Category of filtered algebras over Rational Field, Category of graded modules over Rational Field] sage: AlgebrasWithBasis(QQ).Graded().base_ring() @@ -56,16 +57,64 @@ def _repr_object_names(self): """ return "graded {}".format(self.base_category()._repr_object_names()) + @classmethod + def default_super_categories(cls, category, *args): + r""" + Return the default super categories of ``category.Graded()``. + + Mathematical meaning: every graded object (module, algebra, + etc.) is a filtered object with the (implicit) filtration + defined by `F_i = \bigoplus_{j \leq i} G_j`. + + INPUT: + + - ``cls`` -- the class ``GradedModulesCategory`` + - ``category`` -- a category + + OUTPUT: a (join) category + + In practice, this returns ``category.Filtered()``, joined + together with the result of the method + :meth:`RegressiveCovariantConstructionCategory.default_super_categories() ` + (that is the join of ``category.Filtered()`` and ``cat`` for + each ``cat`` in the super categories of ``category``). + + EXAMPLES: + + Consider ``category=Algebras()``, which has ``cat=Modules()`` + as super category. Then, a grading of an algebra `G` + is also a filtration of `G`:: + + sage: Algebras(QQ).Graded().super_categories() + [Category of filtered algebras over Rational Field, + Category of graded modules over Rational Field] + + This resulted from the following call:: + + sage: sage.categories.graded_modules.GradedModulesCategory.default_super_categories(Algebras(QQ)) + Join of Category of filtered algebras over Rational Field + and Category of graded modules over Rational Field + """ + cat = super(GradedModulesCategory, cls).default_super_categories(category, *args) + return Category.join([category.Filtered(), cat]) + class GradedModules(GradedModulesCategory): - """ + r""" The category of graded modules. + We consider every graded module `M = \bigoplus_i M_i` as a + filtered module under the (natural) filtration given by + + .. MATH:: + + F_i = \bigoplus_{j < i} M_j. + EXAMPLES:: sage: GradedModules(ZZ) Category of graded modules over Integer Ring sage: GradedModules(ZZ).super_categories() - [Category of modules over Integer Ring] + [Category of filtered modules over Integer Ring] The category of graded modules defines the graded structure which shall be preserved by morphisms:: @@ -77,71 +126,9 @@ class GradedModules(GradedModulesCategory): sage: TestSuite(GradedModules(ZZ)).run() """ - - def extra_super_categories(self): - r""" - Adds :class:`VectorSpaces` to the super categories of ``self`` if - the base ring is a field. - - EXAMPLES:: - - sage: Modules(QQ).Graded().extra_super_categories() - [Category of vector spaces over Rational Field] - sage: Modules(ZZ).Graded().extra_super_categories() - [] - - This makes sure that ``Modules(QQ).Graded()`` returns an - instance of :class:`GradedModules` and not a join category of - an instance of this class and of ``VectorSpaces(QQ)``:: - - sage: type(Modules(QQ).Graded()) - - - .. TODO:: - - Get rid of this workaround once there is a more systematic - approach for the alias ``Modules(QQ)`` -> ``VectorSpaces(QQ)``. - Probably the later should be a category with axiom, and - covariant constructions should play well with axioms. - """ - from sage.categories.modules import Modules - from sage.categories.fields import Fields - base_ring = self.base_ring() - if base_ring in Fields: - return [Modules(base_ring)] - else: - return [] - - class SubcategoryMethods: - - @cached_method - def Connected(self): - r""" - Return the full subcategory of the connected objects of ``self``. - - EXAMPLES:: - - sage: Modules(ZZ).Graded().Connected() - Category of graded connected modules over Integer Ring - sage: Coalgebras(QQ).Graded().Connected() - Join of Category of graded connected modules over Rational Field - and Category of coalgebras over Rational Field - sage: GradedAlgebrasWithBasis(QQ).Connected() - Category of graded connected algebras with basis over Rational Field - - TESTS:: - - sage: TestSuite(Modules(ZZ).Graded().Connected()).run() - sage: Coalgebras(QQ).Graded().Connected.__module__ - 'sage.categories.graded_modules' - """ - return self._with_axiom("Connected") - - class Connected(CategoryWithAxiom_over_base_ring): - pass - class ParentMethods: pass class ElementMethods: pass + diff --git a/src/sage/categories/graded_modules_with_basis.py b/src/sage/categories/graded_modules_with_basis.py index 9bb99fc5a95..a1c4a5d5c2e 100644 --- a/src/sage/categories/graded_modules_with_basis.py +++ b/src/sage/categories/graded_modules_with_basis.py @@ -20,8 +20,8 @@ class GradedModulesWithBasis(GradedModulesCategory): sage: C = GradedModulesWithBasis(ZZ); C Category of graded modules with basis over Integer Ring sage: sorted(C.super_categories(), key=str) - [Category of graded modules over Integer Ring, - Category of modules with basis over Integer Ring] + [Category of filtered modules with basis over Integer Ring, + Category of graded modules over Integer Ring] sage: C is ModulesWithBasis(ZZ).Graded() True @@ -30,164 +30,8 @@ class GradedModulesWithBasis(GradedModulesCategory): sage: TestSuite(C).run() """ class ParentMethods: - - # TODO: which syntax do we prefer? - # A.basis(degree = 3) - # A.basis().subset(degree=3) - - # This is related to the following design question: - # If F = (f_i)_{i\in I} is a family, should ``F.subset(degree = 3)`` - # be the elements of F of degree 3 or those whose index is of degree 3? - - def basis(self, d=None): - """ - Returns the basis for (an homogeneous component of) this graded module - - INPUT: - - - `d` -- non negative integer or ``None``, optional (default: ``None``) - - If `d` is None, returns a basis of the module. - Otherwise, returns the basis of the homogeneous component of degree `d`. - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: A.basis(4) - Lazy family (Term map from Partitions to An example of a graded module with basis: the free module on partitions over Integer Ring(i))_{i in Partitions of the integer 4} - - Without arguments, the full basis is returned:: - - sage: A.basis() - Lazy family (Term map from Partitions to An example of a graded module with basis: the free module on partitions over Integer Ring(i))_{i in Partitions} - sage: A.basis() - Lazy family (Term map from Partitions to An example of a graded module with basis: the free module on partitions over Integer Ring(i))_{i in Partitions} - """ - from sage.sets.family import Family - if d is None: - return Family(self._indices, self.monomial) - else: - return Family(self._indices.subset(size=d), self.monomial) + pass class ElementMethods: + pass - def is_homogeneous(self): - """ - Return whether this element is homogeneous. - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: x=A(Partition((3,2,1))) - sage: y=A(Partition((4,4,1))) - sage: z=A(Partition((2,2,2))) - sage: (3*x).is_homogeneous() - True - sage: (x - y).is_homogeneous() - False - sage: (x+2*z).is_homogeneous() - True - """ - degree_on_basis = self.parent().degree_on_basis - degree = None - for m in self.support(): - if degree is None: - degree = degree_on_basis(m) - else: - if degree != degree_on_basis(m): - return False - return True - - def degree(self): - """ - The degree of this element in the graded module. - - .. note:: - - This raises an error if the element is not homogeneous. - Another implementation option would be to return the - maximum of the degrees of the homogeneous summands. - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: x = A(Partition((3,2,1))) - sage: y = A(Partition((4,4,1))) - sage: z = A(Partition((2,2,2))) - sage: x.degree() - 6 - sage: (x + 2*z).degree() - 6 - sage: (y - x).degree() - Traceback (most recent call last): - ... - ValueError: Element is not homogeneous. - """ - if not self.support(): - raise ValueError("The zero element does not have a well-defined degree.") - if self.is_homogeneous(): - return self.parent().degree_on_basis(self.leading_support()) - else: - raise ValueError("Element is not homogeneous.") - - def homogeneous_component(self, n): - """ - Return the homogeneous component of degree ``n`` of this - element. - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: x = A.an_element(); x - 2*P[] + 2*P[1] + 3*P[2] - sage: x.homogeneous_component(-1) - 0 - sage: x.homogeneous_component(0) - 2*P[] - sage: x.homogeneous_component(1) - 2*P[1] - sage: x.homogeneous_component(2) - 3*P[2] - sage: x.homogeneous_component(3) - 0 - - TESTS: - - Check that this really return ``A.zero()`` and not a plain ``0``:: - - sage: x.homogeneous_component(3).parent() is A - True - """ - degree_on_basis = self.parent().degree_on_basis - return self.parent().sum_of_terms((i, c) - for (i, c) in self - if degree_on_basis(i) == n) - - def truncate(self, n): - """ - Return the sum of the homogeneous components of degree ``< n`` of this element - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: x = A.an_element(); x - 2*P[] + 2*P[1] + 3*P[2] - sage: x.truncate(0) - 0 - sage: x.truncate(1) - 2*P[] - sage: x.truncate(2) - 2*P[] + 2*P[1] - sage: x.truncate(3) - 2*P[] + 2*P[1] + 3*P[2] - - TESTS: - - Check that this really return ``A.zero()`` and not a plain ``0``:: - - sage: x.truncate(0).parent() is A - True - """ - degree_on_basis = self.parent().degree_on_basis - return self.parent().sum_of_terms((i, c) for (i, c) in self - if degree_on_basis(i) < n) diff --git a/src/sage/categories/graphs.py b/src/sage/categories/graphs.py new file mode 100644 index 00000000000..1be42bb94a5 --- /dev/null +++ b/src/sage/categories/graphs.py @@ -0,0 +1,108 @@ +""" +Graphs +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_singleton import Category_singleton +from sage.categories.simplicial_complexes import SimplicialComplexes + +class Graphs(Category_singleton): + r""" + The category of graphs. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs(); C + Category of graphs + + TESTS:: + + sage: TestSuite(C).run() + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: Graphs().super_categories() + [Category of simplicial complexes] + """ + return [SimplicialComplexes()] + + class ParentMethods: + @abstract_method + def vertices(self): + """ + Return the vertices of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.vertices() + [0, 1, 2, 3, 4] + """ + + @abstract_method + def edges(self): + """ + Return the edges of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.edges() + [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] + """ + + def dimension(self): + """ + Return the dimension of ``self`` as a CW complex. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.dimension() + 1 + """ + if self.edges(): + return 1 + return 0 + + def facets(self): + """ + Return the facets of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.facets() + [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] + """ + return self.edges() + + def faces(self): + """ + Return the faces of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: sorted(C.faces(), key=lambda x: (x.dimension(), x.value)) + [0, 1, 2, 3, 4, (0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] + """ + return set(self.edges()).union(self.vertices()) + diff --git a/src/sage/categories/groups.py b/src/sage/categories/groups.py index eaa0ed868e8..59d32d80c03 100644 --- a/src/sage/categories/groups.py +++ b/src/sage/categories/groups.py @@ -19,6 +19,7 @@ from sage.categories.algebra_functor import AlgebrasCategory from sage.categories.cartesian_product import CartesianProductsCategory, cartesian_product from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.categories.topological_spaces import TopologicalSpacesCategory class Groups(CategoryWithAxiom): """ @@ -480,6 +481,7 @@ def conjugacy_class(self): return self.parent().conjugacy_class(self) Finite = LazyImport('sage.categories.finite_groups', 'FiniteGroups') + Lie = LazyImport('sage.categories.lie_groups', 'LieGroups', 'Lie') #Algebras = LazyImport('sage.categories.group_algebras', 'GroupAlgebras') class Commutative(CategoryWithAxiom): @@ -914,6 +916,7 @@ def lift(i, gen): # Infinitely generated # This does not return a good output, but it is "correct" # TODO: Figure out a better way to do things + from sage.categories.cartesian_product import cartesian_product gens_prod = cartesian_product([Family(G.group_generators(), lambda g: (i, g)) for i,G in enumerate(F)]) @@ -942,3 +945,15 @@ def order(self): """ from sage.misc.misc_c import prod return prod(c.cardinality() for c in self.cartesian_factors()) + + class Topological(TopologicalSpacesCategory): + """ + Category of topological groups. + + A topological group `G` is a group which has a topology such that + multiplication and taking inverses are continuous functions. + + REFERENCES: + + - :wikipedia:`Topological_group` + """ diff --git a/src/sage/categories/highest_weight_crystals.py b/src/sage/categories/highest_weight_crystals.py index 25b64b2e993..d2b99a7cbbc 100644 --- a/src/sage/categories/highest_weight_crystals.py +++ b/src/sage/categories/highest_weight_crystals.py @@ -428,11 +428,22 @@ def _Hom_(self, Y, category=None, **options): to The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]] sage: type(H) + + TESTS: + + Check that we fallback first to trying a crystal homset + (:trac:`19458`):: + + sage: Binf = crystals.infinity.Tableaux(['A',2]) + sage: Bi = crystals.elementary.Elementary(Binf.cartan_type(), 1) + sage: tens = Bi.tensor(Binf) + sage: Hom(Binf, tens) + Set of Crystal Morphisms from ... """ if category is None: category = self.category() - elif not category.is_subcategory(HighestWeightCrystals()): - raise TypeError("{} is not a subcategory of HighestWeightCrystals()".format(category)) + elif not category.is_subcategory(Crystals()): + raise TypeError("{} is not a subcategory of Crystals()".format(category)) if Y not in Crystals(): raise TypeError("{} is not a crystal".format(Y)) return HighestWeightCrystalHomset(self, Y, category=category, **options) diff --git a/src/sage/categories/homset.py b/src/sage/categories/homset.py index 51a3f327d18..17721fb54e7 100644 --- a/src/sage/categories/homset.py +++ b/src/sage/categories/homset.py @@ -283,17 +283,13 @@ def Hom(X, Y, category=None, check=True): sage: S = SimplicialComplex([[1,2], [1,4]]); S.rename("S") sage: Hom(S, S, SimplicialComplexes()) - Set of Morphisms from S to S in Category of simplicial complexes + Set of Morphisms from S to S in Category of finite simplicial complexes - sage: H = Hom(Set(), S, Sets()) - Traceback (most recent call last): - ... - ValueError: S is not in Category of sets + sage: Hom(Set(), S, Sets()) + Set of Morphisms from {} to S in Category of sets - sage: H = Hom(S, Set(), Sets()) - Traceback (most recent call last): - ... - ValueError: S is not in Category of sets + sage: Hom(S, Set(), Sets()) + Set of Morphisms from S to {} in Category of sets sage: H = Hom(S, S, ChainComplexes(QQ)) Traceback (most recent call last): @@ -651,7 +647,7 @@ def __reduce__(self): (, (Vector space of dimension 2 over Rational Field, Vector space of dimension 3 over Rational Field, - Category of vector spaces with basis over quotient fields, + Category of finite dimensional vector spaces with basis over (quotient fields and metric spaces), False)) TESTS:: @@ -859,7 +855,7 @@ def __call__(self, x=None, y=None, check=True, **options): sage: H = Hom(Set([1,2,3]), Set([1,2,3])) sage: f = H( lambda x: 4-x ) sage: f.parent() - Set of Morphisms from {1, 2, 3} to {1, 2, 3} in Category of sets + Set of Morphisms from {1, 2, 3} to {1, 2, 3} in Category of finite sets sage: f(1), f(2), f(3) # todo: not implemented sage: H = Hom(ZZ, QQ, Sets()) @@ -1168,18 +1164,18 @@ def reversed(self): sage: H = Hom(ZZ^2, ZZ^3); H Set of Morphisms from Ambient free module of rank 2 over - the principal ideal domain Integer Ring to Ambient free - module of rank 3 over the principal ideal domain Integer - Ring in Category of modules with basis over (euclidean - domains and infinite enumerated sets) + the principal ideal domain Integer Ring to Ambient free module + of rank 3 over the principal ideal domain Integer Ring in + Category of finite dimensional modules with basis over (euclidean + domains and infinite enumerated sets and metric spaces) sage: type(H) sage: H.reversed() Set of Morphisms from Ambient free module of rank 3 over - the principal ideal domain Integer Ring to Ambient free - module of rank 2 over the principal ideal domain Integer - Ring in Category of modules with basis over (euclidean - domains and infinite enumerated sets) + the principal ideal domain Integer Ring to Ambient free module + of rank 2 over the principal ideal domain Integer Ring in + Category of finite dimensional modules with basis over (euclidean + domains and infinite enumerated sets and metric spaces) sage: type(H.reversed()) """ diff --git a/src/sage/categories/hopf_algebras.py b/src/sage/categories/hopf_algebras.py index eba5add5d9a..46c5dd8fccc 100644 --- a/src/sage/categories/hopf_algebras.py +++ b/src/sage/categories/hopf_algebras.py @@ -8,13 +8,13 @@ # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ #****************************************************************************** - from sage.misc.lazy_import import LazyImport from category import Category from category_types import Category_over_base_ring from sage.categories.bialgebras import Bialgebras from sage.categories.tensor import TensorProductsCategory # tensor from sage.categories.realizations import RealizationsCategory +from sage.categories.super_modules import SuperModulesCategory from sage.misc.cachefunc import cached_method #from sage.misc.lazy_attribute import lazy_attribute @@ -46,7 +46,7 @@ def super_categories(self): def dual(self): """ - Returns the dual category + Return the dual category EXAMPLES: @@ -61,9 +61,10 @@ def dual(self): WithBasis = LazyImport('sage.categories.hopf_algebras_with_basis', 'HopfAlgebrasWithBasis') class ElementMethods: + def antipode(self): """ - Returns the antipode of self. + Return the antipode of self EXAMPLES:: @@ -104,6 +105,8 @@ class Morphism(Category): """ pass + class Super(SuperModulesCategory): + pass class TensorProducts(TensorProductsCategory): """ diff --git a/src/sage/categories/hopf_algebras_with_basis.py b/src/sage/categories/hopf_algebras_with_basis.py index 3a2f8a1dbd2..fb01c14d369 100644 --- a/src/sage/categories/hopf_algebras_with_basis.py +++ b/src/sage/categories/hopf_algebras_with_basis.py @@ -27,8 +27,7 @@ class HopfAlgebrasWithBasis(CategoryWithAxiom_over_base_ring): Category of hopf algebras with basis over Rational Field sage: C.super_categories() [Category of hopf algebras over Rational Field, - Category of algebras with basis over Rational Field, - Category of coalgebras with basis over Rational Field] + Category of bialgebras with basis over Rational Field] We now show how to use a simple Hopf algebra, namely the group algebra of the dihedral group (see also AlgebrasWithBasis):: @@ -147,6 +146,7 @@ def example(self, G = None): FiniteDimensional = LazyImport('sage.categories.finite_dimensional_hopf_algebras_with_basis', 'FiniteDimensionalHopfAlgebrasWithBasis') Graded = LazyImport('sage.categories.graded_hopf_algebras_with_basis', 'GradedHopfAlgebrasWithBasis') + Super = LazyImport('sage.categories.super_hopf_algebras_with_basis', 'SuperHopfAlgebrasWithBasis') class ParentMethods: diff --git a/src/sage/categories/lie_groups.py b/src/sage/categories/lie_groups.py new file mode 100644 index 00000000000..3883822f105 --- /dev/null +++ b/src/sage/categories/lie_groups.py @@ -0,0 +1,72 @@ +r""" +Lie Groups +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +#from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_types import Category_over_base_ring +from sage.categories.groups import Groups +from sage.categories.manifolds import Manifolds + +class LieGroups(Category_over_base_ring): + r""" + The category of Lie groups. + + A Lie group is a topological group with a smooth manifold structure. + + EXAMPLES:: + + sage: from sage.categories.lie_groups import LieGroups + sage: C = LieGroups(QQ); C + Category of Lie groups over Rational Field + + TESTS:: + + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.lie_groups import LieGroups + sage: LieGroups(QQ).super_categories() + [Category of topological groups, + Category of smooth manifolds over Rational Field] + """ + return [Groups().Topological(), Manifolds(self.base()).Smooth()] + + def additional_structure(self): + r""" + Return ``None``. + + Indeed, the category of Lie groups defines no new + structure: a morphism of topological spaces and of smooth + manifolds is a morphism as Lie groups. + + .. SEEALSO:: :meth:`Category.additional_structure` + + EXAMPLES:: + + sage: from sage.categories.lie_groups import LieGroups + sage: LieGroups(QQ).additional_structure() + """ + return None + + # Because Lie is a name that deserves to be capitalized + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: from sage.categories.lie_groups import LieGroups + sage: LieGroups(QQ) # indirect doctest + Category of Lie groups over Rational Field + """ + return "Lie groups over {}".format(self.base_ring()) + diff --git a/src/sage/categories/magmas.py b/src/sage/categories/magmas.py index c3f8f9ab458..42f03fc918a 100644 --- a/src/sage/categories/magmas.py +++ b/src/sage/categories/magmas.py @@ -468,9 +468,11 @@ def _test_one(self, **options): for x in tester.some_elements(): tester.assert_(x * one == x) tester.assert_(one * x == x) - # Check that one is immutable by asking its hash; - tester.assertEqual(type(one.__hash__()), int) - tester.assertEqual(one.__hash__(), one.__hash__()) + # Check that one is immutable if it looks like we can test this + if hasattr(one,"is_immutable"): + tester.assertEqual(one.is_immutable(),True) + if hasattr(one,"is_mutable"): + tester.assertEqual(one.is_mutable(),False) def is_empty(self): r""" diff --git a/src/sage/categories/manifolds.py b/src/sage/categories/manifolds.py new file mode 100644 index 00000000000..4a6433b95ec --- /dev/null +++ b/src/sage/categories/manifolds.py @@ -0,0 +1,349 @@ +r""" +Manifolds +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_types import Category_over_base_ring +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.sets_cat import Sets +from sage.categories.fields import Fields + +class Manifolds(Category_over_base_ring): + r""" + The category of manifolds over any topological field. + + Let `k` be a topological field. A `d`-dimensional `k`-*manifold* `M` + is a second countable Hausdorff space such that the neighborhood of + any point `x \in M` is homeomorphic to `k^d`. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR); C + Category of manifolds over Real Field with 53 bits of precision + sage: C.super_categories() + [Category of topological spaces] + + TESTS:: + + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + def __init__(self, base, name=None): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR) + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + if base not in Fields().Topological(): + raise ValueError("base must be a topological field") + Category_over_base_ring.__init__(self, base, name) + + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).super_categories() + [Category of topological spaces] + """ + return [Sets().Topological()] + + def additional_structure(self): + r""" + Return ``None``. + + Indeed, the category of manifolds defines no new + structure: a morphism of topological spaces between + manifolds is a manifold morphism. + + .. SEEALSO:: :meth:`Category.additional_structure` + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).additional_structure() + """ + return None + + class ParentMethods: + @abstract_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(RR).example() + sage: M.dimension() + 3 + """ + + class SubcategoryMethods: + @cached_method + def Connected(self): + """ + Return the full subcategory of the connected objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Connected() + Category of connected manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: Manifolds(RR).Connected.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('Connected') + + @cached_method + def FiniteDimensional(self): + """ + Return the full subcategory of the finite dimensional + objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR).Connected().FiniteDimensional(); C + Category of finite dimensional connected manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Connected().FiniteDimensional.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('FiniteDimensional') + + @cached_method + def Differentiable(self): + """ + Return the subcategory of the differentiable objects + of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Differentiable() + Category of differentiable manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(RR).Differentiable()).run() + sage: Manifolds(RR).Differentiable.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('Differentiable') + + @cached_method + def Smooth(self): + """ + Return the subcategory of the smooth objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Smooth() + Category of smooth manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(RR).Smooth()).run() + sage: Manifolds(RR).Smooth.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('Smooth') + + @cached_method + def Analytic(self): + """ + Return the subcategory of the analytic objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Analytic() + Category of analytic manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(RR).Analytic()).run() + sage: Manifolds(RR).Analytic.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('Analytic') + + @cached_method + def AlmostComplex(self): + """ + Return the subcategory of the almost complex objects + of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).AlmostComplex() + Category of almost complex manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(RR).AlmostComplex()).run() + sage: Manifolds(RR).AlmostComplex.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('AlmostComplex') + + @cached_method + def Complex(self): + """ + Return the subcategory of manifolds over `\CC` of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(CC).Complex() + Category of complex manifolds over + Complex Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(CC).Complex()).run() + sage: Manifolds(CC).Complex.__module__ + 'sage.categories.manifolds' + """ + return ComplexManifolds(self.base())._with_axioms(self.axioms()) + + class Differentiable(CategoryWithAxiom_over_base_ring): + """ + The category of differentiable manifolds. + + A differentiable manifold is a manifold with a differentiable atlas. + """ + + class Smooth(CategoryWithAxiom_over_base_ring): + """ + The category of smooth manifolds. + + A smooth manifold is a manifold with a smooth atlas. + """ + def extra_super_categories(self): + """ + Return the extra super categories of ``self``. + + A smooth manifold is differentiable. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Smooth().super_categories() # indirect doctest + [Category of differentiable manifolds + over Real Field with 53 bits of precision] + """ + return [Manifolds(self.base()).Differentiable()] + + class Analytic(CategoryWithAxiom_over_base_ring): + r""" + The category of complex manifolds. + + An analytic manifold is a manifold with an analytic atlas. + """ + def extra_super_categories(self): + """ + Return the extra super categories of ``self``. + + An analytic manifold is smooth. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Analytic().super_categories() # indirect doctest + [Category of smooth manifolds + over Real Field with 53 bits of precision] + """ + return [Manifolds(self.base()).Smooth()] + + class AlmostComplex(CategoryWithAxiom_over_base_ring): + r""" + The category of almost complex manifolds. + + An *almost complex manifold* `M` is a manifold with a smooth tensor + field `J` of rank `(1, 1)` such that `J^2 = -1` when regarded as a + vector bundle isomorphism `J : TM \to TM` on the tangent bundle. + The tensor field `J` is called the *almost complex structure* of `M`. + """ + def extra_super_categories(self): + """ + Return the extra super categories of ``self``. + + An almost complex manifold is smooth. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).AlmostComplex().super_categories() # indirect doctest + [Category of smooth manifolds + over Real Field with 53 bits of precision] + """ + return [Manifolds(self.base()).Smooth()] + + class FiniteDimensional(CategoryWithAxiom_over_base_ring): + """ + Category of finite dimensional manifolds. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR).FiniteDimensional() + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + + class Connected(CategoryWithAxiom_over_base_ring): + """ + The category of connected manifolds. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR).Connected() + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + +class ComplexManifolds(Category_over_base_ring): + r""" + The category of complex manifolds. + + A `d`-dimensional complex manifold is a manifold whose underlying + vector space is `\CC^d` and has a holomorphic atlas. + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).super_categories() + [Category of topological spaces] + """ + return [Manifolds(self.base()).Analytic()] + diff --git a/src/sage/categories/map.pyx b/src/sage/categories/map.pyx index 499b1b22486..00fc6ec3d61 100644 --- a/src/sage/categories/map.pyx +++ b/src/sage/categories/map.pyx @@ -628,9 +628,13 @@ cdef class Map(Element): sage: R. = QQ[] sage: f = R.hom([x+y, x-y], R) sage: f.category_for() - Join of Category of unique factorization domains and Category of commutative algebras over quotient fields + Join of Category of unique factorization domains + and Category of commutative algebras over (quotient fields and metric spaces) sage: f.category() - Category of endsets of unital magmas and right modules over quotient fields and left modules over quotient fields + Category of endsets of unital magmas + and right modules over (quotient fields and metric spaces) + and left modules over (quotient fields and metric spaces) + FIXME: find a better name for this method """ diff --git a/src/sage/categories/metric_spaces.py b/src/sage/categories/metric_spaces.py new file mode 100644 index 00000000000..c22ef8008db --- /dev/null +++ b/src/sage/categories/metric_spaces.py @@ -0,0 +1,258 @@ +r""" +Metric Spaces +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.categories.category import Category +from sage.categories.category_with_axiom import CategoryWithAxiom +from sage.categories.covariant_functorial_construction import RegressiveCovariantConstructionCategory +from sage.categories.with_realizations import WithRealizationsCategory + +class MetricSpacesCategory(RegressiveCovariantConstructionCategory): + + _functor_category = "Metric" + + @classmethod + def default_super_categories(cls, category): + """ + Return the default super categories of ``category.Metric()``. + + Mathematical meaning: if `A` is a metric space in the + category `C`, then `A` is also a topological space. + + INPUT: + + - ``cls`` -- the class ``MetricSpaces`` + - ``category`` -- a category `Cat` + + OUTPUT: + + A (join) category + + In practice, this returns ``category.Metric()``, joined + together with the result of the method + :meth:`RegressiveCovariantConstructionCategory.default_super_categories() + ` + (that is the join of ``category`` and ``cat.Metric()`` for + each ``cat`` in the super categories of ``category``). + + EXAMPLES: + + Consider ``category=Groups()``. Then, a group `G` with a metric + is simultaneously a topological group by itself, and a + metric space:: + + sage: Groups().Metric().super_categories() + [Category of topological groups, Category of metric spaces] + + This resulted from the following call:: + + sage: sage.categories.metric_spaces.MetricSpacesCategory.default_super_categories(Groups()) + Join of Category of topological groups and Category of metric spaces + """ + return Category.join([category.Topological(), + super(MetricSpacesCategory, cls).default_super_categories(category)]) + + # We currently don't have a use for this, but we probably will + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: Groups().Metric() # indirect doctest + Join of Category of topological groups and Category of metric spaces + """ + return "metric {}".format(self.base_category()._repr_object_names()) + +class MetricSpaces(MetricSpacesCategory): + r""" + The category of metric spaces. + + A *metric* on a set `S` is a function `d : S \times S \to \RR` + such that: + + - `d(a, b) \geq 0`, + - `d(a, b) = 0` if and only if `a = b`. + + A metric space is a set `S` with a distinguished metric. + + .. RUBRIC:: Implementation + + Objects in this category must implement either a ``dist`` on the parent + or the elements or ``metric`` on the parent; otherwise this will cause + an infinite recursion. + + .. TODO:: + + - Implement a general geodesics class. + - Implement a category for metric additive groups + and move the generic distance `d(a, b) = |a - b|` there. + - Incorperate the length of a geodesic as part of the default + distance cycle. + + EXAMPLES:: + + sage: from sage.categories.metric_spaces import MetricSpaces + sage: C = MetricSpaces() + sage: C + Category of metric spaces + sage: TestSuite(C).run() + """ + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: Sets().Metric() # indirect doctest + Category of metric spaces + """ + return "metric spaces" + + class ParentMethods: + def _test_metric(self, **options): + r""" + Test that this metric space has a properly implemented metric. + + INPUT: + + - ``options`` -- any keyword arguments accepted + by :meth:`_tester` + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: UHP._test_metric() + sage: elts = [UHP.random_element() for i in range(5)] + sage: UHP._test_metric(some_elements=elts) + """ + tester = self._tester(**options) + S = tester.some_elements() + dist = self.metric() + for a in S: + for b in S: + d = dist(a, b) + if a != b: + tester.assertGreater(d, 0) + else: + tester.assertEqual(d, 0) + + def metric(self): + """ + Return the metric of ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: m = UHP.metric() + sage: p1 = UHP.get_point(5 + 7*I) + sage: p2 = UHP.get_point(1.0 + I) + sage: m(p1, p2) + 2.23230104635820 + """ + return lambda a,b: a.dist(b) + + def dist(self, a, b): + """ + Return the distance between ``a`` and ``b`` in ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: p1 = UHP.get_point(5 + 7*I) + sage: p2 = UHP.get_point(1.0 + I) + sage: UHP.dist(p1, p2) + 2.23230104635820 + + sage: PD = HyperbolicPlane().PD() + sage: PD.dist(PD.get_point(0), PD.get_point(I/2)) + arccosh(5/3) + + TESTS:: + + sage: RR.dist(-1, pi) + 4.14159265358979 + sage: RDF.dist(1, -1/2) + 1.5 + sage: CC.dist(3, 2) + 1.00000000000000 + sage: CC.dist(-1, I) + 1.41421356237310 + sage: CDF.dist(-1, I) + 1.4142135623730951 + """ + return (self(a) - self(b)).abs() + + class ElementMethods: + def abs(self): + """ + Return the absolute value of ``self``. + + EXAMPLES:: + + sage: CC(I).abs() + 1.00000000000000 + """ + P = self.parent() + return P.metric()(self, P.zero()) + + def dist(self, b): + """ + Return the distance between ``self`` and ``other``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: p1 = UHP.get_point(5 + 7*I) + sage: p2 = UHP.get_point(1 + I) + sage: p1.dist(p2) + arccosh(33/7) + """ + return self.parent().dist(self, b) + + class WithRealizations(WithRealizationsCategory): + class ParentMethods: + def dist(self, a, b): + """ + Return the distance between ``a`` and ``b`` by converting them + to a realization of ``self`` and doing the computation. + + EXAMPLES:: + + sage: H = HyperbolicPlane() + sage: PD = H.PD() + sage: p1 = PD.get_point(0) + sage: p2 = PD.get_point(I/2) + sage: H.dist(p1, p2) + arccosh(5/3) + """ + R = self.a_realization() + return R.dist(R(a), R(b)) + + class SubcategoryMethods: + @cached_method + def Complete(self): + """ + Return the full subcategory of the complete objects of ``self``. + + EXAMPLES:: + + sage: Sets().Metric().Complete() + Category of complete metric spaces + + TESTS:: + + sage: TestSuite(Sets().Metric().Complete()).run() + sage: Sets().Metric().Complete.__module__ + 'sage.categories.metric_spaces' + """ + return self._with_axiom('Complete') + + class Complete(CategoryWithAxiom): + """ + The category of complete metric spaces. + """ + diff --git a/src/sage/categories/modules.py b/src/sage/categories/modules.py index 70d3773f667..80c74638a97 100644 --- a/src/sage/categories/modules.py +++ b/src/sage/categories/modules.py @@ -17,8 +17,9 @@ from sage.categories.homsets import HomsetsCategory from category import Category, JoinCategory from category_types import Category_module, Category_over_base_ring -from tensor import TensorProductsCategory +from sage.categories.tensor import TensorProductsCategory, tensor from dual import DualObjectsCategory +from sage.categories.cartesian_product import CartesianProductsCategory from sage.categories.sets_cat import Sets from sage.categories.bimodules import Bimodules from sage.categories.fields import Fields @@ -347,6 +348,43 @@ def FiniteDimensional(self): """ return self._with_axiom("FiniteDimensional") + @cached_method + def Filtered(self, base_ring=None): + r""" + Return the subcategory of the filtered objects of ``self``. + + INPUT: + + - ``base_ring`` -- this is ignored + + EXAMPLES:: + + sage: Modules(ZZ).Filtered() + Category of filtered modules over Integer Ring + + sage: Coalgebras(QQ).Filtered() + Join of Category of filtered modules over Rational Field + and Category of coalgebras over Rational Field + + sage: AlgebrasWithBasis(QQ).Filtered() + Category of filtered algebras with basis over Rational Field + + .. TODO:: + + - Explain why this does not commute with :meth:`WithBasis` + - Improve the support for covariant functorial + constructions categories over a base ring so as to + get rid of the ``base_ring`` argument. + + TESTS:: + + sage: Coalgebras(QQ).Graded.__module__ + 'sage.categories.modules' + """ + assert base_ring is None or base_ring is self.base_ring() + from sage.categories.filtered_modules import FilteredModulesCategory + return FilteredModulesCategory.category_of(self) + @cached_method def Graded(self, base_ring=None): r""" @@ -383,6 +421,42 @@ def Graded(self, base_ring=None): from sage.categories.graded_modules import GradedModulesCategory return GradedModulesCategory.category_of(self) + @cached_method + def Super(self, base_ring=None): + r""" + Return the super-analogue category of ``self``. + + INPUT: + + - ``base_ring`` -- this is ignored + + EXAMPLES:: + + sage: Modules(ZZ).Super() + Category of super modules over Integer Ring + + sage: Coalgebras(QQ).Super() + Category of super coalgebras over Rational Field + + sage: AlgebrasWithBasis(QQ).Super() + Category of super algebras with basis over Rational Field + + .. TODO:: + + - Explain why this does not commute with :meth:`WithBasis` + - Improve the support for covariant functorial + constructions categories over a base ring so as to + get rid of the ``base_ring`` argument. + + TESTS:: + + sage: Coalgebras(QQ).Super.__module__ + 'sage.categories.modules' + """ + assert base_ring is None or base_ring is self.base_ring() + from sage.categories.super_modules import SuperModulesCategory + return SuperModulesCategory.category_of(self) + @cached_method def WithBasis(self): r""" @@ -429,11 +503,28 @@ def extra_super_categories(self): else: return [] + Filtered = LazyImport('sage.categories.filtered_modules', 'FilteredModules') Graded = LazyImport('sage.categories.graded_modules', 'GradedModules') + Super = LazyImport('sage.categories.super_modules', 'SuperModules') WithBasis = LazyImport('sage.categories.modules_with_basis', 'ModulesWithBasis') class ParentMethods: - pass + @cached_method + def tensor_square(self): + """ + Returns the tensor square of ``self`` + + EXAMPLES:: + + sage: A = HopfAlgebrasWithBasis(QQ).example() + sage: A.tensor_square() + An example of Hopf algebra with basis: + the group algebra of the Dihedral group of order 6 + as a permutation group over Rational Field # An example + of Hopf algebra with basis: the group algebra of the Dihedral + group of order 6 as a permutation group over Rational Field + """ + return tensor([self, self]) class ElementMethods: @@ -576,3 +667,45 @@ def extra_super_categories(self): """ from magmatic_algebras import MagmaticAlgebras return [MagmaticAlgebras(self.base_category().base_ring())] + + class CartesianProducts(CartesianProductsCategory): + """ + The category of modules constructed as cartesian products of modules + + This construction gives the direct product of modules. The + implementation is based on the following resources: + + - http://groups.google.fr/group/sage-devel/browse_thread/thread/35a72b1d0a2fc77a/348f42ae77a66d16#348f42ae77a66d16 + - http://en.wikipedia.org/wiki/Direct_product + """ + def extra_super_categories(self): + """ + A cartesian product of modules is endowed with a natural + module structure. + + EXAMPLES:: + + sage: Modules(ZZ).CartesianProducts().extra_super_categories() + [Category of modules over Integer Ring] + sage: Modules(ZZ).CartesianProducts().super_categories() + [Category of Cartesian products of commutative additive groups, + Category of modules over Integer Ring] + """ + return [self.base_category()] + + class ParentMethods: + def base_ring(self): + """ + Return the base ring of this cartesian product. + + EXAMPLES:: + + sage: E = CombinatorialFreeModule(ZZ, [1,2,3]) + sage: F = CombinatorialFreeModule(ZZ, [2,3,4]) + sage: C = cartesian_product([E, F]); C + Free module generated by {1, 2, 3} over Integer Ring (+) + Free module generated by {2, 3, 4} over Integer Ring + sage: C.base_ring() + Integer Ring + """ + return self._sets[0].base_ring() diff --git a/src/sage/categories/modules_with_basis.py b/src/sage/categories/modules_with_basis.py index c780127492a..aa4dc15e85c 100644 --- a/src/sage/categories/modules_with_basis.py +++ b/src/sage/categories/modules_with_basis.py @@ -16,8 +16,10 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -from sage.misc.lazy_import import LazyImport +from sage.misc.lazy_import import LazyImport, lazy_import +from sage.misc.lazy_attribute import lazy_attribute from sage.misc.cachefunc import cached_method +from sage.misc.abstract_method import abstract_method from sage.misc.sage_itertools import max_cmp, min_cmp from sage.categories.homsets import HomsetsCategory from sage.categories.cartesian_product import CartesianProductsCategory @@ -25,8 +27,9 @@ from sage.categories.dual import DualObjectsCategory from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.modules import Modules +from sage.categories.poor_man_map import PoorManMap +from sage.rings.infinity import Infinity from sage.structure.element import Element, parent -from sage.misc.lazy_import import lazy_import lazy_import('sage.modules.with_basis.morphism', ['ModuleMorphismByLinearity', 'ModuleMorphismFromMatrix', @@ -123,6 +126,12 @@ class ModulesWithBasis(CategoryWithAxiom_over_base_ring): .. TODO:: ``End(X)`` is an algebra. + .. NOTE:: + + This category currently requires an implementation of an + element method ``support``. Once :trac:`18066` is merged, an + implementation of an ``items`` method will be required. + TESTS:: sage: TestSuite(ModulesWithBasis(ZZ)).run() @@ -152,7 +161,7 @@ def _call_(self, x): Vector space of dimension 3 over Rational Field If ``x`` itself is not a module with basis, but there is a - canonical one associated to it, the later is returned:: + canonical one associated to it, the latter is returned:: sage: CQ(AbelianVariety(Gamma0(37))) # indirect doctest Vector space of dimension 4 over Rational Field @@ -181,7 +190,15 @@ def is_abelian(self): return self.base_ring().is_field() FiniteDimensional = LazyImport('sage.categories.finite_dimensional_modules_with_basis', 'FiniteDimensionalModulesWithBasis') + Filtered = LazyImport('sage.categories.filtered_modules_with_basis', 'FilteredModulesWithBasis') Graded = LazyImport('sage.categories.graded_modules_with_basis', 'GradedModulesWithBasis') + Super = LazyImport('sage.categories.super_modules_with_basis', 'SuperModulesWithBasis') + + # To implement a module_with_basis you need to implement the + # following methods: + # - On the parent class, either basis() or an _indices attribute and + # monomial(). + # - On the element class, monomial_coefficients(). class ParentMethods: @cached_method @@ -227,7 +244,9 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, - ``on_basis`` -- a function `f` from `I` to `Y` - ``diagonal`` -- a function `d` from `I` to `R` - ``function`` -- a function `f` from `X` to `Y` - - ``matrix`` -- a matrix of size `\dim X \times \dim Y` or `\dim Y \times \dim X` + - ``matrix`` -- a matrix of size `\dim Y \times \dim X` + (if the keyword ``side`` is set to ``'left'``) or + `\dim Y \times \dim X` (if this keyword is ``'right'``) Further options include: @@ -321,7 +340,8 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, sage: phi.category_for() # todo: not implemented (ZZ is currently not in Modules(ZZ)) Category of modules over Integer Ring - Or more generaly any ring admitting a coercion map from the base ring:: + Or more generaly any ring admitting a coercion map from + the base ring:: sage: phi = X.module_morphism(on_basis=lambda i: i, codomain=RR ) sage: phi( 2 * X.monomial(1) + 3 * X.monomial(-1) ) @@ -378,13 +398,17 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, sage: phi = X.module_morphism( on_basis=on_basis, codomain=Y ) Traceback (most recent call last): ... - ValueError: codomain(=Free module generated by {'z'} over Rational Field) should be a module over the base ring of the domain(=Free module generated by {'x', 'y'} over Real Field with 53 bits of precision) + ValueError: codomain(=Free module generated by {'z'} over Rational Field) + should be a module over the base ring of the + domain(=Free module generated by {'x', 'y'} over Real Field with 53 bits of precision) sage: Y = CombinatorialFreeModule(RR['q'],['z']) sage: phi = Y.module_morphism( on_basis=on_basis, codomain=X ) Traceback (most recent call last): ... - ValueError: codomain(=Free module generated by {'x', 'y'} over Real Field with 53 bits of precision) should be a module over the base ring of the domain(=Free module generated by {'z'} over Univariate Polynomial Ring in q over Real Field with 53 bits of precision) + ValueError: codomain(=Free module generated by {'x', 'y'} over Real Field with 53 bits of precision) + should be a module over the base ring of the + domain(=Free module generated by {'z'} over Univariate Polynomial Ring in q over Real Field with 53 bits of precision) With the ``diagonal=d`` argument, this constructs the @@ -392,7 +416,7 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, .. MATH:: - `g(x_i) = d(i) y_i` + `g(x_i) = d(i) y_i`. This assumes that the respective bases `x` and `y` of `X` and `Y` have the same index set `I`:: @@ -512,7 +536,7 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, ValueError: diagonal (=3) should be a function """ - if not len([x for x in [matrix, on_basis, function, diagonal] if x is not None]) == 1: + if len([x for x in [matrix, on_basis, function, diagonal] if x is not None]) != 1: raise ValueError("module_morphism() takes exactly one option out of `matrix`, `on_basis`, `function`, `diagonal`") if matrix is not None: return ModuleMorphismFromMatrix(domain=self, matrix=matrix, **keywords) @@ -764,6 +788,277 @@ def tensor(*parents): """ return parents[0].__class__.Tensor(parents, category = tensor.category_from_parents(parents)) + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: S = SymmetricGroupAlgebra(QQ, 4) + sage: S.cardinality() + +Infinity + sage: S = SymmetricGroupAlgebra(GF(2), 4) # not tested -- MRO bug :trac:`15475` + sage: S.cardinality() # not tested -- MRO bug :trac:`15475` + 16777216 + sage: S.cardinality().factor() # not tested -- MRO bug :trac:`15475` + 2^24 + + sage: E. = ExteriorAlgebra(QQ) + sage: E.cardinality() + +Infinity + sage: E. = ExteriorAlgebra(GF(3)) + sage: E.cardinality() + 81 + + sage: s = SymmetricFunctions(GF(2)).s() + sage: s.cardinality() + +Infinity + """ + if self.dimension() == Infinity: + return Infinity + return self.base_ring().cardinality() ** self.dimension() + + def monomial(self, i): + """ + Return the basis element indexed by ``i``. + + INPUT: + + - ``i`` -- an element of the index set + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F.monomial('a') + B['a'] + + ``F.monomial`` is in fact (almost) a map:: + + sage: F.monomial + Term map from {'a', 'b', 'c'} to Free module generated by {'a', 'b', 'c'} over Rational Field + """ + return self.basis()[i] + + def _sum_of_monomials(self, indices): + """ + TESTS:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F._sum_of_monomials(['a', 'b']) + B['a'] + B['b'] + """ + # This is the generic implementation. When implementing a + # concrete instance of a module with basis, you probably want + # to override it with something faster. + return self.sum(self.monomial(index) for index in indices) + + @lazy_attribute + def sum_of_monomials(self): + """ + Return the sum of the basis elements with indices in + ``indices``. + + INPUT: + + - ``indices`` -- an list (or iterable) of indices of basis + elements + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F.sum_of_monomials(['a', 'b']) + B['a'] + B['b'] + + sage: F.sum_of_monomials(['a', 'b', 'a']) + 2*B['a'] + B['b'] + + ``F.sum_of_monomials`` is in fact (almost) a map:: + + sage: F.sum_of_monomials + A map to Free module generated by {'a', 'b', 'c'} over Rational Field + """ + # domain = iterables of basis indices of self. + return PoorManMap(self._sum_of_monomials, codomain = self) + + def monomial_or_zero_if_none(self, i): + """ + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F.monomial_or_zero_if_none('a') + B['a'] + sage: F.monomial_or_zero_if_none(None) + 0 + """ + if i is None: + return self.zero() + return self.monomial(i) + + def term(self, index, coeff=None): + """ + Construct a term in ``self``. + + INPUT: + + - ``index`` -- the index of a basis element + - ``coeff`` -- an element of the coefficient ring (default: one) + + OUTPUT: + + ``coeff * B[index]``, where ``B`` is the basis of ``self``. + + EXAMPLES:: + + sage: m = matrix([[0,1],[1,1]]) + sage: J. = JordanAlgebra(m) + sage: J.term(1, -2) + 0 + (-2, 0) + + Design: should this do coercion on the coefficient ring? + """ + if coeff is None: + coeff = self.base_ring().one() + return coeff * self.monomial(index) + + def sum_of_terms(self, terms): + """ + Construct a sum of terms of ``self``. + + INPUT: + + - ``terms`` -- a list (or iterable) of pairs ``(index, coeff)`` + + OUTPUT: + + Sum of ``coeff * B[index]`` over all ``(index, coeff)`` in + ``terms``, where ``B`` is the basis of ``self``. + + EXAMPLES:: + + sage: m = matrix([[0,1],[1,1]]) + sage: J. = JordanAlgebra(m) + sage: J.sum_of_terms([(0, 2), (2, -3)]) + 2 + (0, -3) + """ + return self.sum(self.term(index, coeff) for (index, coeff) in terms) + + def linear_combination(self, iter_of_elements_coeff, factor_on_left=True): + """ + Return the linear combination `\lambda_1 v_1 + \cdots + + \lambda_k v_k` (resp. the linear combination `v_1 \lambda_1 + + \cdots + v_k \lambda_k`) where ``iter_of_elements_coeff`` iterates + through the sequence `((\lambda_1, v_1), ..., (\lambda_k, v_k))`. + + INPUT: + + - ``iter_of_elements_coeff`` -- iterator of pairs + ``(element, coeff)`` with ``element`` in ``self`` and + ``coeff`` in ``self.base_ring()`` + + - ``factor_on_left`` -- (optional) if ``True``, the coefficients + are multiplied from the left; if ``False``, the coefficients + are multiplied from the right + + EXAMPLES:: + + sage: m = matrix([[0,1],[1,1]]) + sage: J. = JordanAlgebra(m) + sage: J.linear_combination(((a+b, 1), (-2*b + c, -1))) + 1 + (3, -1) + """ + if factor_on_left: + return self.sum(coeff * element + for element, coeff in iter_of_elements_coeff) + else: + return self.sum(element * coeff + for element, coeff in iter_of_elements_coeff) + + def _apply_module_morphism(self, x, on_basis, codomain=False): + """ + Return the image of ``x`` under the module morphism defined by + extending :func:`on_basis` by linearity. + + INPUT: + + - ``x`` -- a element of ``self`` + + - ``on_basis`` -- a function that takes in an object indexing + a basis element and returns an element of the codomain + + - ``codomain`` -- (optional) the codomain of the morphism (by + default, it is computed using :func:`on_basis`) + + If ``codomain`` is not specified, then the function tries to + compute the codomain of the module morphism by finding the image + of one of the elements in the support; hence :func:`on_basis` + should return an element whose parent is the codomain. + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).schur() + sage: a = s([3]) + s([2,1]) + s([1,1,1]) + sage: b = 2*a + sage: f = lambda part: Integer( len(part) ) + sage: s._apply_module_morphism(a, f) #1+2+3 + 6 + sage: s._apply_module_morphism(b, f) #2*(1+2+3) + 12 + sage: s._apply_module_morphism(s(0), f) + 0 + sage: s._apply_module_morphism(s(1), f) + 0 + sage: s._apply_module_morphism(s(1), lambda part: len(part), ZZ) + 0 + sage: s._apply_module_morphism(s(1), lambda part: len(part)) + Traceback (most recent call last): + ... + ValueError: codomain could not be determined + """ + if x == self.zero(): + if not codomain: + from sage.combinat.family import Family + B = Family(self.basis()) + try: + z = B.first() + except StopIteration: + raise ValueError('codomain could not be determined') + codomain = on_basis(z).parent() + return codomain.zero() + + if not codomain: + keys = x.support() + key = keys[0] + try: + codomain = on_basis(key).parent() + except Exception: + raise ValueError('codomain could not be determined') + + if hasattr( codomain, 'linear_combination' ): + mc = x.monomial_coefficients(copy=False) + return codomain.linear_combination( (on_basis(key), coeff) + for key, coeff in mc.iteritems() ) + else: + return_sum = codomain.zero() + mc = x.monomial_coefficients(copy=False) + for key, coeff in mc.iteritems(): + return_sum += coeff * on_basis(key) + return return_sum + + def _apply_module_endomorphism(self, x, on_basis): + """ + This takes in a function ``on_basis`` from the basis indices + to the elements of ``self``, and applies it linearly to ``x``. + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).schur() + sage: f = lambda part: 2*s(part.conjugate()) + sage: s._apply_module_endomorphism( s([2,1]) + s([1,1,1]), f) + 2*s[2, 1] + 2*s[3] + """ + mc = x.monomial_coefficients(copy=False) + return self.linear_combination( (on_basis(key), coeff) + for key, coeff in mc.iteritems() ) class ElementMethods: # TODO: Define the appropriate element methods here (instead of in @@ -777,6 +1072,310 @@ class ElementMethods: # """ # return self._lmul_(-self.parent().base_ring().one(), self) + @abstract_method + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements + in the support of ``self`` and whose values are the + corresponding coefficients. + + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] + 3*B['c'] + sage: d = f.monomial_coefficients() + sage: d['a'] + 1 + sage: d['c'] + 3 + + TESTS: + + We check that we make a copy of the coefficient dictonary:: + + sage: F = CombinatorialFreeModule(ZZ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] + 3*B['c'] + sage: d = f.monomial_coefficients() + sage: d['a'] = 5 + sage: f + B['a'] + 3*B['c'] + """ + + def __getitem__(self, m): + """ + Return the coefficient of ``m`` in ``self``. + + EXAMPLES:: + + sage: p = Partition([2,1]) + sage: q = Partition([1,1,1]) + sage: s = SymmetricFunctions(QQ).schur() + sage: a = s(p) + sage: a._coefficient_fast([2,1]) + Traceback (most recent call last): + ... + TypeError: unhashable type: 'list' + + :: + + sage: a._coefficient_fast(p) + 1 + sage: a._coefficient_fast(q) + 0 + sage: a[p] + 1 + sage: a[q] + 0 + """ + return self.monomial_coefficients(copy=False).get(m, self.base_ring().zero()) + + def coefficient(self, m): + """ + Return the coefficient of ``m`` in ``self`` and raise an error + if ``m`` is not in the basis indexing set. + + INPUT: + + - ``m`` -- a basis index of the parent of ``self`` + + OUTPUT: + + The ``B[m]``-coordinate of ``self`` with respect to the basis + ``B``. Here, ``B`` denotes the given basis of the parent of + ``self``. + + EXAMPLES:: + + sage: s = CombinatorialFreeModule(QQ, Partitions()) + sage: z = s([4]) - 2*s([2,1]) + s([1,1,1]) + s([1]) + sage: z.coefficient([4]) + 1 + sage: z.coefficient([2,1]) + -2 + sage: z.coefficient(Partition([2,1])) + -2 + sage: z.coefficient([1,2]) + Traceback (most recent call last): + ... + AssertionError: [1, 2] should be an element of Partitions + sage: z.coefficient(Composition([2,1])) + Traceback (most recent call last): + ... + AssertionError: [2, 1] should be an element of Partitions + + Test that ``coefficient`` also works for those parents that do + not yet have an element_class:: + + sage: G = DihedralGroup(3) + sage: F = CombinatorialFreeModule(QQ, G) + sage: hasattr(G, "element_class") + False + sage: g = G.an_element() + sage: (2*F.monomial(g)).coefficient(g) + 2 + """ + # NT: coefficient_fast should be the default, just with appropriate assertions + # that can be turned on or off + C = self.parent().basis().keys() + # TODO: This should raise a ValueError - TS + assert m in C, "%s should be an element of %s"%(m, C) + if hasattr(C, "element_class") and not isinstance(m, C.element_class): + m = C(m) + return self[m] + + def is_zero(self): + """ + Return ``True`` if and only if ``self == 0``. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: f.is_zero() + False + sage: F.zero().is_zero() + True + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: s([2,1]).is_zero() + False + sage: s(0).is_zero() + True + sage: (s([2,1]) - s([2,1])).is_zero() + True + """ + zero = self.parent().base_ring().zero() + return all(v == zero for v in self.monomial_coefficients(copy=False).values()) + + def __len__(self): + """ + Return the number of basis elements whose coefficients in + ``self`` are nonzero. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: len(f) + 2 + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) + sage: len(z) + 4 + """ + zero = self.parent().base_ring().zero() + return len([key for key, coeff in self.monomial_coefficients(copy=False).iteritems() + if coeff != zero]) + + def length(self): + """ + Return the number of basis elements whose coefficients in + ``self`` are nonzero. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: f.length() + 2 + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) + sage: z.length() + 4 + """ + return len(self) + + def support(self): + """ + Return a list of the objects indexing the basis of + ``self.parent()`` whose corresponding coefficients of + ``self`` are non-zero. + + This method returns these objects in an arbitrary order. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: sorted(f.support()) + ['a', 'c'] + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) + sage: sorted(z.support()) + [[1], [1, 1, 1], [2, 1], [4]] + """ + zero = self.parent().base_ring().zero() + return [key for key, coeff in self.monomial_coefficients(copy=False).iteritems() + if coeff != zero] + + def monomials(self): + """ + Return a list of the monomials of ``self`` (in an arbitrary + order). + + The monomials of an element `a` are defined to be the basis + elements whose corresponding coefficients of `a` are + non-zero. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] + 2*B['c'] + sage: f.monomials() + [B['a'], B['c']] + + sage: (F.zero()).monomials() + [] + """ + P = self.parent() + return [P.monomial(key) for key in self.support()] + + def terms(self): + """ + Return a list of the (non-zero) terms of ``self`` (in an + arbitrary order). + + .. SEEALSO:: :meth:`monomials` + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] + 2*B['c'] + sage: f.terms() + [B['a'], 2*B['c']] + """ + P = self.parent() + zero = P.base_ring().zero() + return [P.term(key, value) + for key, value in self.monomial_coefficients(copy=False).iteritems() + if value != zero] + + def coefficients(self, sort=True): + """ + Return a list of the (non-zero) coefficients appearing on + the basis elements in ``self`` (in an arbitrary order). + + INPUT: + + - ``sort`` -- (default: ``True``) to sort the coefficients + based upon the default ordering of the indexing set + + .. SEEALSO:: + + :meth:`~sage.categories.finite_dimensional_modules_with_basis.FiniteDimensionalModulesWithBasis.ElementMethods.dense_coefficient_list` + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: f.coefficients() + [1, -3] + sage: f = B['c'] - 3*B['a'] + sage: f.coefficients() + [-3, 1] + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) + sage: z.coefficients() + [1, 1, 1, 1] + """ + zero = self.parent().base_ring().zero() + mc = self.monomial_coefficients(copy=False) + if not sort: + return [value for key, value in mc.iteritems() if value != zero] + + v = sorted([(key, value) for key, value in mc.iteritems() + if value != zero]) + return [value for key, value in v] def support_of_term(self): """ @@ -801,7 +1400,7 @@ def support_of_term(self): if len(self) == 1: return self.support()[0] else: - raise ValueError("%s is not a single term"%(self)) + raise ValueError("{} is not a single term".format(self)) def leading_support(self, cmp=None): r""" @@ -831,7 +1430,6 @@ def leading_support(self, cmp=None): """ return max_cmp(self.support(), cmp) - def leading_item(self, cmp=None): r""" Return the pair ``(k, c)`` where diff --git a/src/sage/categories/monoids.py b/src/sage/categories/monoids.py index 19359a7b2e9..8bcd6676a12 100644 --- a/src/sage/categories/monoids.py +++ b/src/sage/categories/monoids.py @@ -18,7 +18,7 @@ from sage.categories.semigroups import Semigroups from sage.misc.lazy_import import LazyImport from sage.categories.subquotients import SubquotientsCategory -from sage.categories.cartesian_product import CartesianProductsCategory, cartesian_product +from sage.categories.cartesian_product import CartesianProductsCategory from sage.categories.algebra_functor import AlgebrasCategory from sage.categories.with_realizations import WithRealizationsCategory from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets @@ -588,6 +588,7 @@ def lift(i, gen): # Infinitely generated # This does not return a good output, but it is "correct" # TODO: Figure out a better way to do things + from sage.categories.cartesian_product import cartesian_product gens_prod = cartesian_product([Family(M.monoid_generators(), lambda g: (i, g)) for i,M in enumerate(F)]) diff --git a/src/sage/categories/morphism.pyx b/src/sage/categories/morphism.pyx index a6c357407ba..a76d6ab4aa3 100644 --- a/src/sage/categories/morphism.pyx +++ b/src/sage/categories/morphism.pyx @@ -162,7 +162,10 @@ cdef class Morphism(Map): sage: R. = ZZ[] sage: f = R.hom([t**2]) sage: f.category() - Category of endsets of unital magmas and right modules over (euclidean domains and infinite enumerated sets) and left modules over (euclidean domains and infinite enumerated sets) + Category of endsets of unital magmas and right modules over + (euclidean domains and infinite enumerated sets and metric spaces) + and left modules over (euclidean domains + and infinite enumerated sets and metric spaces) sage: K = CyclotomicField(12) sage: L = CyclotomicField(132) diff --git a/src/sage/categories/posets.py b/src/sage/categories/posets.py index 5dc0113ada8..52baeec2e41 100644 --- a/src/sage/categories/posets.py +++ b/src/sage/categories/posets.py @@ -683,6 +683,9 @@ def is_antichain_of_poset(self, o): """ return all(not self.lt(x,y) for x in o for y in o) + CartesianProduct = LazyImport( + 'sage.combinat.posets.cartesian_product', 'CartesianProductPoset') + class ElementMethods: pass # TODO: implement xy, x>=y appropriately once #10130 is resolved diff --git a/src/sage/categories/primer.py b/src/sage/categories/primer.py index b8c70db515d..5e32c54e985 100644 --- a/src/sage/categories/primer.py +++ b/src/sage/categories/primer.py @@ -348,9 +348,12 @@ sage: ZZ.category() Join of Category of euclidean domains and Category of infinite enumerated sets + and Category of metric spaces sage: ZZ.categories() - [Join of Category of euclidean domains and Category of infinite enumerated sets, + [Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces, Category of euclidean domains, Category of principal ideal domains, Category of unique factorization domains, Category of gcd domains, Category of integral domains, Category of domains, @@ -360,7 +363,8 @@ Category of commutative magmas, Category of unital magmas, Category of magmas, Category of commutative additive groups, ..., Category of additive magmas, Category of infinite enumerated sets, Category of enumerated sets, - Category of infinite sets, Category of sets, + Category of infinite sets, Category of metric spaces, + Category of topological spaces, Category of sets, Category of sets with partial maps, Category of objects] diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index 5367f65199b..4373c1708f6 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -2,10 +2,16 @@ Coercion via Construction Functors """ import six -from functor import Functor -from basic import * +from sage.misc.lazy_import import lazy_import +from functor import Functor, IdentityFunctor_generic -from sage.structure.parent import CoercionException +lazy_import('sage.categories.commutative_additive_groups', 'CommutativeAdditiveGroups') +lazy_import('sage.categories.commutative_rings', 'CommutativeRings') +lazy_import('sage.categories.groups', 'Groups') +lazy_import('sage.categories.objects', 'Objects') +lazy_import('sage.categories.rings', 'Rings') + +lazy_import('sage.structure.parent', 'CoercionException') # TODO, think through the rankings, and override pushout where necessary. @@ -272,6 +278,89 @@ def expand(self): # See the pushout() function below for explanation. coercion_reversed = False + def common_base(self, other_functor, self_bases, other_bases): + r""" + This function is called by :func:`pushout` when no common parent + is found in the construction tower. + + .. NOTE:: + + The main use is for multivariate construction functors, + which use this function to implement recursion for + :func:`pushout`. + + INPUT: + + - ``other_functor`` -- a construction functor. + + - ``self_bases`` -- the arguments passed to this functor. + + - ``other_bases`` -- the arguments passed to the functor + ``other_functor``. + + OUTPUT: + + Nothing, since a + :class:`~sage.structure.coerce_exceptions.CoercionException` + is raised. + + .. NOTE:: + + Overload this function in derived class, see + e.e. :class:`MultivariateConstructionFunctor`. + + TESTS:: + + sage: from sage.categories.pushout import pushout + sage: pushout(QQ, cartesian_product([ZZ])) # indirect doctest + Traceback (most recent call last): + ... + CoercionException: No common base ("join") found for + FractionField(Integer Ring) and The cartesian_product functorial construction(Integer Ring). + """ + self._raise_common_base_exception_( + other_functor, self_bases, other_bases) + + def _raise_common_base_exception_(self, other_functor, + self_bases, other_bases, + reason=None): + r""" + Raise a coercion exception. + + INPUT: + + - ``other_functor`` -- a functor. + + - ``self_bases`` -- the arguments passed to this functor. + + - ``other_bases`` -- the arguments passed to the functor + ``other_functor``. + + - ``reason`` -- a string or ``None`` (default). + + TESTS:: + + sage: from sage.categories.pushout import pushout + sage: pushout(QQ, cartesian_product([QQ])) # indirect doctest + Traceback (most recent call last): + ... + CoercionException: No common base ("join") found for + FractionField(Integer Ring) and The cartesian_product functorial construction(Rational Field). + """ + if not isinstance(self_bases, (tuple, list)): + self_bases = (self_bases,) + if not isinstance(other_bases, (tuple, list)): + other_bases = (other_bases,) + if reason is None: + reason = '.' + else: + reason = ': ' + reason + '.' + raise CoercionException( + 'No common base ("join") found for %s(%s) and %s(%s)%s' % + (self, ', '.join(str(b) for b in self_bases), + other_functor, ', '.join(str(b) for b in other_bases), + reason)) + class CompositeConstructionFunctor(ConstructionFunctor): """ @@ -468,6 +557,7 @@ def __init__(self): True """ + from sage.categories.sets_cat import Sets ConstructionFunctor.__init__(self, Sets(), Sets()) def _apply_functor(self, x): @@ -547,6 +637,83 @@ def __mul__(self, other): return self +class MultivariateConstructionFunctor(ConstructionFunctor): + """ + An abstract base class for functors that take + multiple inputs (e.g. cartesian products). + + TESTS:: + + sage: from sage.categories.pushout import pushout + sage: A = cartesian_product((QQ['z'], QQ)) + sage: B = cartesian_product((ZZ['t']['z'], QQ)) + sage: pushout(A, B) + The cartesian product of (Univariate Polynomial Ring in z over + Univariate Polynomial Ring in t over Rational Field, + Rational Field) + sage: A.construction() + (The cartesian_product functorial construction, + (Univariate Polynomial Ring in z over Rational Field, Rational Field)) + sage: pushout(A, B) + The cartesian product of (Univariate Polynomial Ring in z over Univariate Polynomial Ring in t over Rational Field, Rational Field) + """ + def common_base(self, other_functor, self_bases, other_bases): + r""" + This function is called by :func:`pushout` when no common parent + is found in the construction tower. + + INPUT: + + - ``other_functor`` -- a construction functor. + + - ``self_bases`` -- the arguments passed to this functor. + + - ``other_bases`` -- the arguments passed to the functor + ``other_functor``. + + OUTPUT: + + A parent. + + If no common base is found a :class:`sage.structure.coerce_exceptions.CoercionException` + is raised. + + .. NOTE:: + + Overload this function in derived class, see + e.g. :class:`MultivariateConstructionFunctor`. + + TESTS:: + + sage: from sage.categories.pushout import pushout + sage: pushout(cartesian_product([ZZ]), QQ) # indirect doctest + Traceback (most recent call last): + ... + CoercionException: No common base ("join") found for + The cartesian_product functorial construction(Integer Ring) and FractionField(Integer Ring): + (Multivariate) functors are incompatible. + sage: pushout(cartesian_product([ZZ]), cartesian_product([ZZ, QQ])) # indirect doctest + Traceback (most recent call last): + ... + CoercionException: No common base ("join") found for + The cartesian_product functorial construction(Integer Ring) and + The cartesian_product functorial construction(Integer Ring, Rational Field): + Functors need the same number of arguments. + """ + if self != other_functor: + self._raise_common_base_exception_( + other_functor, self_bases, other_bases, + '(Multivariate) functors are incompatible') + if len(self_bases) != len(other_bases): + self._raise_common_base_exception_( + other_functor, self_bases, other_bases, + 'Functors need the same number of arguments') + from sage.structure.element import get_coercion_model + Z_bases = tuple(get_coercion_model().common_parent(S, O) + for S, O in zip(self_bases, other_bases)) + return self(Z_bases) + + class PolynomialFunctor(ConstructionFunctor): """ Construction functor for univariate polynomial rings. @@ -631,7 +798,7 @@ def _apply_functor_to_morphism(self, f): TEST:: sage: P = ZZ['x'].construction()[0] - sage: P(ZZ.hom(GF(3))) + sage: P(ZZ.hom(GF(3))) # indirect doctest Ring morphism: From: Univariate Polynomial Ring in x over Integer Ring To: Univariate Polynomial Ring in x over Finite Field of size 3 @@ -1942,6 +2109,8 @@ def __init__(self): sage: F(ZZ['t']) Fraction Field of Univariate Polynomial Ring in t over Integer Ring """ + from sage.categories.integral_domains import IntegralDomains + from sage.categories.fields import Fields Functor.__init__(self, IntegralDomains(), Fields()) def _apply_functor(self, R): @@ -3191,7 +3360,7 @@ def pushout(R, S): ....: coercion_reversed = True ....: def __init__(self): ....: ConstructionFunctor.__init__(self, Rings(), Rings()) - ....: def __call__(self, R): + ....: def _apply_functor(self, R): ....: return EvenPolynomialRing(R.base(), R.variable_name()) ....: sage: pushout(EvenPolynomialRing(QQ, 'x'), ZZ) @@ -3218,13 +3387,233 @@ def pushout(R, S): sage: pushout(EvenPolynomialRing(QQ, 'x')^2, RR['x']^2) Ambient free module of rank 2 over the principal ideal domain Univariate Polynomial Ring in x over Real Field with 53 bits of precision + Some more tests related to univariate/multivariate + constructions. We consider a generalization of polynomial rings, + where in addition to the coefficient ring `C` we also specify + an additive monoid `E` for the exponents of the indeterminate. + In particular, the elements of such a parent are given by + + .. MATH:: + + \sum_{i=0}^I c_i X^{e_i} + + with `c_i \in C` and `e_i \in E`. We define + :: + + sage: class GPolynomialRing(Parent): + ....: def __init__(self, coefficients, var, exponents): + ....: self.coefficients = coefficients + ....: self.var = var + ....: self.exponents = exponents + ....: super(GPolynomialRing, self).__init__(category=Rings()) + ....: def _repr_(self): + ....: return 'Generalized Polynomial Ring in %s^(%s) over %s' % ( + ....: self.var, self.exponents, self.coefficients) + ....: def construction(self): + ....: return GPolynomialFunctor(self.var, self.exponents), self.coefficients + ....: def _coerce_map_from_(self, R): + ....: return self.coefficients.has_coerce_map_from(R) + + and + :: + + sage: class GPolynomialFunctor(ConstructionFunctor): + ....: rank = 10 + ....: def __init__(self, var, exponents): + ....: self.var = var + ....: self.exponents = exponents + ....: ConstructionFunctor.__init__(self, Rings(), Rings()) + ....: def _repr_(self): + ....: return 'GPoly[%s^(%s)]' % (self.var, self.exponents) + ....: def _apply_functor(self, coefficients): + ....: return GPolynomialRing(coefficients, self.var, self.exponents) + ....: def merge(self, other): + ....: if isinstance(other, GPolynomialFunctor) and self.var == other.var: + ....: exponents = pushout(self.exponents, other.exponents) + ....: return GPolynomialFunctor(self.var, exponents) + + We can construct a parent now in two different ways:: + + sage: GPolynomialRing(QQ, 'X', ZZ) + Generalized Polynomial Ring in X^(Integer Ring) over Rational Field + sage: GP_ZZ = GPolynomialFunctor('X', ZZ); GP_ZZ + GPoly[X^(Integer Ring)] + sage: GP_ZZ(QQ) + Generalized Polynomial Ring in X^(Integer Ring) over Rational Field + + Since the construction + :: + + sage: GP_ZZ(QQ).construction() + (GPoly[X^(Integer Ring)], Rational Field) + + uses the coefficient ring, we have the usual coercion with respect + to this parameter:: + + sage: pushout(GP_ZZ(ZZ), GP_ZZ(QQ)) + Generalized Polynomial Ring in X^(Integer Ring) over Rational Field + sage: pushout(GP_ZZ(ZZ['t']), GP_ZZ(QQ)) + Generalized Polynomial Ring in X^(Integer Ring) over Univariate Polynomial Ring in t over Rational Field + sage: pushout(GP_ZZ(ZZ['a,b']), GP_ZZ(ZZ['b,c'])) + Generalized Polynomial Ring in X^(Integer Ring) + over Multivariate Polynomial Ring in a, b, c over Integer Ring + sage: pushout(GP_ZZ(ZZ['a,b']), GP_ZZ(QQ['b,c'])) + Generalized Polynomial Ring in X^(Integer Ring) + over Multivariate Polynomial Ring in a, b, c over Rational Field + sage: pushout(GP_ZZ(ZZ['a,b']), GP_ZZ(ZZ['c,d'])) + Traceback (most recent call last): + ... + CoercionException: ('Ambiguous Base Extension', ...) + + :: + + sage: GP_QQ = GPolynomialFunctor('X', QQ) + sage: pushout(GP_ZZ(ZZ), GP_QQ(ZZ)) + Generalized Polynomial Ring in X^(Rational Field) over Integer Ring + sage: pushout(GP_QQ(ZZ), GP_ZZ(ZZ)) + Generalized Polynomial Ring in X^(Rational Field) over Integer Ring + + :: + + sage: GP_ZZt = GPolynomialFunctor('X', ZZ['t']) + sage: pushout(GP_ZZt(ZZ), GP_QQ(ZZ)) + Generalized Polynomial Ring in X^(Univariate Polynomial Ring in t + over Rational Field) over Integer Ring + + :: + + sage: pushout(GP_ZZ(ZZ), GP_QQ(QQ)) + Generalized Polynomial Ring in X^(Rational Field) over Rational Field + sage: pushout(GP_ZZ(QQ), GP_QQ(ZZ)) + Generalized Polynomial Ring in X^(Rational Field) over Rational Field + sage: pushout(GP_ZZt(QQ), GP_QQ(ZZ)) + Generalized Polynomial Ring in X^(Univariate Polynomial Ring in t + over Rational Field) over Rational Field + sage: pushout(GP_ZZt(ZZ), GP_QQ(QQ)) + Generalized Polynomial Ring in X^(Univariate Polynomial Ring in t + over Rational Field) over Rational Field + sage: pushout(GP_ZZt(ZZ['a,b']), GP_QQ(ZZ['c,d'])) + Traceback (most recent call last): + ... + CoercionException: ('Ambiguous Base Extension', ...) + sage: pushout(GP_ZZt(ZZ['a,b']), GP_QQ(ZZ['b,c'])) + Generalized Polynomial Ring in X^(Univariate Polynomial Ring in t over Rational Field) + over Multivariate Polynomial Ring in a, b, c over Integer Ring + + Some tests with cartesian products:: + + sage: from sage.sets.cartesian_product import CartesianProduct + sage: A = CartesianProduct((ZZ['x'], QQ['y'], QQ['z']), Sets().CartesianProducts()) + sage: B = CartesianProduct((ZZ['x'], ZZ['y'], ZZ['t']['z']), Sets().CartesianProducts()) + sage: A.construction() + (The cartesian_product functorial construction, + (Univariate Polynomial Ring in x over Integer Ring, + Univariate Polynomial Ring in y over Rational Field, + Univariate Polynomial Ring in z over Rational Field)) + sage: pushout(A, B) + The cartesian product of + (Univariate Polynomial Ring in x over Integer Ring, + Univariate Polynomial Ring in y over Rational Field, + Univariate Polynomial Ring in z over Univariate Polynomial Ring in t over Rational Field) + sage: pushout(ZZ, cartesian_product([ZZ, QQ])) + Traceback (most recent call last): + ... + CoercionException: 'NoneType' object is not iterable + + :: + + sage: from sage.categories.pushout import PolynomialFunctor + sage: from sage.sets.cartesian_product import CartesianProduct + sage: class CartesianProductPoly(CartesianProduct): + ....: def __init__(self, polynomial_rings): + ....: sort = sorted(polynomial_rings, key=lambda P: P.variable_name()) + ....: super(CartesianProductPoly, self).__init__(sort, Sets().CartesianProducts()) + ....: def vars(self): + ....: return tuple(P.variable_name() for P in self.cartesian_factors()) + ....: def _pushout_(self, other): + ....: if isinstance(other, CartesianProductPoly): + ....: s_vars = self.vars() + ....: o_vars = other.vars() + ....: if s_vars == o_vars: + ....: return + ....: return pushout(CartesianProductPoly( + ....: self.cartesian_factors() + + ....: tuple(f for f in other.cartesian_factors() + ....: if f.variable_name() not in s_vars)), + ....: CartesianProductPoly( + ....: other.cartesian_factors() + + ....: tuple(f for f in self.cartesian_factors() + ....: if f.variable_name() not in o_vars))) + ....: C = other.construction() + ....: if C is None: + ....: return + ....: elif isinstance(C[0], PolynomialFunctor): + ....: return pushout(self, CartesianProductPoly((other,))) + + :: + + sage: pushout(CartesianProductPoly((ZZ['x'],)), + ....: CartesianProductPoly((ZZ['y'],))) + The cartesian product of + (Univariate Polynomial Ring in x over Integer Ring, + Univariate Polynomial Ring in y over Integer Ring) + sage: pushout(CartesianProductPoly((ZZ['x'], ZZ['y'])), + ....: CartesianProductPoly((ZZ['x'], ZZ['z']))) + The cartesian product of + (Univariate Polynomial Ring in x over Integer Ring, + Univariate Polynomial Ring in y over Integer Ring, + Univariate Polynomial Ring in z over Integer Ring) + sage: pushout(CartesianProductPoly((QQ['a,b']['x'], QQ['y'])), + ....: CartesianProductPoly((ZZ['b,c']['x'], SR['z']))) + The cartesian product of + (Univariate Polynomial Ring in x over + Multivariate Polynomial Ring in a, b, c over Rational Field, + Univariate Polynomial Ring in y over Rational Field, + Univariate Polynomial Ring in z over Symbolic Ring) + + :: + + sage: pushout(CartesianProductPoly((ZZ['x'],)), ZZ['y']) + The cartesian product of + (Univariate Polynomial Ring in x over Integer Ring, + Univariate Polynomial Ring in y over Integer Ring) + sage: pushout(QQ['b,c']['y'], CartesianProductPoly((ZZ['a,b']['x'],))) + The cartesian product of + (Univariate Polynomial Ring in x over + Multivariate Polynomial Ring in a, b over Integer Ring, + Univariate Polynomial Ring in y over + Multivariate Polynomial Ring in b, c over Rational Field) + + :: + + sage: pushout(CartesianProductPoly((ZZ['x'],)), ZZ) + Traceback (most recent call last): + ... + CoercionException: No common base ("join") found for + The cartesian_product functorial construction(...) and None(Integer Ring): + (Multivariate) functors are incompatible. + AUTHORS: - -- Robert Bradshaw + - Robert Bradshaw + - Peter Bruin + - Simon King + - Daniel Krenn + - David Roe """ if R is S or R == S: return R + if hasattr(R, '_pushout_'): + P = R._pushout_(S) + if P is not None: + return P + + if hasattr(S, '_pushout_'): + P = S._pushout_(R) + if P is not None: + return P + if isinstance(R, type): R = type_to_parent(R) @@ -3236,6 +3625,15 @@ def pushout(R, S): Rs = [c[1] for c in R_tower] Ss = [c[1] for c in S_tower] + # If there is a multivariate construction functor in the tower, we must chop off the end + # because tuples don't have has_coerce_map_from functions and to align with the + # modification of Rs and Ss below + from sage.structure.parent import Parent + if not isinstance(Rs[-1], Parent): + Rs = Rs[:-1] + if not isinstance(Ss[-1], Parent): + Ss = Ss[:-1] + if R in Ss: if not any(c[0].coercion_reversed for c in S_tower[1:]): return S @@ -3248,6 +3646,7 @@ def pushout(R, S): R_tower, S_tower = S_tower, R_tower # look for join + Z = None if Ss[-1] in Rs: if Rs[-1] == Ss[-1]: while Rs and Ss and Rs[-1] == Ss[-1]: @@ -3272,12 +3671,14 @@ def pushout(R, S): Ss.pop() Z = Rs.pop() + if Z is None and R_tower[-1][0] is not None: + Z = R_tower[-1][0].common_base(S_tower[-1][0], R_tower[-1][1], S_tower[-1][1]) + R_tower = expand_tower(R_tower[:len(Rs)]) + S_tower = expand_tower(S_tower[:len(Ss)]) else: - raise CoercionException("No common base") - - # Rc is a list of functors from Z to R and Sc is a list of functors from Z to S - R_tower = expand_tower(R_tower[:len(Rs)+1]) - S_tower = expand_tower(S_tower[:len(Ss)+1]) + # Rc is a list of functors from Z to R and Sc is a list of functors from Z to S + R_tower = expand_tower(R_tower[:len(Rs)+1]) + S_tower = expand_tower(S_tower[:len(Ss)+1]) Rc = [c[0] for c in R_tower[1:]] Sc = [c[0] for c in S_tower[1:]] @@ -3558,11 +3959,14 @@ def construction_tower(R): """ tower = [(None, R)] c = R.construction() + from sage.structure.parent import Parent while c is not None: f, R = c if not isinstance(f, ConstructionFunctor): f = BlackBoxConstructionFunctor(f) tower.append((f,R)) + if not isinstance(R, Parent): + break c = R.construction() return tower diff --git a/src/sage/categories/regular_crystals.py b/src/sage/categories/regular_crystals.py index 9fa8085633b..85deb76afeb 100644 --- a/src/sage/categories/regular_crystals.py +++ b/src/sage/categories/regular_crystals.py @@ -453,8 +453,7 @@ def wt_zero(x): if checker(y): edges.append([x, y, i]) from sage.graphs.all import DiGraph - G = DiGraph(edges) - G.add_vertices(X) + G = DiGraph([X, edges], format="vertices_and_edges", immutable=True) if have_dot2tex(): G.set_latex_options(format="dot2tex", edge_labels=True, color_by_label=self.cartan_type()._index_set_coloring) @@ -560,7 +559,7 @@ def demazure_operator_simple(self, i, ring = None): sage: K = crystals.KirillovReshetikhin(['A',2,1],2,1) sage: t = K(rows=[[3],[2]]) sage: t.demazure_operator_simple(0) - B[[[2, 3]]] + B[[[1, 2]]] + B[[[1, 2]]] + B[[[2, 3]]] TESTS:: @@ -874,8 +873,8 @@ def dual_equivalence_class(self, index_set=None): if y not in visited: todo.add(y) from sage.graphs.graph import Graph - G = Graph(edges, multiedges=True) - G.add_vertices(visited) + G = Graph([visited, edges], format="vertices_and_edges", + immutable=True, multiedges=True) if have_dot2tex(): G.set_latex_options(format="dot2tex", edge_labels=True, color_by_label=self.cartan_type()._index_set_coloring) diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index dd4f4e3912c..41e312055f3 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -580,16 +580,17 @@ def quotient(self, I, names=None): sage: F. = FreeAlgebra(QQ) sage: from sage.rings.noncommutative_ideals import Ideal_nc + sage: from itertools import product sage: class PowerIdeal(Ideal_nc): - ... def __init__(self, R, n): - ... self._power = n - ... Ideal_nc.__init__(self,R,[R.prod(m) for m in CartesianProduct(*[R.gens()]*n)]) - ... def reduce(self,x): - ... R = self.ring() - ... return add([c*R(m) for m,c in x if len(m) n: - from random import sample - S = sample(S, n) - - for x, y in S: + from sage.misc.misc import some_tuples + for x,y in some_tuples(S, 2, tester._max_runs): tester.assertEqual(x==y, y==x, LazyFormat("non symmetric equality: %s but %s")%( print_compare(x, y), print_compare(y, x))) @@ -1280,14 +1297,9 @@ def _test_elements_neq(self, **options): """ tester = self._tester(**options) S = list(tester.some_elements()) + [None, 0] - n = tester._max_runs - from sage.combinat.cartesian_product import CartesianProduct - S = CartesianProduct(S,S) - if len(S) > n: - from random import sample - S = sample(S, n) - for x,y in S: + from sage.misc.misc import some_tuples + for x,y in some_tuples(S, 2, tester._max_runs): tester.assertNotEqual(x == y, x != y, LazyFormat("__eq__ and __ne__ inconsistency:\n" " %s == %s returns %s but %s != %s returns %s")%( @@ -1400,10 +1412,32 @@ def _test_cardinality(self, **options): # Functorial constructions CartesianProduct = CartesianProduct - def cartesian_product(*parents): + def cartesian_product(*parents, **kwargs): """ Return the cartesian product of the parents. + INPUT: + + - ``parents`` -- a list (or other iterable) of parents. + + - ``category`` -- (default: ``None``) the category the + cartesian product belongs to. If ``None`` is passed, + then + :meth:`~sage.categories.covariant_functorial_construction.CovariantFactorialConstruction.category_from_parents` + is used to determine the category. + + - ``extra_category`` -- (default: ``None``) a category + that is added to the cartesian product in addition + to the categories obtained from the parents. + + - other keyword arguments will passed on to the class used + for this cartesian product (see also + :class:`~sage.sets.cartesian_product.CartesianProduct`). + + OUTPUT: + + The cartesian product. + EXAMPLES:: sage: C = AlgebrasWithBasis(QQ) @@ -1423,10 +1457,31 @@ def cartesian_product(*parents): sage: C.category() Join of Category of rings and ... and Category of Cartesian products of commutative additive groups + + :: + + sage: cartesian_product([ZZ, ZZ], category=Sets()).category() + Category of sets + sage: cartesian_product([ZZ, ZZ]).category() + Join of + Category of Cartesian products of commutative rings and + Category of Cartesian products of enumerated sets + sage: cartesian_product([ZZ, ZZ], extra_category=Posets()).category() + Join of + Category of Cartesian products of commutative rings and + Category of posets and + Category of Cartesian products of enumerated sets """ - return parents[0].CartesianProduct( - parents, - category = cartesian_product.category_from_parents(parents)) + category = kwargs.pop('category', None) + extra_category = kwargs.pop('extra_category', None) + + category = category or cartesian_product.category_from_parents(parents) + if extra_category: + if isinstance(category, (list, tuple)): + category = tuple(category) + (extra_category,) + else: + category = category & extra_category + return parents[0].CartesianProduct(parents, category=category, **kwargs) def algebra(self, base_ring, category=None): """ @@ -1655,6 +1710,10 @@ def __invert__(self): Facade = LazyImport('sage.categories.facade_sets', 'FacadeSets') Finite = LazyImport('sage.categories.finite_sets', 'FiniteSets', at_startup=True) + Topological = LazyImport('sage.categories.topological_spaces', + 'TopologicalSpaces', 'Topological', at_startup=True) + Metric = LazyImport('sage.categories.metric_spaces', 'MetricSpaces', + 'Mertic', at_startup=True) class Infinite(CategoryWithAxiom): @@ -1986,6 +2045,105 @@ def example(self): class ParentMethods: + def __iter__(self): + r""" + Return a lexicographic iterator for the elements of this cartesian product. + + EXAMPLES:: + + sage: for x,y in cartesian_product([Set([1,2]), Set(['a','b'])]): + ....: print x,y + 1 a + 1 b + 2 a + 2 b + + sage: A = FiniteEnumeratedSets()(["a", "b"]) + sage: B = FiniteEnumeratedSets().example(); B + An example of a finite enumerated set: {1,2,3} + sage: C = cartesian_product([A, B, A]); C + The cartesian product of ({'a', 'b'}, An example of a finite enumerated set: {1,2,3}, {'a', 'b'}) + sage: C in FiniteEnumeratedSets() + True + sage: list(C) + [('a', 1, 'a'), ('a', 1, 'b'), ('a', 2, 'a'), ('a', 2, 'b'), ('a', 3, 'a'), ('a', 3, 'b'), + ('b', 1, 'a'), ('b', 1, 'b'), ('b', 2, 'a'), ('b', 2, 'b'), ('b', 3, 'a'), ('b', 3, 'b')] + sage: C.__iter__.__module__ + 'sage.categories.enumerated_sets' + + sage: F22 = GF(2).cartesian_product(GF(2)) + sage: list(F22) + [(0, 0), (0, 1), (1, 0), (1, 1)] + + sage: C = cartesian_product([Permutations(10)]*4) + sage: it = iter(C) + sage: next(it) + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + sage: next(it) + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 10, 9]) + + .. WARNING:: + + The elements are returned in lexicographic order, + which gives a valid enumeration only if all + factors, but possibly the first one, are + finite. So the following one is fine:: + + sage: it = iter(cartesian_product([ZZ, GF(2)])) + sage: [next(it) for _ in range(10)] + [(0, 0), (0, 1), (1, 0), (1, 1), + (-1, 0), (-1, 1), (2, 0), (2, 1), + (-2, 0), (-2, 1)] + + But this one is not:: + + sage: it = iter(cartesian_product([GF(2), ZZ])) + sage: [next(it) for _ in range(10)] + doctest:...: UserWarning: Sage is not able to determine + whether the factors of this cartesian product are + finite. The lexicographic ordering might not go through + all elements. + [(0, 0), (0, 1), (0, -1), (0, 2), (0, -2), + (0, 3), (0, -3), (0, 4), (0, -4), (0, 5)] + + .. NOTE:: + + Here it would be faster to use :func:`itertools.product` for sets + of small size. But the latter expands all factor in memory! + So we can not reasonably use it in general. + + ALGORITHM: + + Recipe 19.9 in the Python Cookbook by Alex Martelli + and David Ascher. + """ + if any(f not in Sets().Finite() for f in self.cartesian_factors()[1:]): + from warnings import warn + warn("Sage is not able to determine whether the factors of " + "this cartesian product are finite. The lexicographic " + "ordering might not go through all elements.") + + # visualize an odometer, with "wheels" displaying "digits"...: + factors = list(self.cartesian_factors()) + wheels = map(iter, factors) + digits = [next(it) for it in wheels] + while True: + yield self._cartesian_product_of_elements(digits) + for i in range(len(digits)-1, -1, -1): + try: + digits[i] = next(wheels[i]) + break + except StopIteration: + wheels[i] = iter(factors[i]) + digits[i] = next(wheels[i]) + else: + break @cached_method def an_element(self): @@ -2034,7 +2192,15 @@ def is_finite(self): True """ f = self.cartesian_factors() - return any(c.is_empty() for c in f) or all(c.is_finite() for c in f) + try: + # Note: some parent might not implement "is_empty". So we + # carefully isolate this test. + test = any(c.is_empty() for c in f) + except (AttributeError, NotImplementedError): + pass + else: + if test: return test + return all(c.is_finite() for c in f) def cardinality(self): r""" @@ -2067,7 +2233,7 @@ def cardinality(self): # Note: some parent might not implement "is_empty". So we # carefully isolate this test. is_empty = any(c.is_empty() for c in f) - except Exception: + except (AttributeError,NotImplementedError): pass else: if is_empty: @@ -2580,3 +2746,6 @@ def _repr_(self): The subset algebra of {1, 2, 3} over Rational Field in the realization Blah """ return "{} in the realization {}".format(self.realization_of(), self._realization_name()) + +# Moved from sage.categories.cartesian_product to avoid circular import errors +cartesian_product = CartesianProductFunctor() diff --git a/src/sage/categories/simplicial_complexes.py b/src/sage/categories/simplicial_complexes.py new file mode 100644 index 00000000000..aa19a8a277b --- /dev/null +++ b/src/sage/categories/simplicial_complexes.py @@ -0,0 +1,101 @@ +""" +Simplicial Complexes +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_singleton import Category_singleton +from sage.categories.category_with_axiom import CategoryWithAxiom +#from sage.categories.cw_complexes import CWComplexes +from sage.categories.sets_cat import Sets + +class SimplicialComplexes(Category_singleton): + r""" + The category of abstract simplicial complexes. + + An abstract simplicial complex `A` is a collection of sets `X` + such that: + + - `\emptyset \in A`, + - if `X \subset Y \in A`, then `X \in A`. + + .. TODO:: + + Implement the category of simplicial complexes considered + as :class:`CW complexes ` + and rename this to the category of ``AbstractSimplicialComplexes`` + with appropriate functors. + + EXAMPLES:: + + sage: from sage.categories.simplicial_complexes import SimplicialComplexes + sage: C = SimplicialComplexes(); C + Category of simplicial complexes + + TESTS:: + + sage: TestSuite(C).run() + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.simplicial_complexes import SimplicialComplexes + sage: SimplicialComplexes().super_categories() + [Category of sets] + """ + return [Sets()] + + class Finite(CategoryWithAxiom): + """ + Category of finite simplicial complexes. + """ + class ParentMethods: + @cached_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: S = SimplicialComplex([[1,3,4], [1,2],[2,5],[4,5]]) + sage: S.dimension() + 2 + """ + return max(c.dimension() for c in self.facets()) + + class ParentMethods: + @abstract_method + def facets(self): + """ + Return the facets of ``self``. + + EXAMPLES:: + + sage: S = SimplicialComplex([[1,3,4], [1,2],[2,5],[4,5]]) + sage: S.facets() + {(1, 2), (1, 3, 4), (2, 5), (4, 5)} + """ + + @abstract_method + def faces(self): + """ + Return the faces of ``self``. + + EXAMPLES:: + + sage: S = SimplicialComplex([[1,3,4], [1,2],[2,5],[4,5]]) + sage: S.faces() + {-1: {()}, + 0: {(1,), (2,), (3,), (4,), (5,)}, + 1: {(1, 2), (1, 3), (1, 4), (2, 5), (3, 4), (4, 5)}, + 2: {(1, 3, 4)}} + """ + diff --git a/src/sage/categories/super_algebras.py b/src/sage/categories/super_algebras.py new file mode 100644 index 00000000000..6071afdbce8 --- /dev/null +++ b/src/sage/categories/super_algebras.py @@ -0,0 +1,67 @@ +r""" +Super Algebras +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.super_modules import SuperModulesCategory +from sage.categories.algebras import Algebras +from sage.categories.modules import Modules +from sage.misc.lazy_import import LazyImport + +class SuperAlgebras(SuperModulesCategory): + """ + The category of super algebras. + + An `R`-*super algebra* is an `R`-super module `A` endowed with an + `R`-algebra structure satisfying + + .. MATH:: + + A_0 A_0 \subseteq A_0, \qquad + A_0 A_1 \subseteq A_1, \qquad + A_1 A_0 \subseteq A_1, \qquad + A_1 A_1 \subseteq A_0 + + and `1 \in A_0`. + + EXAMPLES:: + + sage: Algebras(ZZ).Super() + Category of super algebras over Integer Ring + + TESTS:: + + sage: TestSuite(Algebras(ZZ).Super()).run() + """ + def extra_super_categories(self): + """ + EXAMPLES:: + + sage: Algebras(ZZ).Super().super_categories() # indirect doctest + [Category of graded algebras over Integer Ring, + Category of super modules over Integer Ring] + """ + return [self.base_category().Graded()] + + class ParentMethods: + def graded_algebra(self): + r""" + Return the associated graded algebra to ``self``. + + .. WARNING:: + + Because a super module `M` is naturally `\ZZ / 2 \ZZ`-graded, and + graded modules have a natural filtration induced by the grading, if + `M` has a different filtration, then the associated graded module + `\operatorname{gr} M \neq M`. This is most apparent with super + algebras, such as the :class:`differential Weyl algebra + `, and the + multiplication may not coincide. + """ + raise NotImplementedError + diff --git a/src/sage/categories/super_algebras_with_basis.py b/src/sage/categories/super_algebras_with_basis.py new file mode 100644 index 00000000000..9a4a1bc05a0 --- /dev/null +++ b/src/sage/categories/super_algebras_with_basis.py @@ -0,0 +1,61 @@ +r""" +Super algebras with basis +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.super_modules import SuperModulesCategory +from sage.categories.algebras import Algebras +from sage.categories.modules import Modules + +class SuperAlgebrasWithBasis(SuperModulesCategory): + """ + The category of super algebras with a distinguished basis + + EXAMPLES:: + + sage: C = Algebras(ZZ).WithBasis().Super(); C + Category of super algebras with basis over Integer Ring + + TESTS:: + + sage: TestSuite(C).run() + """ + def extra_super_categories(self): + """ + EXAMPLES:: + + sage: C = Algebras(ZZ).WithBasis().Super() + sage: sorted(C.super_categories(), key=str) # indirect doctest + [Category of graded algebras with basis over Integer Ring, + Category of super algebras over Integer Ring, + Category of super modules with basis over Integer Ring] + """ + return [self.base_category().Graded()] + + class ParentMethods: + def graded_algebra(self): + r""" + Return the associated graded module to ``self``. + + See :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra` + for the definition and the properties of this. + + .. SEEALSO:: + + :meth:`~sage.categories.filtered_modules_with_basis.ParentMethods.graded_algebra` + + EXAMPLES:: + + sage: W. = algebras.DifferentialWeyl(QQ) + sage: W.graded_algebra() + Graded Algebra of Differential Weyl algebra of + polynomials in x, y over Rational Field + """ + from sage.algebras.associated_graded import AssociatedGradedAlgebra + return AssociatedGradedAlgebra(self) + diff --git a/src/sage/categories/super_hopf_algebras_with_basis.py b/src/sage/categories/super_hopf_algebras_with_basis.py new file mode 100644 index 00000000000..28347832bc4 --- /dev/null +++ b/src/sage/categories/super_hopf_algebras_with_basis.py @@ -0,0 +1,30 @@ +r""" +Super Hopf algebras with basis +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.super_modules import SuperModulesCategory + +class SuperHopfAlgebrasWithBasis(SuperModulesCategory): + """ + The category of super Hopf algebras with a distinguished basis. + + EXAMPLES:: + + sage: C = HopfAlgebras(ZZ).WithBasis().Super(); C + Category of super hopf algebras with basis over Integer Ring + sage: sorted(C.super_categories(), key=str) + [Category of super algebras with basis over Integer Ring, + Category of super coalgebras with basis over Integer Ring, + Category of super hopf algebras over Integer Ring] + + TESTS:: + + sage: TestSuite(C).run() + """ + diff --git a/src/sage/categories/super_modules.py b/src/sage/categories/super_modules.py new file mode 100644 index 00000000000..a0a06dddf3d --- /dev/null +++ b/src/sage/categories/super_modules.py @@ -0,0 +1,229 @@ +r""" +Super modules +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_class_attribute +from sage.categories.category import Category +from sage.categories.category_types import Category_over_base_ring +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.covariant_functorial_construction import CovariantConstructionCategory +from sage.categories.modules import Modules + +# Note, a commutative algebra is not a commutative super algebra, +# therefore the following whitelist. +axiom_whitelist = frozenset(["Facade", "Finite", "Infinite", + "FiniteDimensional", "Connected", "WithBasis", + # "Commutative", + "Associative", "Inverse", "Unital", "Division", + "AdditiveCommutative", "AdditiveAssociative", + "AdditiveInverse", "AdditiveUnital", + "NoZeroDivisors", "Distributive"]) + +class SuperModulesCategory(CovariantConstructionCategory, Category_over_base_ring): + @classmethod + def default_super_categories(cls, category, *args): + """ + Return the default super categories of `F_{Cat}(A,B,...)` for + `A,B,...` parents in `Cat`. + + INPUT: + + - ``cls`` -- the category class for the functor `F` + - ``category`` -- a category `Cat` + - ``*args`` -- further arguments for the functor + + OUTPUT: + + A join category. + + This implements the property that subcategories constructed by + the set of whitelisted axioms is a subcategory. + + EXAMPLES:: + + sage: HopfAlgebras(ZZ).WithBasis().FiniteDimensional().Super() # indirect doctest + Category of finite dimensional super hopf algebras with basis over Integer Ring + """ + axioms = axiom_whitelist.intersection(category.axioms()) + C = super(SuperModulesCategory, cls).default_super_categories(category, *args) + return C._with_axioms(axioms) + + def __init__(self, base_category): + """ + EXAMPLES:: + + sage: C = Algebras(QQ).Super() + sage: C + Category of super algebras over Rational Field + sage: C.base_category() + Category of algebras over Rational Field + sage: sorted(C.super_categories(), key=str) + [Category of graded algebras over Rational Field, + Category of super modules over Rational Field] + + sage: AlgebrasWithBasis(QQ).Super().base_ring() + Rational Field + sage: HopfAlgebrasWithBasis(QQ).Super().base_ring() + Rational Field + """ + super(SuperModulesCategory, self).__init__(base_category, base_category.base_ring()) + + _functor_category = "Super" + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: AlgebrasWithBasis(QQ).Super() # indirect doctest + Category of super algebras with basis over Rational Field + """ + return "super {}".format(self.base_category()._repr_object_names()) + +class SuperModules(SuperModulesCategory): + r""" + The category of super modules. + + An `R`-*super module* (where `R` is a ring) is an `R`-module `M` equipped + with a decomposition `M = M_0 \oplus M_1` into two `R`-submodules + `M_0` and `M_1` (called the *even part* and the *odd part* of `M`, + respectively). + + Thus, an `R`-super module automatically becomes a `\ZZ / 2 \ZZ`-graded + `R`-module, with `M_0` being the degree-`0` component and `M_1` being the + degree-`1` component. + + EXAMPLES:: + + sage: Modules(ZZ).Super() + Category of super modules over Integer Ring + sage: Modules(ZZ).Super().super_categories() + [Category of graded modules over Integer Ring] + + The category of super modules defines the super structure which + shall be preserved by morphisms:: + + sage: Modules(ZZ).Super().additional_structure() + Category of super modules over Integer Ring + + TESTS:: + + sage: TestSuite(Modules(ZZ).Super()).run() + """ + def super_categories(self): + """ + EXAMPLES:: + + sage: Modules(ZZ).Super().super_categories() + [Category of graded modules over Integer Ring] + + Nota bene:: + + sage: Modules(QQ).Super() + Category of super modules over Rational Field + sage: Modules(QQ).Super().super_categories() + [Category of graded modules over Rational Field] + """ + return [self.base_category().Graded()] + + def extra_super_categories(self): + r""" + Adds :class:`VectorSpaces` to the super categories of ``self`` if + the base ring is a field. + + EXAMPLES:: + + sage: Modules(QQ).Super().extra_super_categories() + [Category of vector spaces over Rational Field] + sage: Modules(ZZ).Super().extra_super_categories() + [] + + This makes sure that ``Modules(QQ).Super()`` returns an + instance of :class:`SuperModules` and not a join category of + an instance of this class and of ``VectorSpaces(QQ)``:: + + sage: type(Modules(QQ).Super()) + + + .. TODO:: + + Get rid of this workaround once there is a more systematic + approach for the alias ``Modules(QQ)`` -> ``VectorSpaces(QQ)``. + Probably the latter should be a category with axiom, and + covariant constructions should play well with axioms. + """ + from sage.categories.modules import Modules + from sage.categories.fields import Fields + base_ring = self.base_ring() + if base_ring in Fields: + return [Modules(base_ring)] + else: + return [] + + class ParentMethods: + pass + + class ElementMethods: + def is_even_odd(self): + """ + Return ``0`` if ``self`` is an even element or ``1`` + if an odd element. + + .. NOTE:: + + The default implementation assumes that the even/odd is + determined by the parity of :meth:`degree`. + + Overwrite this method if the even/odd behavior is desired + to be independent. + + EXAMPLES:: + + sage: cat = Algebras(QQ).WithBasis().Super() + sage: C = CombinatorialFreeModule(QQ, Partitions(), category=cat) + sage: C.degree_on_basis = sum + sage: C.basis()[2,2,1].is_even_odd() + 1 + sage: C.basis()[2,2].is_even_odd() + 0 + """ + return self.degree() % 2 + + def is_even(self): + """ + Return if ``self`` is an even element. + + EXAMPLES:: + + sage: cat = Algebras(QQ).WithBasis().Super() + sage: C = CombinatorialFreeModule(QQ, Partitions(), category=cat) + sage: C.degree_on_basis = sum + sage: C.basis()[2,2,1].is_even() + False + sage: C.basis()[2,2].is_even() + True + """ + return self.is_even_odd() == 0 + + def is_odd(self): + """ + Return if ``self`` is an odd element. + + EXAMPLES:: + + sage: cat = Algebras(QQ).WithBasis().Super() + sage: C = CombinatorialFreeModule(QQ, Partitions(), category=cat) + sage: C.degree_on_basis = sum + sage: C.basis()[2,2,1].is_odd() + True + sage: C.basis()[2,2].is_odd() + False + """ + return self.is_even_odd() == 1 + diff --git a/src/sage/categories/super_modules_with_basis.py b/src/sage/categories/super_modules_with_basis.py new file mode 100644 index 00000000000..43b3dc1215a --- /dev/null +++ b/src/sage/categories/super_modules_with_basis.py @@ -0,0 +1,185 @@ +r""" +Super modules with basis +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.super_modules import SuperModulesCategory + +class SuperModulesWithBasis(SuperModulesCategory): + """ + The category of super modules with a distinguished basis. + + An `R`-*super module with a distinguished basis* is an + `R`-super module equipped with an `R`-module basis whose elements are + homogeneous. + + EXAMPLES:: + + sage: C = GradedModulesWithBasis(ZZ); C + Category of graded modules with basis over Integer Ring + sage: sorted(C.super_categories(), key=str) + [Category of filtered modules with basis over Integer Ring, + Category of graded modules over Integer Ring] + sage: C is ModulesWithBasis(ZZ).Graded() + True + + TESTS:: + + sage: TestSuite(C).run() + """ + class ParentMethods: + def _even_odd_on_basis(self, m): + """ + Return the parity of the basis element indexed by ``m``. + + OUTPUT: + + ``0`` if ``m`` is for an even element or ``1`` if ``m`` + is for an odd element. + + .. NOTE:: + + The default implementation assumes that the even/odd is + determined by the parity of :meth:`degree`. + + Overwrite this method if the even/odd behavior is desired + to be independent. + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: C._even_odd_on_basis((0,)) + 1 + sage: C._even_odd_on_basis((0,1)) + 0 + """ + return self.degree_on_basis(m) % 2 + + class ElementMethods: + def is_super_homogeneous(self): + r""" + Return whether this element is homogeneous, in the sense + of a super module (i.e., is even or odd). + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: a = x + y + sage: a.is_super_homogeneous() + True + sage: a = x*y + 4 + sage: a.is_super_homogeneous() + True + sage: a = x*y + x - 3*y + 4 + sage: a.is_super_homogeneous() + False + + The exterior algebra has a `\ZZ` grading, which induces the + `\ZZ / 2\ZZ` grading. However the definition of homogeneous + elements differs because of the different gradings:: + + sage: E. = ExteriorAlgebra(QQ) + sage: a = x*y + 4 + sage: a.is_super_homogeneous() + True + sage: a.is_homogeneous() + False + """ + even_odd = self.parent()._even_odd_on_basis + degree = None + for m in self.support(): + if degree is None: + degree = even_odd(m) + else: + if degree != even_odd(m): + return False + return True + + def is_even_odd(self): + """ + Return ``0`` if ``self`` is an even element and ``1`` if + ``self`` is an odd element. + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: a = x + y + sage: a.is_even_odd() + 1 + sage: a = x*y + 4 + sage: a.is_even_odd() + 0 + sage: a = x + 4 + sage: a.is_even_odd() + Traceback (most recent call last): + ... + ValueError: element is not homogeneous + + sage: E. = ExteriorAlgebra(QQ) + sage: (x*y).is_even_odd() + 0 + """ + if not self.support(): + raise ValueError("the zero element does not have a well-defined degree") + if not self.is_super_homogeneous(): + raise ValueError("element is not homogeneous") + return self.parent()._even_odd_on_basis(self.leading_support()) + + def even_component(self): + """ + Return the even component of ``self``. + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: a = x*y + x - 3*y + 4 + sage: a.even_component() + x*y + 4 + + TESTS: + + Check that this really return ``A.zero()`` and not a plain ``0``:: + + sage: a = x + y + sage: a.even_component().parent() is C + True + """ + even_odd = self.parent()._even_odd_on_basis + return self.parent().sum_of_terms((i, c) + for (i, c) in self + if even_odd(i) == 0) + + def odd_component(self): + """ + Return the odd component of ``self``. + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: a = x*y + x - 3*y + 4 + sage: a.odd_component() + x - 3*y + + TESTS: + + Check that this really return ``A.zero()`` and not a plain ``0``:: + + sage: a = x*y + sage: a.odd_component().parent() is C + True + """ + even_odd = self.parent()._even_odd_on_basis + return self.parent().sum_of_terms((i, c) + for (i, c) in self + if even_odd(i) == 1) + diff --git a/src/sage/categories/topological_spaces.py b/src/sage/categories/topological_spaces.py new file mode 100644 index 00000000000..41f98e0decc --- /dev/null +++ b/src/sage/categories/topological_spaces.py @@ -0,0 +1,108 @@ +r""" +Topological Spaces +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.categories.category_with_axiom import CategoryWithAxiom +from sage.categories.covariant_functorial_construction import RegressiveCovariantConstructionCategory +from sage.categories.sets_cat import Sets + +class TopologicalSpacesCategory(RegressiveCovariantConstructionCategory): + + _functor_category = "Topological" + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: Groups().Topological() # indirect doctest + Category of topological groups + """ + return "topological {}".format(self.base_category()._repr_object_names()) + +class TopologicalSpaces(TopologicalSpacesCategory): + """ + The category of topological spaces. + + EXAMPLES:: + + sage: Sets().Topological() + Category of topological spaces + sage: Sets().Topological().super_categories() + [Category of sets] + + The category of topological spaces defines the topological structure, + which shall be preserved by morphisms:: + + sage: Sets().Topological().additional_structure() + Category of topological spaces + + TESTS:: + + sage: TestSuite(Sets().Topological()).run() + """ + # We must override the general object because the names don't match + _base_category_class = (Sets,) + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: Sets().Topological() # indirect doctest + Category of topological spaces + """ + return "topological spaces" + + class SubcategoryMethods: + @cached_method + def Connected(self): + """ + Return the full subcategory of the connected objects of ``self``. + + EXAMPLES:: + + sage: Sets().Topological().Connected() + Category of connected topological spaces + + TESTS:: + + sage: TestSuite(Sets().Topological().Connected()).run() + sage: Sets().Topological().Connected.__module__ + 'sage.categories.topological_spaces' + """ + return self._with_axiom('Connected') + + @cached_method + def Compact(self): + """ + Return the subcategory of the compact objects of ``self``. + + EXAMPLES:: + + sage: Sets().Topological().Compact() + Category of compact topological spaces + + TESTS:: + + sage: TestSuite(Sets().Topological().Compact()).run() + sage: Sets().Topological().Compact.__module__ + 'sage.categories.topological_spaces' + """ + return self._with_axiom('Compact') + + class Connected(CategoryWithAxiom): + """ + The category of connected topological spaces. + """ + + class Compact(CategoryWithAxiom): + """ + The category of compact topological spaces. + """ + diff --git a/src/sage/categories/unique_factorization_domains.py b/src/sage/categories/unique_factorization_domains.py index 50c6fd107a4..ddedb674d3b 100644 --- a/src/sage/categories/unique_factorization_domains.py +++ b/src/sage/categories/unique_factorization_domains.py @@ -124,6 +124,87 @@ def is_unique_factorization_domain(self, proof=True): """ return True + def _gcd_univariate_polynomial(self, f, g): + """ + Return the greatest common divisor of ``f`` and ``g``. + + INPUT: + + - ``f``, ``g`` -- two polynomials defined over this UFD. + + .. NOTE:: + + This is a helper method for + :meth:`sage.rings.polynomial.polynomial_element.Polynomial.gcd`. + + ALGORITHM: + + Algorithm 3.3.1 in [GTM138]_, based on pseudo-division. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: S. = R[] + sage: p = (-3*x^2 - x)*T^3 - 3*x*T^2 + (x^2 - x)*T + 2*x^2 + 3*x - 2 + sage: q = (-x^2 - 4*x - 5)*T^2 + (6*x^2 + x + 1)*T + 2*x^2 - x + sage: quo,rem=p.pseudo_quo_rem(q); quo,rem + ((3*x^4 + 13*x^3 + 19*x^2 + 5*x)*T + 18*x^4 + 12*x^3 + 16*x^2 + 16*x, + (-113*x^6 - 106*x^5 - 133*x^4 - 101*x^3 - 42*x^2 - 41*x)*T - 34*x^6 + 13*x^5 + 54*x^4 + 126*x^3 + 134*x^2 - 5*x - 50) + sage: (-x^2 - 4*x - 5)^(3-2+1) * p == quo*q + rem + True + + REFERENCES: + + .. [GTM138] Henri Cohen. A Course in Computational Number Theory. + Graduate Texts in Mathematics, vol. 138. Springer, 1993. + """ + if f.degree() < g.degree(): + A,B = g, f + else: + A,B = f, g + + if B.is_zero(): + return A + + a = b = self.zero() + for c in A.coefficients(): + a = a.gcd(c) + if a.is_one(): + break + for c in B.coefficients(): + b = b.gcd(c) + if b.is_one(): + break + + parent = f.parent() + + d = a.gcd(b) + A = parent(A/a) + B = parent(B/b) + g = h = 1 + + delta = A.degree()-B.degree() + _,R = A.pseudo_quo_rem(B) + + while R.degree() > 0: + A = B + B = parent(R/(g*h**delta)) + g = A.leading_coefficient() + h = self(h*g**delta/h**delta) + delta = A.degree() - B.degree() + _, R = A.pseudo_quo_rem(B) + + if R.is_zero(): + b = self.zero() + for c in B.coefficients(): + b = b.gcd(c) + if b.is_one(): + break + + return parent(d*B/b) + + return d + class ElementMethods: # prime? # squareFree diff --git a/src/sage/coding/all.py b/src/sage/coding/all.py index 84e1b6e81f5..5895ac96efc 100644 --- a/src/sage/coding/all.py +++ b/src/sage/coding/all.py @@ -1,7 +1,6 @@ from sage.misc.lazy_import import lazy_import -from code_constructions import (permutation_action, - walsh_matrix,cyclotomic_cosets) +from code_constructions import (permutation_action, walsh_matrix,cyclotomic_cosets) from sage.misc.superseded import deprecated_callable_import deprecated_callable_import(15445, @@ -62,14 +61,14 @@ elias_bound_asymp, mrrw1_bound_asymp) -from linear_code import (LinearCode, LinearCodeFromVectorSpace, - best_known_linear_code, - best_known_linear_code_www, - bounds_minimum_distance, - self_orthogonal_binary_codes) +lazy_import("sage.coding.linear_code", ["LinearCode",\ + "LinearCodeFromVectorSpace",\ + "best_known_linear_code",\ + "best_known_linear_code_www",\ + "bounds_minimum_distance", + "self_orthogonal_binary_codes"]) from sd_codes import self_dual_codes_binary - lazy_import("sage.coding.delsarte_bounds", ["Krawtchouk", "delsarte_bound_hamming_space", "delsarte_bound_additive_hamming_space"]) diff --git a/src/sage/coding/codes_catalog.py b/src/sage/coding/codes_catalog.py index d97b3b99c87..9af7b3a908c 100644 --- a/src/sage/coding/codes_catalog.py +++ b/src/sage/coding/codes_catalog.py @@ -22,7 +22,7 @@ CyclicCode, CyclicCodeFromCheckPolynomial, DuadicCodeEvenPair, DuadicCodeOddPair, ExtendedBinaryGolayCode, ExtendedQuadraticResidueCode, ExtendedTernaryGolayCode, - HammingCode, LinearCodeFromCheckMatrix, + HammingCode, LinearCode, LinearCodeFromCheckMatrix, QuadraticResidueCode, QuadraticResidueCodeEvenPair, QuadraticResidueCodeOddPair, RandomLinearCode, ReedSolomonCode, TernaryGolayCode, @@ -30,6 +30,7 @@ from guava import BinaryReedMullerCode, QuasiQuadraticResidueCode, RandomLinearCodeGuava -from sage.misc.rest_index_of_methods import gen_rest_table_index -import sys -__doc__ = __doc__.format(INDEX_OF_FUNCTIONS=gen_rest_table_index(sys.modules[__name__], only_local_functions=False)) +import encoders_catalog as encoders +from sage.misc.rest_index_of_methods import gen_rest_table_index as _gen_rest_table_index +import sys as _sys +__doc__ = __doc__.format(INDEX_OF_FUNCTIONS=_gen_rest_table_index(_sys.modules[__name__], only_local_functions=False)) diff --git a/src/sage/coding/encoder.py b/src/sage/coding/encoder.py new file mode 100644 index 00000000000..d5051e7ccfe --- /dev/null +++ b/src/sage/coding/encoder.py @@ -0,0 +1,325 @@ +r""" +Encoder + +Representation of a bijection between a message space and a code. +""" + +#***************************************************************************** +# Copyright (C) 2015 David Lucas +# +# 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 sage.modules.free_module_element import vector +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.structure.sage_object import SageObject + +class Encoder(SageObject): + r""" + Abstract top-class for :class:`Encoder` objects. + + Every encoder class should inherit from this abstract class. + + To implement an encoder, you need to: + + - inherit from :class:`Encoder`, + + - call ``Encoder.__init__`` in the subclass constructor. + Example: ``super(SubclassName, self).__init__(code)``. + By doing that, your subclass will have its ``code`` parameter initialized. + + - Then, if the message space is a vector space, default implementations of :meth:`encode` and + :meth:`unencode_nocheck` methods are provided. These implementations rely on :meth:`generator_matrix` + which you need to override to use the default implementations. + + - If the message space is not of the form `F^k`, where `F` is a finite field, + you cannot have a generator matrix. + In that case, you need to override :meth:`encode`, :meth:`unencode_nocheck` and + :meth:`message_space`. + + - By default, comparison of :class:`Encoder` (using methods ``__eq__`` and ``__ne__`` ) are + by memory reference: if you build the same encoder twice, they will be different. If you + need something more clever, override ``__eq__`` and ``__ne__`` in your subclass. + + - As :class:`Encoder` is not designed to be instantiated, it does not have any representation + methods. You should implement ``_repr_`` and ``_latex_`` methods in the sublclass. + + REFERENCES: + + .. [Nielsen] Johan S. R. Nielsen, (https://bitbucket.org/jsrn/codinglib/) + """ + + def __init__(self, code): + r""" + Initializes mandatory parameters for an :class:`Encoder` object. + + This method only exists for inheritance purposes as it initializes + parameters that need to be known by every linear code. An abstract + encoder object should never be created. + + INPUT: + + - ``code`` -- the associated code of ``self`` + + EXAMPLES: + + We first create a new :class:`Encoder` subclass:: + + sage: class EncoderExample(sage.coding.encoder.Encoder): + ....: def __init__(self, code): + ....: super(EncoderExample, self).__init__(code) + + We now create a member of our newly made class:: + + sage: G = Matrix(GF(2), [[1, 0, 0, 1], [0, 1, 1, 1]]) + sage: C = LinearCode(G) + sage: E = EncoderExample(C) + + We can check its parameters:: + + sage: E.code() + Linear code of length 4, dimension 2 over Finite Field of size 2 + """ + self._code = code + + def encode(self, word): + r""" + Transforms an element of the message space into a codeword. + + This is a default implementation which assumes that the message + space of the encoder is `F^{k}`, where `F` is + :meth:`sage.coding.linear_code.AbstractLinearCode.base_field` + and `k` is :meth:`sage.coding.linear_code.AbstractLinearCode.dimension`. + If this is not the case, this method should be overwritten by the subclass. + + .. NOTE:: + + :meth:`encode` might be a partial function over ``self``'s :meth:`message_space`. + One should use the exception :class:`EncodingError` to catch attempts + to encode words that are outside of the message space. + + INPUT: + + - ``word`` -- a vector of the message space of the ``self``. + + OUTPUT: + + - a vector of :meth:`code`. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: word = vector(GF(2), (0, 1, 1, 0)) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.encode(word) + (1, 1, 0, 0, 1, 1, 0) + + If ``word`` is not in the message space of ``self``, it will return an exception:: + + sage: word = random_vector(GF(7), 4) + sage: E.encode(word) + Traceback (most recent call last): + ... + ValueError: The value to encode must be in Vector space of dimension 4 over Finite Field of size 2 + """ + M = self.message_space() + if word not in M: + raise ValueError("The value to encode must be in %s" % M) + return vector(word) * self.generator_matrix() + + def unencode(self, c, nocheck=False): + r""" + Returns the message corresponding to the codeword ``c``. + + This is the inverse of :meth:`encode`. + + INPUT: + + - ``c`` -- a vector of the same length as :meth:`code` over the + base field of :meth:`code`. + + - ``nocheck`` -- (default: ``False``) checks if ``c`` is in ``self``. You might set + this to ``True`` to disable the check for saving computation. Note that if ``c`` is + not in ``self`` and ``nocheck = True``, then the output of :meth:`unencode` is + not defined (except that it will be in the message space of ``self``). + + OUTPUT: + + - an element of the message space of ``self`` + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: c = vector(GF(2), (1, 1, 0, 0, 1, 1, 0)) + sage: c in C + True + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.unencode(c) + (0, 1, 1, 0) + + TESTS: + + If ``nocheck`` is set to ``False``, and one provides a word which is not in + :meth:`code`, :meth:`unencode` will return an error:: + + sage: c = vector(GF(2), (0, 1, 0, 0, 1, 1, 0)) + sage: c in C + False + sage: E.unencode(c, False) + Traceback (most recent call last): + ... + EncodingError: Given word is not in the code + + If ones tries to unencode a codeword of a code of dimension 0, it + returns the empty vector:: + + sage: G = Matrix(GF(17), []) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: c = C.random_element() + sage: E.unencode(c) + () + """ + if nocheck == False and c not in self.code(): + raise EncodingError("Given word is not in the code") + return self.unencode_nocheck(c) + + @cached_method + def _unencoder_matrix(self): + r""" + Finds an information set for the matrix ``G`` returned by :meth:`generator_matrix`, + and returns the inverse of that submatrix of ``G``. + + AUTHORS: + + This function is taken from codinglib [Nielsen]_ + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = C.encoder() + sage: E._unencoder_matrix() + ( + [0 0 1 1] + [0 1 0 1] + [1 1 1 0] + [0 1 1 1], (0, 1, 2, 3) + ) + """ + info_set = self.code().information_set() + Gt = self.generator_matrix().matrix_from_columns(info_set) + return (Gt.inverse(), info_set) + + def unencode_nocheck(self, c): + r""" + Returns the message corresponding to ``c``. + + When ``c`` is not a codeword, the output is unspecified. + + AUTHORS: + + This function is taken from codinglib [Nielsen]_ + + INPUT: + + - ``c`` -- a vector of the same length as ``self`` over the + base field of ``self`` + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: c = vector(GF(2), (1, 1, 0, 0, 1, 1, 0)) + sage: c in C + True + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.unencode_nocheck(c) + (0, 1, 1, 0) + + Taking a vector that does not belong to ``C`` will not raise an error but + probably just give a non-sensical result:: + + sage: c = vector(GF(2), (1, 1, 0, 0, 1, 1, 1)) + sage: c in C + False + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.unencode_nocheck(c) + (0, 1, 1, 0) + sage: m = vector(GF(2), (0, 1, 1, 0)) + sage: c1 = E.encode(m) + sage: c == c1 + False + """ + U, info_set = self._unencoder_matrix() + cc = vector(self.code().base_ring(), [c[i] for i in info_set]) + return cc * U + + def code(self): + r""" + Returns the code for this :class:`Encoder`. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = C.encoder() + sage: E.code() == C + True + """ + return self._code + + def message_space(self): + r""" + Returns the ambient space of allowed input to :meth:`encode`. + Note that :meth:`encode` is possibly a partial function over + the ambient space. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = C.encoder() + sage: E.message_space() + Vector space of dimension 4 over Finite Field of size 2 + """ + return self.code().base_field()**(self.code().dimension()) + + @abstract_method(optional = True) + def generator_matrix(self): + r""" + Returns a generator matrix of the associated code of ``self``. + + This is an abstract method and it should be implemented separately. + Reimplementing this for each subclass of :class:`Encoder` is not mandatory + (as a generator matrix only makes sense when the message space is of the `F^k`, + where `F` is the base field of :meth:`code`.) + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = C.encoder() + sage: E.generator_matrix() + [1 1 1 0 0 0 0] + [1 0 0 1 1 0 0] + [0 1 0 1 0 1 0] + [1 1 0 1 0 0 1] + """ + +class EncodingError(Exception): + r""" + Special exception class to indicate an error during encoding or unencoding. + """ + pass diff --git a/src/sage/coding/encoders_catalog.py b/src/sage/coding/encoders_catalog.py new file mode 100644 index 00000000000..36a0f84e34a --- /dev/null +++ b/src/sage/coding/encoders_catalog.py @@ -0,0 +1,16 @@ +r""" +Index of encoders + +The ``codes.encoders`` object may be used to access the encoders that Sage can build. + +:class:`linear_code.LinearCodeGeneratorMatrixEncoder ` + +.. NOTE:: + + To import these names into the global namespace, use: + + sage: from sage.coding.encoders_catalog import * +""" + +from sage.misc.lazy_import import lazy_import as _lazy_import +_lazy_import('sage.coding.linear_code', 'LinearCodeGeneratorMatrixEncoder') diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index 2674dc4565d..f39e6e6d77c 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -216,6 +216,7 @@ import sage.modules.free_module as fm import sage.modules.module as module from sage.categories.modules import Modules +from copy import copy from sage.interfaces.all import gap from sage.rings.finite_rings.constructor import FiniteField as GF from sage.groups.perm_gps.permgroup import PermutationGroup @@ -237,7 +238,7 @@ from sage.misc.decorators import rename_keyword from sage.misc.cachefunc import cached_method from sage.misc.superseded import deprecated_function_alias - +from encoder import Encoder ZZ = IntegerRing() VectorSpace = fm.VectorSpace @@ -709,12 +710,6 @@ class AbstractLinearCode(module.Module): So, every Linear Code-related class should inherit from this abstract class. - This class provides: - - - ``length``, the length of the code - - - numerous methods that will work for any linear code (including families) - To implement a linear code, you need to: - inherit from AbstractLinearCode @@ -726,19 +721,35 @@ class AbstractLinearCode(module.Module): You need of course to complete the constructor by adding any additional parameter needed to describe properly the code defined in the subclass. - - reimplement ``generator_matrix()`` method + - fill the dictionary of its encoders in ``sage.coding.__init__.py`` file. Example: + I want to link the encoder ``MyEncoderClass`` to ``MyNewCodeClass`` + under the name ``MyEncoderName``. + All I need to do is to write this line in the ``__init__.py`` file: + ``MyNewCodeClass._registered_encoders["NameOfMyEncoder"] = MyEncoderClass`` and all instances of + ``MyNewCodeClass`` will be able to use instances of ``MyEncoderClass``. As AbstractLinearCode is not designed to be implemented, it does not have any representation - methods. You should implement ``_repr_`` and ``_latex_`` methods in the sublclass. + methods. You should implement ``_repr_`` and ``_latex_`` methods in the subclass. .. NOTE:: - AbstractLinearCode embeds some generic implementations of helper methods like ``__cmp__`` or ``__eq__``. - As they are designed to fit for every linear code, they mostly use the generator matrix - and thus can be long for certain families of code. In that case, overriding these methods is encouraged. + :class:`AbstractLinearCode` has generic implementations of the comparison methods ``__cmp`` + and ``__eq__`` which use the generator matrix and are quite slow. In subclasses you are + encouraged to override these functions. + + .. WARNING:: + + The default encoder should always have `F^{k}` as message space, with `k` the dimension + of the code and `F` is the base ring of the code. + A lot of methods of the abstract class rely on the knowledge of a generator matrix. + It is thus strongly recommended to set an encoder with a generator matrix implemented + as a default encoder. """ - def __init__(self, base_field, length): + + _registered_encoders = {} + + def __init__(self, base_field, length, default_encoder_name): """ Initializes mandatory parameters for a Linear Code object. @@ -752,13 +763,15 @@ def __init__(self, base_field, length): - ``length`` -- the length of ``self`` + - ``default_encoder_name`` -- the name of the default encoder of ``self`` + EXAMPLES: We first create a new LinearCode subclass:: sage: class CodeExample(sage.coding.linear_code.AbstractLinearCode): ....: def __init__(self, field, length, dimension, generator_matrix): - ....: sage.coding.linear_code.AbstractLinearCode.__init__(self,field, length) + ....: sage.coding.linear_code.AbstractLinearCode.__init__(self,field, length, "GeneratorMatrix") ....: self._dimension = dimension ....: self._generator_matrix = generator_matrix ....: def generator_matrix(self): @@ -802,10 +815,31 @@ def __init__(self, base_field, length): Traceback (most recent call last): ... ValueError: length must be a Python int or a Sage Integer + + If the name of the default encoder is not known by the class, it will raise + an exception:: + + sage: class CodeExample(sage.coding.linear_code.AbstractLinearCode): + ....: def __init__(self, field, length, dimension, generator_matrix): + ....: sage.coding.linear_code.AbstractLinearCode.__init__(self,field, length, "Fail") + ....: self._dimension = dimension + ....: self._generator_matrix = generator_matrix + ....: def generator_matrix(self): + ....: return self._generator_matrix + ....: def _repr_(self): + ....: return "Dummy code of length %d, dimension %d over %s" % (self.length(), self.dimension(), self.base_field()) + + sage: C = CodeExample(GF(17), 10, 5, generator_matrix) + Traceback (most recent call last): + ... + ValueError: You must set a valid encoder as default encoder for this code, by completing __init__.py """ if not isinstance(length, (int, Integer)): raise ValueError("length must be a Python int or a Sage Integer") self._length = Integer(length) + if not default_encoder_name in self._registered_encoders: + raise ValueError("You must set a valid encoder as default encoder for this code, by completing __init__.py") + self._default_encoder_name = default_encoder_name cat = Modules(base_field).FiniteDimensional().WithBasis().Finite() facade_for = VectorSpace(base_field, self._length) self.Element = type(facade_for.an_element()) #for when we made this a non-facade parent @@ -843,6 +877,68 @@ def _an_element_(self): """ return self.gens()[0] + def add_encoder(self, name, encoder): + r""" + Adds an encoder to the list of registered encoders of ``self``. + + .. NOTE:: + + This method only adds ``encoder`` to ``self``, and not to any member of the class + of ``self``. To know how to add an :class:`sage.coding.encoder.Encoder`, please refer + to the documentation of :class:`AbstractLinearCode`. + + INPUT: + + - ``name`` -- the string name for the encoder + + - ``encoder`` -- the class name of the encoder + + EXAMPLES: + + First of all, we create a (very basic) new encoder:: + + sage: class MyEncoder(sage.coding.encoder.Encoder): + ....: def __init__(self, code): + ....: super(MyEncoder, self).__init__(code) + ....: def _repr_(self): + ....: return "MyEncoder encoder with associated code %s" % self.code() + + We now create a new code:: + + sage: C = codes.HammingCode(3, GF(2)) + + We can add our new encoder to the list of available encoders of C:: + + sage: C.add_encoder("MyEncoder", MyEncoder) + sage: C.encoders_available() + ['MyEncoder', 'GeneratorMatrix'] + + We can verify that any new code will not know MyEncoder:: + + sage: C2 = codes.HammingCode(3, GF(3)) + sage: C2.encoders_available() + ['GeneratorMatrix'] + + TESTS: + + It is impossible to use a name which is in the dictionnary of available encoders:: + + sage: C.add_encoder("GeneratorMatrix", MyEncoder) + Traceback (most recent call last): + ... + ValueError: There is already a registered encoder with this name + """ + if self._registered_encoders == self.__class__._registered_encoders: + self._registered_encoders = copy(self._registered_encoders) + reg_enc = self._registered_encoders + if name in reg_enc: + raise ValueError("There is already a registered encoder with this name") + reg_enc[name] = encoder + else: + if name in self._registered_encoders: + raise ValueError("There is already a registered encoder with this name") + reg_enc[name] = encoder + def automorphism_group_gens(self, equivalence="semilinear"): r""" Return generators of the automorphism group of ``self``. @@ -1638,6 +1734,133 @@ def __eq__(self, right): return False return True + def encode(self, word, encoder_name=None, **kwargs): + r""" + Transforms an element of a message space into a codeword. + + INPUT: + + - ``word`` -- a vector of a message space of the code. + + - ``encoder_name`` -- (default: ``None``) Name of the encoder which will be used + to encode ``word``. The default encoder of ``self`` will be used if + default value is kept. + + - ``kwargs`` -- all additional arguments are forwarded to the construction of the + encoder that is used. + + .. NOTE:: + + The default encoder always has `F^{k}` as message space, with `k` the dimension + of ``self`` and `F` the base ring of ``self``. + + OUTPUT: + + - a vector of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: word = vector((0, 1, 1, 0)) + sage: C.encode(word) + (1, 1, 0, 0, 1, 1, 0) + + It is possible to manually choose the encoder amongst the list of the available ones:: + + sage: C.encoders_available() + ['GeneratorMatrix'] + sage: word = vector((0, 1, 1, 0)) + sage: C.encode(word, 'GeneratorMatrix') + (1, 1, 0, 0, 1, 1, 0) + """ + E = self.encoder(encoder_name, **kwargs) + return E.encode(word) + + @cached_method + def encoder(self, encoder_name=None, **kwargs): + r""" + Returns an encoder of ``self``. + + The returned encoder provided by this method is cached. + + This methods creates a new instance of the encoder subclass designated by ``encoder_name``. + While it is also possible to do the same by directly calling the subclass' constructor, + it is strongly advised to use this method to take advantage of the caching mechanism. + + INPUT: + + - ``encoder_name`` -- (default: ``None``) name of the encoder which will be + returned. The default encoder of ``self`` will be used if + default value is kept. + + - ``kwargs`` -- all additional arguments are forwarded to the constructor of the encoder + this method will return. + + OUTPUT: + + - an Encoder object. + + .. NOTE:: + + The default encoder always has `F^{k}` as message space, with `k` the dimension + of ``self`` and `F` the base ring of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: C.encoder() + Generator matrix-based encoder for Linear code of length 7, dimension 4 over Finite Field of size 2 + + We check that the returned encoder is cached:: + + sage: C.encoder.is_in_cache() + True + + If the name of an encoder which is not known by ``self`` is passed, + an exception will be raised:: + + sage: C.encoders_available() + ['GeneratorMatrix'] + sage: C.encoder('NonExistingEncoder') + Traceback (most recent call last): + ... + ValueError: Passed Encoder name not known + """ + if encoder_name is None: + encoder_name = self._default_encoder_name + if encoder_name in self._registered_encoders: + encClass = self._registered_encoders[encoder_name] + E = encClass(self, **kwargs) + return E + else: + raise ValueError("Passed Encoder name not known") + + def encoders_available(self, classes=False): + r""" + Returns a list of the available encoders' names for ``self``. + + INPUT: + + - ``classes`` -- (default: ``False``) if ``classes`` is set to ``True``, it also + returns the encoders' classes associated with the encoders' names. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: C.encoders_available() + ['GeneratorMatrix'] + + sage: C.encoders_available(True) + {'GeneratorMatrix': + } + """ + if classes == True: + return copy(self._registered_encoders) + return self._registered_encoders.keys() + def extended_code(self): r""" If ``self`` is a linear code of length `n` defined over `F` then this @@ -1809,8 +2032,31 @@ def __getitem__(self, i): codeword.set_immutable() return codeword - def generator_matrix(self): - return NotImplementedError("This method must be set in subclasses") + def generator_matrix(self, encoder_name=None, **kwargs): + r""" + Returns a generator matrix of ``self``. + + INPUT: + + - ``encoder_name`` -- (default: ``None``) name of the encoder which will be + used to compute the generator matrix. The default encoder of ``self`` + will be used if default value is kept. + + - ``kwargs`` -- all additional arguments are forwarded to the construction of the + encoder that is used. + + EXAMPLES:: + + sage: G = matrix(GF(3),2,[1,-1,1,-1,1,1]) + sage: code = LinearCode(G) + sage: code.generator_matrix() + [1 2 1] + [2 1 1] + """ + E = self.encoder(encoder_name, **kwargs) + return E.generator_matrix() + + gen_mat = deprecated_function_alias(17973, generator_matrix) def generator_matrix_systematic(self): """ @@ -1896,11 +2142,13 @@ def __iter__(self): FiniteFieldsubspace_iterator return FiniteFieldsubspace_iterator(self.generator_matrix(), immutable=True) - + @cached_method def information_set(self): """ Return an information set of the code. + Return value of this method is cached. + A set of column positions of a generator matrix of a code is called an information set if the corresponding columns form a square matrix of full rank. @@ -2391,7 +2639,7 @@ def permutation_automorphism_group(self, algorithm="partition"): print "\n Using the %s codewords of weight %s \n Supergroup size: \n %s\n "%(wts[wt],wt,size) gap.eval("Cwt:=Filtered(eltsC,c->WeightCodeword(c)=%s)"%wt) # bottleneck 2 (repeated gap.eval("matCwt:=List(Cwt,c->VectorCodeword(c))") # for each i until stop = 1) - if gap("Length(matCwt)") > 0: + if gap("Length(matCwt)") > 0: A = gap("MatrixAutomorphisms(matCwt)") G2 = gap("Intersection2(%s,%s)"%(str(A).replace("\n",""),str(Gp).replace("\n",""))) # bottleneck 3 Gp = G2 @@ -2873,7 +3121,7 @@ def spectrum(self, algorithm=None): input = code2leon(self) + "::code" import os, subprocess lines = subprocess.check_output([os.path.join(guava_bin_dir, 'wtdist'), input]) - import StringIO # to use the already present output parser + import StringIO # to use the already present output parser wts = [0]*(n+1) s = 0 for L in StringIO.StringIO(lines).readlines(): @@ -3010,6 +3258,42 @@ def syndrome(self, r): """ return self.parity_check_matrix()*r + def unencode(self, c, encoder_name=None, nocheck=False, **kwargs): + r""" + Returns the message corresponding to ``c``. + + This is the inverse of :meth:`encode`. + + INPUT: + + - ``c`` -- a codeword of ``self`` + + - ``encoder_name`` -- (default: ``None``) name of the decoder which will be used + to decode ``word``. The default decoder of ``self`` will be used if + default value is kept. + + - ``nocheck`` -- (default: ``False``) checks if ``c`` is in ``self``. You might set + this to ``True`` to disable the check for saving computation. Note that if ``c`` is + not in ``self`` and ``nocheck = True``, then the output of :meth:`unencode` is + not defined (except that it will be in the message space of ``self``). + + - ``kwargs`` -- all additional arguments are forwarded to the construction of the + encoder that is used. + + OUTPUT: + + - an element of the message space of ``encoder_name`` of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: c = vector(GF(2), (1, 1, 0, 0, 1, 1, 0)) + sage: C.unencode(c) + (0, 1, 1, 0) + """ + E = self.encoder(encoder_name, **kwargs) + return E.unencode(c, nocheck) def weight_enumerator(self, names="xy", name2=None): """ @@ -3341,7 +3625,7 @@ def __init__(self, generator_matrix, d=None): if generator_matrix.nrows() == 0: raise ValueError("this linear code contains no non-zero vector") - super(LinearCode, self).__init__(base_ring, generator_matrix.ncols()) + super(LinearCode, self).__init__(base_ring, generator_matrix.ncols(), "GeneratorMatrix") self._generator_matrix = generator_matrix self._dimension = generator_matrix.rank() self._minimum_distance = d @@ -3360,9 +3644,18 @@ def _repr_(self): """ return "Linear code of length %s, dimension %s over %s"%(self.length(), self.dimension(), self.base_ring()) - def generator_matrix(self): + def generator_matrix(self, encoder_name=None, **kwargs): r""" - Return a generator matrix of this code. + Returns a generator matrix of ``self``. + + INPUT: + + - ``encoder_name`` -- (default: ``None``) name of the encoder which will be + used to compute the generator matrix. ``self._generator_matrix`` + will be returned if default value is kept. + + - ``kwargs`` -- all additional arguments are forwarded to the construction of the + encoder that is used. EXAMPLES:: @@ -3372,6 +3665,80 @@ def generator_matrix(self): [1 2 1] [2 1 1] """ - return self._generator_matrix + if encoder_name is None or encoder_name is 'GeneratorMatrix': + return self._generator_matrix + return super(LinearCode, self).generator_matrix(encoder_name, **kwargs) - gen_mat = deprecated_function_alias(17973, generator_matrix) + + +####################### encoders ############################### +class LinearCodeGeneratorMatrixEncoder(Encoder): + r""" + Encoder based on generator_matrix for Linear codes. + + This is the default encoder of a generic linear code, and should never be used for other codes than + :class:`LinearCode`. + + INPUT: + + - ``code`` -- The associated :class:`LinearCode` of this encoder. + """ + + def __init__(self, code): + r""" + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E + Generator matrix-based encoder for Linear code of length 7, dimension 4 over Finite Field of size 2 + """ + super(LinearCodeGeneratorMatrixEncoder, self).__init__(code) + + def _repr_(self): + r""" + Returns a string representation of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E + Generator matrix-based encoder for Linear code of length 7, dimension 4 over Finite Field of size 2 + """ + return "Generator matrix-based encoder for %s" % self.code() + + def _latex_(self): + r""" + Returns a latex representation of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: latex(E) + \textnormal{Generator matrix-based encoder for }[7, 4]\textnormal{ Linear code over }\Bold{F}_{2} + """ + return "\\textnormal{Generator matrix-based encoder for }%s" % self.code()._latex_() + + @cached_method + def generator_matrix(self): + r""" + Returns a generator matrix of the associated code of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.generator_matrix() + [1 1 1 0 0 0 0] + [1 0 0 1 1 0 0] + [0 1 0 1 0 1 0] + [1 1 0 1 0 0 1] + """ + return self.code().generator_matrix() +LinearCode._registered_encoders["GeneratorMatrix"] = LinearCodeGeneratorMatrixEncoder diff --git a/src/sage/combinat/affine_permutation.py b/src/sage/combinat/affine_permutation.py index 42910e4469d..b9ad2739432 100644 --- a/src/sage/combinat/affine_permutation.py +++ b/src/sage/combinat/affine_permutation.py @@ -778,16 +778,18 @@ def to_lehmer_code(self, typ='decreasing', side='right'): EXAMPLES:: + sage: import itertools sage: A=AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) - sage: CP=CartesianProduct( ('increasing','decreasing'),('left','right') ) - sage: for a in CP: - ....: p.to_lehmer_code(a[0],a[1]) + sage: orders = ('increasing','decreasing') + sage: sides = ('left','right') + sage: for o,s in itertools.product(orders, sides): + ....: p.to_lehmer_code(o,s) [2, 3, 2, 0, 1, 2, 0, 0] [2, 2, 0, 0, 2, 1, 0, 3] [3, 1, 0, 0, 2, 1, 0, 3] [0, 3, 3, 0, 1, 2, 0, 1] - sage: for a in CP: + sage: for a in itertools.product(orders, sides): ....: A.from_lehmer_code(p.to_lehmer_code(a[0],a[1]), a[0],a[1])==p True True @@ -2198,15 +2200,17 @@ def from_lehmer_code(self, C, typ='decreasing', side='right'): EXAMPLES:: + sage: import itertools sage: A=AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.to_lehmer_code() [0, 3, 3, 0, 1, 2, 0, 1] sage: A.from_lehmer_code(p.to_lehmer_code())==p True - sage: CP=CartesianProduct( ('increasing','decreasing'),('left','right') ) - sage: for a in CP: - ....: A.from_lehmer_code(p.to_lehmer_code(a[0],a[1]),a[0],a[1])==p + sage: orders = ('increasing','decreasing') + sage: sides = ('left','right') + sage: for o,s in itertools.product(orders,sides): + ....: A.from_lehmer_code(p.to_lehmer_code(o,s),o,s)==p True True True diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 6edebf986ea..bbfce97acdf 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -36,6 +36,8 @@ #Permutations from permutation import Permutation, Permutations, Arrangements, PermutationOptions, CyclicPermutations, CyclicPermutationsOfPartition from affine_permutation import AffinePermutationGroup +lazy_import('sage.combinat.colored_permutations', ['ColoredPermutations', + 'SignedPermutations']) from derangements import Derangements lazy_import('sage.combinat.baxter_permutations', ['BaxterPermutations']) @@ -46,9 +48,8 @@ #PerfectMatchings from perfect_matching import PerfectMatching, PerfectMatchings -# Integer lists lex - -from integer_list import IntegerListsLex as IntegerListsLex +# Integer lists +from integer_lists import IntegerListsLex #Compositions from composition import Composition, Compositions diff --git a/src/sage/combinat/alternating_sign_matrix.py b/src/sage/combinat/alternating_sign_matrix.py index 930a703e6af..b199b91f966 100644 --- a/src/sage/combinat/alternating_sign_matrix.py +++ b/src/sage/combinat/alternating_sign_matrix.py @@ -104,6 +104,17 @@ def __init__(self, parent, asm): self._matrix = asm Element.__init__(self, parent) + def __hash__(self): + r""" + TESTS:: + + sage: A = AlternatingSignMatrices(3) + sage: elt = A([[1, 0, 0],[0, 1, 0],[0, 0, 1]]) + sage: hash(elt) + 12 + """ + return hash(self._matrix) + def _repr_(self): """ Return a string representation of ``self``. @@ -521,11 +532,30 @@ def to_fully_packed_loop(self): from sage.combinat.fully_packed_loop import FullyPackedLoop return FullyPackedLoop(self) + def link_pattern(self): + """ + Return the link pattern corresponding to the fully packed loop + corresponding to self. + + EXAMPLES: + + We can extract the underlying link pattern (a non-crossing + partition) from a fully packed loop:: + + sage: A = AlternatingSignMatrix([[0, 1, 0], [1, -1, 1], [0, 1, 0]]) + sage: A.link_pattern() + [(1, 2), (3, 6), (4, 5)] + + sage: B = AlternatingSignMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + sage: B.link_pattern() + [(1, 6), (2, 5), (3, 4)] + """ + return self.to_fully_packed_loop().link_pattern() @combinatorial_map(name='gyration') def gyration(self): r""" - Return the alternating sign matrix obtained by applying the gyration + Return the alternating sign matrix obtained by applying gyration to the height function in bijection with ``self``. Gyration acts on height functions as follows. Go through the entries of @@ -535,11 +565,6 @@ def gyration(self): still a height function. Gyration was first defined in [Wieland00]_ as an action on fully-packed loops. - REFERENCES: - - .. [Wieland00] B. Wieland. *A large dihedral symmetry of the set of - alternating sign matrices*. Electron. J. Combin. 7 (2000). - EXAMPLES:: sage: A = AlternatingSignMatrices(3) @@ -557,7 +582,6 @@ def gyration(self): [0 1 0] [0 0 1] [1 0 0] - sage: A = AlternatingSignMatrices(3) sage: A([[1, 0, 0],[0, 1, 0],[0, 0, 1]]).gyration().gyration() [ 0 1 0] @@ -789,25 +813,70 @@ def ASM_compatible_smaller(self): return(output) @combinatorial_map(name='to Dyck word') - def to_dyck_word(self): + def to_dyck_word(self, method): r""" - Return the Dyck word determined by the last diagonal of - the monotone triangle corresponding to ``self``. + Return a Dyck word determined by the specified method. + + The method 'last_diagonal' uses the last diagonal of the monotone + triangle corresponding to ``self``. The method 'link_pattern' returns + the Dyck word in bijection with the link pattern of the fully packed + loop. + + Note that these two methods in general yield different Dyck words for a + given alternating sign matrix. + + INPUT: + + - ``method`` - + + - ``'last_diagonal'`` + - ``'link_pattern'`` EXAMPLES:: sage: A = AlternatingSignMatrices(3) - sage: A([[0,1,0],[1,0,0],[0,0,1]]).to_dyck_word() + sage: A([[0,1,0],[1,0,0],[0,0,1]]).to_dyck_word(method = 'last_diagonal') [1, 1, 0, 0, 1, 0] - sage: d = A([[0,1,0],[1,-1,1],[0,1,0]]).to_dyck_word(); d + sage: d = A([[0,1,0],[1,-1,1],[0,1,0]]).to_dyck_word(method = 'last_diagonal'); d [1, 1, 0, 1, 0, 0] sage: parent(d) Complete Dyck words - """ - MT = self.to_monotone_triangle() - nplus = self._matrix.nrows() + 1 - parkfn = [nplus - row[0] for row in list(MT) if len(row) > 0] - return NonDecreasingParkingFunction(parkfn).to_dyck_word().reverse() + sage: A = AlternatingSignMatrices(3) + sage: asm = A([[0,1,0],[1,0,0],[0,0,1]]) + sage: asm.to_dyck_word(method = 'link_pattern') + [1, 0, 1, 0, 1, 0] + sage: asm = A([[0,1,0],[1,-1,1],[0,1,0]]) + sage: asm.to_dyck_word(method = 'link_pattern') + [1, 0, 1, 1, 0, 0] + sage: A = AlternatingSignMatrices(4) + sage: asm = A([[0,0,1,0],[1,0,0,0],[0,1,-1,1],[0,0,1,0]]) + sage: asm.to_dyck_word(method = 'link_pattern') + [1, 1, 1, 0, 1, 0, 0, 0] + sage: asm.to_dyck_word() + Traceback (most recent call last): + ... + TypeError: to_dyck_word() takes exactly 2 arguments (1 given) + sage: asm.to_dyck_word(method = 'notamethod') + Traceback (most recent call last): + ... + ValueError: unknown method 'notamethod' + """ + if method == 'last_diagonal': + MT = self.to_monotone_triangle() + nplus = self._matrix.nrows() + 1 + parkfn = [nplus - row[0] for row in list(MT) if len(row) > 0] + return NonDecreasingParkingFunction(parkfn).to_dyck_word().reverse() + + elif method == 'link_pattern': + from sage.combinat.perfect_matching import PerfectMatching + from sage.combinat.dyck_word import DyckWords + p = PerfectMatching(self.link_pattern()).to_non_crossing_set_partition() + asm = self.to_matrix() + n = asm.nrows() + d = DyckWords(n) + return d.from_noncrossing_partition(p) + + raise ValueError("unknown method '%s'" % method) def number_negative_ones(self): """ @@ -891,6 +960,8 @@ def to_semistandard_tableau(self): ssyt[i][j] = mt[j][-(i+1)] return SemistandardTableau(ssyt) + + def left_key(self): r""" Return the left key of the alternating sign matrix ``self``. @@ -1101,7 +1172,9 @@ def _element_constructor_(self, asm): raise ValueError("Cannot convert between alternating sign matrices of different sizes") if asm in MonotoneTriangles(self._n): return self.from_monotone_triangle(asm) - return self.element_class(self, self._matrix_space(asm)) + m = self._matrix_space(asm) + m.set_immutable() + return self.element_class(self, m) Element = AlternatingSignMatrix @@ -1147,7 +1220,9 @@ def from_monotone_triangle(self, triangle): asm.append(row) prev = v - return self.element_class(self, self._matrix_space(asm)) + m = self._matrix_space(asm) + m.set_immutable() + return self.element_class(self, m) def from_corner_sum(self, corner): r""" diff --git a/src/sage/combinat/cartesian_product.py b/src/sage/combinat/cartesian_product.py index 125eabd2928..e0f292fc1a4 100644 --- a/src/sage/combinat/cartesian_product.py +++ b/src/sage/combinat/cartesian_product.py @@ -25,15 +25,17 @@ def CartesianProduct(*iters): """ - Returns the combinatorial class of the Cartesian product of - \*iters. + This is deprecated. Use :obj:`cartesian_product` instead. EXAMPLES:: sage: cp = CartesianProduct([1,2], [3,4]); cp - Cartesian product of [1, 2], [3, 4] + doctest:...: DeprecationWarning: CartesianProduct is deprecated. Use + cartesian_product instead + See http://trac.sagemath.org/18411 for details. + The cartesian product of ({1, 2}, {3, 4}) sage: cp.list() - [[1, 3], [1, 4], [2, 3], [2, 4]] + [(1, 3), (1, 4), (2, 3), (2, 4)] Note that you must not use a generator-type object that is returned by a function (using "yield"). They cannot be copied or @@ -48,24 +50,106 @@ def CartesianProduct(*iters): ValueError: generators are not allowed, see the documentation (type "CartesianProduct?") for a workaround - You either create a list of all values or you use - :class:`sage.combinat.misc.IterableFunctionCall` to make a - (copy-able) iterator:: + The usage of iterable is also deprecated, so the following will no longer be + supported:: sage: from sage.combinat.misc import IterableFunctionCall - sage: CartesianProduct(IterableFunctionCall(a, 3), IterableFunctionCall(b)).list() - [[3, 'a'], [3, 'b'], [6, 'a'], [6, 'b']] - - See the documentation for - :class:`~sage.combinat.misc.IterableFunctionCall` for more - information. + sage: C = CartesianProduct(IterableFunctionCall(a, 3), IterableFunctionCall(b)) + doctest:...: DeprecationWarning: Usage of IterableFunctionCall in + CartesianProduct is deprecated. You can use EnumeratedSetFromIterator + (in sage.sets.set_from_iterator) instead. + See http://trac.sagemath.org/18411 for details. + sage: list(C) + doctest:...: UserWarning: Sage is not able to determine whether the + factors of this cartesian product are finite. The lexicographic ordering + might not go through all elements. + [(3, 'a'), (3, 'b'), (6, 'a'), (6, 'b')] + + You might use + :class:`~sage.sets.set_from_iterator.EnumeratedSetFromIterator` for that + purpose.:: + + sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator + sage: A = EnumeratedSetFromIterator(a, (3,), category=FiniteEnumeratedSets()) + sage: B = EnumeratedSetFromIterator(b, category=FiniteEnumeratedSets()) + sage: C = cartesian_product([A, B]) + sage: C.list() + [(3, 'a'), (3, 'b'), (6, 'a'), (6, 'b')] """ if any(isgenerator(i) for i in iters): raise ValueError('generators are not allowed, see the documentation '+ '(type "CartesianProduct?") for a workaround') - return CartesianProduct_iters(*iters) + + from sage.misc.superseded import deprecation + deprecation(18411, "CartesianProduct is deprecated. Use cartesian_product instead") + + from sage.combinat.misc import IterableFunctionCall + from sage.sets.set_from_iterator import EnumeratedSetFromIterator + deprecate_ifc = False + iiters = [] + for a in iters: + if isinstance(a, IterableFunctionCall): + deprecate_ifc = True + iiters.append(EnumeratedSetFromIterator(a.f, a.args, a.kwargs)) + else: + iiters.append(a) + iters = tuple(iiters) + + if deprecate_ifc: + deprecation(18411, """Usage of IterableFunctionCall in CartesianProduct is deprecated. You can use EnumeratedSetFromIterator (in sage.sets.set_from_iterator) instead.""") + + from sage.categories.cartesian_product import cartesian_product + return cartesian_product(iters) class CartesianProduct_iters(CombinatorialClass): + r""" + Cartesian product of finite sets. + + This class will soon be deprecated (see :trac:`18411` and :trac:`19195`). + One should instead use the functorial construction + :class:`cartesian_product `. + The main differences in behavior are: + + - construction: ``CartesianProduct`` takes as many argument as + there are factors whereas ``cartesian_product`` takes a single + list (or iterable) of factors; + + - representation of elements: elements are represented by plain + Python list for ``CartesianProduct`` versus a custom element + class for ``cartesian_product``; + + - membership testing: because of the above, plain Python lists are + not considered as elements of a ``cartesian_product``. + + All of these is illustrated in the examples below. + + EXAMPLES:: + + sage: F1 = ['a', 'b'] + sage: F2 = [1, 2, 3, 4] + sage: F3 = Permutations(3) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: C = CartesianProduct_iters(F1, F2, F3) + sage: c = cartesian_product([F1, F2, F3]) + + sage: type(C.an_element()) + + sage: type(c.an_element()) + + + sage: l = ['a', 1, Permutation([3,2,1])] + sage: l in C + True + sage: l in c + False + sage: elt = c(l) + sage: elt + ('a', 1, [3, 2, 1]) + sage: elt in c + True + sage: elt.parent() is c + True + """ def __init__(self, *iters): """ TESTS:: @@ -84,13 +168,20 @@ def __contains__(self, x): """ EXAMPLES:: - sage: cp = CartesianProduct([1,2],[3,4]) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: cp = CartesianProduct_iters([1,2],[3,4]) sage: [1,3] in cp True sage: [1,2] in cp False sage: [1, 3, 1] in cp False + + Note that it differs with the behavior of cartesian products:: + + sage: cp = cartesian_product([[1,2], [3,4]]) + sage: [1,3] in cp + False """ try: return len(x) == len(self.iters) and all(x[i] in self.iters[i] for i in range(len(self.iters))) @@ -101,7 +192,8 @@ def __repr__(self): """ EXAMPLES:: - sage: CartesianProduct(range(2), range(3)) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters(range(2), range(3)) Cartesian product of [0, 1], [0, 1, 2] """ return "Cartesian product of " + ", ".join(map(str, self.iters)) @@ -113,18 +205,19 @@ def cardinality(self): EXAMPLES:: - sage: CartesianProduct(range(2), range(3)).cardinality() + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters(range(2), range(3)).cardinality() 6 - sage: CartesianProduct(range(2), xrange(3)).cardinality() + sage: CartesianProduct_iters(range(2), xrange(3)).cardinality() 6 - sage: CartesianProduct(range(2), xrange(3), xrange(4)).cardinality() + sage: CartesianProduct_iters(range(2), xrange(3), xrange(4)).cardinality() 24 This works correctly for infinite objects:: - sage: CartesianProduct(ZZ, QQ).cardinality() + sage: CartesianProduct_iters(ZZ, QQ).cardinality() +Infinity - sage: CartesianProduct(ZZ, []).cardinality() + sage: CartesianProduct_iters(ZZ, []).cardinality() 0 """ return self._mrange.cardinality() @@ -145,15 +238,16 @@ def __len__(self): EXAMPLES:: - sage: C = CartesianProduct(xrange(3), xrange(4)) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: C = CartesianProduct_iters(xrange(3), xrange(4)) sage: len(C) 12 - sage: C = CartesianProduct(ZZ, QQ) + sage: C = CartesianProduct_iters(ZZ, QQ) sage: len(C) Traceback (most recent call last): ... TypeError: cardinality does not fit into a Python int. - sage: C = CartesianProduct(ZZ, []) + sage: C = CartesianProduct_iters(ZZ, []) sage: len(C) 0 """ @@ -165,9 +259,10 @@ def list(self): EXAMPLES:: - sage: CartesianProduct(range(3), range(3)).list() + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters(range(3), range(3)).list() [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]] - sage: CartesianProduct('dog', 'cat').list() + sage: CartesianProduct_iters('dog', 'cat').list() [['d', 'c'], ['d', 'a'], ['d', 't'], @@ -191,9 +286,10 @@ def __iter__(self): EXAMPLES:: - sage: [e for e in CartesianProduct(range(3), range(3))] + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: [e for e in CartesianProduct_iters(range(3), range(3))] [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]] - sage: [e for e in CartesianProduct('dog', 'cat')] + sage: [e for e in CartesianProduct_iters('dog', 'cat')] [['d', 'c'], ['d', 'a'], ['d', 't'], @@ -213,9 +309,10 @@ def is_finite(self): EXAMPLES:: - sage: CartesianProduct(ZZ, []).is_finite() + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters(ZZ, []).is_finite() True - sage: CartesianProduct(4,4).is_finite() + sage: CartesianProduct_iters(4,4).is_finite() Traceback (most recent call last): ... ValueError: Unable to determine whether this product is finite @@ -237,14 +334,15 @@ def unrank(self, x): EXAMPLES:: - sage: C = CartesianProduct(xrange(1000), xrange(1000), xrange(1000)) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: C = CartesianProduct_iters(xrange(1000), xrange(1000), xrange(1000)) sage: C[238792368] [238, 792, 368] Check for :trac:`15919`:: sage: FF = IntegerModRing(29) - sage: C = CartesianProduct(FF, FF, FF) + sage: C = CartesianProduct_iters(FF, FF, FF) sage: C.unrank(0) [0, 0, 0] """ @@ -271,7 +369,8 @@ def random_element(self): EXAMPLES:: - sage: CartesianProduct('dog', 'cat').random_element() + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters('dog', 'cat').random_element() ['d', 'a'] """ return [rnd.choice(_) for _ in self.iters] diff --git a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py index d019d916ccf..fff2fcb9fcf 100644 --- a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py +++ b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py @@ -44,7 +44,8 @@ from sage.rings.all import FractionField, PolynomialRing from sage.rings.fraction_field_element import FractionFieldElement from sage.sets.all import Set -from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import QuiverMutationType_Irreducible, QuiverMutationType_Reducible +from sage.graphs.digraph import DiGraph +from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import QuiverMutationType_Irreducible, QuiverMutationType_Reducible from sage.combinat.cluster_algebra_quiver.mutation_type import is_mutation_finite from sage.misc.misc import exists from random import randint @@ -212,6 +213,7 @@ def __init__(self, data, frozen=None, is_principal=False, user_labels=None, user # Copy the following attributes from data self._M = copy( data._M ) + self._M.set_immutable() self._B = copy( data._B ) self._n = data._n self._m = data._m @@ -268,6 +270,7 @@ def __init__(self, data, frozen=None, is_principal=False, user_labels=None, user quiver = ClusterQuiver( data ) self._M = copy(quiver._M) # B-tilde exchange matrix + self._M.set_immutable() self._n = quiver._n self._m = quiver._m self._B = copy(self._M[:self._n,:self._n]) # Square Part of the B_matrix @@ -284,7 +287,7 @@ def __init__(self, data, frozen=None, is_principal=False, user_labels=None, user self._user_labels_prefix = user_labels_prefix # initialize the rest - + self._C = matrix.identity(self._n) self._use_c_vec = True @@ -299,7 +302,7 @@ def __init__(self, data, frozen=None, is_principal=False, user_labels=None, user self._mut_path = [ ] self._track_mut = True - + if user_labels: self._sanitize_init_vars(user_labels, user_labels_prefix) else: @@ -396,6 +399,7 @@ def use_c_vectors(self, use=True, bot_is_c=False, force=False): self._C = copy(self._M[self._m:(self._n+self._m),:self._n]) self._BC = copy(self._M) self._M = self._M[:self._m:self._n] + self._M.set_immutable() self._bot_is_c = False def use_g_vectors(self, use=True, force=False): @@ -617,7 +621,7 @@ def use_fpolys(self, use=True, user_labels=None, user_labels_prefix=None): self._yhat = dict([ (self._U.gen(j),prod([self._R.gen(i)**self._M[i,j] for i in xrange(self._n+self._m)])) for j in xrange(self._n)]) elif self._cluster: raise ValueError("should not be possible to have cluster variables without f-polynomials") # added this as a sanity check. This error should never appear however. - elif self._track_mut == True: #If we can navigate from the root to where we are + elif self._track_mut == True: # If we can navigate from the root to where we are if not self._use_g_vec: self.use_g_vectors(True) catchup = ClusterSeed(self._b_initial, user_labels=user_labels, user_labels_prefix=user_labels_prefix) @@ -711,20 +715,20 @@ def track_mutations(self, use=True): def _sanitize_init_vars(self, user_labels, user_labels_prefix = 'x'): r""" Warning: This is an internal method that rewrites a user-given set of cluster variable names into a format that Sage can utilize. - + INPUT: - + - ``user_labels`` -- The labels that need sanitizing - ``user_labels_prefix`` -- (default:'x') The prefix to use for labels if integers given for labels - + EXAMPLES:: - + sage: S = ClusterSeed(['A',4]); S._init_vars {0: 'x0', 1: 'x1', 2: 'x2', 3: 'x3', 4: 'y0', 5: 'y1', 6: 'y2', 7: 'y3'} sage: S._sanitize_init_vars([1,2,3,4],'z') sage: S._init_vars {0: 'z1', 1: 'z2', 2: 'z3', 3: 'z4'} - + sage: S = ClusterSeed(['A',4]); S._init_vars {0: 'x0', 1: 'x1', 2: 'x2', 3: 'x3', 4: 'y0', 5: 'y1', 6: 'y2', 7: 'y3'} sage: S._sanitize_init_vars(['a', 'b', 'c', 'd']) @@ -827,13 +831,13 @@ def __eq__(self, other): True sage: S.mutate([0,1,0,1,0]) - sage: S.__eq__( T ) + sage: S.__eq__( T ) False sage: S.cluster() [x1, x0] sage: T.cluster() [x0, x1] - + sage: S.mutate([0,1,0,1,0]) sage: S.__eq__( T ) True @@ -857,6 +861,27 @@ def __eq__(self, other): d_vec = self.d_matrix() == other.d_matrix() return g_vec and c_vec and d_vec and clusters and ExMat + def __hash__(self): + """ + Return a hash of ``self``. + + EXAMPLES:: + + sage: Q = ClusterSeed(['A',5]) + sage: hash(Q) # indirect doctest + -5649412990944896369 # 64-bit + 222337679 # 32-bit + """ + # mat_hash = self._M.__hash__() + if self._use_fpolys: + return tuple(self.cluster()).__hash__() + elif self._use_g_vec: + return self.g_matrix().__hash__() + elif self._use_c_vec: + return self.c_matrix().__hash__() + elif self._use_d_vec: + return self.d_matrix().__hash__() + def _repr_(self): r""" Returns the description of ``self``. @@ -1076,7 +1101,7 @@ def b_matrix(self): [ 0 0 0 1] [ 0 0 -2 0] """ - return copy( self._M ) + return copy(self._M) def ground_field(self): r""" @@ -1182,18 +1207,18 @@ def mutations(self): sage: S.mutate([0,1,0,2]) sage: S.mutations() [0, 1, 0, 2] - + sage: S.track_mutations(False) sage: S.mutations() Traceback (most recent call last): ... - ValueError: Not recording mutation sequence. Need to track mutations. + ValueError: Not recording mutation sequence. Need to track mutations. """ if self._track_mut: - return copy(self._mut_path) + return copy(self._mut_path) else: - raise ValueError("Not recording mutation sequence. Need to track mutations.") - + raise ValueError("Not recording mutation sequence. Need to track mutations.") + def cluster_variable(self, k): r""" Generates a cluster variable using F-polynomials @@ -1224,7 +1249,7 @@ def cluster_variable(self, k): else: raise ValueError('No cluster variable with index or label ' + str(k) + '.') elif self._track_mut: # if we can recreate the clusters - catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) + catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) catchup.use_c_vectors(use=self._use_c_vec, bot_is_c=self._bot_is_c) catchup.mutate(self.mutations()) return catchup.cluster_variable(k) @@ -1257,7 +1282,7 @@ def cluster(self): if not self._use_fpolys: if self._track_mut: # if we can recreate the clusters - catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) + catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) catchup.use_c_vectors(use=self._use_c_vec, bot_is_c=self._bot_is_c) catchup.mutate(self.mutations()) return catchup.cluster() @@ -1344,7 +1369,7 @@ def f_polynomial(self,k): return self._F[IE[k]] elif self._track_mut: - catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) + catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) catchup.use_c_vectors(use=self._use_c_vec, bot_is_c=self._bot_is_c) catchup.mutate(self.mutations()) @@ -1514,7 +1539,7 @@ def _g_mutate(self, k): J[j,k] += max(0, -eps*B[j,k]) J[k,k] = -1 self._G = self._G*J - + def c_vector(self,k): r""" Returns the ``k``-th *c-vector* of ``self``. It is obtained as the @@ -1630,7 +1655,7 @@ def d_vector(self, k): catchup.use_fpolys(False) catchup.use_g_vectors(False) catchup.use_c_vectors(False) - + catchup.mutate(self.mutations()) return copy(catchup._D).column(k) else: @@ -1639,7 +1664,7 @@ def d_vector(self, k): def d_matrix(self, show_warnings=True): r""" Returns the matrix of *d-vectors* of ``self``. - + EXAMPLES:: sage: S = ClusterSeed(['A',4]); S.d_matrix() [-1 0 0 0] @@ -1667,7 +1692,7 @@ def d_matrix(self, show_warnings=True): catchup.use_g_vectors(False) catchup.use_c_vectors(False) catchup.track_mutations(False) - + catchup.mutate(self.mutations()) return catchup.d_matrix() elif show_warnings: @@ -2300,7 +2325,7 @@ def mutate(self, sequence, inplace=True): # function should return either integer or sequence sequence = sequence(seed) - + if sequence is None: raise ValueError('Not mutating: No vertices given.') @@ -2311,17 +2336,17 @@ def mutate(self, sequence, inplace=True): n, m = seed.n(), seed.m() V = range(n)+IE - + if seed._use_fpolys and isinstance(sequence, str): sequence = seed.cluster_index(sequence) if sequence is None: raise ValueError("Variable provided is not in our cluster") - + if (sequence in xrange(n)) or (sequence in IE): seqq = [sequence] else: seqq = sequence - + if isinstance(seqq, tuple): @@ -2335,7 +2360,7 @@ def mutate(self, sequence, inplace=True): #raise ValueError('The quiver cannot be mutated at the vertex ' + str( v )) seq = iter(seqq) - + for k in seq: if k in xrange(n): @@ -2358,6 +2383,7 @@ def mutate(self, sequence, inplace=True): seed._BC.mutate(k) seed._M = copy(seed._BC[:n+m,:n]) + self._M.set_immutable() if seed._use_c_vec: seed._C = seed._BC[n+m:2*n+m,:n+m] @@ -2377,21 +2403,21 @@ def mutate(self, sequence, inplace=True): if not inplace: return seed - + def cluster_index(self, cluster_str): r""" Returns the index of a cluster if use_fpolys is on INPUT: - + - ``cluster_str`` -- The string to look for in the cluster - + OUTPUT: - + Returns an integer or None if the string is not a cluster variable - + EXAMPLES:: - + sage: S = ClusterSeed(['A',4],user_labels=['x','y','z','w']); S.mutate('x') sage: S.cluster_index('x') sage: S.cluster_index('(y+1)/x') @@ -2403,7 +2429,7 @@ def cluster_index(self, cluster_str): cluster_str = ClusterVariable( FractionField(self._R), c.numerator(), c.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable',xdim=self._n ) if cluster_str in self.cluster(): return self.cluster().index(cluster_str) - + return None def mutation_sequence(self, sequence, show_sequence=False, fig_size=1.2,return_output='seed'): @@ -2720,7 +2746,7 @@ def exchangeable_part(self): from sage.combinat.cluster_algebra_quiver.mutation_class import _principal_part eval_dict = dict( [ ( self.y(i), 1 ) for i in xrange(self._m) ] ) - seed = ClusterSeed( _principal_part( self._M ), is_principal = True, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) + seed = ClusterSeed( _principal_part( self._M ), is_principal = True, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) seed.use_c_vectors(self._use_c_vec) seed.use_fpolys(self._use_fpolys) seed.use_g_vectors(self._use_g_vec) @@ -2803,7 +2829,8 @@ def universal_extension(self): A = 2 - self.b_matrix().apply_map(abs).transpose() - rs = CartanMatrix(A).root_space() + # We give the indexing set of the Cartan matrix to be [1, 2, ..., n] + rs = CartanMatrix(A, index_set=range(1,A.ncols()+1)).root_space() almost_positive_coroots = rs.almost_positive_roots() sign = [-1 if all(x <= 0 for x in self.b_matrix()[i]) else 1 @@ -2812,7 +2839,7 @@ def universal_extension(self): for alpha in almost_positive_coroots]) M = self._M.stack(C) - seed = ClusterSeed(M, is_principal = False, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) + seed = ClusterSeed(M, is_principal = False, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) seed.use_c_vectors(self._use_c_vec) seed.use_fpolys(self._use_fpolys) seed.use_g_vectors(self._use_g_vec) @@ -2846,7 +2873,7 @@ def principal_extension(self): [ 0 0 1 0 0] [ 0 0 0 1 0] [ 0 0 0 0 1] - + sage: S = ClusterSeed(['A',4],user_labels=['a','b','c','d']) sage: T= S.principal_extension() sage: T.cluster() @@ -2870,7 +2897,7 @@ def principal_extension(self): self._user_labels = self._user_labels + ['y%s'%i for i in xrange(self._n)] elif isinstance(self._user_labels,dict): self._user_labels.update( {(i+self._n):'y%s'%i for i in xrange(self._n)} ) - seed = ClusterSeed(M, is_principal = is_principal, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) + seed = ClusterSeed(M, is_principal = is_principal, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) seed.use_c_vectors(self._use_c_vec) seed.use_fpolys(self._use_fpolys) seed.use_g_vectors(self._use_g_vec) @@ -2948,7 +2975,7 @@ def set_cluster( self, cluster, force=False ): sage: S.set_cluster(cluster2, force=True) sage: S.cluster() [x0, (x1 + 1)/x2, (x0*x2 + x1 + 1)/(x1*x2)] - + sage: S = ClusterSeed(['A',3]); S.use_fpolys(False) sage: S.set_cluster([1,1,1]) Warning: clusters not being tracked so this command is ignored. @@ -2965,7 +2992,7 @@ def set_cluster( self, cluster, force=False ): self._cluster = [ FractionField(self._R)(x) for x in cluster ][0:self._n] self._is_principal = None else: - print("Warning: clusters not being tracked so this command is ignored.") + print("Warning: clusters not being tracked so this command is ignored.") def reset_cluster( self ): r""" @@ -2992,12 +3019,12 @@ def reset_cluster( self ): sage: T.reset_cluster() sage: T.cluster() [x0, x1, x2] - + sage: S = ClusterSeed(['B',3],user_labels=[[1,2],[2,3],[3,4]],user_labels_prefix='p') sage: S.mutate([0,1]) sage: S.cluster() [(p_2_3 + 1)/p_1_2, (p_1_2*p_3_4^2 + p_2_3 + 1)/(p_1_2*p_2_3), p_3_4] - + sage: S.reset_cluster() sage: S.cluster() [p_1_2, p_2_3, p_3_4] @@ -3014,7 +3041,7 @@ def reset_cluster( self ): self._F = dict([(i,self._U(1)) for i in self._init_exch.values()]) if self._use_fpolys: self.set_cluster(self._R.gens(), force=True) - + def reset_coefficients( self ): r""" Resets the coefficients of ``self`` to the frozen variables but keeps the current cluster. @@ -3049,15 +3076,19 @@ def reset_coefficients( self ): [ 0 1 0] [ 0 0 1] """ - n,m = self._n, self._m + n, m = self._n, self._m if not n == m: - raise ValueError("The numbers of cluster variables and of frozen variables do not coincide.") + raise ValueError("The numbers of cluster variables " + "and of frozen variables do not coincide.") + newM = copy(self._M) for i in xrange(m): for j in xrange(n): if i == j: - self._M[i+n,j] = 1 + newM[i + n, j] = 1 else: - self._M[i+n,j] = 0 + newM[i + n, j] = 0 + self._M = newM + self._M.set_immutable() self._quiver = None self._is_principal = None @@ -3731,7 +3762,7 @@ def variable_class(self, depth=infinity, ignore_bipartite_belt=False): var_iter = self.variable_class_iter( depth=depth, ignore_bipartite_belt=ignore_bipartite_belt ) return sorted(var_iter) - def is_finite( self ): + def is_finite(self): r""" Returns True if ``self`` is of finite type. @@ -3929,6 +3960,70 @@ def greedy(self, a1, a2, method='by_recursion'): raise ValueError("Greedy elements are only currently " "defined for cluster seeds of rank two.") + def oriented_exchange_graph(self): + """ + Return the oriented exchange graph of ``self`` as a directed + graph. + + The seed must be a cluster seed for a cluster algebra of + finite type with principal coefficients (the corresponding + quiver must have mutable vertices 0,1,...,n-1). + + EXAMPLES:: + + sage: S = ClusterSeed(['A', 2]).principal_extension() + sage: G = S.oriented_exchange_graph(); G + Digraph on 5 vertices + sage: G.out_degree_sequence() + [2, 1, 1, 1, 0] + + sage: S = ClusterSeed(['B', 2]).principal_extension() + sage: G = S.oriented_exchange_graph(); G + Digraph on 6 vertices + sage: G.out_degree_sequence() + [2, 1, 1, 1, 1, 0] + + TESTS:: + + sage: S = ClusterSeed(['A',[2,2],1]) + sage: S.oriented_exchange_graph() + Traceback (most recent call last): + ... + TypeError: only works for finite mutation type + + sage: S = ClusterSeed(['A', 2]) + sage: S.oriented_exchange_graph() + Traceback (most recent call last): + ... + TypeError: only works for principal coefficients + """ + if not self._mutation_type.is_finite(): + raise TypeError('only works for finite mutation type') + + if not self._is_principal: + raise TypeError('only works for principal coefficients') + + covers = [] + n = self.n() + stack = [self] + known_clusters = [] + while stack: + i = stack.pop() + Vari = tuple(sorted(i.cluster())) + B = i.b_matrix() + for k in range(n): + # check if green + if all(B[i2][k] >= 0 for i2 in range(n, 2 * n)): + j = i.mutate(k, inplace=False) + Varj = tuple(sorted(j.cluster())) + covers.append((Vari, Varj)) + if not(Varj in known_clusters): + known_clusters += [Varj] + stack.append(j) + + return DiGraph(covers) + + def _bino(n, k): """ Binomial coefficient which we define as zero for negative n. @@ -4099,9 +4194,9 @@ def get_green_vertices(C): INPUT: - ``C`` -- The C matrix to check - + EXAMPLES:: - + sage: from sage.combinat.cluster_algebra_quiver.cluster_seed import get_green_vertices sage: S = ClusterSeed(['A',4]); S.mutate([1,2,3,2,0,1,2,0,3]) sage: get_green_vertices(S.c_matrix()) @@ -4219,4 +4314,3 @@ def almost_positive_root( self ): return sum( [ root[i]*Phiplus[ i+1 ] for i in range(self._n) ] ) else: raise ValueError('The cluster algebra for %s is not of finite type.'%self._repr_()) - diff --git a/src/sage/combinat/cluster_algebra_quiver/quiver.py b/src/sage/combinat/cluster_algebra_quiver/quiver.py index be2cbbba0d6..3df226d2e61 100644 --- a/src/sage/combinat/cluster_algebra_quiver/quiver.py +++ b/src/sage/combinat/cluster_algebra_quiver/quiver.py @@ -1,12 +1,27 @@ r""" Quiver -A *quiver* is an oriented graphs without loops, two-cycles, or multiple edges. The edges are labelled by pairs `(i,-j)` -such that the matrix `M = (m_{ab})` with `m_{ab} = i, m_{ba} = -j` for an edge `(i,-j)` between vertices `a` and `b` is skew-symmetrizable. +A *quiver* is an oriented graph without loops, two-cycles, or multiple +edges. The edges are labelled by pairs `(i,-j)` (with `i` and `j` being +positive integers) such that the matrix `M = (m_{ab})` with +`m_{ab} = i, m_{ba} = -j` for an edge `(i,-j)` between vertices +`a` and `b` is skew-symmetrizable. -For the compendium on the cluster algebra and quiver package see +.. WARNING: - http://arxiv.org/abs/1102.4844. + This is not the standard definition of a quiver. Normally, in + cluster algebra theory, a quiver is defined as an oriented graph + without loops and two-cycles but with multiple edges allowed; the + edges are unlabelled. This notion of quivers, however, can be seen + as a particular case of our notion of quivers. Namely, if we have + a quiver (in the regular sense of this word) with (precisely) + `i` edges from `a` to `b`, then we represent it by a quiver + (in our sense of this word) with an edge from `a` to `b` labelled + by the pair `(i,-i)`. + +For the compendium on the cluster algebra and quiver package see :: + + http://arxiv.org/abs/1102.4844. AUTHORS: @@ -31,7 +46,6 @@ from sage.combinat.cluster_algebra_quiver.mutation_class import _principal_part, _digraph_mutate, _matrix_to_digraph, _dg_canonical_form, _mutation_class_iter, _digraph_to_dig6, _dig6_to_matrix from sage.combinat.cluster_algebra_quiver.mutation_type import _connected_mutation_type, _mutation_type_from_data, is_mutation_finite -from sage.groups.perm_gps.permgroup import PermutationGroup class ClusterQuiver(SageObject): """ @@ -251,6 +265,7 @@ def __init__( self, data, frozen=None ): print 'The input data is a quiver, therefore the additional parameter frozen is ignored.' self._M = copy(data._M) + self._M.set_immutable() self._n = data._n self._m = data._m self._digraph = copy( data._digraph ) @@ -266,6 +281,7 @@ def __init__( self, data, frozen=None ): print 'The input data is a matrix, therefore the additional parameter frozen is ignored.' self._M = copy(data).sparse_matrix() + self._M.set_immutable() self._n = n = self._M.ncols() self._m = m = self._M.nrows() - self._n self._digraph = _matrix_to_digraph( self._M ) @@ -330,6 +346,7 @@ def __init__( self, data, frozen=None ): self._digraph = dg self._vertex_dictionary = {} self._M = M + self._M.set_immutable() if n+m == 0: self._description = 'Quiver without vertices' elif n+m == 1: @@ -364,6 +381,18 @@ def __eq__(self, other): """ return isinstance(other, ClusterQuiver) and self._M == other._M + def __hash__(self): + """ + Return a hash of ``self``. + + EXAMPLES:: + + sage: Q = ClusterQuiver(['A',5]) + sage: hash(Q) # indirect doctest + 16 + """ + return self._M.__hash__() + def _repr_(self): """ Returns the description of ``self``. @@ -701,7 +730,7 @@ def b_matrix(self): [ 0 0 0 1] [ 0 0 -2 0] """ - return copy( self._M ) + return copy(self._M) def digraph(self): """ @@ -942,7 +971,8 @@ def m(self): def canonical_label( self, certify=False ): """ - Returns the canonical labelling of ``self``, see sage.graphs.graph.GenericGraph.canonical_label. + Returns the canonical labelling of ``self``, see + :meth:`sage.graphs.graph.GenericGraph.canonical_label`. INPUT: @@ -1277,6 +1307,7 @@ def mutate(self, data, inplace=True): M = _edge_list_to_matrix( dg.edge_iterator(), n, m ) if inplace: self._M = M + self._M.set_immutable() self._digraph = dg else: Q = ClusterQuiver( M ) @@ -1387,6 +1418,7 @@ def reorient( self, data ): dg_new.add_edge( edge[1],edge[0],edge[2] ) self._digraph = dg_new self._M = _edge_list_to_matrix( dg_new.edges(), self._n, self._m ) + self._M.set_immutable() self._mutation_type = None elif all( type(edge) in [list,tuple] and len(edge)==2 for edge in data ): edges = self._digraph.edges(labels=False) @@ -1396,6 +1428,7 @@ def reorient( self, data ): self._digraph.delete_edge(edge[1],edge[0]) self._digraph.add_edge(edge[0],edge[1],label) self._M = _edge_list_to_matrix( self._digraph.edges(), self._n, self._m ) + self._M.set_immutable() self._mutation_type = None else: raise ValueError('The order is no total order on the vertices of the quiver or a list of edges to be oriented.') @@ -1777,3 +1810,100 @@ def relabel(self, relabelling, inplace=True): quiver._vertex_dictionary = relabelling return quiver + def d_vector_fan(self): + r""" + Return the d-vector fan associated with the quiver. + + It is the fan whose maximal cones are generated by the + d-matrices of the clusters. + + This is a complete simplicial fan (and even smooth when the + initial quiver is acyclic). It only makes sense for quivers of + finite type. + + EXAMPLES:: + + sage: Fd = ClusterQuiver([[1,2]]).d_vector_fan(); Fd + Rational polyhedral fan in 2-d lattice N + sage: Fd.ngenerating_cones() + 5 + + sage: Fd = ClusterQuiver([[1,2],[2,3]]).d_vector_fan(); Fd + Rational polyhedral fan in 3-d lattice N + sage: Fd.ngenerating_cones() + 14 + sage: Fd.is_smooth() + True + + sage: Fd = ClusterQuiver([[1,2],[2,3],[3,1]]).d_vector_fan(); Fd + Rational polyhedral fan in 3-d lattice N + sage: Fd.ngenerating_cones() + 14 + sage: Fd.is_smooth() + False + + TESTS:: + + sage: ClusterQuiver(['A',[2,2],1]).d_vector_fan() + Traceback (most recent call last): + ... + ValueError: only makes sense for quivers of finite type + """ + from cluster_seed import ClusterSeed + from sage.geometry.fan import Fan + from sage.geometry.cone import Cone + + if not(self.is_finite()): + raise ValueError('only makes sense for quivers of finite type') + seed = ClusterSeed(self) + return Fan([Cone(s.d_matrix().columns()) + for s in seed.mutation_class()]) + + def g_vector_fan(self): + r""" + Return the g-vector fan associated with the quiver. + + It is the fan whose maximal cones are generated by the + g-matrices of the clusters. + + This is a complete simplicial fan. It is only supported for + quivers of finite type. + + EXAMPLES:: + + sage: Fg = ClusterQuiver([[1,2]]).g_vector_fan(); Fg + Rational polyhedral fan in 2-d lattice N + sage: Fg.ngenerating_cones() + 5 + + sage: Fg = ClusterQuiver([[1,2],[2,3]]).g_vector_fan(); Fg + Rational polyhedral fan in 3-d lattice N + sage: Fg.ngenerating_cones() + 14 + sage: Fg.is_smooth() + True + + sage: Fg = ClusterQuiver([[1,2],[2,3],[3,1]]).g_vector_fan(); Fg + Rational polyhedral fan in 3-d lattice N + sage: Fg.ngenerating_cones() + 14 + sage: Fg.is_smooth() + True + + TESTS:: + + sage: ClusterQuiver(['A',[2,2],1]).g_vector_fan() + Traceback (most recent call last): + ... + ValueError: only supported for quivers of finite type + """ + from cluster_seed import ClusterSeed + from sage.geometry.fan import Fan + from sage.geometry.cone import Cone + + if not(self.is_finite()): + raise ValueError('only supported for quivers of finite type') + seed = ClusterSeed(self).principal_extension() + return Fan([Cone(s.g_matrix().columns()) + for s in seed.mutation_class()]) + diff --git a/src/sage/combinat/colored_permutations.py b/src/sage/combinat/colored_permutations.py new file mode 100644 index 00000000000..399d26b67ba --- /dev/null +++ b/src/sage/combinat/colored_permutations.py @@ -0,0 +1,1108 @@ +r""" +Colored Permutations + +.. TODO:: + + Much of the colored permutations (and element) class can be + generalized to `G \wr S_n` +""" +import itertools + +from sage.categories.groups import Groups +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.categories.finite_coxeter_groups import FiniteCoxeterGroups +from sage.structure.element import MultiplicativeGroupElement +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod + +from sage.combinat.permutation import Permutations +from sage.matrix.constructor import diagonal_matrix +from sage.rings.finite_rings.integer_mod_ring import IntegerModRing +from sage.rings.number_field.number_field import CyclotomicField +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.all import ZZ + + +class ColoredPermutation(MultiplicativeGroupElement): + """ + A colored permutation. + """ + def __init__(self, parent, colors, perm): + """ + Initialize ``self``. + + TESTS:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: TestSuite(s1*s2*t).run() + """ + self._colors = tuple(colors) + self._perm = perm + MultiplicativeGroupElement.__init__(self, parent=parent) + + def __hash__(self): + r""" + TESTS:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: hash(s1), hash(s2), hash(t) + (2666658751600856334, 3639282354432100950, 3639281107336048003) # 64-bit + (-1973744370, 88459862, -1467077245) # 32-bit + """ + return hash(self._perm) ^ hash(self._colors) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: s1*s2*t + [[1, 0, 0], [3, 1, 2]] + """ + return repr([list(self._colors), self._perm]) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: latex(s1*s2*t) + [3_{1}, 1_{0}, 2_{0}] + """ + ret = "[" + ret += ", ".join("{}_{{{}}}".format(x, self._colors[i]) + for i, x in enumerate(self._perm)) + return ret + "]" + + def _mul_(self, other): + """ + Multiply ``self`` and ``other``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: s1*s2*s1 == s2*s1*s2 + True + """ + colors = tuple(self._colors[i] + other._colors[val - 1] # -1 for indexing + for i, val in enumerate(self._perm)) + p = self._perm._left_to_right_multiply_on_right(other._perm) + return self.__class__(self.parent(), colors, p) + + def inverse(self): + """ + Return the inverse of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: ~t + [[0, 0, 3], [1, 2, 3]] + sage: all(x * ~x == C.one() for x in C.gens()) + True + """ + ip = ~self._perm + return self.__class__(self.parent(), + tuple([-self._colors[i - 1] for i in ip]), # -1 for indexing + ip) + + __invert__ = inverse + + def __eq__(self, other): + """ + Check equality. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: s1*s2*s1 == s2*s1*s2 + True + sage: t^4 == C.one() + True + sage: s1*s2 == s2*s1 + False + """ + if not isinstance(other, ColoredPermutation): + return False + return (self.parent() is other.parent() + and self._colors == other._colors + and self._perm == other._perm) + + def __ne__(self, other): + """ + Check inequality. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: s1*s2*s1 != s2*s1*s2 + False + sage: s1*s2 != s2*s1 + True + """ + return not self.__eq__(other) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t + sage: list(x) + [(1, 3), (0, 1), (0, 2)] + """ + for i, p in enumerate(self._perm): + yield (self._colors[i], p) + + def one_line_form(self): + """ + Return the one line form of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t + sage: x + [[1, 0, 0], [3, 1, 2]] + sage: x.one_line_form() + [(1, 3), (0, 1), (0, 2)] + """ + return list(self) + + def colors(self): + """ + Return the colors of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t + sage: x.colors() + [1, 0, 0] + """ + return list(self._colors) + + def permutation(self): + """ + Return the permutation of ``self``. + + This is obtained by forgetting the colors. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t + sage: x.permutation() + [3, 1, 2] + """ + return self._perm + + def to_matrix(self): + """ + Return a matrix of ``self``. + + The colors are mapped to roots of unity. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t*s2; x.one_line_form() + [(1, 2), (0, 1), (0, 3)] + sage: M = x.to_matrix(); M + [ 0 1 0] + [zeta4 0 0] + [ 0 0 1] + + The matrix multiplication is in the *opposite* order:: + + sage: M == s2.to_matrix()*t.to_matrix()*s2.to_matrix()*s1.to_matrix() + True + """ + Cp = CyclotomicField(self.parent()._m) + g = Cp.gen() + D = diagonal_matrix(Cp, [g ** i for i in self._colors]) + return self._perm.to_matrix() * D + + +# TODO: Parts of this should be put in the category of complex +# reflection groups +class ColoredPermutations(Parent, UniqueRepresentation): + r""" + The group of `m`-colored permutations on `\{1, 2, \ldots, n\}`. + + Let `S_n` be the symmetric group on `n` letters and `C_m` be the cyclic + group of order `m`. The `m`-colored permutation group on `n` letters + is given by `P_n^m = C_m \wr S_n`. This is also the complex reflection + group `G(m, 1, n)`. + + We define our multiplication by + + .. MATH:: + + ((s_1, \ldots s_n), \sigma) \cdot ((t_1, \ldots, t_n), \tau) + = ((s_1 t_{\sigma(1)}, \ldots, s_n t_{\sigma(n)}), \tau \sigma). + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3); C + 4-colored permutations of size 3 + sage: s1,s2,t = C.gens() + sage: (s1, s2, t) + ([[0, 0, 0], [2, 1, 3]], [[0, 0, 0], [1, 3, 2]], [[0, 0, 1], [1, 2, 3]]) + sage: s1*s2 + [[0, 0, 0], [3, 1, 2]] + sage: s1*s2*s1 == s2*s1*s2 + True + sage: t^4 == C.one() + True + sage: s2*t*s2 + [[0, 1, 0], [1, 2, 3]] + + We can also create a colored permutation by passing + either a list of tuples consisting of ``(color, element)``:: + + sage: x = C([(2,1), (3,3), (3,2)]); x + [[2, 3, 3], [1, 3, 2]] + + or a list of colors and a permutation:: + + sage: C([[3,3,1], [1,3,2]]) + [[3, 3, 1], [1, 3, 2]] + + There is also the natural lift from permutations:: + + sage: P = Permutations(3) + sage: C(P.an_element()) + [[0, 0, 0], [3, 1, 2]] + + REFERENCES: + + - :wikipedia:`Generalized_symmetric_group` + - :wikipedia:`Complex_reflection_group` + """ + def __init__(self, m, n, category=None): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: TestSuite(C).run() + sage: C = ColoredPermutations(2, 3) + sage: TestSuite(C).run() + sage: C = ColoredPermutations(1, 3) + sage: TestSuite(C).run() + """ + if m <= 0: + raise ValueError("m must be a positive integer") + self._m = m + self._n = n + self._C = IntegerModRing(self._m) + self._P = Permutations(self._n) + if category is None: + category = (Groups(), FiniteEnumeratedSets()) + Parent.__init__(self, category=category) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: ColoredPermutations(4, 3) + 4-colored permutations of size 3 + """ + return "{}-colored permutations of size {}".format(self._m, self._n) + + @cached_method + def one(self): + """ + Return the identity element of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.one() + [[0, 0, 0], [1, 2, 3]] + """ + return self.element_class(self, [self._C.zero()] * self._n, + self._P.identity()) + + @cached_method + def gens(self): + """ + Return the generators of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.gens() + ([[0, 0, 0], [2, 1, 3]], + [[0, 0, 0], [1, 3, 2]], + [[0, 0, 1], [1, 2, 3]]) + """ + zero = [self._C.zero()] * self._n + g = [] + for i in range(self._n - 1): + p = range(1, self._n + 1) + p[i] = i + 2 + p[i + 1] = i + 1 + g.append(self.element_class(self, zero, self._P(p))) + zero[-1] = self._C.one() + g.append(self.element_class(self, zero, self._P.identity())) + return tuple(g) + + def matrix_group(self): + """ + Return the matrix group corresponding to ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.matrix_group() + Matrix group over Cyclotomic Field of order 4 and degree 2 with 3 generators ( + [0 1 0] [1 0 0] [ 1 0 0] + [1 0 0] [0 0 1] [ 0 1 0] + [0 0 1], [0 1 0], [ 0 0 zeta4] + ) + """ + from sage.groups.matrix_gps.finitely_generated import MatrixGroup + return MatrixGroup([g.to_matrix() for g in self.gens()]) + + def _element_constructor_(self, x): + """ + Construct an element of ``self`` from ``x``. + + INPUT: + + Either a list of pairs (color, element) + or a pair of lists (colors, elements). + + TESTS:: + + sage: C = ColoredPermutations(4, 3) + sage: x = C([(2,1), (3,3), (3,2)]); x + [[2, 3, 3], [1, 3, 2]] + sage: x == C([[2,3,3], [1,3,2]]) + True + """ + if isinstance(x, list): + if isinstance(x[0], tuple): + c = [] + p = [] + for k in x: + if len(k) != 2: + raise ValueError("input must be pairs (color, element)") + c.append(self._C(k[0])) + p.append(k[1]) + return self.element_class(self, c, self._P(p)) + + if len(x) != 2: + raise ValueError("input must be a pair of a list of colors and a permutation") + return self.element_class(self, [self._C(v) for v in x[0]], self._P(x[1])) + + def _coerce_map_from_(self, C): + """ + Return a coerce map from ``C`` if it exists and ``None`` otherwise. + + EXAMPLES:: + + sage: C = ColoredPermutations(2, 3) + sage: S = SignedPermutations(3) + sage: C.has_coerce_map_from(S) + True + + sage: C = ColoredPermutations(4, 3) + sage: C.has_coerce_map_from(S) + False + sage: S = SignedPermutations(4) + sage: C.has_coerce_map_from(S) + False + + sage: P = Permutations(3) + sage: C.has_coerce_map_from(P) + True + sage: P = Permutations(4) + sage: C.has_coerce_map_from(P) + False + """ + if isinstance(C, Permutations) and C.n == self._n: + return lambda P, x: P.element_class(P, [P._C.zero()]*P._n, x) + if self._m == 2 and isinstance(C, SignedPermutations) and C._n == self._n: + return lambda P, x: P.element_class(P, + [P._C.zero() if v == 1 else P._C.one() + for v in x._colors], + x._perm) + return super(ColoredPermutations, self)._coerce_map_from_(C) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(2, 2) + sage: [x for x in C] + [[[0, 0], [1, 2]], + [[0, 1], [1, 2]], + [[1, 0], [1, 2]], + [[1, 1], [1, 2]], + [[0, 0], [2, 1]], + [[0, 1], [2, 1]], + [[1, 0], [2, 1]], + [[1, 1], [2, 1]]] + """ + for p in self._P: + for c in itertools.product(self._C, repeat=self._n): + yield self.element_class(self, c, p) + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.cardinality() + 384 + sage: C.cardinality() == 4**3 * factorial(3) + True + """ + return self._m ** self._n * self._P.cardinality() + + def rank(self): + """ + Return the rank of ``self``. + + The rank of a complex reflection group is equal to the dimension + of the complex vector space the group acts on. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 12) + sage: C.rank() + 12 + sage: C = ColoredPermutations(7, 4) + sage: C.rank() + 4 + sage: C = ColoredPermutations(1, 4) + sage: C.rank() + 3 + """ + if self._m == 1: + return self._n - 1 + return self._n + + def degrees(self): + """ + Return the degrees of ``self``. + + The degrees of a complex reflection group are the degrees of + the fundamental invariants of the ring of polynomial invariants. + + If `m = 1`, then we are in the special case of the symmetric group + and the degrees are `(2, 3, \ldots, n, n+1)`. Otherwise the degrees + are `(m, 2m, \ldots, nm)`. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.degrees() + [4, 8, 12] + sage: S = ColoredPermutations(1, 3) + sage: S.degrees() + [2, 3] + + We now check that the product of the degrees is equal to the + cardinality of ``self``:: + + sage: prod(C.degrees()) == C.cardinality() + True + sage: prod(S.degrees()) == S.cardinality() + True + """ + if self._m == 1: # Special case for the usual symmetric group + return range(2, self._n + 1) + return [self._m * i for i in range(1, self._n + 1)] + + def codegrees(self): + r""" + Return the codegrees of ``self``. + + Let `G` be a complex reflection group. The codegrees + `d_1^* \leq d_2^* \leq \cdots \leq d_{\ell}^*` of `G` can be + defined by: + + .. MATH:: + + \prod_{i=1}^{\ell} (q - d_i^* - 1) + = \sum_{g \in G} \det(g) q^{\dim(V^g)}, + + where `V` is the natural complex vector space that `G` acts on + and `\ell` is the :meth:`rank`. + + If `m = 1`, then we are in the special case of the symmetric group + and the codegrees are `(n-2, n-3, \ldots 1, 0)`. Otherwise the degrees + are `((n-1)m, (n-2)m, \ldots, m, 0)`. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.codegrees() + [8, 4, 0] + sage: S = ColoredPermutations(1, 3) + sage: S.codegrees() + [1, 0] + + TESTS: + + We check the polynomial identity:: + + sage: R. = ZZ[] + sage: C = ColoredPermutations(3, 2) + sage: f = prod(q - ds - 1 for ds in C.codegrees()) + sage: d = lambda x: sum(1 for e in x.to_matrix().eigenvalues() if e == 1) + sage: g = sum(det(x.to_matrix()) * q**d(x) for x in C) + sage: f == g + True + """ + if self._m == 1: # Special case for the usual symmetric group + return list(reversed(range(self._n - 1))) + return [self._m * i for i in reversed(range(self._n))] + + def number_of_reflection_hyperplanes(self): + """ + Return the number of reflection hyperplanes of ``self``. + + The number of reflection hyperplanes of a complex reflection + group is equal to the sum of the codegrees plus the rank. + + EXAMPLES:: + + sage: C = ColoredPermutations(1, 2) + sage: C.number_of_reflection_hyperplanes() + 1 + sage: C = ColoredPermutations(1, 3) + sage: C.number_of_reflection_hyperplanes() + 3 + sage: C = ColoredPermutations(4, 12) + sage: C.number_of_reflection_hyperplanes() + 276 + """ + return sum(self.codegrees()) + self.rank() + + def fixed_point_polynomial(self, q=None): + r""" + The fixed point polynomial of ``self``. + + The fixed point polynomial `f_G` of a complex reflection group `G` + is counting the dimensions of fixed points subspaces: + + .. MATH:: + + f_G(q) = \sum_{w \in W} q^{\dim V^w}. + + Furthermore, let `d_1, d_2, \ldots, d_{\ell}` be the degrees of `G`, + where `\ell` is the :meth:`rank`. Then the fixed point polynomial + is given by + + .. MATH:: + + f_G(q) = \prod_{i=1}^{\ell} (q + d_i - 1). + + INPUT: + + - ``q`` -- (default: the generator of ``ZZ['q']``) the parameter `q` + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.fixed_point_polynomial() + q^3 + 21*q^2 + 131*q + 231 + + sage: S = ColoredPermutations(1, 3) + sage: S.fixed_point_polynomial() + q^2 + 3*q + 2 + + TESTS: + + We check the against the degrees and codegrees:: + + sage: R. = ZZ[] + sage: C = ColoredPermutations(4, 3) + sage: C.fixed_point_polynomial(q) == prod(q + d - 1 for d in C.degrees()) + True + """ + if q is None: + q = PolynomialRing(ZZ, 'q').gen(0) + return prod(q + d - 1 for d in self.degrees()) + + def is_well_generated(self): + """ + Return if ``self`` is a well-generated complex reflection group. + + A complex reflection group `G` is well-generated if it is + generated by `\ell` reflections. Equivalently, `G` is well-generated + if `d_i + d_i^* = d_{\ell}` for all `1 \leq i \leq \ell`. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.is_well_generated() + True + sage: C = ColoredPermutations(2, 8) + sage: C.is_well_generated() + True + sage: C = ColoredPermutations(1, 4) + sage: C.is_well_generated() + True + """ + deg = self.degrees() + dstar = self.codegrees() + return all(deg[-1] == d + dstar[i] for i, d in enumerate(deg)) + + Element = ColoredPermutation + +##################################################################### +## Signed permutations + + +class SignedPermutation(ColoredPermutation): + """ + A signed permutation. + """ + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: s4*s1*s2*s3*s4 + [-4, 1, 2, -3] + """ + return repr(list(self)) + + _latex_ = _repr_ + + def _mul_(self, other): + """ + Multiply ``self`` and ``other``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4; x + [-4, 1, 2, -3] + sage: x * x + [3, -4, 1, -2] + + :: + + sage: s1*s2*s1 == s1*s2*s1 + True + sage: s3*s4*s3*s4 == s4*s3*s4*s3 + True + """ + colors = tuple(self._colors[i] * other._colors[val - 1] # -1 for indexing + for i, val in enumerate(self._perm)) + p = self._perm._left_to_right_multiply_on_right(other._perm) + return self.__class__(self.parent(), colors, p) + + def inverse(self): + """ + Return the inverse of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4 + sage: ~x + [2, 3, -4, -1] + sage: x * ~x == S.one() + True + """ + ip = ~self._perm + return self.__class__(self.parent(), + tuple([self._colors[i - 1] for i in ip]), # -1 for indexing + ip) + + __invert__ = inverse + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4 + sage: [a for a in x] + [-4, 1, 2, -3] + """ + for i, p in enumerate(self._perm): + yield self._colors[i] * p + + def to_matrix(self): + """ + Return a matrix of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4 + sage: M = x.to_matrix(); M + [ 0 1 0 0] + [ 0 0 1 0] + [ 0 0 0 -1] + [-1 0 0 0] + + The matrix multiplication is in the *opposite* order:: + + sage: m1,m2,m3,m4 = [g.to_matrix() for g in S.gens()] + sage: M == m4 * m3 * m2 * m1 * m4 + True + """ + return self._perm.to_matrix() * diagonal_matrix(self._colors) + + def has_left_descent(self, i): + """ + Return ``True`` if ``i`` is a left descent of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4 + sage: [x.has_left_descent(i) for i in S.index_set()] + [True, False, False, True] + """ + n = self.parent()._n + if i == n: + return self._colors[-1] == -1 + if self._colors[i - 1] == -1: + return self._colors[i] == 1 or self._perm[i - 1] < self._perm[i] + return self._colors[i] == 1 and self._perm[i - 1] > self._perm[i] + + +class SignedPermutations(ColoredPermutations): + r""" + Group of signed permutations. + + The group of signed permutations is also known as the hyperoctahedral + group, the Coxeter group of type `B_n`, and the 2-colored permutation + group. Thus it can be constructed as the wreath product `S_2 \wr S_n`. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.group_generators() + sage: x = s4*s1*s2*s3*s4; x + [-4, 1, 2, -3] + sage: x^4 == S.one() + True + + This is a finite Coxeter group of type `B_n`:: + + sage: S.canonical_representation() + Finite Coxeter group over Universal Cyclotomic Field with Coxeter matrix: + [1 3 2 2] + [3 1 3 2] + [2 3 1 4] + [2 2 4 1] + sage: S.long_element() + [-4, -3, -2, -1] + sage: S.long_element().reduced_word() + [4, 3, 4, 2, 3, 4, 1, 2, 3, 4] + + We can also go between the 2-colored permutation group:: + + sage: C = ColoredPermutations(2, 3) + sage: S = SignedPermutations(3) + sage: S.an_element() + [-3, 1, 2] + sage: C(S.an_element()) + [[1, 0, 0], [3, 1, 2]] + sage: S(C(S.an_element())) == S.an_element() + True + sage: S(C.an_element()) + [1, 2, 3] + + There is also the natural lift from permutations:: + + sage: P = Permutations(3) + sage: x = S(P.an_element()); x + [3, 1, 2] + sage: x.parent() + Signed permutations of 3 + + REFERENCES: + + - :wikipedia:`Hyperoctahedral_group` + """ + def __init__(self, n): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: TestSuite(S).run() + """ + ColoredPermutations.__init__(self, 2, n, FiniteCoxeterGroups()) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: SignedPermutations(4) + Signed permutations of 4 + """ + return "Signed permutations of {}".format(self._n) + + @cached_method + def one(self): + """ + Return the identity element of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.one() + [1, 2, 3, 4] + """ + return self.element_class(self, [ZZ.one()] * self._n, + self._P.identity()) + + def simple_reflection(self, i): + r""" + Return the ``i``-th simple reflection of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.simple_reflection(1) + [2, 1, 3, 4] + sage: S.simple_reflection(4) + [1, 2, 3, -4] + """ + if i not in self.index_set(): + raise ValueError("i must be in the index set") + if i < self._n: + p = range(1, self._n + 1) + p[i - 1] = i + 1 + p[i] = i + return self.element_class(self, [ZZ.one()] * self._n, self._P(p)) + temp = [ZZ.one()] * self._n + temp[-1] = -ZZ.one() + return self.element_class(self, temp, self._P.identity()) + + @cached_method + def gens(self): + """ + Return the generators of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.gens() + ([2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3], [1, 2, 3, -4]) + """ + return tuple(self.simple_reflection(i) for i in self.index_set()) + + def _element_constructor_(self, x): + """ + Construct an element of ``self`` from ``x``. + + TESTS:: + + sage: S = SignedPermutations(3) + sage: x = S([(+1,1), (-1,3), (-1,2)]); x + [1, -3, -2] + sage: x == S([[+1,-1,-1], [1,3,2]]) + True + sage: x == S([1, -3, -2]) + True + """ + if isinstance(x, list): + if isinstance(x[0], tuple): + c = [] + p = [] + for k in x: + if len(k) != 2: + raise ValueError("input must be pairs (sign, element)") + if k[0] != 1 and k[0] != -1: + raise ValueError("the sign must be +1 or -1") + c.append(ZZ(k[0])) + p.append(k[1]) + return self.element_class(self, c, self._P(p)) + + if len(x) == self._n: + c = [] + p = [] + one = ZZ.one() + for v in x: + if v > 0: + c.append(one) + p.append(v) + else: + c.append(-one) + p.append(-v) + return self.element_class(self, c, self._P(p)) + + if len(x) != 2: + raise ValueError("input must be a pair of a list of signs and a permutation") + if any(s != 1 and s != -1 for s in x[0]): + raise ValueError("the sign must be +1 or -1") + return self.element_class(self, [ZZ(v) for v in x[0]], self._P(x[1])) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(2) + sage: [x for x in S] + [[1, 2], [1, -2], [-1, 2], [-1, -2], + [2, 1], [2, -1], [-2, 1], [-2, -1]] + """ + pmone = [ZZ.one(), -ZZ.one()] + for p in self._P: + for c in itertools.product(pmone, repeat=self._n): + yield self.element_class(self, c, p) + + def _coerce_map_from_(self, C): + """ + Return a coerce map from ``C`` if it exists and ``None`` otherwise. + + EXAMPLES:: + + sage: C = ColoredPermutations(2, 3) + sage: S = SignedPermutations(3) + sage: S.has_coerce_map_from(C) + True + + sage: C = ColoredPermutations(4, 3) + sage: S.has_coerce_map_from(C) + False + + sage: P = Permutations(3) + sage: C.has_coerce_map_from(P) + True + sage: P = Permutations(4) + sage: C.has_coerce_map_from(P) + False + """ + if isinstance(C, Permutations) and C.n == self._n: + return lambda P, x: P.element_class(P, [1]*P._n, x) + if isinstance(C, ColoredPermutations) and C._n == self._n and C._m == 2: + return lambda P, x: P.element_class(P, + [1 if v == 0 else -1 + for v in x._colors], + x._perm) + return super(SignedPermutations, self)._coerce_map_from_(C) + + @cached_method + def index_set(self): + """ + Return the index set of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.index_set() + (1, 2, 3, 4) + """ + return tuple(range(1, self._n + 1)) + + def coxeter_matrix(self): + """ + Return the Coxeter matrix of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.coxeter_matrix() + [1 3 2 2] + [3 1 3 2] + [2 3 1 4] + [2 2 4 1] + """ + from sage.combinat.root_system.cartan_type import CartanType + return CartanType(['B', self._n]).coxeter_matrix() + + def long_element(self, index_set=None): + """ + Return the longest element of ``self``, or of the + parabolic subgroup corresponding to the given ``index_set``. + + INPUT: + + - ``index_set`` -- (optional) a subset (as a list or iterable) + of the nodes of the indexing set + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.long_element() + [-4, -3, -2, -1] + """ + if index_set is not None: + return super(SignedPermutations, self).long_element() + p = range(self._n, 0, -1) + return self.element_class(self, [-ZZ.one()] * self._n, self._P(p)) + + Element = SignedPermutation + +# TODO: Make this a subgroup +#class EvenSignedPermutations(SignedPermutations): +# """ +# Group of even signed permutations. +# """ +# def _repr_(self): +# """ +# Return a string representation of ``self``. +# """ +# return "Even signed permtuations of {}".format(self._n) +# +# def __iter__(self): +# """ +# Iterate over ``self``. +# """ +# for s in SignedPermutations.__iter__(self): +# total = 0 +# for pm in s._colors: +# if pm == -1: +# total += 1 +# +# if total % 2 == 0: +# yield s diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 93eaea33942..ee2be8cc2ec 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -2774,9 +2774,12 @@ def bell_polynomial(n, k): .. MATH:: - B_{n,k}(x_1, x_2, \ldots, x_{n-k+1}) = \sum_{\sum{j_i}=k, \sum{i j_i} - =n} \frac{n!}{j_1!j_2!\cdots} \frac{x_1}{1!}^j_1 \frac{x_2}{2!}^j_2 - \cdots. + B_{n,k}(x_0, x_1, \ldots, x_{n-k}) = + \sum_{\sum{j_i}=k, \sum{(i+1) j_i}=n} + \frac{n!}{j_0!j_1!\cdots j_{n-k}!} + \left(\frac{x_0}{(0+1)!}\right)^{j_0} + \left(\frac{x_1}{(1+1)!}\right)^{j_1} \cdots + \left(\frac{x_{n-k}}{(n-k+1)!}\right)^{j_{n-k}}. INPUT: @@ -2786,14 +2789,30 @@ def bell_polynomial(n, k): OUTPUT: - - a polynomial in `n-k+1` variables over `\QQ` + - a polynomial in `n-k+1` variables over `\ZZ` EXAMPLES:: sage: bell_polynomial(6,2) - 10*x_3^2 + 15*x_2*x_4 + 6*x_1*x_5 + 10*x2^2 + 15*x1*x3 + 6*x0*x4 sage: bell_polynomial(6,3) - 15*x_2^3 + 60*x_1*x_2*x_3 + 15*x_1^2*x_4 + 15*x1^3 + 60*x0*x1*x2 + 15*x0^2*x3 + + TESTS: + + Check that :trac:`18338` is fixed:: + + sage: bell_polynomial(0,0).parent() + Multivariate Polynomial Ring in x over Integer Ring + + sage: for n in (0..4): + ....: print [bell_polynomial(n,k).coefficients() for k in (0..n)] + [[1]] + [[], [1]] + [[], [1], [1]] + [[], [1], [3], [1]] + [[], [1], [3, 4], [6], [1]] + REFERENCES: @@ -2802,24 +2821,24 @@ def bell_polynomial(n, k): AUTHORS: - Blair Sutton (2009-01-26) + - Thierry Monteil (2015-09-29): the result must always be a polynomial. """ + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.combinat.partition import Partitions from sage.rings.arith import factorial - vars = ZZ[tuple(['x_'+str(i) for i in range(1, n-k+2)])].gens() - result = 0 + R = PolynomialRing(ZZ, 'x', n-k+1) + vars = R.gens() + result = R.zero() for p in Partitions(n, length=k): - factorial_product = 1 + factorial_product = 1 power_factorial_product = 1 for part, count in p.to_exp_dict().iteritems(): factorial_product *= factorial(count) power_factorial_product *= factorial(part)**count - - coefficient = factorial(n) / (factorial_product * power_factorial_product) + coefficient = factorial(n) // (factorial_product * power_factorial_product) result += coefficient * prod([vars[i - 1] for i in p]) - return result - def fibonacci_sequence(start, stop=None, algorithm=None): r""" Return an iterator over the Fibonacci sequence, for all fibonacci diff --git a/src/sage/combinat/composition.py b/src/sage/combinat/composition.py index 028a1d5d980..a49a587798d 100644 --- a/src/sage/combinat/composition.py +++ b/src/sage/combinat/composition.py @@ -33,10 +33,12 @@ from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent +from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from sage.rings.all import ZZ from combinat import CombinatorialElement -from cartesian_product import CartesianProduct -from integer_list import IntegerListsLex +from sage.categories.cartesian_product import cartesian_product + +from integer_lists import IntegerListsLex import __builtin__ from sage.rings.integer import Integer from sage.combinat.combinatorial_map import combinatorial_map @@ -748,10 +750,16 @@ def finer(self): sage: C = Composition([3,2]).finer() sage: C.cardinality() 8 - sage: list(C) + sage: C.list() [[1, 1, 1, 1, 1], [1, 1, 1, 2], [1, 2, 1, 1], [1, 2, 2], [2, 1, 1, 1], [2, 1, 2], [3, 1, 1], [3, 2]] + + sage: Composition([]).finer() + {[]} """ - return CartesianProduct(*[Compositions(i) for i in self]).map(Composition.sum) + if not self: + return FiniteEnumeratedSet([self]) + else: + return cartesian_product([Compositions(i) for i in self]).map(Composition.sum) def is_finer(self, co2): """ diff --git a/src/sage/combinat/composition_signed.py b/src/sage/combinat/composition_signed.py index 918fcf2241f..eff46c83d51 100644 --- a/src/sage/combinat/composition_signed.py +++ b/src/sage/combinat/composition_signed.py @@ -16,8 +16,9 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools + from composition import Compositions_n, Composition -import cartesian_product from sage.rings.all import Integer from sage.rings.arith import binomial import __builtin__ @@ -130,8 +131,7 @@ def __iter__(self): """ for comp in Compositions_n.__iter__(self): l = len(comp) - a = [[1,-1] for i in range(l)] - for sign in cartesian_product.CartesianProduct(*a): + for sign in itertools.product([1,-1], repeat=l): yield [ sign[i]*comp[i] for i in range(l)] from sage.structure.sage_object import register_unpickle_override diff --git a/src/sage/combinat/crystals/affine.py b/src/sage/combinat/crystals/affine.py index 136a1873c32..bc74fad55f0 100644 --- a/src/sage/combinat/crystals/affine.py +++ b/src/sage/combinat/crystals/affine.py @@ -15,6 +15,7 @@ from sage.misc.abstract_method import abstract_method from sage.categories.regular_crystals import RegularCrystals from sage.categories.finite_crystals import FiniteCrystals +from sage.structure.element import parent from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element_wrapper import ElementWrapper @@ -349,7 +350,7 @@ def e(self, i): [[3]] sage: b.e(1) """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.e0() else: x = self.lift().e(i) @@ -374,7 +375,7 @@ def f(self, i): [[1]] sage: b.f(2) """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.f0() else: x = self.lift().f(i) @@ -417,7 +418,7 @@ def epsilon(self, i): sage: [x.epsilon(1) for x in A.list()] [0, 1, 0] """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.epsilon0() else: return self.lift().epsilon(i) @@ -455,18 +456,50 @@ def phi(self, i): sage: [x.phi(1) for x in A.list()] [1, 0, 0] """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.phi0() else: return self.lift().phi(i) - def __lt__(self, other): + def __eq__(self, other): """ Non elements of the crystal are incomparable with elements of the crystal (or should it return ``NotImplemented``?). Elements of this crystal are compared using the comparison in the underlying classical crystal. + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: b==c + False + sage: b==b + True + sage: b==1 + False + """ + return parent(self) is parent(other) and self.value == other.value + + def __ne__(self, other): + """" + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: b!=c + True + sage: b!=b + False + sage: b!=1 + True + """ + return not self == other + + def __lt__(self, other): + """" EXAMPLES:: sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) @@ -479,9 +512,74 @@ def __lt__(self, other): sage: bc + False + sage: b>b + False + sage: c>b + True + """ + return parent(self) is parent(other) and self.value > other.value + + def __le__(self, other): + """" + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: b<=c + True + sage: b<=b + True + sage: c<=b + False + """ + return parent(self) is parent(other) and self.value <= other.value + + def __ge__(self, other): + """" + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: c>=b + True + sage: b>=b + True + sage: b>=c + False + """ + return parent(self) is parent(other) and self.value >= other.value + + def __cmp__(self, other): + """" + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: cmp(b,c) + -1 + sage: cmp(b,b) + 0 + + If the parent are different, it uses comparison of the parents:: + + sage: cmp(b,1) == cmp(b.parent(), ZZ) + True + """ + return cmp(parent(self), parent(other)) or cmp(self.value, other.value) AffineCrystalFromClassical.Element = AffineCrystalFromClassicalElement diff --git a/src/sage/combinat/crystals/affinization.py b/src/sage/combinat/crystals/affinization.py index 3e0c1bb2713..62b1fb5c213 100644 --- a/src/sage/combinat/crystals/affinization.py +++ b/src/sage/combinat/crystals/affinization.py @@ -17,6 +17,7 @@ # http://www.gnu.org/licenses/ #**************************************************************************** +from sage.structure.element import parent from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element import Element @@ -187,11 +188,23 @@ def _latex_(self): from sage.misc.latex import latex return latex(self._b) + "({})".format(self._m) - def __eq__(self, other): + def __hash__(self): + r""" + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['A',2,1], 2, 2).affinization() + sage: mg = A.module_generators[0] + sage: hash(mg) + -6948036233304877976 # 64-bit + -1420700568 # 32-bit """ - Check equality. + return hash(self._b) ^ hash(self._m) - EXAMPLES:: + def __cmp__(self, other): + """ + Comparison. + + TESTS:: sage: A = crystals.KirillovReshetikhin(['A',2,1], 2, 2).affinization() sage: mg = A.module_generators[0] @@ -203,17 +216,6 @@ def __eq__(self, other): sage: A = crystals.AffinizationOf(KT) sage: A(KT.module_generators[3], 1).f(0) == A.module_generators[0] True - """ - if not isinstance(other, AffinizationOfCrystal.Element): - return False - return self.parent() == other.parent() \ - and self._b == other._b and self._m == other._m - - def __ne__(self, other): - """ - Check inequality. - - EXAMPLES:: sage: A = crystals.KirillovReshetikhin(['A',2,1], 2, 2).affinization() sage: mg = A.module_generators[0] @@ -221,14 +223,7 @@ def __ne__(self, other): True sage: mg != mg.f(2).e(2) False - """ - return not self.__eq__(other) - def __lt__(self, other): - """ - Check less than. - - EXAMPLES:: sage: A = crystals.KirillovReshetikhin(['A',2,1], 2, 2).affinization() sage: S = A.subcrystal(max_depth=2) @@ -241,8 +236,12 @@ def __lt__(self, other): [[1, 2], [2, 3]](1), [[1, 2], [3, 3]](1), [[2, 2], [3, 3]](2)] + """ - return self._m < other._m or (self._m == other._m and self._b < other._b) + if parent(self) is parent(other): + return cmp(self._m, other._m) or cmp(self._b, other._b) + else: + return cmp(parent(self), parent(other)) def e(self, i): """ diff --git a/src/sage/combinat/crystals/alcove_path.py b/src/sage/combinat/crystals/alcove_path.py index b8cacdf58c2..1e73f182842 100644 --- a/src/sage/combinat/crystals/alcove_path.py +++ b/src/sage/combinat/crystals/alcove_path.py @@ -714,23 +714,33 @@ def epsilon(self, i): def weight(self): """ - Returns the weight of self. + Return the weight of ``self``. EXAMPLES:: sage: C = crystals.AlcovePaths(['A',2],[2,0]) sage: for i in C: i.weight() - 2*Lambda[1] - Lambda[2] - Lambda[1] - Lambda[2] - -2*Lambda[1] + 2*Lambda[2] - -Lambda[1] - -2*Lambda[2] + (0, 2, 0) + (0, 0, 1) + (0, 1, -1) + (0, -2, 2) + (0, -1, 0) + (0, 0, -2) sage: B = crystals.AlcovePaths(['A',2,1],[1,0,0]) sage: p = B.module_generators[0].f_string([0,1,2]) sage: p.weight() Lambda[0] - delta + + TESTS: + + Check that crystal morphisms work (:trac:`19481`):: + + sage: C1 = crystals.AlcovePaths(['A',2],[1,0]) + sage: C2 = crystals.AlcovePaths(['A',2],[2,0]) + sage: phi = C1.crystal_morphism(C2.module_generators, scaling_factors={1:2, 2:2}) + sage: [phi(x) for x in C1] + [(), ((alpha[1], 0),), ((alpha[1], 0), (alpha[1] + alpha[2], 0))] """ root_space = self.parent().R.root_space() weight = -self.parent().weight @@ -738,7 +748,10 @@ def weight(self): root = root_space(i.root) weight = -i.height*root + weight.reflection(root) - return -weight + WLR = self.parent().weight_lattice_realization() + B = WLR.basis() + return WLR._from_dict({i: Integer(c) for i,c in -weight}, + remove_zeros=False) #def __repr__(self): #return str(self.integer_sequence()) diff --git a/src/sage/combinat/crystals/elementary_crystals.py b/src/sage/combinat/crystals/elementary_crystals.py index 925c11ba1f8..3745064c7b5 100644 --- a/src/sage/combinat/crystals/elementary_crystals.py +++ b/src/sage/combinat/crystals/elementary_crystals.py @@ -83,6 +83,7 @@ from sage.combinat.root_system.cartan_type import CartanType from sage.combinat.root_system.root_system import RootSystem from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ class AbstractSingleCrystalElement(Element): r""" @@ -102,6 +103,17 @@ def __lt__(self,other): """ return False + def __hash__(self): + r""" + TESTS:: + + sage: C = crystals.elementary.Component("D7") + sage: c = C.highest_weight_vector() + sage: hash(c) # random + 879 + """ + return hash(self.parent()) + def __eq__(self,other): r""" EXAMPLES:: @@ -787,7 +799,7 @@ def _element_constructor_(self, m): sage: B(721) 721 """ - return self.element_class(self, m) + return self.element_class(self, ZZ(m)) def weight_lattice_realization(self): """ @@ -817,6 +829,16 @@ def __init__(self, parent, m): self._m = m Element.__init__(self, parent) + def __hash__(self): + r""" + TESTS:: + + sage: B = crystals.elementary.Elementary(['B',7],7) + sage: hash(B(17)) + 17 + """ + return hash(self._m) + def _repr_(self): r""" EXAMPLES:: diff --git a/src/sage/combinat/crystals/fast_crystals.py b/src/sage/combinat/crystals/fast_crystals.py index 9bf2a3ec596..7372dbd1f9d 100644 --- a/src/sage/combinat/crystals/fast_crystals.py +++ b/src/sage/combinat/crystals/fast_crystals.py @@ -364,7 +364,17 @@ def _repr_(self): else: raise NotImplementedError - def __eq__(self, other): + def __hash__(self): + r""" + TESTS:: + + sage: C = crystals.FastRankTwo(['A',2],shape=[2,1]) + sage: hash(C(0)) + 0 + """ + return hash(self.value) + + def __cmp__(self, other): """ EXAMPLES:: @@ -376,14 +386,6 @@ def __eq__(self, other): False sage: C(0) == D(0) False - """ - return self.__class__ is other.__class__ and \ - self.parent() == other.parent() and \ - self.value == other.value - - def __ne__(self, other): - """ - EXAMPLES:: sage: C = crystals.FastRankTwo(['A',2],shape=[2,1]) sage: D = crystals.FastRankTwo(['B',2],shape=[2,1]) @@ -393,13 +395,6 @@ def __ne__(self, other): True sage: C(0) != D(0) True - """ - return not self == other - - - def __cmp__(self, other): - """ - EXAMPLES:: sage: C = crystals.FastRankTwo(['A',2],shape=[2,1]) sage: C(1) < C(2) @@ -411,11 +406,10 @@ def __cmp__(self, other): sage: C(1) <= C(1) True """ - if type(self) is not type(other): - return cmp(type(self), type(other)) - if self.parent() != other.parent(): - return cmp(self.parent(), other.parent()) - return self.parent().cmp_elements(self, other) + if parent(self) is parent(other): + return cmp(self.value, other.value) + else: + return cmp(parent(self), parent(other)) def e(self, i): """ @@ -436,7 +430,6 @@ def e(self, i): r = self.parent()._rootoperators[self.value][2] return self.parent()(r) if r is not None else None - def f(self, i): """ Returns the action of `f_i` on self. diff --git a/src/sage/combinat/crystals/monomial_crystals.py b/src/sage/combinat/crystals/monomial_crystals.py index 147c9b5aecc..876a40600b7 100644 --- a/src/sage/combinat/crystals/monomial_crystals.py +++ b/src/sage/combinat/crystals/monomial_crystals.py @@ -162,6 +162,18 @@ def _repr_(self): return_str += "Y(%s,%s) "%(L[x][0][0],L[x][0][1]) return return_str + def __hash__(self): + r""" + TESTS:: + + sage: M = crystals.infinity.NakajimaMonomials(['C',5]) + sage: m1 = M.module_generators[0].f(1) + sage: hash(m1) + 4715601665014767730 # 64-bit + -512614286 # 32-bit + """ + return hash(frozenset(tuple(self._dict.iteritems()))) + def __eq__(self,other): r""" EXAMPLES:: diff --git a/src/sage/combinat/crystals/subcrystal.py b/src/sage/combinat/crystals/subcrystal.py index 341c3540231..031b1d6c1d2 100644 --- a/src/sage/combinat/crystals/subcrystal.py +++ b/src/sage/combinat/crystals/subcrystal.py @@ -25,6 +25,7 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.element import parent from sage.structure.parent import Parent from sage.structure.element_wrapper import ElementWrapper from sage.categories.crystals import Crystals @@ -127,6 +128,8 @@ def __classcall_private__(cls, ambient, contained=None, generators=None, generators = ambient.module_generators category = Crystals().or_subcategory(category) + if ambient in FiniteCrystals() or isinstance(contained, frozenset): + category = category.Finite() if virtualization is not None: if scaling_factors is None: @@ -250,6 +253,18 @@ def cardinality(self): sage: S = B.subcrystal(max_depth=4) sage: S.cardinality() 22 + + TESTS: + + Check that :trac:`19481` is fixed:: + + sage: from sage.combinat.crystals.virtual_crystal import VirtualCrystal + sage: A = crystals.infinity.Tableaux(['A',3]) + sage: V = VirtualCrystal(A, {1:(1,3), 2:(2,)}, {1:1, 2:2}, cartan_type=['C',2]) + sage: V.cardinality() + Traceback (most recent call last): + ... + NotImplementedError: unknown cardinality """ if self._cardinality is not None: return self._cardinality @@ -261,7 +276,10 @@ def cardinality(self): except AttributeError: if self in FiniteCrystals(): return Integer(len(self.list())) - card = super(Subcrystal, self).cardinality() + try: + card = super(Subcrystal, self).cardinality() + except AttributeError: + raise NotImplementedError("unknown cardinality") if card == infinity: self._cardinality = card return card @@ -285,9 +303,9 @@ class Element(ElementWrapper): """ An element of a subcrystal. Wraps an element in the ambient crystal. """ - def __lt__(self, other): + def __eq__(self, other): """ - Check less than. + Check sorting EXAMPLES:: @@ -307,11 +325,87 @@ def __lt__(self, other): [[-1, -1]](1), [[-1, -1]](2)] """ - if not isinstance(other, Subcrystal.Element): - return False - if other.parent() is not self.parent(): - return False - return self.value < other.value + return parent(self) is parent(other) and self.value == other.value + + def __ne__(self, other): + """ + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['C',2,1], 1,2).affinization() + sage: S = A.subcrystal(max_depth=2) + sage: ([(i,j) for i in range(len(S)) for j in range(len(S)) if S[i]!=S[j]] + ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if + ....: S[i].value!=S[j].value]) + True + """ + return parent(self) is not parent(other) or self.value != other.value + + def __lt__(self, other): + """ + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['C',2,1], 1,2).affinization() + sage: S = A.subcrystal(max_depth=2) + sage: ([(i,j) for i in range(len(S)) for j in range(len(S)) if S[i]S[j]] + ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if + ....: S[i].value>S[j].value]) + True + """ + return parent(self) is parent(other) and self.value > other.value + + def __ge__(self, other): + """ + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['C',2,1], 1,2).affinization() + sage: S = A.subcrystal(max_depth=2) + sage: ([(i,j) for i in range(len(S)) for j in range(len(S)) if S[i]>=S[j]] + ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if + ....: S[i].value>=S[j].value]) + True + """ + return parent(self) is parent(other) and self.value >= other.value + + def __cmp__(self, other): + """ + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['C',2,1], 1,2).affinization() + sage: S = A.subcrystal(max_depth=2) + sage: ([(i,j,cmp(S[i],S[j])) for i in range(len(S)) for j in range(len(S))] + ....: == [(i,j,cmp(S[i].value,S[j].value)) for i in range(len(S)) for j in range(len(S))]) + True + """ + if parent(self) is parent(other): + return cmp(self.value, other.value) + else: + return cmp(parent(self), parent(other)) def e(self, i): """ diff --git a/src/sage/combinat/crystals/tensor_product.py b/src/sage/combinat/crystals/tensor_product.py index 3e78cf09a71..95968325cad 100644 --- a/src/sage/combinat/crystals/tensor_product.py +++ b/src/sage/combinat/crystals/tensor_product.py @@ -36,11 +36,11 @@ from sage.structure.element import parent from sage.structure.global_options import GlobalOptions from sage.categories.category import Category +from sage.categories.cartesian_product import cartesian_product from sage.categories.classical_crystals import ClassicalCrystals from sage.categories.regular_crystals import RegularCrystals from sage.categories.sets_cat import Sets from sage.combinat.root_system.cartan_type import CartanType -from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.combinat import CombinatorialElement from sage.combinat.partition import Partition from sage.combinat.tableau import Tableau @@ -808,7 +808,7 @@ def __init__(self, crystals, **options): raise ValueError("you need to specify the Cartan type if the tensor product list is empty") else: self._cartan_type = crystals[0].cartan_type() - self.cartesian_product = CartesianProduct(*self.crystals) + self.cartesian_product = cartesian_product(self.crystals) self.module_generators = self def _repr_(self): diff --git a/src/sage/combinat/crystals/virtual_crystal.py b/src/sage/combinat/crystals/virtual_crystal.py index eef6051c311..9f72c2f4291 100644 --- a/src/sage/combinat/crystals/virtual_crystal.py +++ b/src/sage/combinat/crystals/virtual_crystal.py @@ -168,6 +168,16 @@ def __classcall_private__(cls, ambient, virtualization, scaling_factors, sage: V2 = psi2.image() sage: V1 is V2 True + + TESTS: + + Check that :trac:`19481` is fixed:: + + sage: from sage.combinat.crystals.virtual_crystal import VirtualCrystal + sage: A = crystals.Tableaux(['A',3], shape=[2,1,1]) + sage: V = VirtualCrystal(A, {1:(1,3), 2:(2,)}, {1:1, 2:2}, cartan_type=['C',2]) + sage: V.category() + Category of finite crystals """ if cartan_type is None: cartan_type = ambient.cartan_type() @@ -181,6 +191,8 @@ def __classcall_private__(cls, ambient, virtualization, scaling_factors, scaling_factors = Family(scaling_factors) category = Crystals().or_subcategory(category) + if ambient in FiniteCrystals() or isinstance(contained, frozenset): + category = category.Finite() return super(Subcrystal, cls).__classcall__(cls, ambient, virtualization, scaling_factors, contained, tuple(generators), cartan_type, diff --git a/src/sage/combinat/designs/block_design.py b/src/sage/combinat/designs/block_design.py index a8692897ac4..523d25953de 100644 --- a/src/sage/combinat/designs/block_design.py +++ b/src/sage/combinat/designs/block_design.py @@ -165,7 +165,7 @@ def are_hyperplanes_in_projective_geometry_parameters(v, k, lmbda, return_parame return (True, (q,d)) if return_parameters else True -def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): +def ProjectiveGeometryDesign(n, d, F, algorithm=None, point_coordinates=True, check=True): """ Return a projective geometry design. @@ -188,6 +188,10 @@ def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): GAP's "design" package must be available in this case, and that it can be installed with the ``gap_packages`` spkg. + - ``point_coordinates`` -- ``True`` by default. Ignored and assumed to be ``False`` if + ``algorithm="gap"``. If ``True``, the ground set is indexed by coordinates in `F^{n+1}`. + Otherwise the ground set is indexed by integers, + EXAMPLES: The set of `d`-dimensional subspaces in a `n`-dimensional projective space @@ -203,6 +207,18 @@ def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): sage: PG.is_t_design(return_parameters=True) (True, (2, 85, 5, 1)) + Use coordinates:: + + sage: PG = designs.ProjectiveGeometryDesign(2,1,GF(3)) + sage: PG.blocks()[0] + [(1, 0, 0), (1, 0, 1), (1, 0, 2), (0, 0, 1)] + + Use indexing by integers:: + + sage: PG = designs.ProjectiveGeometryDesign(2,1,GF(3),point_coordinates=0) + sage: PG.blocks()[0] + [0, 1, 2, 12] + Check that the constructor using gap also works:: sage: BD = designs.ProjectiveGeometryDesign(2, 1, GF(2), algorithm="gap") # optional - gap_packages (design package) @@ -224,7 +240,10 @@ def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): m.set_immutable() b.append(points[m]) blocks.append(b) - return BlockDesign(len(points), blocks, name="ProjectiveGeometryDesign", check=check) + B = BlockDesign(len(points), blocks, name="ProjectiveGeometryDesign", check=check) + if point_coordinates: + B.relabel({i:p[0] for p,i in points.iteritems()}) + return B if algorithm == "gap": # Requires GAP's Design from sage.interfaces.gap import gap gap.load_package("design") diff --git a/src/sage/combinat/designs/incidence_structures.py b/src/sage/combinat/designs/incidence_structures.py index b34aae434a3..56e822814e7 100644 --- a/src/sage/combinat/designs/incidence_structures.py +++ b/src/sage/combinat/designs/incidence_structures.py @@ -398,7 +398,7 @@ def __contains__(self, block): True sage: ["Am", "I", "finally", "done ?"] in IS False - sage: IS = designs.ProjectiveGeometryDesign(3, 1, GF(2)) + sage: IS = designs.ProjectiveGeometryDesign(3, 1, GF(2), point_coordinates=False) sage: [3,8,7] in IS True sage: [3,8,9] in IS @@ -1640,6 +1640,101 @@ def is_t_design(self, t=None, v=None, k=None, l=None, return_parameters=False): ll = b return (True, (tt,v,k,ll)) if return_parameters else True + def is_generalized_quadrangle(self, verbose=False, parameters=False): + r""" + Test if the incidence structure is a generalized quadrangle. + + An incidence structure is a generalized quadrangle iff (see [BH12]_, + section 9.6): + + - two blocks intersect on at most one point. + + - For every point `p` not in a block `B`, there is a unique block `B'` + intersecting both `\{p\}` and `B` + + It is a *regular* generalized quadrangle if furthermore: + + - it is `s+1`-:meth:`uniform ` for some positive integer `s`. + + - it is `t+1`-:meth:`regular ` for some positive integer `t`. + + For more information, see the :wikipedia:`Generalized_quadrangle`. + + .. NOTE:: + + Some references (e.g. [PT09]_ or [GQwiki]_) only allow *regular* + generalized quadrangles. To use such a definition, see the + ``parameters`` optional argument described below, or the methods + :meth:`is_regular` and :meth:`is_uniform`. + + INPUT: + + - ``verbose`` (boolean) -- whether to print an explanation when the + instance is not a generalized quadrangle. + + - ``parameters`` (boolean; ``False``) -- if set to ``True``, the + function returns a pair ``(s,t)`` instead of ``True`` answers. In this + case, `s` and `t` are the integers defined above if they exist (each + can be set to ``False`` otherwise). + + EXAMPLE:: + + sage: h = designs.CremonaRichmondConfiguration() + sage: h.is_generalized_quadrangle() + True + + This is actually a *regular* generalized quadrangle:: + + sage: h.is_generalized_quadrangle(parameters=True) + (2, 2) + + TESTS:: + + sage: H = IncidenceStructure((2*graphs.CompleteGraph(3)).edges(labels=False)) + sage: H.is_generalized_quadrangle(verbose=True) + Some point is at distance >3 from some block. + False + + sage: G = graphs.CycleGraph(5) + sage: B = list(G.subgraph_search_iterator(graphs.PathGraph(3))) + sage: H = IncidenceStructure(B) + sage: H.is_generalized_quadrangle(verbose=True) + Two blocks intersect on >1 points. + False + + sage: hypergraphs.CompleteUniform(4,2).is_generalized_quadrangle(verbose=1) + Some point has two projections on some line. + False + """ + # The distance between a point and a line in the incidence graph is odd + # and must be <= 3. Thus, the diameter is at most 4 + g = self.incidence_graph() + if g.diameter() > 4: + if verbose: + print "Some point is at distance >3 from some block." + return False + + # There is a unique projection of a point on a line. Thus, the girth of + # g is at least 7 + girth = g.girth() + if girth == 4: + if verbose: + print "Two blocks intersect on >1 points." + return False + elif girth == 6: + if verbose: + print "Some point has two projections on some line." + return False + + if parameters: + s = self.is_uniform() + t = self.is_regular() + s = s-1 if (s is not False and s>=2) else False + t = t-1 if (t is not False and t>=2) else False + return (s,t) + else: + return True + def dual(self, algorithm=None): """ Return the dual of the incidence structure. diff --git a/src/sage/combinat/diagram_algebras.py b/src/sage/combinat/diagram_algebras.py index a063059f5ec..b4f977d86c0 100644 --- a/src/sage/combinat/diagram_algebras.py +++ b/src/sage/combinat/diagram_algebras.py @@ -27,17 +27,18 @@ CombinatorialFreeModuleElement) from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation +from sage.combinat.combinat import bell_number, catalan_number from sage.structure.global_options import GlobalOptions from sage.combinat.set_partition import SetPartitions, SetPartition from sage.combinat.partition import Partitions +from sage.combinat.symmetric_group_algebra import SymmetricGroupAlgebra_n from sage.combinat.permutation import Permutations -from sage.combinat.combinat import (bell_number, catalan_number) from sage.sets.set import Set from sage.graphs.graph import Graph from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute from sage.misc.flatten import flatten from sage.rings.all import ZZ -import operator BrauerDiagramOptions = GlobalOptions(name='Brauer diagram', doc=r""" @@ -1222,7 +1223,8 @@ def __init__(self, k, q, base_ring, prefix, diagrams, category=None): self._q = base_ring(q) self._k = k self._base_diagrams = diagrams - category = Algebras(base_ring).FiniteDimensional().WithBasis().or_subcategory(category) + category = Algebras(base_ring.category()).FiniteDimensional().WithBasis() + category = category.or_subcategory(category) CombinatorialFreeModule.__init__(self, base_ring, diagrams, category=category, prefix=prefix, bracket=False) @@ -1244,10 +1246,21 @@ def _element_constructor_(self, set_partition): True sage: D([{1,2},{-1,-2}]) == b_elt True + sage: S = SymmetricGroupAlgebra(R,2) + sage: D(S([2,1])) + P{{-2, 1}, {-1, 2}} + sage: D2 = da.DiagramAlgebra(2, x, R, 'P', da.PlanarDiagrams(2)) + sage: D2(S([1,2])) + P{{-2, 2}, {-1, 1}} + sage: D2(S([2,1])) + Traceback (most recent call last): + ... + ValueError: {{-2, 1}, {-1, 2}} is not an index of a basis element """ if self.basis().keys().is_parent_of(set_partition): return self.basis()[set_partition] - + if isinstance(set_partition, SymmetricGroupAlgebra_n.Element): + return self._apply_module_morphism(set_partition, self._perm_to_Blst, self) sp = self._base_diagrams(set_partition) # attempt conversion if sp in self.basis().keys(): return self.basis()[sp] @@ -1272,6 +1285,27 @@ def __getitem__(self, i): return self.basis()[i] raise ValueError("{0} is not an index of a basis element".format(i)) + def _perm_to_Blst(self, w): + """ + Convert the permutation ``w`` to an element of ``self``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: S = SymmetricGroupAlgebra(R,2) + sage: import sage.combinat.diagram_algebras as da + sage: D2 = da.DiagramAlgebra(2, x, R, 'P', da.PlanarDiagrams(2)) + sage: D2._perm_to_Blst([2,1]) + Traceback (most recent call last): + ... + ValueError: {{-2, 1}, {-1, 2}} is not an index of a basis element + """ + ## 'perm' is a permutation in one-line notation + ## turns w into an expression suitable for the element constructor. + u = sorted(w) + p = [[u[i],-x] for i,x in enumerate(w)] + return self[p] + def order(self): r""" Return the order of ``self``. @@ -1625,6 +1659,14 @@ class PartitionAlgebra(DiagramAlgebra): sage: a*a 17*P{{-1}, {1}} + Symmetric group algebra elements can also be coerced into the + partition algebra:: + + sage: S = SymmetricGroupAlgebra(SR, 2) + sage: A = PartitionAlgebra(2, x, SR) + sage: S([2,1])*A([[1,-1],[2,-2]]) + P{{-2, 1}, {-1, 2}} + REFERENCES: .. [HR2005] Tom Halverson and Arun Ram, *Partition algebras*. European @@ -1679,6 +1721,31 @@ def _repr_(self): return "Partition Algebra of rank {} with parameter {} over {}".format( self._k, self._q, self.base_ring()) + def _coerce_map_from_(self, R): + """ + Return a coerce map from ``R`` if one exists and ``None`` otherwise. + + EXAMPLES:: + + sage: R. = QQ[] + sage: S = SymmetricGroupAlgebra(R, 4) + sage: A = PartitionAlgebra(4, x, R) + sage: A._coerce_map_from_(S) + Generic morphism: + From: Symmetric group algebra of order 4 over Univariate Polynomial Ring in x over Rational Field + To: Partition Algebra of rank 4 with parameter x over Univariate Polynomial Ring in x over Rational Field + sage: Sp = SymmetricGroupAlgebra(QQ, 4) + sage: A._coerce_map_from_(Sp) + Generic morphism: + From: Symmetric group algebra of order 4 over Rational Field + To: Partition Algebra of rank 4 with parameter x over Univariate Polynomial Ring in x over Rational Field + """ + if isinstance(R, SymmetricGroupAlgebra_n): + if R.n == self._k and self.base_ring().has_coerce_map_from(R.base_ring()): + return R.module_morphism(self._perm_to_Blst, codomain=self) + return None + return super(PartitionAlgebra, self)._coerce_map_from_(R) + class SubPartitionAlgebra(DiagramAlgebra): """ A subalgebra of the partition algebra indexed by a subset of the diagrams. @@ -1695,15 +1762,11 @@ def __init__(self, k, q, base_ring, prefix, diagrams, category=None): True """ DiagramAlgebra.__init__(self, k, q, base_ring, prefix, diagrams, category) - amb = self.ambient() - self.module_morphism(self.lift, codomain=amb, - category=self.category()).register_as_coercion() - #These methods allow for a sub algebra to be correctly identified in a partition algebra + #These methods allow for a subalgebra to be correctly identified in a partition algebra def ambient(self): r""" Return the partition algebra ``self`` is a sub-algebra of. - Generally, this method is not called directly. EXAMPLES:: @@ -1712,13 +1775,12 @@ def ambient(self): sage: BA.ambient() Partition Algebra of rank 2 with parameter x over Symbolic Ring """ - return PartitionAlgebra(self._k, self._q, self.base_ring(), prefix=self._prefix) + return self.lift.codomain() - def lift(self, x): + @lazy_attribute + def lift(self): r""" - Lift a diagram subalgebra element to the corresponding element - in the ambient space. This method is not intended to be called - directly. + Return the lift map from diagram subalgebra to the ambient space. EXAMPLES:: @@ -1730,20 +1792,16 @@ def lift(self, x): sage: lifted.parent() is BA.ambient() True """ - if x not in self: - raise ValueError("{0} is not in {1}".format(x, self)) - monomial_indices = x.support() - monomial_coefficients = x.coefficients() - result = 0 - for i in xrange(len(monomial_coefficients)): - result += monomial_coefficients[i]*self.ambient().monomial(monomial_indices[i]) - return result + amb = PartitionAlgebra(self._k, self._q, self.base_ring(), prefix=self._prefix) + phi = self.module_morphism(lambda d: amb.monomial(d), + codomain=amb, category=self.category()) + phi.register_as_coercion() + return phi def retract(self, x): r""" Retract an appropriate partition algebra element to the - corresponding element in the partition subalgebra. This method - is not intended to be called directly. + corresponding element in the partition subalgebra. EXAMPLES:: @@ -1754,14 +1812,10 @@ def retract(self, x): sage: BA.retract(E) in BA True """ - if x not in self.ambient() or reduce(operator.mul, [(i in self._indices) for i in x.support()]) == 0: + if ( x not in self.ambient() + or any(i not in self._indices for i in x.support()) ): raise ValueError("{0} cannot retract to {1}".format(x, self)) - monomial_indices = x.support() - monomial_coefficients = x.coefficients() - result = self.zero() - for i in xrange(len(monomial_coefficients)): - result += monomial_coefficients[i]*self.monomial(monomial_indices[i]) - return result + return self._from_dict(x._monomial_coefficients, remove_zeros=False) class BrauerAlgebra(SubPartitionAlgebra): r""" @@ -1789,8 +1843,8 @@ class BrauerAlgebra(SubPartitionAlgebra): EXAMPLES: - We now define the Brauer algebra of rank `2` with parameter ``x`` over - `\ZZ`:: + We now define the Brauer algebra of rank `2` with parameter ``x`` + over `\ZZ`:: sage: R. = ZZ[] sage: B = BrauerAlgebra(2, x, R) @@ -1810,6 +1864,15 @@ class BrauerAlgebra(SubPartitionAlgebra): x*B{{-2, -1}, {1, 2}} sage: b[2]^5 x^4*B{{-2, -1}, {1, 2}} + + Note, also that since the symmetric group algebra is contained in + the Brauer algebra, there is also a conversion between the two. :: + + sage: R. = ZZ[] + sage: B = BrauerAlgebra(2, x, R) + sage: S = SymmetricGroupAlgebra(R, 2) + sage: S([2,1])*B([[1,-1],[2,-2]]) + B{{-2, 1}, {-1, 2}} """ global_options = BrauerDiagramOptions @@ -1857,6 +1920,32 @@ def _repr_(self): return "Brauer Algebra of rank {} with parameter {} over {}".format( self._k, self._q, self.base_ring()) + # TODO: Make a mixin class for diagram algebras that have coercions from SGA? + def _coerce_map_from_(self, R): + """ + Return a coerce map from ``R`` if one exists and ``None`` otherwise. + + EXAMPLES:: + + sage: R. = QQ[] + sage: S = SymmetricGroupAlgebra(R, 4) + sage: A = BrauerAlgebra(4, x, R) + sage: A._coerce_map_from_(S) + Generic morphism: + From: Symmetric group algebra of order 4 over Univariate Polynomial Ring in x over Rational Field + To: Brauer Algebra of rank 4 with parameter x over Univariate Polynomial Ring in x over Rational Field + sage: Sp = SymmetricGroupAlgebra(QQ, 4) + sage: A._coerce_map_from_(Sp) + Generic morphism: + From: Symmetric group algebra of order 4 over Rational Field + To: Brauer Algebra of rank 4 with parameter x over Univariate Polynomial Ring in x over Rational Field + """ + if isinstance(R, SymmetricGroupAlgebra_n): + if R.n == self._k and self.base_ring().has_coerce_map_from(R.base_ring()): + return R.module_morphism(self._perm_to_Blst, codomain=self) + return None + return super(BrauerAlgebra, self)._coerce_map_from_(R) + def _element_constructor_(self, set_partition): r""" Construct an element of ``self``. @@ -1878,6 +1967,45 @@ def _element_constructor_(self, set_partition): set_partition = to_Brauer_partition(set_partition, k = self.order()) return DiagramAlgebra._element_constructor_(self, set_partition) + def jucys_murphy(self, j): + r""" + Return the ``j``-th generalized Jucys-Murphy element of ``self``. + + The `j`-th Jucys-Murphy element of a Brauer algebra is simply + the `j`-th Jucys-Murphy element of the symmetric group algebra + with an extra `(z-1)/2` term, where ``z`` is the parameter + of the Brauer algebra. + + REFERENCES: + + .. [Naz96] Maxim Nazarov, Young's Orthogonal Form for Brauer's + Centralizer Algebra. Journal of Algebra 182 (1996), 664--693. + + EXAMPLES:: + + sage: z = var('z') + sage: B = BrauerAlgebra(3,z) + sage: B.jucys_murphy(1) + (1/2*z-1/2)*B{{-3, 3}, {-2, 2}, {-1, 1}} + sage: B.jucys_murphy(3) + -B{{-3, -2}, {-1, 1}, {2, 3}} - B{{-3, -1}, {-2, 2}, {1, 3}} + + B{{-3, 1}, {-2, 2}, {-1, 3}} + B{{-3, 2}, {-2, 3}, {-1, 1}} + + (1/2*z-1/2)*B{{-3, 3}, {-2, 2}, {-1, 1}} + """ + if j < 1: + raise ValueError("Jucys-Murphy index must be positive") + k = self.order() + if j > k: + raise ValueError("Jucys-Murphy index cannot be greater than the order of the algebra") + I = lambda x: self._indices(to_Brauer_partition(x, k=k)) + R = self.base_ring() + one = R.one() + d = {self.one_basis(): R( (self._q-1) / 2 )} + for i in range(1,j): + d[I([[i,-j],[j,-i]])] = one + d[I([[i,j],[-i,-j]])] = -one + return self._from_dict(d, remove_zeros=True) + class TemperleyLiebAlgebra(SubPartitionAlgebra): r""" A Temperley--Lieb algebra. @@ -1987,7 +2115,16 @@ def _element_constructor_(self, set_partition): True sage: TL([{1,2},{-1,-2}]) == b_elt True + sage: S = SymmetricGroupAlgebra(R, 2) + sage: TL(S([1,2])) + T{{-2, 2}, {-1, 1}} + sage: TL(S([2,1])) + Traceback (most recent call last): + ... + ValueError: {{-2, 1}, {-1, 2}} is not an index of a basis element """ + if isinstance(set_partition, SymmetricGroupAlgebra_n.Element): + return SubPartitionAlgebra._element_constructor_(self, set_partition) set_partition = to_Brauer_partition(set_partition, k = self.order()) return SubPartitionAlgebra._element_constructor_(self, set_partition) @@ -2146,7 +2283,7 @@ def __init__(self, k, q, base_ring, prefix): sage: TestSuite(I).run() # Not tested -- needs non-unital algebras category """ # This should be the category of non-unital fin-dim algebras with basis - category = Algebras(base_ring).FiniteDimensional().WithBasis() + category = Algebras(base_ring.category()).FiniteDimensional().WithBasis() SubPartitionAlgebra.__init__(self, k, q, base_ring, prefix, IdealDiagrams(k), category) diff --git a/src/sage/combinat/enumerated_sets.py b/src/sage/combinat/enumerated_sets.py index a39220a900e..2aa80c1895e 100644 --- a/src/sage/combinat/enumerated_sets.py +++ b/src/sage/combinat/enumerated_sets.py @@ -15,7 +15,7 @@ - :class:`~sage.combinat.subset.Subsets`, :class:`~sage.combinat.combination.Combinations` - :class:`~sage.combinat.permutation.Arrangements`, :class:`~sage.combinat.tuple.Tuples` - :class:`~sage.sets.finite_enumerated_set.FiniteEnumeratedSet` -- :class:`~DisjointUnionEnumeratedSets`, :class:`~CartesianProduct` +- :class:`~DisjointUnionEnumeratedSets` Integer lists ------------- @@ -123,7 +123,7 @@ - :ref:`sage.combinat.dlx` - :ref:`sage.combinat.matrices.dlxcpp` - :ref:`sage.combinat.species` -- :class:`~sage.combinat.integer_list.IntegerListsLex` +- :class:`~sage.combinat.integer_lists.IntegerListsLex` - :class:`~sage.combinat.integer_vectors_mod_permgroup.IntegerVectorsModPermutationGroup` Low level enumerated sets diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index d79c83937d0..330bdbc7210 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -49,6 +49,7 @@ :meth:`~FiniteStateMachine.predecessors` | List of predecessors of a state :meth:`~FiniteStateMachine.induced_sub_finite_state_machine` | Induced sub-machine :meth:`~FiniteStateMachine.accessible_components` | Accessible components + :meth:`~FiniteStateMachine.coaccessible_components` | Coaccessible components :meth:`~FiniteStateMachine.final_components` | Final components (connected components which cannot be left again) @@ -86,7 +87,9 @@ :meth:`~FiniteStateMachine.delete_transition` | Delete a transition :meth:`~FiniteStateMachine.remove_epsilon_transitions` | Remove epsilon transitions (not implemented) :meth:`~FiniteStateMachine.split_transitions` | Split transitions with input words of length ``> 1`` - :meth:`~FiniteStateMachine.determine_alphabets` | Determines input and output alphabets + :meth:`~FiniteStateMachine.determine_alphabets` | Determine input and output alphabets + :meth:`~FiniteStateMachine.determine_input_alphabet` | Determine input alphabet + :meth:`~FiniteStateMachine.determine_output_alphabet` | Determine output alphabet :meth:`~FiniteStateMachine.construct_final_word_out` | Construct final output by implicitly reading trailing letters; cf. :meth:`~FiniteStateMachine.with_final_word_out` @@ -110,6 +113,7 @@ :meth:`Automaton.is_equivalent` | Checks for equivalent automata :meth:`~FiniteStateMachine.is_Markov_chain` | Checks for a Markov chain :meth:`~FiniteStateMachine.is_monochromatic` | Checks whether the colors of all states are equal + :meth:`~FiniteStateMachine.number_of_words` | Determine the number of successful paths :meth:`~FiniteStateMachine.asymptotic_moments` | Main terms of expectation and variance of sums of labels :meth:`~FiniteStateMachine.moments_waiting_time` | Moments of the waiting time for first true output :meth:`~FiniteStateMachine.epsilon_successors` | Epsilon successors of a state @@ -2971,6 +2975,38 @@ class FiniteStateMachine(sage.structure.sage_object.SageObject): ValueError: with_final_word_out cannot be specified when copying another finite state machine. + :trac:`19454` rewrote automatic detection of the alphabets:: + + sage: def transition_function(state, letter): + ....: return (0, 3 + letter) + sage: T1 = Transducer(transition_function, + ....: input_alphabet=[0, 1], + ....: initial_states=[0], + ....: final_states=[0]) + sage: T1.output_alphabet + [3, 4] + sage: T2 = Transducer([(0, 0, 0, 3), (0, 0, 0, 4)], + ....: initial_states=[0], + ....: final_states=[0]) + sage: T2.output_alphabet + [3, 4] + sage: T = Transducer([(0, 0, 1, 2)]) + sage: (T.input_alphabet, T.output_alphabet) + ([1], [2]) + sage: T = Transducer([(0, 0, 1, 2)], determine_alphabets=False) + sage: (T.input_alphabet, T.output_alphabet) + (None, None) + sage: T = Transducer([(0, 0, 1, 2)], input_alphabet=[0, 1]) + sage: (T.input_alphabet, T.output_alphabet) + ([0, 1], [2]) + sage: T = Transducer([(0, 0, 1, 2)], output_alphabet=[2, 3]) + sage: (T.input_alphabet, T.output_alphabet) + ([1], [2, 3]) + sage: T = Transducer([(0, 0, 1, 2)], input_alphabet=[0, 1], + ....: output_alphabet=[2, 3]) + sage: (T.input_alphabet, T.output_alphabet) + ([0, 1], [2, 3]) + .. automethod:: __call__ """ @@ -3140,9 +3176,6 @@ def __init__(self, self.add_transition(L) else: raise TypeError('Wrong input data for transition.') - if determine_alphabets is None and input_alphabet is None \ - and output_alphabet is None: - determine_alphabets = True elif hasattr(data, '__iter__'): # data is a something that is iterable, # items are transitions @@ -3155,15 +3188,17 @@ def __init__(self, self.add_transition(transition) else: raise TypeError('Wrong input data for transition.') - if determine_alphabets is None and input_alphabet is None \ - and output_alphabet is None: - determine_alphabets = True elif hasattr(data, '__call__'): self.add_from_transition_function(data) else: raise TypeError('Cannot decide what to do with data.') - if determine_alphabets: + if determine_alphabets is None and data is not None: + if input_alphabet is None: + self.determine_input_alphabet() + if output_alphabet is None: + self.determine_output_alphabet() + elif determine_alphabets: self.determine_alphabets() if with_final_word_out is not None: @@ -5126,27 +5161,27 @@ def default_function(transitions): len(relabeledFSM.states()), dictionary) - def determine_alphabets(self, reset=True): + def determine_input_alphabet(self, reset=True): """ - Determines the input and output alphabet according to the - transitions in self. + Determine the input alphabet according to the transitions + of this finite state machine. INPUT: - - ``reset`` -- If reset is ``True``, then the existing input - and output alphabets are erased, otherwise new letters are - appended to the existing alphabets. + - ``reset`` -- a boolean (default: ``True``). If ``True``, then + the existing input alphabet is erased, otherwise new letters are + appended to the existing alphabet. OUTPUT: Nothing. - After this operation the input alphabet and the output - alphabet of self are a list of letters. + After this operation the input alphabet of this finite state machine + is a list of letters. .. TODO:: - At the moment, the letters of the alphabets need to be hashable. + At the moment, the letters of the alphabet need to be hashable. EXAMPLES:: @@ -5154,32 +5189,126 @@ def determine_alphabets(self, reset=True): ....: (2, 2, 1, 1), (2, 2, 0, 0)], ....: final_states=[1], ....: determine_alphabets=False) - sage: T.state(1).final_word_out = [1, 4] sage: (T.input_alphabet, T.output_alphabet) (None, None) - sage: T.determine_alphabets() + sage: T.determine_input_alphabet() sage: (T.input_alphabet, T.output_alphabet) - ([0, 1, 2], [0, 1, 4]) - """ + ([0, 1, 2], None) + + .. SEEALSO:: + + :meth:`determine_output_alphabet`, + :meth:`determine_alphabets`. + """ if reset: ain = set() - aout = set() else: ain = set(self.input_alphabet) - aout = set(self.output_alphabet) for t in self.iter_transitions(): for letter in t.word_in: ain.add(letter) + self.input_alphabet = list(ain) + + + def determine_output_alphabet(self, reset=True): + """ + Determine the output alphabet according to the transitions + of this finite state machine. + + INPUT: + + - ``reset`` -- a boolean (default: ``True``). If ``True``, then + the existing output alphabet is erased, otherwise new letters are + appended to the existing alphabet. + + OUTPUT: + + Nothing. + + After this operation the output alphabet of this finite state machine + is a list of letters. + + .. TODO:: + + At the moment, the letters of the alphabet need to be hashable. + + EXAMPLES:: + + sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1), + ....: (2, 2, 1, 1), (2, 2, 0, 0)], + ....: final_states=[1], + ....: determine_alphabets=False) + sage: T.state(1).final_word_out = [1, 4] + sage: (T.input_alphabet, T.output_alphabet) + (None, None) + sage: T.determine_output_alphabet() + sage: (T.input_alphabet, T.output_alphabet) + (None, [0, 1, 4]) + + .. SEEALSO:: + + :meth:`determine_input_alphabet`, + :meth:`determine_alphabets`. + """ + if reset: + aout = set() + else: + aout = set(self.output_alphabet) + + for t in self.iter_transitions(): for letter in t.word_out: aout.add(letter) for s in self.iter_final_states(): for letter in s.final_word_out: aout.add(letter) - self.input_alphabet = list(ain) self.output_alphabet = list(aout) + def determine_alphabets(self, reset=True): + """ + Determine the input and output alphabet according to the + transitions in this finite state machine. + + INPUT: + + - ``reset`` -- If reset is ``True``, then the existing input + and output alphabets are erased, otherwise new letters are + appended to the existing alphabets. + + OUTPUT: + + Nothing. + + After this operation the input alphabet and the output + alphabet of this finite state machine are a list of letters. + + .. TODO:: + + At the moment, the letters of the alphabets need to be hashable. + + EXAMPLES:: + + sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1), + ....: (2, 2, 1, 1), (2, 2, 0, 0)], + ....: final_states=[1], + ....: determine_alphabets=False) + sage: T.state(1).final_word_out = [1, 4] + sage: (T.input_alphabet, T.output_alphabet) + (None, None) + sage: T.determine_alphabets() + sage: (T.input_alphabet, T.output_alphabet) + ([0, 1, 2], [0, 1, 4]) + + .. SEEALSO:: + + :meth:`determine_input_alphabet`, + :meth:`determine_output_alphabet`. + """ + self.determine_input_alphabet(reset) + self.determine_output_alphabet(reset) + + #************************************************************************* # get states and transitions #************************************************************************* @@ -6988,7 +7117,7 @@ def epsilon_successors(self, state): def accessible_components(self): """ - Returns a new finite state machine with the accessible states + Return a new finite state machine with the accessible states of self and all transitions between those states. INPUT: @@ -7018,6 +7147,9 @@ def accessible_components(self): sage: F.accessible_components() Automaton with 1 state + .. SEEALSO:: + :meth:`coaccessible_components` + TESTS: Check whether input of length > 1 works:: @@ -7050,6 +7182,47 @@ def accessible(from_state, read): return result + def coaccessible_components(self): + r""" + Return the sub-machine induced by the coaccessible states of this + finite state machine. + + OUTPUT: + + A finite state machine of the same type as this finite state + machine. + + EXAMPLES:: + + sage: A = automata.ContainsWord([1, 1], + ....: input_alphabet=[0, 1]).complement().minimization().relabeled() + sage: A.transitions() + [Transition from 0 to 0: 0|-, + Transition from 0 to 0: 1|-, + Transition from 1 to 1: 0|-, + Transition from 1 to 2: 1|-, + Transition from 2 to 1: 0|-, + Transition from 2 to 0: 1|-] + sage: A.initial_states() + [1] + sage: A.final_states() + [1, 2] + sage: C = A.coaccessible_components() + sage: C.transitions() + [Transition from 1 to 1: 0|-, + Transition from 1 to 2: 1|-, + Transition from 2 to 1: 0|-] + + .. SEEALSO:: + :meth:`accessible_components`, + :meth:`induced_sub_finite_state_machine` + """ + DG = self.digraph().reverse() + coaccessible_states = DG.breadth_first_search( + [_.label() for _ in self.iter_final_states()]) + return self.induced_sub_finite_state_machine( + [self.state(_) for _ in coaccessible_states]) + # ************************************************************************* # creating new finite state machines # ************************************************************************* @@ -8402,14 +8575,15 @@ def projection(self, what='input'): return new - def transposition(self): + def transposition(self, reverse_output_labels=True): """ Returns a new finite state machine, where all transitions of the input finite state machine are reversed. INPUT: - Nothing. + - ``reverse_output_labels`` -- a boolean (default: ``True``): whether to reverse + output labels. OUTPUT: @@ -8429,6 +8603,28 @@ def transposition(self): sage: aut.transposition().initial_states() ['1', '2'] + :: + + sage: A = Automaton([(0, 1, [1, 0])], + ....: initial_states=[0], + ....: final_states=[1]) + sage: A([1, 0]) + True + sage: A.transposition()([0, 1]) + True + + :: + + sage: T = Transducer([(0, 1, [1, 0], [1, 0])], + ....: initial_states=[0], + ....: final_states=[1]) + sage: T([1, 0]) + [1, 0] + sage: T.transposition()([0, 1]) + [0, 1] + sage: T.transposition(reverse_output_labels=False)([0, 1]) + [1, 0] + TESTS: @@ -8448,6 +8644,11 @@ def transposition(self): """ from copy import deepcopy + if reverse_output_labels: + rewrite_output = lambda word: list(reversed(word)) + else: + rewrite_output = lambda word: word + transposition = self.empty_copy() for state in self.iter_states(): @@ -8456,7 +8657,8 @@ def transposition(self): for transition in self.iter_transitions(): transposition.add_transition( transition.to_state.label(), transition.from_state.label(), - transition.word_in, transition.word_out) + list(reversed(transition.word_in)), + rewrite_output(transition.word_out)) for initial in self.iter_initial_states(): state = transposition.state(initial.label()) @@ -9731,6 +9933,105 @@ def predecessors(self, state, valid_input=None): return(done) + def number_of_words(self, variable=sage.symbolic.ring.SR.var('n')): + r""" + Return the number of successful input words of given length. + + INPUT: + + - ``variable`` -- a symbol denoting the length of the words, + by default `n`. + + OUTPUT: + + A symbolic expression. + + EXAMPLES:: + + sage: NAFpm = Automaton([(0, 0, 0), (0, 1, 1), (0, 1, -1), (1, 0, 0)], + ....: initial_states=[0], + ....: final_states=[0, 1]) + sage: N = NAFpm.number_of_words(); N + 4/3*2^n - 1/3*(-1)^n + sage: all(len(list(NAFpm.language(_))) + ....: - len(list(NAFpm.language(_-1))) == N.subs(n=_) + ....: for _ in range(1, 6)) + True + sage: NAFp = Automaton([(0, 0, 0), (0, 1, 1), (1, 0, 0)], + ....: initial_states=[0], + ....: final_states=[0, 1]) + sage: N = NAFp.number_of_words(); N + 1.170820393249937?*1.618033988749895?^n + - 0.1708203932499369?*(-0.618033988749895?)^n + sage: all(len(list(NAFp.language(_))) + ....: - len(list(NAFp.language(_-1))) == N.subs(n=_) + ....: for _ in range(1, 6)) + True + + The adjacency matrix of the following example is a Jordan matrix of size 3 to + the eigenvalue 4:: + + sage: J3 = Automaton([(0, 1, -1), (1, 2, -1)], + ....: initial_states=[0], + ....: final_states=[0, 1, 2]) + sage: for i in range(3): + ....: for j in range(4): + ....: new_transition = J3.add_transition(i, i, j) + sage: J3.adjacency_matrix(entry=lambda t: 1) + [4 1 0] + [0 4 1] + [0 0 4] + sage: N = J3.number_of_words(); N + 1/2*4^(n - 2)*(n - 1)*n + 4^(n - 1)*n + 4^n + sage: all(len(list(J3.language(_))) + ....: - len(list(J3.language(_-1))) == N.subs(n=_) + ....: for _ in range(1, 6)) + True + + TESTS:: + + sage: A = Automaton([(0, 0, 0), (0, 1, 0)], + ....: initial_states=[0]) + sage: A.number_of_words() + Traceback (most recent call last): + ... + NotImplementedError: Finite State Machine must be deterministic. + """ + from sage.matrix.constructor import matrix + from sage.modules.free_module_element import vector + from sage.rings.arith import falling_factorial + from sage.rings.integer_ring import ZZ + from sage.rings.qqbar import QQbar + from sage.symbolic.ring import SR + + def jordan_block_power(block, exponent): + eigenvalue = SR(block[0, 0]) + return matrix(block.nrows(), + block.nrows(), + lambda i, j: eigenvalue**(exponent-(j-i))* + falling_factorial(exponent, j-i)/ZZ(j-i).factorial() + if j>= i else 0) + + def matrix_power(A, exponent): + J, T = A.jordan_form(QQbar, transformation=True) + assert T*J*T.inverse() == A + Jpower = matrix.block_diagonal( + [jordan_block_power(J.subdivision(j, j), exponent) + for j in range(len(J.subdivisions()[0])+1) ]) + P = Jpower.parent() + result = P(T)*Jpower*P(T).inverse() + assert all(result.subs(n=_) == A**_ for _ in range(5)) + return result + + if not self.is_deterministic(): + raise NotImplementedError("Finite State Machine must be deterministic.") + + left = vector(ZZ(s.is_initial) for s in self.iter_states()) + right = vector(ZZ(s.is_final) for s in self.iter_states()) + A = self.adjacency_matrix(entry=lambda t: 1) + return left*matrix_power(A, variable)*right + + def asymptotic_moments(self, variable=sage.symbolic.ring.SR.var('n')): r""" Returns the main terms of expectation and variance of the sum @@ -11025,13 +11326,7 @@ def determinisation(self): sage: A = Automaton([(0, 1, 1), (0, 2, [1, 1]), (0, 3, [1, 1, 1]), ....: (1, 0, -1), (2, 0, -2), (3, 0, -3)], ....: initial_states=[0], final_states=[0, 1, 2, 3]) - sage: B = A.determinisation().relabeled() - sage: all(t.to_state.label() == 2 for t in - ....: B.state(2).transitions) - True - sage: B.state(2).is_final - False - sage: B.delete_state(2) # this is a sink + sage: B = A.determinisation().relabeled().coaccessible_components() sage: sorted(B.transitions()) [Transition from 0 to 1: 1|-, Transition from 1 to 0: -1|-, diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index f75f58e56e9..3a9c4679b2a 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -12,23 +12,19 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element import Element, have_same_parent from sage.structure.parent import Parent -from sage.structure.element import have_same_parent from sage.structure.indexed_generators import IndexedGenerators -from sage.modules.free_module_element import vector from sage.misc.misc import repr_lincomb from sage.modules.module import Module from sage.rings.all import Integer import sage.structure.element from sage.combinat.family import Family from sage.sets.finite_enumerated_set import FiniteEnumeratedSet -from sage.combinat.cartesian_product import CartesianProduct +from sage.combinat.cartesian_product import CartesianProduct_iters from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets from sage.misc.cachefunc import cached_method -from sage.misc.all import lazy_attribute -from sage.categories.poor_man_map import PoorManMap +from sage.misc.lazy_attribute import lazy_attribute from sage.categories.all import Category, Sets, ModulesWithBasis from sage.combinat.dict_addition import dict_addition, dict_linear_combination -from sage.sets.family import Family from sage.typeset.ascii_art import AsciiArt, empty_ascii_art # TODO: move the content of this class to CombinatorialFreeModule.Element and ModulesWithBasis.Element @@ -131,12 +127,19 @@ def __hash__(self): """ return hash(frozenset(self._monomial_coefficients.items())) - def monomial_coefficients(self): + def monomial_coefficients(self, copy=True): """ Return the internal dictionary which has the combinatorial objects indexing the basis as keys and their corresponding coefficients as values. + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + EXAMPLES:: sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) @@ -168,6 +171,8 @@ def monomial_coefficients(self): sage: d[ Partition([3,2]) ] 2 """ + if copy: + return dict(self._monomial_coefficients) return self._monomial_coefficients def _sorted_items_for_printing(self): @@ -257,12 +262,10 @@ def _ascii_art_(self): s = empty_ascii_art # "" first = True - i = 0 if scalar_mult is None: scalar_mult = "*" - all_atomic = True for (monomial,c) in terms: b = repr_monomial(monomial) # PCR if c != 0: @@ -516,10 +519,10 @@ def _sub_(self, other): F = self.parent() return F._from_dict( dict_linear_combination( [ ( self._monomial_coefficients, 1 ), (other._monomial_coefficients, -1 ) ] ), remove_zeros=False ) - def _coefficient_fast(self, m, default=None): + def _coefficient_fast(self, m): """ - Returns the coefficient of m in self, where m is key in - self._monomial_coefficients. + Return the coefficient of ``m`` in ``self``, where ``m`` is key in + ``self._monomial_coefficients``. EXAMPLES:: @@ -536,238 +539,24 @@ def _coefficient_fast(self, m, default=None): sage: a._coefficient_fast(p) 1 - sage: a._coefficient_fast(p, 2) - 1 sage: a._coefficient_fast(q) 0 - sage: a._coefficient_fast(q, 2) - 2 sage: a[p] 1 sage: a[q] 0 """ - if default is None: - default = self.base_ring()(0) - return self._monomial_coefficients.get(m, default) + return self._monomial_coefficients.get(m, self.base_ring().zero()) __getitem__ = _coefficient_fast - def coefficient(self, m): - """ - EXAMPLES:: - - sage: s = CombinatorialFreeModule(QQ, Partitions()) - sage: z = s([4]) - 2*s([2,1]) + s([1,1,1]) + s([1]) - sage: z.coefficient([4]) - 1 - sage: z.coefficient([2,1]) - -2 - sage: z.coefficient(Partition([2,1])) - -2 - sage: z.coefficient([1,2]) - Traceback (most recent call last): - ... - AssertionError: [1, 2] should be an element of Partitions - sage: z.coefficient(Composition([2,1])) - Traceback (most recent call last): - ... - AssertionError: [2, 1] should be an element of Partitions - - Test that coefficient also works for those parents that do not yet have an element_class:: - - sage: G = DihedralGroup(3) - sage: F = CombinatorialFreeModule(QQ, G) - sage: hasattr(G, "element_class") - False - sage: g = G.an_element() - sage: (2*F.monomial(g)).coefficient(g) - 2 - """ - # NT: coefficient_fast should be the default, just with appropriate assertions - # that can be turned on or off - C = self.parent()._indices - assert m in C, "%s should be an element of %s"%(m, C) - if hasattr(C, "element_class") and not isinstance(m, C.element_class): - m = C(m) - return self._coefficient_fast(m) - - - def is_zero(self): - """ - Returns True if and only self == 0. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: f.is_zero() - False - sage: F.zero().is_zero() - True - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: s([2,1]).is_zero() - False - sage: s(0).is_zero() - True - sage: (s([2,1]) - s([2,1])).is_zero() - True - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - return all( v == zero for v in self._monomial_coefficients.values() ) - - def __len__(self): - """ - Returns the number of basis elements of self with nonzero - coefficients. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: len(f) - 2 - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) - sage: len(z) - 4 - """ - return self.length() - - def length(self): - """ - Returns the number of basis elements of self with nonzero - coefficients. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: f.length() - 2 - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) - sage: z.length() - 4 - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - return len( [ key for key, coeff in self._monomial_coefficients.iteritems() if coeff != zero ] ) - - def support(self): - """ - Returns a list of the combinatorial objects indexing the basis - elements of self which non-zero coefficients. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: f.support() - ['a', 'c'] - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) - sage: z.support() - [[1], [1, 1, 1], [2, 1], [4]] - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - - supp = sorted([ key for key, coeff in self._monomial_coefficients.iteritems() if coeff != zero ]) - - return supp - - def monomials(self): - """ - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] + 2*B['c'] - sage: f.monomials() - [B['a'], B['c']] - - sage: (F.zero()).monomials() - [] - """ - P = self.parent() - BR = P.base_ring() - zero = BR( 0 ) - one = BR( 1 ) - - supp = sorted([ key for key, coeff in self._monomial_coefficients.iteritems() if coeff != zero ]) - - return [ P._from_dict( { key : one }, remove_zeros=False ) for key in supp ] - - def terms(self): - """ - Returns a list of the terms of ``self`` - - .. seealso:: :meth:`monomials` - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] + 2*B['c'] - sage: f.terms() - [B['a'], 2*B['c']] - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - v = sorted([ ( key, value ) for key, value in self._monomial_coefficients.iteritems() if value != zero ]) - from_dict = self.parent()._from_dict - return [ from_dict( { key : value } ) for key,value in v ] - - def coefficients(self): - """ - Returns a list of the coefficients appearing on the basis elements in - self. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: f.coefficients() - [1, -3] - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) - sage: z.coefficients() - [1, 1, 1, 1] - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - v = sorted([ ( key, value ) for key, value in self._monomial_coefficients.iteritems() if value != zero ]) - return [ value for key,value in v ] - def _vector_(self, new_base_ring=None): """ Returns ``self`` as a dense vector INPUT: - - ``new_base_ring`` -- a ring (default: None) + - ``new_base_ring`` -- a ring (default: ``None``) OUTPUT: a dense :func:`FreeModule` vector @@ -808,8 +597,8 @@ def _vector_(self, new_base_ring=None): sage: a == QS3.from_vector(a.to_vector()) True - If ''new_base_ring'' is specified, then a vector over - ''new_base_ring'' is returned:: + If ``new_base_ring`` is specified, then a vector over + ``new_base_ring`` is returned:: sage: a._vector_(RDF) (2.0, 0.0, 0.0, 0.0, 0.0, 4.0) @@ -838,9 +627,10 @@ def _vector_(self, new_base_ring=None): to_vector = _vector_ - def _acted_upon_(self, scalar, self_on_left = False): + def _acted_upon_(self, scalar, self_on_left=False): """ - Returns the action of a scalar on self + Return the action of ``scalar`` (an element of the base ring) on + ``self``. EXAMPLES:: @@ -920,7 +710,7 @@ def _acted_upon_(self, scalar, self_on_left = False): def __div__(self, x, self_on_left=False ): """ - Division by coefficients + Division by coefficients. EXAMPLES:: @@ -937,19 +727,19 @@ def __div__(self, x, self_on_left=False ): sage: f/2 B[2] + 2*B[3] """ - if self.base_ring().is_field(): - F = self.parent() - x = self.base_ring()( x ) - x_inv = x**-1 - D = self._monomial_coefficients - if self_on_left: - D = dict_linear_combination( [ ( D, x_inv ) ], factor_on_left=False ) - else: - D = dict_linear_combination( [ ( D, x_inv ) ] ) + if not self.base_ring().is_field(): + return self.map_coefficients(lambda c: _divide_if_possible(c, x)) - return F._from_dict( D, remove_zeros=False ) + F = self.parent() + x = self.base_ring()( x ) + x_inv = x**-1 + D = self._monomial_coefficients + if self_on_left: + D = dict_linear_combination( [ ( D, x_inv ) ], factor_on_left=False ) else: - return self.map_coefficients(lambda c: _divide_if_possible(c, x)) + D = dict_linear_combination( [ ( D, x_inv ) ] ) + + return F._from_dict( D, remove_zeros=False ) def _divide_if_possible(x, y): """ @@ -1118,7 +908,8 @@ class CombinatorialFreeModule(UniqueRepresentation, Module, IndexedGenerators): [('bracket', None), ('generator_cmp', ), ('latex_bracket', False), ('latex_prefix', None), ('latex_scalar_mult', None), ('prefix', 'x'), - ('scalar_mult', '*'), ('tensor_symbol', None)] + ('scalar_mult', '*'), ('string_quotes', True), + ('tensor_symbol', None)] sage: e = F.basis() sage: e['a'] - 3 * e['b'] @@ -1288,10 +1079,11 @@ def __init__(self, R, basis_keys, element_class = None, category = None, prefix= if element_class is not None: self.Element = element_class - # The following is needed by e.g. root systems that don't call - # the classcall and passes lists as basis_keys - if isinstance(basis_keys, (list, tuple)): - basis_keys = FiniteEnumeratedSet(basis_keys) + # The following is to ensure that basis keys is indeed a parent. + # tuple/list are converted to FiniteEnumeratedSet and set/frozenset to + # Set + # (e.g. root systems passes lists) + basis_keys = Sets()(basis_keys, enumerated_set=True) # ignore the optional 'key' since it only affects CachedRepresentation kwds.pop('key', None) @@ -1406,7 +1198,7 @@ def __contains__(self, x): def _element_constructor_(self, x): """ - Coerce x into self. + Convert ``x`` into ``self``. EXAMPLES:: @@ -1505,7 +1297,6 @@ def _element_constructor_(self, x): 2*[1, 2, 3] """ R = self.base_ring() - eclass = self.element_class #Coerce ints to Integers if isinstance(x, int): @@ -1535,7 +1326,7 @@ def _element_constructor_(self, x): def _an_element_impl(self): """ - Returns an element of self, namely the zero element. + Return an element of ``self``, namely the zero element. EXAMPLES:: @@ -1549,7 +1340,7 @@ def _an_element_impl(self): def dimension(self): """ - Returns the dimension of the combinatorial algebra (which is given + Return the dimension of the free module (which is given by the number of elements in the basis). EXAMPLES:: @@ -1584,17 +1375,19 @@ def gens(self): def set_order(self, order): """ - Sets the order of the elements of the basis. + Set the order of the elements of the basis. If :meth:`set_order` has not been called, then the ordering is the one used in the generation of the elements of self's associated enumerated set. - .. WARNING:: Many cached methods depend on this order, in - particular for constructing subspaces and quotients. - Changing the order after some computations have been - cached does not invalidate the cache, and is likely to - introduce inconsistencies. + .. WARNING:: + + Many cached methods depend on this order, in + particular for constructing subspaces and quotients. + Changing the order after some computations have been + cached does not invalidate the cache, and is likely to + introduce inconsistencies. EXAMPLES:: @@ -1612,7 +1405,7 @@ def set_order(self, order): @cached_method def get_order(self): """ - Returns the order of the elements in the basis. + Return the order of the elements in the basis. EXAMPLES:: @@ -1679,7 +1472,7 @@ def _dense_free_module(self, base_ring=None): - ``base_ring`` -- a ring or ``None`` - If ``base_ring`` is None, then the base ring of ``self`` is used. + If ``base_ring`` is ``None``, then the base ring of ``self`` is used. This method is mostly used by :meth:`CombinatorialFreeModule.Element._vector_` @@ -1702,7 +1495,7 @@ def _dense_free_module(self, base_ring=None): def from_vector(self, vector): """ - Build an element of ``self`` from a (sparse) vector + Build an element of ``self`` from a (sparse) vector. .. SEEALSO:: :meth:`get_order`, :meth:`CombinatorialFreeModule.Element._vector_` @@ -1735,101 +1528,16 @@ def __cmp__(self, other): if c: return c return 0 - def _apply_module_morphism( self, x, on_basis, codomain=False ): - """ - Returns the image of ``x`` under the module morphism defined by - extending :func:`on_basis` by linearity. - - INPUT: - - - - ``x`` : a element of self - - - ``on_basis`` - a function that takes in a combinatorial - object indexing a basis element and returns an element of the - codomain - - - ``codomain`` (optional) - the codomain of the morphism, otherwise it is computed - using :func:`on_basis` - - If ``codomain`` is not specified, then the function tries to compute the codomain - of the module morphism by finding the image of one of the elements in the - support, hence :func:`on_basis` should return an element whose parent is the - codomain. - - EXAMPLES:: - - sage: s = SymmetricFunctions(QQ).schur() - sage: a = s([3]) + s([2,1]) + s([1,1,1]) - sage: b = 2*a - sage: f = lambda part: Integer( len(part) ) - sage: s._apply_module_morphism(a, f) #1+2+3 - 6 - sage: s._apply_module_morphism(b, f) #2*(1+2+3) - 12 - sage: s._apply_module_morphism(s(0), f) - 0 - sage: s._apply_module_morphism(s(1), f) - 0 - sage: s._apply_module_morphism(s(1), lambda part: len(part), ZZ) - 0 - sage: s._apply_module_morphism(s(1), lambda part: len(part)) - Traceback (most recent call last): - ... - ValueError: Codomain could not be determined - """ - - if x == self.zero(): - if not codomain: - B = Family(self.basis()) - try: - z = B.first() - except StopIteration: - raise ValueError('Codomain could not be determined') - codomain = on_basis(z).parent() - return codomain.zero() - else: - if not codomain: - keys = x.support() - key = keys[0] - try: - codomain = on_basis(key).parent() - except Exception: - raise ValueError('Codomain could not be determined') - - if hasattr( codomain, 'linear_combination' ): - return codomain.linear_combination( ( on_basis( key ), coeff ) for key, coeff in x._monomial_coefficients.iteritems() ) - else: - return_sum = codomain.zero() - for key, coeff in x._monomial_coefficients.iteritems(): - return_sum += coeff * on_basis( key ) - return return_sum - - def _apply_module_endomorphism(self, x, on_basis): - """ - This takes in a function from the basis elements to the elements of - self and applies it linearly to a. Note that - _apply_module_endomorphism does not require multiplication on - self to be defined. - - EXAMPLES:: - - sage: s = SymmetricFunctions(QQ).schur() - sage: f = lambda part: 2*s(part.conjugate()) - sage: s._apply_module_endomorphism( s([2,1]) + s([1,1,1]), f) - 2*s[2, 1] + 2*s[3] - """ - return self.linear_combination( ( on_basis( key ), coeff ) for key, coeff in x._monomial_coefficients.iteritems() ) - def sum(self, iter_of_elements): """ - overrides method inherited from commutative additive monoid as it is much faster on dicts directly + Return the sum of all elements in ``iter_of_elements``. - INPUT: + Overrides method inherited from commutative additive monoid as it + is much faster on dicts directly. - - ``iter_of_elements``: iterator of elements of ``self`` + INPUT: - Returns the sum of all elements in ``iter_of_elements`` + - ``iter_of_elements`` -- iterator of elements of ``self`` EXAMPLES:: @@ -1839,26 +1547,25 @@ def sum(self, iter_of_elements): sage: F.sum( f for _ in range(5) ) 10*B[1] + 10*B[2] """ - D = dict_addition( element._monomial_coefficients for element in iter_of_elements ) return self._from_dict( D, remove_zeros=False ) - def linear_combination( self, iter_of_elements_coeff, factor_on_left=True ): + def linear_combination(self, iter_of_elements_coeff, factor_on_left=True): """ + Return the linear combination `\lambda_1 v_1 + \cdots + + \lambda_k v_k` (resp. the linear combination `v_1 \lambda_1 + + \cdots + v_k \lambda_k`) where ``iter_of_elements_coeff`` iterates + through the sequence `((\lambda_1, v_1), ..., (\lambda_k, v_k))`. + INPUT: - ``iter_of_elements_coeff`` -- iterator of pairs ``(element, coeff)`` with ``element`` in ``self`` and ``coeff`` in ``self.base_ring()`` - - ``factor_on_left`` (optional) -- if ``True``, the coefficients are + - ``factor_on_left`` -- (optional) if ``True``, the coefficients are multiplied from the left if ``False``, the coefficients are multiplied from the right - Returns the linear combination `\lambda_1 v_1 + ... + \lambda_k v_k` - (resp. the linear combination `v_1 \lambda_1 + ... + v_k \lambda_k`) - where ``iter_of_elements_coeff`` iterates through the sequence - `(\lambda_1, v_1) ... (\lambda_k, v_k)`. - EXAMPLES:: sage: F = CombinatorialFreeModule(QQ,[1,2]) @@ -1871,7 +1578,7 @@ def linear_combination( self, iter_of_elements_coeff, factor_on_left=True ): def term(self, index, coeff=None): """ - Constructs a term in ``self`` + Construct a term in ``self``. INPUT: @@ -1902,15 +1609,14 @@ def _monomial(self, index): """ return self._from_dict( {index: self.base_ring().one()}, remove_zeros = False ) - # This is generic, and should be lifted into modules_with_basis @lazy_attribute def monomial(self): """ - INPUT: + Return the basis element indexed by ``i``. - - ''i'' + INPUT: - Returns the basis element indexed by i + - ``i`` -- an element of the index set EXAMPLES:: @@ -1918,61 +1624,24 @@ def monomial(self): sage: F.monomial('a') B['a'] - F.monomial is in fact (almost) a map:: + ``F.monomial`` is in fact (almost) a map:: sage: F.monomial Term map from {'a', 'b', 'c'} to Free module generated by {'a', 'b', 'c'} over Rational Field """ # Should use a real Map, as soon as combinatorial_classes are enumerated sets, and therefore parents + from sage.categories.poor_man_map import PoorManMap return PoorManMap(self._monomial, domain=self._indices, codomain=self, name="Term map") - def _sum_of_monomials(self, indices): - """ - TESTS:: - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) - sage: F._sum_of_monomials(['a', 'b']) - B['a'] + B['b'] - """ - # TODO: optimize by calling directly _from_dict if we - # know that all indices are distinct as sum_of_terms; - # otherwise, maybe call dict_addition directly - return self.sum(self.monomial(index) for index in indices) - - @lazy_attribute - def sum_of_monomials(self): - """ - INPUT: - - - ''indices'' -- an list (or iterable) of indices of basis elements - - Returns the sum of the corresponding basis elements - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) - sage: F.sum_of_monomials(['a', 'b']) - B['a'] + B['b'] - - sage: F.sum_of_monomials(['a', 'b', 'a']) - 2*B['a'] + B['b'] - - F.sum_of_monomials is in fact (almost) a map:: - - sage: F.sum_of_monomials - A map to Free module generated by {'a', 'b', 'c'} over Rational Field - """ - # domain = sets of self.combinatorial_class(), - return PoorManMap(self._sum_of_monomials, codomain = self) - def sum_of_terms(self, terms, distinct=False): """ - Constructs a sum of terms of ``self`` + Construct a sum of terms of ``self``. INPUT: - - ``terms`` -- a list (or iterable) of pairs (index, coeff) - - ``distinct`` -- whether the indices are guaranteed to be distinct (default: ``False``) + - ``terms`` -- a list (or iterable) of pairs ``(index, coeff)`` + - ``distinct`` -- (default: ``False``) whether the indices are + guaranteed to be distinct EXAMPLES:: @@ -1985,7 +1654,7 @@ def sum_of_terms(self, terms, distinct=False): sage: F.sum_of_terms([('a',2), ('c',3)], distinct = True) 2*B['a'] + 3*B['c'] - .. warning:: + .. WARNING:: Use ``distinct=True`` only if you are sure that the indices are indeed distinct:: @@ -2002,20 +1671,6 @@ def sum_of_terms(self, terms, distinct=False): return self._from_dict(dict(terms)) return self.sum(self.term(index, coeff) for (index, coeff) in terms) - def monomial_or_zero_if_none(self, i): - """ - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) - sage: F.monomial_or_zero_if_none('a') - B['a'] - sage: F.monomial_or_zero_if_none(None) - 0 - """ - if i is None: - return self.zero() - return self.monomial(i) - @cached_method def zero(self): """ @@ -2027,18 +1682,19 @@ def zero(self): """ return self._from_dict({}, remove_zeros=False) - def _from_dict( self, d, coerce=False, remove_zeros=True ): - """ - Construct an element of ``self`` from an `{index: coefficient}` dictionary. + def _from_dict(self, d, coerce=False, remove_zeros=True): + r""" + Construct an element of ``self`` from an ``{index: coefficient}`` + dictionary. INPUT: - - ``d`` -- a dictionary ``{index: coeff}`` where each ``index`` is the - index of a basis element and each ``coeff`` belongs to the + - ``d`` -- a dictionary ``{index: coeff}`` where each ``index`` is + the index of a basis element and each ``coeff`` belongs to the coefficient ring ``self.base_ring()`` - - ``coerce`` -- a boolean (default: ``False``), whether to coerce the - coefficients ``coeff`` to the coefficient ring + - ``coerce`` -- a boolean (default: ``False``), whether to coerce + the coefficients ``coeff`` to the coefficient ring - ``remove_zeros`` -- a boolean (default: ``True``), if some coefficients ``coeff`` may be zero and should therefore be removed @@ -2067,7 +1723,7 @@ def _from_dict( self, d, coerce=False, remove_zeros=True ): sage: s._from_dict({part:0}) 0 - .. warning:: + .. WARNING:: With ``remove_zeros=True``, it is assumed that no coefficient of the dictionary is zero. Otherwise, this may @@ -2079,9 +1735,9 @@ def _from_dict( self, d, coerce=False, remove_zeros=True ): assert isinstance(d, dict) if coerce: R = self.base_ring() - d = dict( (key, R(coeff)) for key,coeff in d.iteritems()) + d = {key: R(coeff) for key,coeff in d.iteritems()} if remove_zeros: - d = dict( (key, coeff) for key, coeff in d.iteritems() if coeff) + d = {key: coeff for key, coeff in d.iteritems() if coeff} return self.element_class( self, d ) class CombinatorialFreeModule_Tensor(CombinatorialFreeModule): @@ -2194,7 +1850,7 @@ def __init__(self, modules, **options): """ from sage.categories.tensor import tensor self._sets = modules - CombinatorialFreeModule.__init__(self, modules[0].base_ring(), CartesianProduct(*[module.basis().keys() for module in modules]).map(tuple), **options) + CombinatorialFreeModule.__init__(self, modules[0].base_ring(), CartesianProduct_iters(*[module.basis().keys() for module in modules]).map(tuple), **options) # the following is not the best option, but it's better than nothing. self._print_options['tensor_symbol'] = options.get('tensor_symbol', tensor.symbol) @@ -2686,3 +2342,4 @@ class Element(CombinatorialFreeModule.Element): # TODO: get rid of this inherita pass CombinatorialFreeModule.CartesianProduct = CombinatorialFreeModule_CartesianProduct + diff --git a/src/sage/combinat/fully_packed_loop.py b/src/sage/combinat/fully_packed_loop.py index 0b053c3809c..a2c3ad3d1da 100644 --- a/src/sage/combinat/fully_packed_loop.py +++ b/src/sage/combinat/fully_packed_loop.py @@ -875,6 +875,37 @@ def plot(self): G.axes(False) return G + def gyration(self): + r""" + Return the fully packed loop obtained by applying gyration + to the alternating sign matrix in bijection with ``self``. + + Gyration was first defined in [Wieland00]_ as an action on + fully-packed loops. + + REFERENCES: + + .. [Wieland00] B. Wieland. *A large dihedral symmetry of the set of + alternating sign matrices*. Electron. J. Combin. 7 (2000). + + EXAMPLES:: + + sage: A = AlternatingSignMatrix([[1, 0, 0],[0, 1, 0],[0, 0, 1]]) + sage: fpl = FullyPackedLoop(A) + sage: fpl.gyration().to_alternating_sign_matrix() + [0 0 1] + [0 1 0] + [1 0 0] + sage: asm = AlternatingSignMatrix([[0, 0, 1],[1, 0, 0],[0, 1, 0]]) + sage: f = FullyPackedLoop(asm) + sage: f.gyration().to_alternating_sign_matrix() + [0 1 0] + [0 0 1] + [1 0 0] + """ + return FullyPackedLoop(self.to_alternating_sign_matrix().gyration()) + + def link_pattern(self): """ Return a link pattern corresponding to a fully packed loop. @@ -1416,4 +1447,4 @@ def _an_element_(self): #ASM = AlternatingSignMatrix(matrix.identity(self._n)) #SVM = ASM.to_six_vertex_model() SVM = SixVertexModel(self._n,boundary_conditions='ice').an_element() - return self.element_class(self, SVM) \ No newline at end of file + return self.element_class(self, SVM) diff --git a/src/sage/combinat/integer_list.py b/src/sage/combinat/integer_list.py index 350c83cf3e4..136d7baa95f 100644 --- a/src/sage/combinat/integer_list.py +++ b/src/sage/combinat/integer_list.py @@ -1,2400 +1,15 @@ -r""" -Enumerated set of lists of integers with constraints, in inverse lexicographic order - -- :class:`IntegerListsLex`: the enumerated set of lists of nonnegative - integers with specified constraints, in inverse lexicographic order. - -- :class:`Envelope`: a utility class for upper (lower) envelope of a - function under constraints. - -HISTORY: - -This generic tool was originally written by Hivert and Thiery in -MuPAD-Combinat in 2000 and ported over to Sage by Mike Hansen in -2007. It was then completely rewritten in 2015 by Gillespie, -Schilling, and Thiery, with the help of many, to deal with -limitations and lack of robustness w.r.t. input. """ -#***************************************************************************** -# Copyright (C) 2015 Bryan Gillespie -# Nicolas M. Thiery -# Anne Schilling -# -# 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 inspect import ismethod -from sage.misc.classcall_metaclass import ClasscallMetaclass, typecall -from sage.misc.constant_function import ConstantFunction -from sage.misc.cachefunc import cached_method -from sage.categories.enumerated_sets import EnumeratedSets -from sage.structure.list_clone import ClonableArray -from sage.structure.parent import Parent -from sage.sets.family import Family -from sage.rings.integer_ring import ZZ - -Infinity = float('+inf') - -class IntegerListsLex(Parent): - r""" - Lists of nonnegative integers with constraints, in inverse lexicographic order. - - An *integer list* is a list `l` of nonnegative integers, its *parts*. The - slope (at position `i`) is the difference ``l[i+1]-l[i]`` between two - consecutive parts. - - This class allows to construct the set `S` of all integer lists - `l` satisfying specified bounds on the sum, the length, the slope, - and the individual parts, enumerated in *inverse* lexicographic - order, that is from largest to smallest in lexicographic - order. Note that, to admit such an enumeration, `S` is almost - necessarily finite (see :ref:`IntegerListsLex_finiteness`). - - The main purpose is to provide a generic iteration engine for all the - enumerated sets like :class:`Partitions`, :class:`Compositions`, - :class:`IntegerVectors`. It can also be used to generate many other - combinatorial objects like Dyck paths, Motzkin paths, etc. Mathematically - speaking, this is a special case of set of integral points of a polytope (or - union thereof, when the length is not fixed). - - INPUT: - - - ``min_sum`` -- a nonnegative integer (default: 0): - a lower bound on ``sum(l)``. - - - ``max_sum`` -- a nonnegative integer or `\infty` (default: `\infty`): - an upper bound on ``sum(l)``. - - - ``n`` -- a nonnegative integer (optional): if specified, this - overrides ``min_sum`` and ``max_sum``. - - - ``min_length`` -- a nonnegative integer (default: `0`): a lower - bound on ``len(l)``. - - - ``max_length`` -- a nonnegative integer or `\infty` (default: - `\infty`): an upper bound on ``len(l)``. - - - ``length`` -- an integer (optional); overrides ``min_length`` - and ``max_length`` if specified; - - - ``min_part`` -- a nonnegative integer: a lower bounds on all - parts: ``min_part <= l[i]`` for ``0 <= i < len(l)``. - - - ``floor`` -- a list of nonnegative integers or a function: lower - bounds on the individual parts `l[i]`. - - If ``floor`` is a list of integers, then ``floor<=l[i]`` for ``0 - <= i < min(len(l), len(floor)``. Similarly, if ``floor`` is a - function, then ``floor(i) <= l[i]`` for ``0 <= i < len(l)``. - - - ``max_part`` -- a nonnegative integer or `\infty`: an upper - bound on all parts: ``l[i] <= max_part`` for ``0 <= i < len(l)``. - - - ``ceiling`` -- upper bounds on the individual parts ``l[i]``; - this takes the same type of input as ``floor``, except that - `\infty` is allowed in addition to integers, and the default - value is `\infty`. - - - ``min_slope`` -- an integer or `-\infty` (default: `-\infty`): - an lower bound on the slope between consecutive parts: - ``min_slope <= l[i+1]-l[i]`` for ``0 <= i < len(l)-1`` - - - ``max_slope`` -- an integer or `+\infty` (defaults: `+\infty`) - an upper bound on the slope between consecutive parts: - ``l[i+1]-l[i] <= max_slope`` for ``0 <= i < len(l)-1`` - - - ``category`` -- a category (default: :class:`FiniteEnumeratedSets`) - - - ``check`` -- boolean (default: ``True``): whether to display the - warnings raised when functions are given as input to ``floor`` - or ``ceiling`` and the errors raised when there is no proper - enumeration. - - - ``name`` -- a string or ``None`` (default: ``None``) if set, - this will be passed down to :meth:`Parent.rename` to specify the - name of ``self``. It is recommented to use directly the rename - method as this feature may become deprecated. - - - ``element_constructor`` -- a function (or callable) that creates - elements of ``self`` from a list. See also :class:`Parent`. - - - ``element_class`` -- a class for the elements of ``self`` - (default: `ClonableArray`). This merely sets the attribute - ``self.Element``. See the examples for details. - - - ``global_options`` -- a :class:`~sage.structure.global_options.GlobalOptions` - object that will be assigned to the attribute - ``_global_options``; for internal use only (subclasses, ...). - - - .. NOTE:: - - When several lists satisfying the constraints differ only by - trailing zeroes, only the shortest one is enumerated (and - therefore counted). The others are still considered valid. - See the examples below. - - This feature is questionable. It is recommended not to rely on - it, as it may eventually be discontinued. - - EXAMPLES: - - We create the enumerated set of all lists of nonnegative integers - of length `3` and sum `2`:: - - sage: C = IntegerListsLex(2, length=3) - sage: C - Integer lists of sum 2 satisfying certain constraints - sage: C.cardinality() - 6 - sage: [p for p in C] - [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] - - sage: [2, 0, 0] in C - True - sage: [2, 0, 1] in C - False - sage: "a" in C - False - sage: ["a"] in C - False - sage: C.first() - [2, 0, 0] - - One can specify lower and upper bounds on each part:: - - sage: list(IntegerListsLex(5, length=3, floor=[1,2,0], ceiling=[3,2,3])) - [[3, 2, 0], [2, 2, 1], [1, 2, 2]] - - When the length is fixed as above, one can also use - :class:`IntegerVectors`:: - - sage: IntegerVectors(2,3).list() - [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] - - Using the slope condition, one can generate integer partitions - (but see :class:`Partitions`):: - - sage: list(IntegerListsLex(4, max_slope=0)) - [[4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]] - - The following is the list of all partitions of `7` with parts at least `2`:: - - sage: list(IntegerListsLex(7, max_slope=0, min_part=2)) - [[7], [5, 2], [4, 3], [3, 2, 2]] - - - .. RUBRIC:: floor and ceiling conditions - - Next we list all partitions of `5` of length at most `3` which are - bounded below by ``[2,1,1]``:: - - sage: list(IntegerListsLex(5, max_slope=0, max_length=3, floor=[2,1,1])) - [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] - - Note that ``[5]`` is considered valid, because the floor - constraints only apply to existing positions in the list. To - obtain instead the partitions containing ``[2,1,1]``, one needs to - use ``min_length`` or ``length``:: - - sage: list(IntegerListsLex(5, max_slope=0, length=3, floor=[2,1,1])) - [[3, 1, 1], [2, 2, 1]] - - Here is the list of all partitions of `5` which are contained in - ``[3,2,2]``:: - - sage: list(IntegerListsLex(5, max_slope=0, max_length=3, ceiling=[3,2,2])) - [[3, 2], [3, 1, 1], [2, 2, 1]] - - This is the list of all compositions of `4` (but see :class:`Compositions`):: - - sage: list(IntegerListsLex(4, min_part=1)) - [[4], [3, 1], [2, 2], [2, 1, 1], [1, 3], [1, 2, 1], [1, 1, 2], [1, 1, 1, 1]] - - This is the list of all integer vectors of sum `4` and length `3`:: - - sage: list(IntegerListsLex(4, length=3)) - [[4, 0, 0], [3, 1, 0], [3, 0, 1], [2, 2, 0], [2, 1, 1], - [2, 0, 2], [1, 3, 0], [1, 2, 1], [1, 1, 2], [1, 0, 3], - [0, 4, 0], [0, 3, 1], [0, 2, 2], [0, 1, 3], [0, 0, 4]] - - For whatever it is worth, the ``floor`` and ``min_part`` - constraints can be combined:: - - sage: L = IntegerListsLex(5, floor=[2,0,2], min_part=1) - sage: L.list() - [[5], [4, 1], [3, 2], [2, 3], [2, 1, 2]] - - This is achieved by updating the floor upon constructing ``L``:: - - sage: [L._floor(i) for i in range(5)] - [2, 1, 2, 1, 1] - - Similarly, the ``ceiling`` and ``max_part`` constraints can be - combined:: - - sage: L = IntegerListsLex(4, ceiling=[2,3,1], max_part=2, length=3) - sage: L.list() - [[2, 2, 0], [2, 1, 1], [1, 2, 1]] - sage: [L._ceiling(i) for i in range(5)] - [2, 2, 1, 2, 2] - - - This can be used to generate Motzkin words (see - :wikipedia:`Motzkin_number`):: - - sage: def motzkin_words(n): - ....: return IntegerListsLex(length=n+1, min_slope=-1, max_slope=1, - ....: ceiling=[0]+[+oo for i in range(n-1)]+[0]) - sage: motzkin_words(4).list() - [[0, 1, 2, 1, 0], - [0, 1, 1, 1, 0], - [0, 1, 1, 0, 0], - [0, 1, 0, 1, 0], - [0, 1, 0, 0, 0], - [0, 0, 1, 1, 0], - [0, 0, 1, 0, 0], - [0, 0, 0, 1, 0], - [0, 0, 0, 0, 0]] - sage: [motzkin_words(n).cardinality() for n in range(8)] - [1, 1, 2, 4, 9, 21, 51, 127] - sage: oeis(_) # optional -- internet - 0: A001006: Motzkin numbers: number of ways of drawing any number - of nonintersecting chords joining n (labeled) points on a circle. - - or Dyck words (see also :class:`DyckWords`), through the bijection - with paths from `(0,0)` to `(n,n)` with left and up steps that remain - below the diagonal:: - - sage: def dyck_words(n): - ....: return IntegerListsLex(length=n, ceiling=range(n+1), min_slope=0) - sage: [dyck_words(n).cardinality() for n in range(8)] - [1, 1, 2, 5, 14, 42, 132, 429] - sage: dyck_words(3).list() - [[0, 1, 2], [0, 1, 1], [0, 0, 2], [0, 0, 1], [0, 0, 0]] - - - .. _IntegerListsLex_finiteness: - - .. RUBRIC:: On finiteness and inverse lexicographic enumeration - - The set of all lists of integers cannot be enumerated in inverse - lexicographic order, since there is no largest list (take `[n]` - for `n` as large as desired):: - - sage: IntegerListsLex().first() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - Here is a variant which could be enumerated in lexicographic order - but not in inverse lexicographic order:: - - sage: L = IntegerListsLex(length=2, ceiling=[Infinity, 0], floor=[0,1]) - sage: for l in L: print l - Traceback (most recent call last): - ... - ValueError: infinite upper bound for values of m - - Even when the sum is specified, it is not necessarily possible to - enumerate *all* elements in inverse lexicographic order. In the - following example, the list ``[1, 1, 1]`` will never appear in the - enumeration:: - - sage: IntegerListsLex(3).first() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - If one wants to proceed anyway, one can sign a waiver by setting - ``check=False`` (again, be warned that some valid lists may never appear):: - - sage: L = IntegerListsLex(3, check=False) - sage: it = iter(L) - sage: [next(it) for i in range(6)] - [[3], [2, 1], [2, 0, 1], [2, 0, 0, 1], [2, 0, 0, 0, 1], [2, 0, 0, 0, 0, 1]] - - In fact, being inverse lexicographically enumerable is almost - equivalent to being finite. The only infinity that can occur would - be from a tail of numbers `0,1` as in the previous example, where - the `1` moves further and further to the right. If there is any - list that is inverse lexicographically smaller than such a - configuration, the iterator would not reach it and hence would not - be considered iterable. Given that the infinite cases are very - specific, at this point only the finite cases are supported - (without signing the waiver). - - The finiteness detection is not complete yet, so some finite cases - may not be supported either, at least not without disabling the - checks. Practical examples of such are welcome. - - .. RUBRIC:: On trailing zeroes, and their caveats - - As mentioned above, when several lists satisfying the constraints - differ only by trailing zeroes, only the shortest one is listed:: - - sage: L = IntegerListsLex(max_length=4, max_part=1) - sage: L.list() - [[1, 1, 1, 1], - [1, 1, 1], - [1, 1, 0, 1], - [1, 1], - [1, 0, 1, 1], - [1, 0, 1], - [1, 0, 0, 1], - [1], - [0, 1, 1, 1], - [0, 1, 1], - [0, 1, 0, 1], - [0, 1], - [0, 0, 1, 1], - [0, 0, 1], - [0, 0, 0, 1], - []] - - and counted:: - - sage: L.cardinality() - 16 - - Still, the others are considered as elements of `L`:: - - sage: L = IntegerListsLex(4,min_length=3,max_length=4) - sage: L.list() - [..., [2, 2, 0], ...] - - sage: [2, 2, 0] in L # in L.list() - True - sage: [2, 2, 0, 0] in L # not in L.list() ! - True - sage: [2, 2, 0, 0, 0] in L - False - - .. RUBRIC:: Specifying functions as input for the floor or ceiling - - We construct all lists of sum `4` and length `4` such that ``l[i] <= i``:: - - sage: list(IntegerListsLex(4, length=4, ceiling=lambda i: i, check=False)) - [[0, 1, 2, 1], [0, 1, 1, 2], [0, 1, 0, 3], [0, 0, 2, 2], [0, 0, 1, 3]] - - .. WARNING:: - - When passing a function as ``floor`` or ``ceiling``, it may - become undecidable to detect improper inverse lexicographic - enumeration. For example, the following example has a finite - enumeration:: - - sage: L = IntegerListsLex(3, floor=lambda i: 1 if i>=2 else 0, check=False) - sage: L.list() - [[3], - [2, 1], - [2, 0, 1], - [1, 2], - [1, 1, 1], - [1, 0, 2], - [1, 0, 1, 1], - [0, 3], - [0, 2, 1], - [0, 1, 2], - [0, 1, 1, 1], - [0, 0, 3], - [0, 0, 2, 1], - [0, 0, 1, 2], - [0, 0, 1, 1, 1]] - - but one cannot decide whether the following has an improper - inverse lexicographic enumeration without computing the floor - all the way to ``Infinity``:: - - sage: L = IntegerListsLex(3, floor=lambda i: 0, check=False) - sage: it = iter(L) - sage: [next(it) for i in range(6)] - [[3], [2, 1], [2, 0, 1], [2, 0, 0, 1], [2, 0, 0, 0, 1], [2, 0, 0, 0, 0, 1]] - - Hence a warning is raised when a function is specified as - input, unless the waiver is signed by setting ``check=False``:: - - sage: L = IntegerListsLex(3, floor=lambda i: 1 if i>=2 else 0) - doctest:... - A function has been given as input of the floor=[...] or ceiling=[...] - arguments of IntegerListsLex. Please see the documentation for the caveats. - If you know what you are doing, you can set check=False to skip this warning. - - Similarly, the algorithm may need to search forever for a - solution when the ceiling is ultimately zero:: - - sage: L = IntegerListsLex(2,ceiling=lambda i:0, check=False) - sage: L.first() # not tested: will hang forever - sage: L = IntegerListsLex(2,ceiling=lambda i:0 if i<20 else 1, check=False) - sage: it = iter(L) - sage: next(it) - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] - sage: next(it) - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1] - sage: next(it) - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1] - - - .. RUBRIC:: Tip: using disjoint union enumerated sets for additional flexibility - - Sometimes, specifying a range for the sum or the length may be too - restrictive. One would want instead to specify a list, or - iterable `L`, of acceptable values. This is easy to achieve using - a :class:`disjoint union of enumerated sets `. - Here we want to accept the values `n=0,2,3`:: - - sage: C = DisjointUnionEnumeratedSets(Family([0,2,3], - ....: lambda n: IntegerListsLex(n, length=2))) - sage: C - Disjoint union of Finite family - {0: Integer lists of sum 0 satisfying certain constraints, - 2: Integer lists of sum 2 satisfying certain constraints, - 3: Integer lists of sum 3 satisfying certain constraints} - sage: C.list() - [[0, 0], - [2, 0], [1, 1], [0, 2], - [3, 0], [2, 1], [1, 2], [0, 3]] - - The price to pay is that the enumeration order is now *graded - lexicographic* instead of lexicographic: first choose the value - according to the order specified by `L`, and use lexicographic - order within each value. Here is we reverse `L`:: - - sage: DisjointUnionEnumeratedSets(Family([3,2,0], - ....: lambda n: IntegerListsLex(n, length=2))).list() - [[3, 0], [2, 1], [1, 2], [0, 3], - [2, 0], [1, 1], [0, 2], - [0, 0]] - - Note that if a given value appears several times, the - corresponding elements will be enumerated several times, which - may, or not, be what one wants:: - - sage: DisjointUnionEnumeratedSets(Family([2,2], - ....: lambda n: IntegerListsLex(n, length=2))).list() - [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] - - Here is a variant where we specify acceptable values for the - length:: - - sage: DisjointUnionEnumeratedSets(Family([0,1,3], - ....: lambda l: IntegerListsLex(2, length=l))).list() - [[2], - [2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] - - - This technique can also be useful to obtain a proper enumeration - on infinite sets by using a graded lexicographic enumeration:: - - sage: C = DisjointUnionEnumeratedSets(Family(NN, - ....: lambda n: IntegerListsLex(n, length=2))) - sage: C - Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} - sage: it = iter(C) - sage: [next(it) for i in range(10)] - [[0, 0], - [1, 0], [0, 1], - [2, 0], [1, 1], [0, 2], - [3, 0], [2, 1], [1, 2], [0, 3]] - - - .. RUBRIC:: Specifying how to construct elements - - This is the list of all monomials of degree `4` which divide the - monomial `x^3y^1z^2` (a monomial being identified with its - exponent vector):: - - sage: R. = QQ[] - sage: m = [3,1,2] - sage: def term(exponents): - ....: return x^exponents[0] * y^exponents[1] * z^exponents[2] - sage: list( IntegerListsLex(4, length=len(m), ceiling=m, element_constructor=term) ) - [x^3*y, x^3*z, x^2*y*z, x^2*z^2, x*y*z^2] - - Note the use of the ``element_constructor`` option to specify how - to construct elements from a plain list. - - A variant is to specify a class for the elements. With the default - element constructor, this class should take as input the parent - ``self`` and a list. Here we want the elements to be constructed - in the class :class:`Partition`:: - - sage: IntegerListsLex(3, max_slope=0, element_class=Partition, global_options=Partitions.global_options).list() - [[3], [2, 1], [1, 1, 1]] - - Note that the :class:`Partition` further assumes the existence of - an attribute ``_global_options`` in the parent, hence the use of the - ``global_options`` parameter. - - .. WARNING:: - - The protocol for specifying the element class and constructor - is subject to changes. - - ALGORITHM: - - The iteration algorithm uses a depth first search through the - prefix tree of the list of integers (see also - :ref:`section-generic-integerlistlex`). While doing so, it does - some lookahead heuristics to attempt to cut dead branches. - - In most practical use cases, most dead branches are cut. Then, - roughly speaking, the time needed to iterate through all the - elements of `S` is proportional to the number of elements, where - the proportion factor is controlled by the length `l` of the - longest element of `S`. In addition, the memory usage is also - controlled by `l`, which is to say negligible in practice. - - Still, there remains much room for efficiency improvements; see - :trac:`18055`, :trac:`18056`. - - .. NOTE:: - - The generation algorithm could in principle be extended to - deal with non-constant slope constraints and with negative - parts. - - TESTS: - - This example from the combinatorics tutorial used to fail before - :trac:`17979` because the floor conditions did not satisfy the - slope conditions:: - - sage: I = IntegerListsLex(16, min_length=2, max_slope=-1, floor=[5,3,3]) - sage: I.list() - [[13, 3], [12, 4], [11, 5], [10, 6], [9, 7], [9, 4, 3], [8, 5, 3], [8, 4, 3, 1], - [7, 6, 3], [7, 5, 4], [7, 5, 3, 1], [7, 4, 3, 2], [6, 5, 4, 1], [6, 5, 3, 2], - [6, 4, 3, 2, 1]] - - :: - - sage: Partitions(2, max_slope=-1, length=2).list() - [] - sage: list(IntegerListsLex(0, floor=ConstantFunction(1), min_slope=0)) - [[]] - sage: list(IntegerListsLex(0, floor=ConstantFunction(1), min_slope=0, max_slope=0)) - [[]] - sage: list(IntegerListsLex(0, max_length=0, floor=ConstantFunction(1), min_slope=0, max_slope=0)) - [[]] - sage: list(IntegerListsLex(0, max_length=0, floor=ConstantFunction(0), min_slope=0, max_slope=0)) - [[]] - sage: list(IntegerListsLex(0, min_part=1, min_slope=0)) - [[]] - sage: list(IntegerListsLex(1, min_part=1, min_slope=0)) - [[1]] - sage: list(IntegerListsLex(0, min_length=1, min_part=1, min_slope=0)) - [] - sage: list(IntegerListsLex(0, min_length=1, min_slope=0)) - [[0]] - sage: list(IntegerListsLex(3, max_length=2)) - [[3], [2, 1], [1, 2], [0, 3]] - sage: partitions = {"min_part": 1, "max_slope": 0} - sage: partitions_min_2 = {"floor": ConstantFunction(2), "max_slope": 0} - sage: compositions = {"min_part": 1} - sage: integer_vectors = lambda l: {"length": l} - sage: lower_monomials = lambda c: {"length": c, "floor": lambda i: c[i]} - sage: upper_monomials = lambda c: {"length": c, "ceiling": lambda i: c[i]} - sage: constraints = { "min_part":1, "min_slope": -1, "max_slope": 0} - sage: list(IntegerListsLex(6, **partitions)) - [[6], - [5, 1], - [4, 2], - [4, 1, 1], - [3, 3], - [3, 2, 1], - [3, 1, 1, 1], - [2, 2, 2], - [2, 2, 1, 1], - [2, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1]] - sage: list(IntegerListsLex(6, **constraints)) - [[6], - [3, 3], - [3, 2, 1], - [2, 2, 2], - [2, 2, 1, 1], - [2, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1]] - sage: list(IntegerListsLex(1, **partitions_min_2)) - [] - sage: list(IntegerListsLex(2, **partitions_min_2)) - [[2]] - sage: list(IntegerListsLex(3, **partitions_min_2)) - [[3]] - sage: list(IntegerListsLex(4, **partitions_min_2)) - [[4], [2, 2]] - sage: list(IntegerListsLex(5, **partitions_min_2)) - [[5], [3, 2]] - sage: list(IntegerListsLex(6, **partitions_min_2)) - [[6], [4, 2], [3, 3], [2, 2, 2]] - sage: list(IntegerListsLex(7, **partitions_min_2)) - [[7], [5, 2], [4, 3], [3, 2, 2]] - sage: list(IntegerListsLex(9, **partitions_min_2)) - [[9], [7, 2], [6, 3], [5, 4], [5, 2, 2], [4, 3, 2], [3, 3, 3], [3, 2, 2, 2]] - sage: list(IntegerListsLex(10, **partitions_min_2)) - [[10], - [8, 2], - [7, 3], - [6, 4], - [6, 2, 2], - [5, 5], - [5, 3, 2], - [4, 4, 2], - [4, 3, 3], - [4, 2, 2, 2], - [3, 3, 2, 2], - [2, 2, 2, 2, 2]] - sage: list(IntegerListsLex(4, **compositions)) - [[4], [3, 1], [2, 2], [2, 1, 1], [1, 3], [1, 2, 1], [1, 1, 2], [1, 1, 1, 1]] - sage: list(IntegerListsLex(6, min_length=1, floor=[7])) - [] - sage: L = IntegerListsLex(10**100,length=1) - sage: L.list() - [[10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]] - - Noted on :trac:`17898`:: - - sage: list(IntegerListsLex(4, min_part=1, length=3, min_slope=1)) - [] - sage: IntegerListsLex(6, ceiling=[4,2], floor=[3,3]).list() - [] - sage: IntegerListsLex(6, min_part=1, max_part=3, max_slope=-4).list() - [] - - Noted in :trac:`17548`, which are now fixed:: - - sage: IntegerListsLex(10, min_part=2, max_slope=-1).list() - [[10], [8, 2], [7, 3], [6, 4], [5, 3, 2]] - sage: IntegerListsLex(5, min_slope=1, floor=[2,1,1], max_part=2).list() - [] - sage: IntegerListsLex(4, min_slope=0, max_slope=0).list() - [[4], [2, 2], [1, 1, 1, 1]] - sage: IntegerListsLex(6, min_slope=-1, max_slope=-1).list() - [[6], [3, 2, 1]] - sage: IntegerListsLex(6, min_length=3, max_length=2, min_part=1).list() - [] - sage: I = IntegerListsLex(3, max_length=2, min_part=1) - sage: I.list() - [[3], [2, 1], [1, 2]] - sage: [1,1,1] in I - False - sage: I=IntegerListsLex(10, ceiling=[4], max_length=1, min_part=1) - sage: I.list() - [] - sage: [4,6] in I - False - sage: I = IntegerListsLex(4, min_slope=1, min_part=1, max_part=2) - sage: I.list() - [] - sage: I = IntegerListsLex(7, min_slope=1, min_part=1, max_part=4) - sage: I.list() - [[3, 4], [1, 2, 4]] - sage: I = IntegerListsLex(4, floor=[2,1], ceiling=[2,2], max_length=2, min_slope=0) - sage: I.list() - [[2, 2]] - sage: I = IntegerListsLex(10, min_part=1, max_slope=-1) - sage: I.list() - [[10], [9, 1], [8, 2], [7, 3], [7, 2, 1], [6, 4], [6, 3, 1], [5, 4, 1], - [5, 3, 2], [4, 3, 2, 1]] - - - .. RUBRIC:: TESTS from comments on :trac:`17979` - - Comment 191:: - - sage: list(IntegerListsLex(1, min_length=2, min_slope=0, max_slope=0)) - [] - - Comment 240:: - - sage: L = IntegerListsLex(min_length=2, max_part=0) - sage: L.list() - [[0, 0]] - - .. RUBRIC:: Tests on the element constructor feature and mutability - - Internally, the iterator works on a single list that is mutated - along the way. The following test makes sure that we actually make a copy of - this list before passing it to ``element_constructor`` in order to - avoid reference effects:: - - sage: from sage.misc.c3_controlled import identity - sage: P = IntegerListsLex(n=3, max_slope=0, min_part=1, element_constructor=identity) - sage: list(P) - [[3], [2, 1], [1, 1, 1]] - - Same, step by step:: - - sage: it = iter(P) - sage: a = next(it); a - [3] - sage: b = next(it); b - [2, 1] - sage: a - [3] - sage: a is b - False - - Tests from `MuPAD-Combinat `_:: - - sage: IntegerListsLex(7, min_length=2, max_length=6, floor=[0,0,2,0,0,1], ceiling=[3,2,3,2,1,2]).cardinality() - 83 - sage: IntegerListsLex(7, min_length=2, max_length=6, floor=[0,0,2,0,1,1], ceiling=[3,2,3,2,1,2]).cardinality() - 53 - sage: IntegerListsLex(5, min_length=2, max_length=6, floor=[0,0,2,0,0,0], ceiling=[2,2,2,2,2,2]).cardinality() - 30 - sage: IntegerListsLex(5, min_length=2, max_length=6, floor=[0,0,1,1,0,0], ceiling=[2,2,2,2,2,2]).cardinality() - 43 - - sage: IntegerListsLex(0, min_length=0, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).first() - [] - - sage: IntegerListsLex(0, min_length=1, max_length=7, floor=[0,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).first() - [0] - sage: IntegerListsLex(0, min_length=1, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).cardinality() - 0 - - sage: IntegerListsLex(2, min_length=0, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() # Was [1,1], due to slightly different specs - [2] - sage: IntegerListsLex(1, min_length=1, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() - [1] - sage: IntegerListsLex(1, min_length=2, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).cardinality() - 0 - sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() - [1, 1, 0, 0, 0] - sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,0,1], ceiling=[4,3,2,3,2,2,1]).first() - [1, 1, 0, 0, 0] - sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).cardinality() - 0 - - sage: IntegerListsLex(4, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).cardinality() - 0 - sage: IntegerListsLex(5, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() - [2, 1, 2] - sage: IntegerListsLex(6, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() - [3, 1, 2] - sage: IntegerListsLex(12, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() - [3, 1, 2, 3, 2, 1] - sage: IntegerListsLex(13, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() - [3, 1, 2, 3, 2, 2] - sage: IntegerListsLex(14, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).cardinality() - 0 - - This used to hang (see comment 389 and fix in :meth:`Envelope.__init__`):: - - sage: IntegerListsLex(7, max_part=0, ceiling=lambda i:i, check=False).list() - [] - """ - __metaclass__ = ClasscallMetaclass - - @staticmethod - def __classcall_private__(cls, n=None, **kwargs): - r""" - Return a disjoint union if ``n`` is a list or iterable. - - TESTS: - - Specifying a list or iterable as argument is deprecated:: - - sage: IntegerListsLex([2,2], length=2).list() - doctest:...: DeprecationWarning: Calling IntegerListsLex with n an iterable is deprecated. Please use DisjointUnionEnumeratedSets or the min_sum and max_sum arguments instead - See http://trac.sagemath.org/17979 for details. - [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] - sage: IntegerListsLex(NN, max_length=3) - Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} - """ - import collections - if isinstance(n, collections.Iterable): - from sage.misc.superseded import deprecation - deprecation(17979, 'Calling IntegerListsLex with n an iterable is deprecated. Please use DisjointUnionEnumeratedSets or the min_sum and max_sum arguments instead') - from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets - return DisjointUnionEnumeratedSets(Family(n, lambda i: IntegerListsLex(i, **kwargs))) - else: - return typecall(cls, n=n, **kwargs) - - def __init__(self, - n=None, - length=None, min_length=0, max_length=Infinity, - floor=None, ceiling=None, - min_part=0, max_part=Infinity, - min_slope=-Infinity, max_slope=Infinity, - min_sum=0, max_sum=Infinity, - name=None, - category=None, - element_constructor=None, element_class=None, - global_options=None, - check=True): - """ - Initialize ``self``. - - TESTS:: - - sage: C = IntegerListsLex(2, length=3) - sage: C == loads(dumps(C)) - True - sage: C == loads(dumps(C)) # this did fail at some point, really! - True - sage: C is loads(dumps(C)) # todo: not implemented - True - sage: C.cardinality().parent() is ZZ - True - sage: TestSuite(C).run() - - sage: IntegerListsLex(min_sum=Infinity).list() - Traceback (most recent call last): - ... - TypeError: unable to coerce to an integer - sage: IntegerListsLex(min_sum=1.4).list() - Traceback (most recent call last): - ... - TypeError: Attempt to coerce non-integral RealNumber to Integer - """ - if category is None: - category = EnumeratedSets().Finite() - - self._check = check - - if n is not None: - min_sum = n - max_sum = n - self._min_sum = ZZ(min_sum) - self._max_sum = ZZ(max_sum) if max_sum != Infinity else Infinity - - if length is not None: - min_length = length - max_length = length - self._min_length = max(ZZ(min_length), 0) - self._max_length = ZZ(max_length) if max_length != Infinity else Infinity - - self._min_slope = ZZ(min_slope) if min_slope != -Infinity else -Infinity - self._max_slope = ZZ(max_slope) if max_slope != Infinity else Infinity - - self._min_part = ZZ(min_part) - if self._min_part < 0: - raise NotImplementedError("strictly negative min_part") - self._max_part = ZZ(max_part) if max_part != Infinity else Infinity - - # self._floor_or_ceiling_is_function will be set to ``True`` - # if a function is given as input for floor or ceiling; in - # this case a warning will be emitted, unless the user sets - # check=False. See the documentation. - self._floor_or_ceiling_is_function = False - if floor is None: - floor = 0 - elif isinstance(floor, (list, tuple)): - floor = tuple(ZZ(i) for i in floor) - if not all(i >= 0 for i in floor): - raise NotImplementedError("negative parts in floor={}".format(floor)) - elif callable(floor): - self._floor_or_ceiling_is_function = True - else: - raise TypeError("floor should be a list, tuple, or function") - self._floor = Envelope(floor, sign=-1, - min_part= self._min_part, max_part= self._max_part, - min_slope= self._min_slope, max_slope=self._max_slope, - min_length=self._min_length) - - if ceiling is None: - ceiling = Infinity - elif isinstance(ceiling, (list, tuple)): - ceiling = tuple(ZZ(i) if i != Infinity else Infinity - for i in ceiling) - if not all(i >= 0 for i in ceiling): - raise NotImplementedError("negative parts in ceiling={}".format(ceiling)) - elif callable(ceiling): - self._floor_or_ceiling_is_function = True - else: - raise ValueError("Unable to parse value of parameter ceiling") - self._ceiling = Envelope(ceiling, sign=1, - min_part= self._min_part, max_part= self._max_part, - min_slope= self._min_slope, max_slope=self._max_slope, - min_length=self._min_length) - - if name is not None: - self.rename(name) - - if self._floor_or_ceiling_is_function and self._check: - from warnings import warn - warn(""" -A function has been given as input of the floor=[...] or ceiling=[...] -arguments of IntegerListsLex. Please see the documentation for the caveats. -If you know what you are doing, you can set check=False to skip this warning.""") - - # Customization of the class and constructor for the elements - - # We set the following attribute to True if the element - # constructor is known to be safe and does not claim ownership - # on the input list. In this case, we can save a copy in Iter.next. - self._element_constructor_is_copy_safe = False - if element_class is not None: - self.Element = element_class - if element_constructor is not None: - if element_constructor is list or element_constructor is tuple: - self._element_constructor_is_copy_safe = True - elif issubclass(self.Element, ClonableArray): - # Not all element class support check=False - element_constructor = self._element_constructor_nocheck - self._element_constructor_is_copy_safe = True - if global_options is not None: - self.global_options = global_options - - Parent.__init__(self, element_constructor=element_constructor, - category=category) - - @cached_method - def _check_finiteness(self): - """ - Check that the constraints define a finite set. - - As mentioned in the description of this class, being finite is - almost equivalent to being inverse lexicographic iterable, - which is what we really care about. - - This set is finite if and only if: - - #. For each `i` such that there exists a list of length at - least `i+1` satisfying the constraints, there exists a - direct or indirect upper bound on the `i`-th part, that - is ``self._ceiling(i)`` is finite. - - #. There exists a global upper bound on the length. - - Failures for 1. are detected and reported later, during the - iteration, namely the first time a prefix including the `i`-th - part is explored. - - This method therefore focuses on 2., namely trying to prove - the existence of an upper bound on the length. It may fail - to do so even when the set is actually finite. - - OUTPUT: - - ``None`` if this method finds a proof that there - exists an upper bound on the length. Otherwise a - ``ValueError`` is raised. - - EXAMPLES:: - - sage: L = IntegerListsLex(4, max_length=4) - sage: L._check_finiteness() - - The following example is infinite:: - - sage: L = IntegerListsLex(4) - sage: L._check_finiteness() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - Indeed:: - - sage: it = iter(IntegerListsLex(4, check=False)) - sage: for _ in range(10): print next(it) - [4] - [3, 1] - [3, 0, 1] - [3, 0, 0, 1] - [3, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 0, 0, 0, 0, 1] - - Unless ``check=False``, :meth:`_check_finiteness` is called as - soon as an iteration is attempted:: - - sage: iter(L) - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - Some other infinite examples:: - - sage: L = IntegerListsLex(ceiling=[0], min_slope=1, max_slope=2) - sage: L.list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - sage: L = IntegerListsLex(ceiling=[0], min_slope=1, max_slope=1) - sage: L.list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - sage: IntegerListsLex(ceiling=[0], min_slope=1, max_slope=1).list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - The following example is actually finite, but not detected as such:: - - sage: IntegerListsLex(7, floor=[4], max_part=4, min_slope=-1).list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - This is sad because the following equivalent example works just fine:: - - sage: IntegerListsLex(7, floor=[4,3], max_part=4, min_slope=-1).list() - [[4, 3]] - - Detecting this properly would require some deeper lookahead, - and the difficulty is to decide how far this lookahead should - search. Until this is fixed, one can disable the checks:: - - sage: IntegerListsLex(7, floor=[4], max_part=4, min_slope=-1, check=False).list() - [[4, 3]] - - If the ceiling or floor is a function, it is much more likely - that a finite set will not be detected as such:: - - sage: IntegerListsLex(ceiling=lambda i: max(3-i,0))._check_finiteness() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - sage: IntegerListsLex(7, ceiling=lambda i:0).list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - The next example shows a case that is finite because we remove - trailing zeroes:: - - sage: list(IntegerListsLex(ceiling=[0], max_slope=0)) - [[]] - sage: L = IntegerListsLex(ceiling=[1], min_slope=1, max_slope=1) - sage: L.list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - In the next examples, there is either no solution, or the region - is bounded:: - - sage: IntegerListsLex(min_sum=10, max_sum=5).list() - [] - sage: IntegerListsLex(max_part=1, min_slope=10).list() - [[1], []] - sage: IntegerListsLex(max_part=100, min_slope=10).first() - [100] - sage: IntegerListsLex(ceiling=[1,Infinity], max_part=2, min_slope=1).list() - [[1, 2], [1], [0, 2], [0, 1, 2], [0, 1], []] - sage: IntegerListsLex(min_sum=1, floor=[1,2], max_part=1).list() - [[1]] - - sage: IntegerListsLex(min_length=2, max_length=1).list() - [] - sage: IntegerListsLex(min_length=-2, max_length=-1).list() - [] - sage: IntegerListsLex(min_length=-1, max_length=-2).list() - [] - sage: IntegerListsLex(min_length=2, max_slope=0, min_slope=1).list() - [] - sage: IntegerListsLex(min_part=2, max_part=1).list() - [[]] - - sage: IntegerListsLex(floor=[0,2], ceiling=[3,1]).list() - [[3], [2], [1], []] - sage: IntegerListsLex(7, ceiling=[2], floor=[4]).list() - [] - sage: IntegerListsLex(7, max_part=0).list() - [] - sage: IntegerListsLex(5, max_part=0, min_slope=0).list() - [] - sage: IntegerListsLex(max_part=0).list() - [[]] - sage: IntegerListsLex(max_sum=1, min_sum=4, min_slope=0).list() - [] - """ - # Trivial cases - if self._max_length < Infinity: - return - if self._max_sum < self._min_sum: - return - if self._min_slope > self._max_slope: - return - if self._max_slope < 0: - return - if self._ceiling.limit() < self._floor.limit(): - return - if self._ceiling.limit() == 0: - # This assumes no trailing zeroes - return - if self._min_slope > 0 and self._ceiling.limit() < Infinity: - return - - # Compute a lower bound on the sum of floor(i) for i=1 to infinity - if self._floor.limit() > 0 or self._min_slope > 0: - floor_sum_lower_bound = Infinity - elif self._floor.limit_start() < Infinity: - floor_sum_lower_bound = sum(self._floor(i) for i in range(self._floor.limit_start())) - else: - floor_sum_lower_bound = 0 - if floor_sum_lower_bound > 0 and self._min_slope >= 0: - floor_sum_lower_bound = Infinity - - if self._max_sum < floor_sum_lower_bound: - return - if self._max_sum == floor_sum_lower_bound and self._max_sum < Infinity: - # This assumes no trailing zeroes - return - - # Variant on ceiling.limit() ==0 where we actually discover that the ceiling limit is 0 - if self._max_slope == 0 and \ - (self._max_sum < Infinity or - (self._ceiling.limit_start() < Infinity and - any(self._ceiling(i) == 0 for i in range(self._ceiling.limit_start()+1)))): - return - - limit_start = max(self._ceiling.limit_start(), self._floor.limit_start()) - if limit_start < Infinity: - for i in range(limit_start+1): - if self._ceiling(i) < self._floor(i): - return - - raise ValueError("Could not prove that the specified constraints yield a finite set") - - - @staticmethod - def _list_function(l, default): - r""" - Generate a function on the nonnegative integers from input. - - This method generates a function on the nonnegative integers - whose values are taken from ``l`` when the input is a valid index - in the list ``l``, and has a default value ``default`` otherwise. - - INPUT: - - - ``l`` -- a list to use as a source of values - - - ``default`` -- a default value to use for indices outside of the list +Deprecated integer list module - OUTPUT: +TESTS:: - A function on the nonnegative integers. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: f = C._list_function([1,2], Infinity) - sage: f(1) - 2 - sage: f(3) - +Infinity - """ - return lambda i: l[i] if i < len(l) else default - - def __eq__(self, other): - r""" - Return whether ``self == other``. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: D = IntegerListsLex(2, length=3); L = D.list(); - sage: E = IntegerListsLex(2, min_length=3) - sage: F = IntegerListsLex(2, length=3, element_constructor=list) - sage: G = IntegerListsLex(4, length=3) - sage: C == C - True - sage: C == D - True - sage: C == E - False - sage: C == F - False - sage: C == None - False - sage: C == G - False - - This is a minimal implementation enabling pickling tests. It - is safe, but one would want the two following objects to be - detected as equal:: - - sage: C = IntegerListsLex(2, ceiling=[1,1,1]) - sage: D = IntegerListsLex(2, ceiling=[1,1,1]) - sage: C == D - False - - TESTS: - - This used to fail due to poor equality testing. See - :trac:`17979`, comment 433:: - - sage: DisjointUnionEnumeratedSets(Family([2,2], - ....: lambda n: IntegerListsLex(n, length=2))).list() - [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] - sage: DisjointUnionEnumeratedSets(Family([2,2], - ....: lambda n: IntegerListsLex(n, length=1))).list() - [[2], [2]] - """ - if self.__class__ != other.__class__: - return False - for key in ["_min_length", "_max_length", "_floor", "_ceiling", "_min_part", "_max_part", "_min_sum", "_max_sum", "Element"]: - if getattr(self, key) != getattr(other, key): - return False - a = self._element_constructor - b = other._element_constructor - if ismethod(a): - a = a.__func__ - if ismethod(b): - b = b.__func__ - return a == b - - def __ne__(self, other): - r""" - Return whether ``self != other``. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: D = IntegerListsLex(2, length=3); L = D.list(); - sage: E = IntegerListsLex(2, max_length=3) - sage: C != D - False - sage: C != E - True - """ - return not self == other - - def _repr_(self): - """ - Return the name of this enumerated set. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: C # indirect doctest - Integer lists of sum 2 satisfying certain constraints - - sage: C = IntegerListsLex(2, length=3, name="A given name") - sage: C - A given name - """ - if self._min_sum == self._max_sum: - return "Integer lists of sum {} satisfying certain constraints".format(self._min_sum) - elif self._max_sum == Infinity: - if self._min_sum == 0: - return "Integer lists with arbitrary sum satisfying certain constraints" - else: - return "Integer lists of sum at least {} satisfying certain constraints".format(self._min_sum) - else: - return "Integer lists of sum between {} and {} satisfying certain constraints".format(self._min_sum,self._max_sum) - - def __contains__(self, comp): - """ - Return ``True`` if ``comp`` meets the constraints imposed by the arguments. - - EXAMPLES:: - - sage: C = IntegerListsLex(n=2, max_length=3, min_slope=0) - sage: all([l in C for l in C]) - True - """ - if len(comp) < self._min_length or len(comp) > self._max_length: - return False - n = sum(comp) - if n < self._min_sum or n > self._max_sum: - return False - for i in range(len(comp)): - if comp[i] < self._floor(i): - return False - if comp[i] > self._ceiling(i): - return False - for i in range(len(comp)-1): - slope = comp[i+1] - comp[i] - if slope < self._min_slope or slope > self._max_slope: - return False - return True - - - def __iter__(self): - """ - Return an iterator for the elements of ``self``. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: list(C) # indirect doctest - [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] - """ - if self._check: - self._check_finiteness() - return IntegerListsLexIter(self) - - def _element_constructor_nocheck(self, l): - r""" - A variant of the standard element constructor that passes - ``check=False`` to the element class. - - EXAMPLES:: - - sage: L = IntegerListsLex(4, max_slope=0) - sage: L._element_constructor_nocheck([1,2,3]) - [1, 2, 3] - - When relevant, this is assigned to - ``self._element_constructor`` by :meth:`__init__`, to avoid - overhead when constructing elements from trusted data in the - iterator:: - - sage: L._element_constructor - - sage: L._element_constructor([1,2,3]) - [1, 2, 3] - """ - return self.element_class(self, l, check=False) - - class Element(ClonableArray): - """ - Element class for :class:`IntegerListsLex`. - """ - def check(self): - """ - Check to make sure this is a valid element in its - :class:`IntegerListsLex` parent. - - EXAMPLES:: - - sage: C = IntegerListsLex(4) - sage: C([4]).check() - True - sage: C([5]).check() # not implemented - False - """ - return self.parent().__contains__(self) - - -# Constants for IntegerListsLexIter._next_state -LOOKAHEAD = 5 -PUSH = 4 -ME = 3 -DECREASE = 2 -POP = 1 -STOP = 0 - -class IntegerListsLexIter: - r""" - Iterator class for IntegerListsLex. - - Let ``T`` be the prefix tree of all lists of nonnegative - integers that satisfy all constraints except possibly for - ``min_length`` and ``min_sum``; let the children of a list - be sorted decreasingly according to their last part. - - The iterator is based on a depth-first search exploration of a - subtree of this tree, trying to cut branches that do not - contain a valid list. Each call of ``next`` iterates through - the nodes of this tree until it finds a valid list to return. - - Here are the attributes describing the current state of the - iterator, and their invariants: - - - ``_parent`` -- the :class:`IntegerListsLex` object this is - iterating on; - - - ``_current_list`` -- the list corresponding to the current - node of the tree; - - - ``_j`` -- the index of the last element of ``_current_list``: - ``self._j == len(self._current_list) - 1``; - - - ``_current_sum`` -- the sum of the parts of ``_current_list``; - - - ``_search_ranges`` -- a list of same length as - ``_current_list``: the range for each part. - - Furthermore, we assume that there is no obvious contradiction - in the contraints: - - - ``self._parent._min_length <= self._parent._max_length``; - - ``self._parent._min_slope <= self._parent._max_slope`` - unless ``self._parent._min_length <= 1``. - - Along this iteration, ``next`` switches between the following - states: - - - LOOKAHEAD: determine whether the current list could be a - prefix of a valid list; - - PUSH: go deeper into the prefix tree by appending the - largest possible part to the current list; - - ME: check whether the current list is valid and if yes return it - - DECREASE: decrease the last part; - - POP: pop the last part of the current list; - - STOP: the iteration is finished. - - The attribute ``_next_state`` contains the next state ``next`` - should enter in. - """ - def __init__(self, parent): - """ - TESTS:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: I = IntegerListsLexIter(C) - sage: I._search_ranges - [] - sage: I._current_list - [] - sage: I._j - -1 - sage: I._current_sum - 0 - """ - self._parent = parent - - self._search_ranges = [] - self._current_list = [] - self._j = -1 # index of last element of _current_list - self._current_sum = 0 # sum of parts in _current_list - - # Make sure that some invariants are respected in the iterator - if parent._min_length <= parent._max_length and \ - (parent._min_slope <= parent._max_slope or parent._min_length <= 1): - self._next_state = PUSH - else: - self._next_state = STOP - - def __iter__(self): - """ - Return ``self`` as per the iterator protocol. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: it = IntegerListsLexIter(C) - sage: it.__iter__() is it - True - """ - return self - - def _push_search(self): - """ - Push search forward, resetting attributes. - - The push may fail if it is discovered that - ``self._current_list`` cannot be extended in a valid way. - - OUTPUT: a boolean: whether the push succeeded - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: I = C.__iter__() - sage: I._j - -1 - sage: I._search_ranges - [] - sage: I._current_list - [] - sage: I._current_sum - 0 - sage: I._push_search() - True - sage: I._j - 0 - sage: I._search_ranges - [(0, 2)] - sage: I._current_list - [2] - sage: I._current_sum - 2 - sage: I._push_search() - True - sage: I._j - 1 - sage: I._search_ranges - [(0, 2), (0, 0)] - sage: I._current_list - [2, 0] - sage: I._current_sum - 2 - """ - p = self._parent - max_sum = p._max_sum - min_length = p._min_length - max_length = p._max_length - if self._j+1 >= max_length: - return False - if self._j+1 >= min_length and self._current_sum == max_sum: - # Cannot add trailing zeroes - return False - - if self._j >= 0: - prev = self._current_list[self._j] - else: - prev = None - interval = self._m_interval(self._j+1, self._parent._max_sum - self._current_sum, prev) - if interval[0] > interval[1]: - return False - - self._j += 1 - m = interval[1] - self._search_ranges.append(interval) - self._current_list.append(m) - self._current_sum += m - return True - - def _pop_search(self): - """ - Go back in search tree. Resetting attributes. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: I = C.__iter__() - sage: I._push_search() - True - sage: I._j - 0 - sage: I._search_ranges - [(0, 2)] - sage: I._current_sum - 2 - sage: I._current_list - [2] - sage: I._pop_search() - sage: I._j - -1 - sage: I._search_ranges - [] - sage: I._current_sum - 0 - sage: I._current_list - [] - """ - if self._j >= 0: # TODO: get rid of this condition - self._j -= 1 - self._search_ranges.pop() - self._current_sum -= self._current_list[-1] - self._current_list.pop() - - def next(self): - r""" - Return the next element in the iteration. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: I = IntegerListsLexIter(C) - sage: next(I) - [2, 0, 0] - sage: next(I) - [1, 1, 0] - """ - p = self._parent - min_sum = p._min_sum - max_length = p._max_length - search_ranges = self._search_ranges - - while True: - assert self._j == len(self._current_list) - 1 - assert self._j == len(self._search_ranges) - 1 - - # LOOK AHEAD - if self._next_state == LOOKAHEAD: - if self._lookahead(): - self._next_state = PUSH - else: - # We should reuse information about the - # reasons for this failure, to avoid when - # possible retrying with smaller values. - # We just do a special case for now: - if self._j + 1 == max_length and self._current_sum < min_sum: - self._next_state = POP - else: - self._next_state = DECREASE - - # PUSH - if self._next_state == PUSH: - if self._push_search(): - self._next_state = LOOKAHEAD - continue - self._next_state = ME - - # ME - if self._next_state == ME: - if self._j == -1: - self._next_state = STOP - else: - self._next_state = DECREASE - if self._internal_list_valid(): - return p._element_constructor( - self._current_list - if p._element_constructor_is_copy_safe - else self._current_list[:]) - - # DECREASE - if self._next_state == DECREASE: - self._current_list[-1] -= 1 - self._current_sum -= 1 - if self._current_list[-1] >= search_ranges[self._j][0]: - self._next_state = LOOKAHEAD - continue - self._next_state = POP - - # POP - if self._next_state == POP: - self._pop_search() - self._next_state = ME - continue - - # STOP - if self._next_state == STOP: - raise StopIteration() - - assert False - - def _internal_list_valid(self): - """ - Return whether the current list in the iteration variable ``self._current_list`` is a valid list. - - This method checks whether the sum of the parts in ``self._current_list`` - is in the right range, whether its length is in the - required range, and whether there are trailing zeroes. It does not check all of the - necessary conditions to verify that an arbitrary list satisfies the - constraints from the corresponding ``IntegerListsLex`` object, and should - not be used except internally in the iterator class. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: I = IntegerListsLexIter(C) - sage: I._current_list - [] - sage: I._internal_list_valid() - False - sage: next(I) - [2, 0, 0] - sage: I._current_list - [2, 0, 0] - sage: I._internal_list_valid() - True - """ - p = self._parent - mu = self._current_list - nu = self._current_sum - l = self._j + 1 - good_sum = (nu >= p._min_sum and nu <= p._max_sum) - good_length = (l >= p._min_length and l <= p._max_length) - no_trailing_zeros = (l <= max(p._min_length,0) or mu[-1] != 0) - return good_sum and good_length and no_trailing_zeros - - def _m_interval(self, i, max_sum, prev=None): - r""" - Return coarse lower and upper bounds for the part ``m`` at position ``i``. - - INPUT: - - - ``i`` -- a nonnegative integer (position) - - - ``max_sum`` -- a nonnegative integer or ``+oo`` - - - ``prev`` -- a nonnegative integer or ``None`` - - Return coarse lower and upper bounds for the value ``m`` - of the part at position ``i`` so that there could exists - some list suffix `v_i,\ldots,v_k` of sum bounded by - ``max_sum`` and satisfying the floor and upper bound - constraints. If ``prev`` is specified, then the slope - conditions between ``v[i-1]=prev`` and ``v[i]=m`` should - also be satisfied. - - Additionally, this raises an error if it can be detected - that some part is neither directly nor indirectly bounded - above, which implies that the constraints possibly do not allow for - an inverse lexicographic iterator. - - OUTPUT: - - A tuple of two integers ``(lower_bound, upper_bound)``. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: I = IntegerListsLexIter(C) - sage: I._m_interval(1,2) - (0, 2) - - The second part is not bounded above, hence we can not - iterate lexicographically through all the elements:: - - sage: IntegerListsLex(ceiling=[2,infinity,3], max_length=3).first() - Traceback (most recent call last): - ... - ValueError: infinite upper bound for values of m - - Same here:: - - sage: IntegerListsLex(ceiling=[2,infinity,2], max_length=3, min_slope=-1).cardinality() - Traceback (most recent call last): - ... - ValueError: infinite upper bound for values of m - - In the following examples however, all parts are - indirectly bounded above:: - - sage: IntegerListsLex(ceiling=[2,infinity,2], length=3, min_slope=-1).cardinality() - 24 - sage: IntegerListsLex(ceiling=[2,infinity,2], max_length=3, max_slope=1).cardinality() - 24 - - sage: IntegerListsLex(max_part=2, max_length=3).cardinality() - 27 - sage: IntegerListsLex(3, max_length=3).cardinality() # parts bounded by n - 10 - sage: IntegerListsLex(max_length=0, min_length=1).list() # no part! - [] - sage: IntegerListsLex(length=0).list() # no part! - [[]] - """ - p = self._parent - - lower_bound = max(0, p._floor(i)) - upper_bound = min(max_sum, p._ceiling(i)) - if prev != None: - lower_bound = max(lower_bound, prev + p._min_slope) - upper_bound = min(upper_bound, prev + p._max_slope) - - ## check for infinite upper bound, in case max_sum is infinite - if p._check and upper_bound == Infinity: - # This assumes that there exists a valid list (which - # is not yet always guaranteed). Then we just - # discovered that part 'i' of this list can be made as - # large as desired, which implies that `self._parent` - # cannot be iterated in inverse lexicographic order - raise ValueError("infinite upper bound for values of m") - - return (lower_bound, upper_bound) - - def _lookahead(self): - r""" - Return whether the current list can possibly be a prefix of a valid list. - - OUTPUT: ``False`` if it is guaranteed that the current - list cannot be a prefix of a valid list and ``True`` - otherwise. - - EXAMPLES:: - - sage: it = iter(IntegerListsLex(length=3, min_sum=2, max_sum=2)) - sage: it._current_list = [0,1] # don't do this at home, kids - sage: it._current_sum = 1 - sage: it._j = 1 - sage: it._lookahead() - True - - sage: it = iter(IntegerListsLex(length=3, min_sum=3, max_sum=2)) - sage: it._current_list = [0,1] - sage: it._current_sum = 1 - sage: it._j = 1 - sage: it._lookahead() - False - - sage: it = iter(IntegerListsLex(min_length=2, max_part=0)) - sage: it._current_list = [0] - sage: it._current_sum = 0 - sage: it._j = 0 - sage: it._lookahead() - True - sage: it._current_list = [0, 0] - sage: it._j = 1 - sage: it._lookahead() - True - sage: it._current_list = [0, 0, 0] - sage: it._j = 2 - sage: it._lookahead() - False - - sage: n = 10**100 - sage: it = iter(IntegerListsLex(n, length=1)) - sage: it._current_list = [n-1] - sage: it._current_sum = n-1 - sage: it._j = 0 - sage: it._lookahead() - False - - sage: it = iter(IntegerListsLex(n=3, min_part=2, min_sum=3, max_sum=3)) - sage: it._current_list = [2] - sage: it._current_sum = 2 - sage: it._j = 0 - sage: it._lookahead() - False - - ALGORITHM: - - Let ``j=self._j`` be the position of the last part `m` of - ``self._current_list``. The current algorithm computes, - for `k=j,j+1,\ldots`, a lower bound `l_k` and an upper - bound `u_k` for `v_0+\dots+v_k`, and stops if none of the - invervals `[l_k, u_k]` intersect ``[min_sum, max_sum]``. - - The lower bound `l_k` is given by the area below - `v_0,\dots,v_{j-1}` prolongated by the lower envelope - between `j` and `k` and starting at `m`. The upper bound - `u_k` is given similarly using the upper envelope. - - The complexity of this algorithm is bounded above by - ``O(max_length)``. When ``max_length=oo``, the algorithm - is guaranteed to terminate, unless ``floor`` is a function - which is eventually constant with value `0`, or which - reaches the value `0` while ``max_slope=0``. - - Indeed, the lower bound `l_k` is increasing with `k`; in - fact it is strictly increasing, unless the local lower bound - at `k` is `0`. Furthermore as soon as ``l_k >= min_sum``, - we can conclude; we can also conclude if we know that the - floor is eventually constant with value `0`, or there is a - local lower bound at `k` is `0` and ``max_slope=0``. - - .. RUBRIC:: Room for improvement - - Improved prediction: the lower bound `l_k` does not take - the slope conditions into account, except for those imposed - by the value `m` at `j`. Similarly for `u_k`. - - Improved speed: given that `l_k` is increasing with `k`, - possibly some dichotomy could be used to search for `k`, - with appropriate caching / fast calculation of the partial - sums. Also, some of the information gained at depth `j` - could be reused at depth `j+1`. - - TESTS:: - - sage: it = iter(IntegerListsLex(1, min_length=2, min_slope=0, max_slope=0, min_sum=1, max_sum=1)) - sage: it._current_list = [0] - sage: it._current_sum = 0 - sage: it._j = 0 - sage: it._lookahead() - False - """ - # Check code for various termination conditions. Possible cases: - # 0. interval [lower, upper] intersects interval [min_sum, max_sum] -- terminate True - # 1. lower sum surpasses max_sum -- terminate False - # 2. iteration surpasses max_length -- terminate False - # 3. upper envelope is smaller than lower envelope -- terminate False - # 4. max_slope <= 0 -- terminate False after upper passes 0 - # 5. ceiling_limit == 0 -- terminate False after reaching larger limit point - # 6. (uncomputable) ceiling function == 0 for all but finitely many input values -- terminate False after reaching (unknown) limit point -- currently hangs - - m = self._current_list[-1] - j = self._j - min_sum = self._parent._min_sum - (self._current_sum-m) - max_sum = self._parent._max_sum - (self._current_sum-m) - - if min_sum > max_sum: - return False - - p = self._parent - - # Beware that without slope conditions, the functions below - # currently forget about the value m at k! - lower_envelope = self._parent._floor.adapt(m,j) - upper_envelope = self._parent._ceiling.adapt(m,j) - lower = 0 # The lower bound `l_k` - upper = 0 # The upper bound `u_k` - - assert j >= 0 - # get to smallest valid number of parts - for k in range(j, p._min_length-1): - # We are looking at lists `v_j,...,v_k` - lo = m if k == j else lower_envelope(k) - up = m if k == j else upper_envelope(k) - if lo > up: - return False - lower += lo - upper += up - - if j < p._min_length and min_sum <= upper and lower <= max_sum: - # There could exist a valid list `v_j,\dots,v_{min_length-1}` - return True - - k = max(p._min_length-1,j) - # Check if any of the intervals intersect the target interval - while k < p._max_length: - lo = m if k == j else lower_envelope(k) - up = m if k == j else upper_envelope(k) - if lo > up: - # There exists no valid list of length >= k - return False - lower += lo - upper += up - assert lower <= upper - - if lower > max_sum: - # There cannot exist a valid list `v_j,\dots,v_l` with l>=k - return False - - if (p._max_slope <= 0 and up <= 0) or \ - (p._ceiling.limit() == 0 and k > p._ceiling.limit_start()): - # This implies v_l=0 for l>=k: that is we would be generating - # a list with trailing zeroes - return False - - if min_sum <= upper and lower <= max_sum: - # There could exist a valid list `v_j,\dots,v_k` - return True - - k += 1 - - return False - - -class Envelope(object): - """ - The (currently approximated) upper (lower) envelope of a function - under the specified constraints. - - INPUT: - - - ``f`` -- a function, list, or tuple; if ``f`` is a list, it is - considered as the function ``f(i)=f[i]``, completed for larger - `i` with ``f(i)=max_part``. - - - ``min_part``, ``max_part``, ``min_slope``, ``max_slope``, ... - as for :class:`IntegerListsLex` (please consult for details). - - - ``sign`` -- (+1 or -1) multiply the input values with ``sign`` - and multiply the output with ``sign``. Setting this to `-1` can - be used to implement a lower envelope. - - The *upper envelope* `U(f)` of `f` is the (pointwise) largest - function which is bounded above by `f` and satisfies the - ``max_part`` and ``max_slope`` conditions. Furthermore, for - ``i,i+1 inf, - '_f_limit_start': 0, - '_max_part': -3, - '_max_slope': inf, - '_min_slope': 1, - '_precomputed': [-6, -5, -4, -3], - '_sign': -1} - sage: TestSuite(f).run(skip="_test_pickling") - sage: Envelope(3, sign=1/3, max_slope=-1, min_length=4) - Traceback (most recent call last): - ... - TypeError: no conversion of this rational to integer - sage: Envelope(3, sign=-2, max_slope=-1, min_length=4) - Traceback (most recent call last): - ... - ValueError: sign should be +1 or -1 - """ - # self._sign = sign for the output values (the sign change for - # f is handled here in __init__) - self._sign = ZZ(sign) - if self._sign == 1: - self._max_part = max_part - self._min_slope = min_slope - self._max_slope = max_slope - if max_part == 0: - # This uses that all entries are nonnegative. - # This is not for speed optimization but for - # setting the limit start and avoid hangs. - # See #17979: comment 389 - f = 0 - elif self._sign == -1: - self._max_part = -min_part - self._min_slope = -max_slope - self._max_slope = -min_slope - else: - raise ValueError("sign should be +1 or -1") - - # Handle different types of f and multiply f with sign - if f == Infinity or f == -Infinity or f in ZZ: - limit_start = 0 - self._max_part = min(self._sign * f, self._max_part) - f = ConstantFunction(Infinity) - elif isinstance(f, (list, tuple)): - limit_start = len(f) - f_tab = [self._sign * i for i in f] - f = lambda k: f_tab[k] if k < len(f_tab) else Infinity - else: - g = f - f = lambda k: self._sign * g(k) - # At this point, this is not really used - limit_start = Infinity - - self._f = f - # For i >= limit_start, f is constant - # This does not necessarily means that self is constant! - self._f_limit_start = limit_start - self._precomputed = [] - - if min_length > 0: - self(min_length-1) - for i in range(min_length-1,0,-1): - self._precomputed[i-1] = min(self._precomputed[i-1], self._precomputed[i] - self._min_slope) - - def __eq__(self, other): - r""" - Return whether ``self == other``. - - This is a minimal implementation enabling pickling tests. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: f = Envelope([3,2,2]) - sage: g = Envelope([3,2,2]) - sage: h = Envelope([3,2,2], min_part=2) - sage: f == f, f == h, f == None - (True, False, False) - - This would be desirable:: - - sage: f == g # todo: not implemented - True - """ - return self.__class__ == other.__class__ and self.__dict__ == other.__dict__ - - def __ne__(self, other): - r""" - Return whether ``self != other``. - - This is a minimal implementation enabling pickling tests. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: f = Envelope([3,2,2]) - sage: g = Envelope([3,2,2]) - sage: h = Envelope([3,2,2], min_part=2) - sage: f != f, f != h, f != None - (False, True, True) - - This would be desirable:: - - sage: f != g # todo: not implemented - False - """ - return not self == other - - def limit_start(self): - """ - Return from which `i` on the bound returned by ``limit`` holds. - - .. SEEALSO:: :meth:`limit` for the precise specifications. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: Envelope([4,1,5]).limit_start() - 3 - sage: Envelope([4,1,5], sign=-1).limit_start() - 3 - - sage: Envelope([4,1,5], max_part=2).limit_start() - 3 - - sage: Envelope(4).limit_start() - 0 - sage: Envelope(4, sign=-1).limit_start() - 0 - - sage: Envelope(lambda x: 3).limit_start() == Infinity - True - sage: Envelope(lambda x: 3, max_part=2).limit_start() == Infinity - True - - sage: Envelope(lambda x: 3, sign=-1, min_part=2).limit_start() == Infinity - True - - """ - return self._f_limit_start - - def limit(self): - """ - Return a bound on the limit of ``self``. - - OUTPUT: a nonnegative integer or `\infty` - - This returns some upper bound for the accumulation points of - this upper envelope. For a lower envelope, a lower bound is - returned instead. - - In particular this gives a bound for the value of ``self`` at - `i` for `i` large enough. Special case: for a lower envelop, - and when the limit is `\infty`, the envelope is guaranteed to - tend to `\infty` instead. - - When ``s=self.limit_start()`` is finite, this bound is - guaranteed to be valid for `i>=s`. - - Sometimes it's better to have a loose bound that starts early; - sometimes the converse holds. At this point which specific - bound and starting point is returned is not set in stone, in - order to leave room for later optimizations. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: Envelope([4,1,5]).limit() - inf - sage: Envelope([4,1,5], max_part=2).limit() - 2 - sage: Envelope([4,1,5], max_slope=0).limit() - 1 - sage: Envelope(lambda x: 3, max_part=2).limit() - 2 - - Lower envelopes:: - - sage: Envelope(lambda x: 3, min_part=2, sign=-1).limit() - 2 - sage: Envelope([4,1,5], min_slope=0, sign=-1).limit() - 5 - sage: Envelope([4,1,5], sign=-1).limit() - 0 - - .. SEEALSO:: :meth:`limit_start` - """ - if self.limit_start() < Infinity and self._max_slope <= 0: - return self(self.limit_start()) - else: - return self._max_part * self._sign - - def __call__(self, k): - """ - Return the value of this envelope at `k`. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: f = Envelope([4,1,5,3,5]) - sage: f.__call__(2) - 5 - sage: [f(i) for i in range(10)] - [4, 1, 5, 3, 5, inf, inf, inf, inf, inf] - - .. NOTE:: - - See the documentation of :class:`Envelope` for tests and - examples. - """ - if k >= len(self._precomputed): - for i in range(len(self._precomputed), k+1): - value = min(self._f(i), self._max_part) - if i>0: - value = min(value, self._precomputed[i-1] + self._max_slope) - self._precomputed.append(value) - return self._precomputed[k] * self._sign - - def adapt(self, m, j): - """ - Return this envelope adapted to an additional local constraint. - - INPUT: - - - ``m`` -- a nonnegative integer (starting value) - - - ``j`` -- a nonnegative integer (position) - - This method adapts this envelope to the additional local - constraint imposed by having a part `m` at position `j`. - Namely, this returns a function which computes, for any `i>j`, - the minimum of the ceiling function and the value restriction - given by the slope conditions. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: f = Envelope(3) - sage: g = f.adapt(1,1) - sage: g is f - True - sage: [g(i) for i in range(10)] - [3, 3, 3, 3, 3, 3, 3, 3, 3, 3] - - sage: f = Envelope(3, max_slope=1) - sage: g = f.adapt(1,1) - sage: [g(i) for i in range(10)] - [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] - - Note that, in both cases above, the adapted envelope is only - guaranteed to be valid for `i>j`! This is to leave potential - room in the future for sharing similar adapted envelopes:: - - sage: g = f.adapt(0,0) - sage: [g(i) for i in range(10)] - [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] - - sage: g = f.adapt(2,2) - sage: [g(i) for i in range(10)] - [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] - - sage: g = f.adapt(3,3) - sage: [g(i) for i in range(10)] - [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] - - Now with a lower envelope:: - - sage: f = Envelope(1, sign=-1, min_slope=-1) - sage: g = f.adapt(2,2) - sage: [g(i) for i in range(10)] - [4, 3, 2, 1, 1, 1, 1, 1, 1, 1] - sage: g = f.adapt(1,3) - sage: [g(i) for i in range(10)] - [4, 3, 2, 1, 1, 1, 1, 1, 1, 1] - """ - if self._max_slope == Infinity: - return self - m *= self._sign - m = m - j * self._max_slope - return lambda i: self._sign * min(m + i*self._max_slope, self._sign*self(i) ) - - -def IntegerListsNN(**kwds): - """ - Lists of nonnegative integers with constraints. - - This function returns the union of ``IntegerListsLex(n, **kwds)`` - where `n` ranges over all nonnegative integers. - - .. WARNING:: this function is likely to disappear in :trac:`17927`. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsNN - sage: L = IntegerListsNN(max_length=3, max_slope=-1) - sage: L - Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} - sage: it = iter(L) - sage: for _ in range(20): - ....: print next(it) - [] - [1] - [2] - [3] - [2, 1] - [4] - [3, 1] - [5] - [4, 1] - [3, 2] - [6] - [5, 1] - [4, 2] - [3, 2, 1] - [7] - [6, 1] - [5, 2] - [4, 3] - [4, 2, 1] - [8] - """ - from sage.rings.semirings.non_negative_integer_semiring import NN - from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets - return DisjointUnionEnumeratedSets(Family(NN, lambda i: IntegerListsLex(i, **kwds))) + sage: from sage.combinat.integer_list import IntegerListsLex + sage: IntegerListsLex(3) + doctest:...: DeprecationWarning: + Importing IntegerListsLex from here is deprecated. If you need to use it, please import it directly from sage.combinat.integer_lists + See http://trac.sagemath.org/18109 for details. + Integer lists of sum 3 satisfying certain constraints +""" +from sage.misc.lazy_import import lazy_import +lazy_import('sage.combinat.integer_list_old', '*', deprecation=18109) +lazy_import('sage.combinat.integer_lists', '*', deprecation=18109) diff --git a/src/sage/combinat/integer_lists/__init__.py b/src/sage/combinat/integer_lists/__init__.py new file mode 100644 index 00000000000..e69886d8799 --- /dev/null +++ b/src/sage/combinat/integer_lists/__init__.py @@ -0,0 +1,6 @@ +from base import IntegerListsBackend, Envelope +from lists import IntegerLists +from invlex import IntegerListsLex + +from sage.structure.sage_object import register_unpickle_override +register_unpickle_override('sage.combinat.integer_list', 'IntegerListsLex', IntegerListsLex) diff --git a/src/sage/combinat/integer_lists/base.pxd b/src/sage/combinat/integer_lists/base.pxd new file mode 100644 index 00000000000..d7850331ffe --- /dev/null +++ b/src/sage/combinat/integer_lists/base.pxd @@ -0,0 +1,15 @@ +cdef class Envelope(object): + cdef readonly sign + cdef f + cdef f_limit_start + cdef list precomputed + cdef readonly max_part + cdef readonly min_slope, max_slope + +cdef class IntegerListsBackend(object): + cdef readonly min_sum, max_sum + cdef readonly min_length, max_length + cdef readonly min_part, max_part + cdef readonly min_slope, max_slope + cdef readonly Envelope floor, ceiling + cdef public dict __cached_methods # Support cached_method diff --git a/src/sage/combinat/integer_lists/base.pyx b/src/sage/combinat/integer_lists/base.pyx new file mode 100644 index 00000000000..de53d35ddd3 --- /dev/null +++ b/src/sage/combinat/integer_lists/base.pyx @@ -0,0 +1,702 @@ +r""" +Enumerated set of lists of integers with constraints: base classes + +- :class:`IntegerListsBackend`: base class for the Cython back-end of + an enumerated set of lists of integers with specified constraints. + +- :class:`Envelope`: a utility class for upper (lower) envelope of a + function under constraints. +""" + +#***************************************************************************** +# Copyright (C) 2015 Bryan Gillespie +# Nicolas M. Thiery +# Anne Schilling +# Jeroen Demeyer +# +# 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 cpython.object cimport Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE +from sage.misc.constant_function import ConstantFunction +from sage.structure.element cimport RingElement +from sage.rings.integer cimport Integer + +Infinity = float('+inf') +MInfinity = float('-inf') + + +cdef class IntegerListsBackend(object): + """ + Base class for the Cython back-end of an enumerated set of lists of + integers with specified constraints. + + This base implements the basic operations, including checking for + containment using :meth:`_contains`, but not iteration. For + iteration, subclass this class and implement an ``_iter()`` method. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: L = IntegerListsBackend(6, max_slope=-1) + sage: L._contains([3,2,1]) + True + """ + def __init__(self, + n=None, length=None, *, + min_length=0, max_length=Infinity, + floor=None, ceiling=None, + min_part=0, max_part=Infinity, + min_slope=MInfinity, max_slope=Infinity, + min_sum=0, max_sum=Infinity): + """ + Initialize ``self``. + + TESTS:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: C = IntegerListsBackend(2, length=3) + sage: C = IntegerListsBackend(min_sum=1.4) + Traceback (most recent call last): + ... + TypeError: Attempt to coerce non-integral RealNumber to Integer + sage: C = IntegerListsBackend(min_sum=Infinity) + Traceback (most recent call last): + ... + TypeError: unable to coerce to an integer + """ + if n is not None: + min_sum = n + max_sum = n + self.min_sum = Integer(min_sum) if min_sum != -Infinity else -Infinity + self.max_sum = Integer(max_sum) if max_sum != Infinity else Infinity + + if length is not None: + min_length = length + max_length = length + self.min_length = Integer(max(min_length, 0)) + self.max_length = Integer(max_length) if max_length != Infinity else Infinity + + self.min_slope = Integer(min_slope) if min_slope != -Infinity else -Infinity + self.max_slope = Integer(max_slope) if max_slope != Infinity else Infinity + + self.min_part = Integer(min_part) if min_part != -Infinity else -Infinity + self.max_part = Integer(max_part) if max_part != Infinity else Infinity + + if isinstance(floor, Envelope): + self.floor = floor + else: + if floor is None: + floor = -Infinity + elif isinstance(floor, (list, tuple)): + floor = tuple(Integer(i) for i in floor) + elif callable(floor): + pass + else: + raise TypeError("floor should be a list, tuple, or function") + self.floor = Envelope(floor, sign=-1, + min_part=self.min_part, max_part=self.max_part, + min_slope=self.min_slope, max_slope=self.max_slope, + min_length=self.min_length) + + if isinstance(ceiling, Envelope): + self.ceiling = ceiling + else: + if ceiling is None: + ceiling = Infinity + elif isinstance(ceiling, (list, tuple)): + ceiling = tuple(Integer(i) if i != Infinity else Infinity + for i in ceiling) + elif callable(ceiling): + pass + else: + raise ValueError("Unable to parse value of parameter ceiling") + self.ceiling = Envelope(ceiling, sign=1, + min_part=self.min_part, max_part=self.max_part, + min_slope=self.min_slope, max_slope=self.max_slope, + min_length=self.min_length) + + def __richcmp__(self, other, int op): + r""" + Basic comparison function, supporting only checking for + equality. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3).backend + sage: D = IntegerListsLex(2, length=3).backend; L = list(D._iter()) + sage: E = IntegerListsLex(2, min_length=3).backend + sage: G = IntegerListsLex(4, length=3).backend + sage: C >= C + True + sage: C == D + True + sage: C != D + False + sage: C == E + False + sage: C != E + True + sage: C == None + False + sage: C == G + False + sage: C <= G + Traceback (most recent call last): + ... + TypeError: IntegerListsBackend can only be compared for equality + """ + cdef IntegerListsBackend left = self + cdef IntegerListsBackend right = other + equal = (type(left) is type(other) and + left.min_length == right.min_length and + left.max_length == right.max_length and + left.min_sum == right.min_sum and + left.max_sum == right.max_sum and + left.min_slope == right.min_slope and + left.max_slope == right.max_slope and + left.floor == right.floor and + left.ceiling == right.ceiling) + if equal: + return (op == Py_EQ or op == Py_LE or op == Py_GE) + if op == Py_EQ: + return False + if op == Py_NE: + return True + else: + raise TypeError("IntegerListsBackend can only be compared for equality") + + def _repr_(self): + """ + Return the name of this enumerated set. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: C = IntegerListsBackend(2, length=3) + sage: C._repr_() + 'Integer lists of sum 2 satisfying certain constraints' + """ + if self.min_sum == self.max_sum: + return "Integer lists of sum {} satisfying certain constraints".format(self.min_sum) + elif self.max_sum == Infinity: + if self.min_sum == 0: + return "Integer lists with arbitrary sum satisfying certain constraints" + else: + return "Integer lists of sum at least {} satisfying certain constraints".format(self.min_sum) + else: + return "Integer lists of sum between {} and {} satisfying certain constraints".format(self.min_sum, self.max_sum) + + def _contains(self, comp): + """ + Return ``True`` if ``comp`` meets the constraints imposed + by the arguments. + + EXAMPLES:: + + sage: C = IntegerListsLex(n=2, max_length=3, min_slope=0) + sage: all([l in C for l in C]) # indirect doctest + True + """ + if len(comp) < self.min_length or len(comp) > self.max_length: + return False + n = sum(comp) + if n < self.min_sum or n > self.max_sum: + return False + for i in range(len(comp)): + if comp[i] < self.floor(i): + return False + if comp[i] > self.ceiling(i): + return False + for i in range(len(comp)-1): + slope = comp[i+1] - comp[i] + if slope < self.min_slope or slope > self.max_slope: + return False + return True + + def __getstate__(self): + """ + Pickle ``self``. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: C = IntegerListsBackend(2, length=3) + sage: C.__getstate__() + {'ceiling': , + 'floor': , + 'max_length': 3, + 'max_part': inf, + 'max_slope': inf, + 'max_sum': 2, + 'min_length': 3, + 'min_part': 0, + 'min_slope': -inf, + 'min_sum': 2} + """ + return {"min_sum": self.min_sum, + "max_sum": self.max_sum, + "min_length": self.min_length, + "max_length": self.max_length, + "min_part": self.min_part, + "max_part": self.max_part, + "min_slope": self.min_slope, + "max_slope": self.max_slope, + "floor": self.floor, + "ceiling": self.ceiling} + + def __setstate__(self, state): + """ + Unpickle ``self`` from the state ``state``. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: C = IntegerListsBackend(2, length=3) + sage: C == loads(dumps(C)) + True + sage: C == loads(dumps(C)) # this did fail at some point, really! + True + sage: C is loads(dumps(C)) # todo: not implemented + True + """ + self.__init__(**state) + + +cdef class Envelope(object): + """ + The (currently approximated) upper (lower) envelope of a function + under the specified constraints. + + INPUT: + + - ``f`` -- a function, list, or tuple; if ``f`` is a list, it is + considered as the function ``f(i)=f[i]``, completed for larger + `i` with ``f(i)=max_part``. + + - ``min_part``, ``max_part``, ``min_slope``, ``max_slope``, ... + as for :class:`IntegerListsLex` (please consult for details). + + - ``sign`` -- (+1 or -1) multiply the input values with ``sign`` + and multiply the output with ``sign``. Setting this to `-1` can + be used to implement a lower envelope. + + The *upper envelope* `U(f)` of `f` is the (pointwise) largest + function which is bounded above by `f` and satisfies the + ``max_part`` and ``max_slope`` conditions. Furthermore, for + ``i,i+1= limit_start, f is constant + # This does not necessarily means that self is constant! + self.f_limit_start = limit_start + self.precomputed = [] + + if min_length > 0: + self(min_length-1) + for i in range(min_length-1,0,-1): + self.precomputed[i-1] = min(self.precomputed[i-1], self.precomputed[i] - self.min_slope) + + def __richcmp__(self, other, int op): + r""" + Basic comparison function, supporting only checking for + equality. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: f = Envelope([3,2,2]) + sage: g = Envelope([3,2,2]) + sage: h = Envelope([3,2,2], min_part=2) + sage: f == f, f == h, f == None + (True, False, False) + sage: f < f, f != h, f != None + (False, True, True) + + This would be desirable:: + + sage: f == g # todo: not implemented + True + """ + cdef Envelope left = self + cdef Envelope right = other + equal = (type(left) is type(other) and + left.sign == right.sign and + left.f == right.f and + left.f_limit_start == right.f_limit_start and + left.max_part == right.max_part and + left.min_slope == right.min_slope and + left.max_slope == right.max_slope) + if equal: + return (op == Py_EQ or op == Py_LE or op == Py_GE) + if op == Py_EQ: + return False + if op == Py_NE: + return True + else: + raise TypeError("Envelopes can only be compared for equality") + + def limit_start(self): + """ + Return from which `i` on the bound returned by ``limit`` holds. + + .. SEEALSO:: :meth:`limit` for the precise specifications. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: Envelope([4,1,5]).limit_start() + 3 + sage: Envelope([4,1,5], sign=-1).limit_start() + 3 + + sage: Envelope([4,1,5], max_part=2).limit_start() + 3 + + sage: Envelope(4).limit_start() + 0 + sage: Envelope(4, sign=-1).limit_start() + 0 + + sage: Envelope(lambda x: 3).limit_start() == Infinity + True + sage: Envelope(lambda x: 3, max_part=2).limit_start() == Infinity + True + + sage: Envelope(lambda x: 3, sign=-1, min_part=2).limit_start() == Infinity + True + + """ + return self.f_limit_start + + def limit(self): + """ + Return a bound on the limit of ``self``. + + OUTPUT: a nonnegative integer or `\infty` + + This returns some upper bound for the accumulation points of + this upper envelope. For a lower envelope, a lower bound is + returned instead. + + In particular this gives a bound for the value of ``self`` at + `i` for `i` large enough. Special case: for a lower envelop, + and when the limit is `\infty`, the envelope is guaranteed to + tend to `\infty` instead. + + When ``s=self.limit_start()`` is finite, this bound is + guaranteed to be valid for `i>=s`. + + Sometimes it's better to have a loose bound that starts early; + sometimes the converse holds. At this point which specific + bound and starting point is returned is not set in stone, in + order to leave room for later optimizations. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: Envelope([4,1,5]).limit() + inf + sage: Envelope([4,1,5], max_part=2).limit() + 2 + sage: Envelope([4,1,5], max_slope=0).limit() + 1 + sage: Envelope(lambda x: 3, max_part=2).limit() + 2 + + Lower envelopes:: + + sage: Envelope(lambda x: 3, min_part=2, sign=-1).limit() + 2 + sage: Envelope([4,1,5], min_slope=0, sign=-1).limit() + 5 + sage: Envelope([4,1,5], sign=-1).limit() + 0 + + .. SEEALSO:: :meth:`limit_start` + """ + if self.limit_start() < Infinity and self.max_slope <= 0: + return self(self.limit_start()) + else: + return self.max_part * self.sign + + def __call__(self, Py_ssize_t k): + """ + Return the value of this envelope at `k`. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: f = Envelope([4,1,5,3,5]) + sage: f.__call__(2) + 5 + sage: [f(i) for i in range(10)] + [4, 1, 5, 3, 5, inf, inf, inf, inf, inf] + + .. NOTE:: + + See the documentation of :class:`Envelope` for tests and + examples. + """ + if k >= len(self.precomputed): + for i in range(len(self.precomputed), k+1): + value = min(self.f(i), self.max_part) + if i > 0: + value = min(value, self.precomputed[i-1] + self.max_slope) + self.precomputed.append(value) + return self.precomputed[k] * self.sign + + def adapt(self, m, j): + """ + Return this envelope adapted to an additional local constraint. + + INPUT: + + - ``m`` -- a nonnegative integer (starting value) + + - ``j`` -- a nonnegative integer (position) + + This method adapts this envelope to the additional local + constraint imposed by having a part `m` at position `j`. + Namely, this returns a function which computes, for any `i>j`, + the minimum of the ceiling function and the value restriction + given by the slope conditions. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: f = Envelope(3) + sage: g = f.adapt(1,1) + sage: g is f + True + sage: [g(i) for i in range(10)] + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3] + + sage: f = Envelope(3, max_slope=1) + sage: g = f.adapt(1,1) + sage: [g(i) for i in range(10)] + [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] + + Note that, in both cases above, the adapted envelope is only + guaranteed to be valid for `i>j`! This is to leave potential + room in the future for sharing similar adapted envelopes:: + + sage: g = f.adapt(0,0) + sage: [g(i) for i in range(10)] + [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] + + sage: g = f.adapt(2,2) + sage: [g(i) for i in range(10)] + [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] + + sage: g = f.adapt(3,3) + sage: [g(i) for i in range(10)] + [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] + + Now with a lower envelope:: + + sage: f = Envelope(1, sign=-1, min_slope=-1) + sage: g = f.adapt(2,2) + sage: [g(i) for i in range(10)] + [4, 3, 2, 1, 1, 1, 1, 1, 1, 1] + sage: g = f.adapt(1,3) + sage: [g(i) for i in range(10)] + [4, 3, 2, 1, 1, 1, 1, 1, 1, 1] + """ + if self.max_slope == Infinity: + return self + m *= self.sign + m = m - j * self.max_slope + return lambda i: self.sign * min(m + i*self.max_slope, self.sign*self(i) ) + + def __reduce__(self): + """ + Pickle ``self``. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: h = Envelope(3, min_part=2) + sage: loads(dumps(h)) == h + True + """ + args = (type(self), + self.sign, self.f, self.f_limit_start, self.precomputed, + self.max_part, self.min_slope, self.max_slope) + return _unpickle_Envelope, args + + +def _unpickle_Envelope(type t, _sign, _f, _f_limit_start, _precomputed, + _max_part, _min_slope, _max_slope): + """ + Internal function to support pickling for :class:`Envelope`. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import Envelope, _unpickle_Envelope + sage: _unpickle_Envelope(Envelope, + ....: 1, lambda i:i, Infinity, [], 4, -1, 3) + + """ + cdef Envelope self = t.__new__(t) + self.sign = _sign + self.f = _f + self.f_limit_start = _f_limit_start + self.precomputed = _precomputed + self.max_part = _max_part + self.min_slope = _min_slope + self.max_slope = _max_slope + return self diff --git a/src/sage/combinat/integer_lists/invlex.pxd b/src/sage/combinat/integer_lists/invlex.pxd new file mode 100644 index 00000000000..143306b4448 --- /dev/null +++ b/src/sage/combinat/integer_lists/invlex.pxd @@ -0,0 +1,3 @@ +from sage.combinat.integer_lists.base cimport IntegerListsBackend +cdef class IntegerListsBackend_invlex(IntegerListsBackend): + cdef public bint check diff --git a/src/sage/combinat/integer_lists/invlex.pyx b/src/sage/combinat/integer_lists/invlex.pyx new file mode 100644 index 00000000000..c1bbc7163ad --- /dev/null +++ b/src/sage/combinat/integer_lists/invlex.pyx @@ -0,0 +1,1678 @@ +r""" +Enumerated set of lists of integers with constraints, in inverse lexicographic order + +- :class:`IntegerListsLex`: the enumerated set of lists of nonnegative + integers with specified constraints, in inverse lexicographic order. + +- :class:`IntegerListsBackend_invlex`: Cython back-end for + :class:`IntegerListsLex`. + +HISTORY: + +This generic tool was originally written by Hivert and Thiery in +MuPAD-Combinat in 2000 and ported over to Sage by Mike Hansen in +2007. It was then completely rewritten in 2015 by Gillespie, +Schilling, and Thiery, with the help of many, to deal with +limitations and lack of robustness w.r.t. input. +""" + +#***************************************************************************** +# Copyright (C) 2015 Bryan Gillespie +# Nicolas M. Thiery +# Anne Schilling +# +# 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 sage.misc.classcall_metaclass import ClasscallMetaclass, typecall +from sage.misc.cachefunc import cached_method +from sage.combinat.integer_lists.base cimport IntegerListsBackend +from sage.combinat.integer_lists.lists import IntegerLists +from sage.combinat.integer_lists.base import Infinity + + +class IntegerListsLex(IntegerLists): + r""" + Lists of nonnegative integers with constraints, in inverse + lexicographic order. + + An *integer list* is a list `l` of nonnegative integers, its *parts*. The + slope (at position `i`) is the difference ``l[i+1]-l[i]`` between two + consecutive parts. + + This class allows to construct the set `S` of all integer lists + `l` satisfying specified bounds on the sum, the length, the slope, + and the individual parts, enumerated in *inverse* lexicographic + order, that is from largest to smallest in lexicographic + order. Note that, to admit such an enumeration, `S` is almost + necessarily finite (see :ref:`IntegerListsLex_finiteness`). + + The main purpose is to provide a generic iteration engine for all the + enumerated sets like :class:`Partitions`, :class:`Compositions`, + :class:`IntegerVectors`. It can also be used to generate many other + combinatorial objects like Dyck paths, Motzkin paths, etc. Mathematically + speaking, this is a special case of set of integral points of a polytope (or + union thereof, when the length is not fixed). + + INPUT: + + - ``min_sum`` -- a nonnegative integer (default: 0): + a lower bound on ``sum(l)``. + + - ``max_sum`` -- a nonnegative integer or `\infty` (default: `\infty`): + an upper bound on ``sum(l)``. + + - ``n`` -- a nonnegative integer (optional): if specified, this + overrides ``min_sum`` and ``max_sum``. + + - ``min_length`` -- a nonnegative integer (default: `0`): a lower + bound on ``len(l)``. + + - ``max_length`` -- a nonnegative integer or `\infty` (default: + `\infty`): an upper bound on ``len(l)``. + + - ``length`` -- an integer (optional); overrides ``min_length`` + and ``max_length`` if specified; + + - ``min_part`` -- a nonnegative integer: a lower bounds on all + parts: ``min_part <= l[i]`` for ``0 <= i < len(l)``. + + - ``floor`` -- a list of nonnegative integers or a function: lower + bounds on the individual parts `l[i]`. + + If ``floor`` is a list of integers, then ``floor<=l[i]`` for ``0 + <= i < min(len(l), len(floor)``. Similarly, if ``floor`` is a + function, then ``floor(i) <= l[i]`` for ``0 <= i < len(l)``. + + - ``max_part`` -- a nonnegative integer or `\infty`: an upper + bound on all parts: ``l[i] <= max_part`` for ``0 <= i < len(l)``. + + - ``ceiling`` -- upper bounds on the individual parts ``l[i]``; + this takes the same type of input as ``floor``, except that + `\infty` is allowed in addition to integers, and the default + value is `\infty`. + + - ``min_slope`` -- an integer or `-\infty` (default: `-\infty`): + an lower bound on the slope between consecutive parts: + ``min_slope <= l[i+1]-l[i]`` for ``0 <= i < len(l)-1`` + + - ``max_slope`` -- an integer or `+\infty` (defaults: `+\infty`) + an upper bound on the slope between consecutive parts: + ``l[i+1]-l[i] <= max_slope`` for ``0 <= i < len(l)-1`` + + - ``category`` -- a category (default: :class:`FiniteEnumeratedSets`) + + - ``check`` -- boolean (default: ``True``): whether to display the + warnings raised when functions are given as input to ``floor`` + or ``ceiling`` and the errors raised when there is no proper + enumeration. + + - ``name`` -- a string or ``None`` (default: ``None``) if set, + this will be passed down to :meth:`Parent.rename` to specify the + name of ``self``. It is recommented to use directly the rename + method as this feature may become deprecated. + + - ``element_constructor`` -- a function (or callable) that creates + elements of ``self`` from a list. See also :class:`Parent`. + + - ``element_class`` -- a class for the elements of ``self`` + (default: `ClonableArray`). This merely sets the attribute + ``self.Element``. See the examples for details. + + - ``global_options`` -- a :class:`~sage.structure.global_options.GlobalOptions` + object that will be assigned to the attribute + ``_global_options``; for internal use only (subclasses, ...). + + + .. NOTE:: + + When several lists satisfying the constraints differ only by + trailing zeroes, only the shortest one is enumerated (and + therefore counted). The others are still considered valid. + See the examples below. + + This feature is questionable. It is recommended not to rely on + it, as it may eventually be discontinued. + + EXAMPLES: + + We create the enumerated set of all lists of nonnegative integers + of length `3` and sum `2`:: + + sage: C = IntegerListsLex(2, length=3) + sage: C + Integer lists of sum 2 satisfying certain constraints + sage: C.cardinality() + 6 + sage: [p for p in C] + [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + + sage: [2, 0, 0] in C + True + sage: [2, 0, 1] in C + False + sage: "a" in C + False + sage: ["a"] in C + False + sage: C.first() + [2, 0, 0] + + One can specify lower and upper bounds on each part:: + + sage: list(IntegerListsLex(5, length=3, floor=[1,2,0], ceiling=[3,2,3])) + [[3, 2, 0], [2, 2, 1], [1, 2, 2]] + + When the length is fixed as above, one can also use + :class:`IntegerVectors`:: + + sage: IntegerVectors(2,3).list() + [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + + Using the slope condition, one can generate integer partitions + (but see :class:`Partitions`):: + + sage: list(IntegerListsLex(4, max_slope=0)) + [[4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]] + + The following is the list of all partitions of `7` with parts at least `2`:: + + sage: list(IntegerListsLex(7, max_slope=0, min_part=2)) + [[7], [5, 2], [4, 3], [3, 2, 2]] + + + .. RUBRIC:: floor and ceiling conditions + + Next we list all partitions of `5` of length at most `3` which are + bounded below by ``[2,1,1]``:: + + sage: list(IntegerListsLex(5, max_slope=0, max_length=3, floor=[2,1,1])) + [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] + + Note that ``[5]`` is considered valid, because the floor + constraints only apply to existing positions in the list. To + obtain instead the partitions containing ``[2,1,1]``, one needs to + use ``min_length`` or ``length``:: + + sage: list(IntegerListsLex(5, max_slope=0, length=3, floor=[2,1,1])) + [[3, 1, 1], [2, 2, 1]] + + Here is the list of all partitions of `5` which are contained in + ``[3,2,2]``:: + + sage: list(IntegerListsLex(5, max_slope=0, max_length=3, ceiling=[3,2,2])) + [[3, 2], [3, 1, 1], [2, 2, 1]] + + This is the list of all compositions of `4` (but see :class:`Compositions`):: + + sage: list(IntegerListsLex(4, min_part=1)) + [[4], [3, 1], [2, 2], [2, 1, 1], [1, 3], [1, 2, 1], [1, 1, 2], [1, 1, 1, 1]] + + This is the list of all integer vectors of sum `4` and length `3`:: + + sage: list(IntegerListsLex(4, length=3)) + [[4, 0, 0], [3, 1, 0], [3, 0, 1], [2, 2, 0], [2, 1, 1], + [2, 0, 2], [1, 3, 0], [1, 2, 1], [1, 1, 2], [1, 0, 3], + [0, 4, 0], [0, 3, 1], [0, 2, 2], [0, 1, 3], [0, 0, 4]] + + For whatever it is worth, the ``floor`` and ``min_part`` + constraints can be combined:: + + sage: L = IntegerListsLex(5, floor=[2,0,2], min_part=1) + sage: L.list() + [[5], [4, 1], [3, 2], [2, 3], [2, 1, 2]] + + This is achieved by updating the floor upon constructing ``L``:: + + sage: [L.floor(i) for i in range(5)] + [2, 1, 2, 1, 1] + + Similarly, the ``ceiling`` and ``max_part`` constraints can be + combined:: + + sage: L = IntegerListsLex(4, ceiling=[2,3,1], max_part=2, length=3) + sage: L.list() + [[2, 2, 0], [2, 1, 1], [1, 2, 1]] + sage: [L.ceiling(i) for i in range(5)] + [2, 2, 1, 2, 2] + + + This can be used to generate Motzkin words (see + :wikipedia:`Motzkin_number`):: + + sage: def motzkin_words(n): + ....: return IntegerListsLex(length=n+1, min_slope=-1, max_slope=1, + ....: ceiling=[0]+[+oo for i in range(n-1)]+[0]) + sage: motzkin_words(4).list() + [[0, 1, 2, 1, 0], + [0, 1, 1, 1, 0], + [0, 1, 1, 0, 0], + [0, 1, 0, 1, 0], + [0, 1, 0, 0, 0], + [0, 0, 1, 1, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 1, 0], + [0, 0, 0, 0, 0]] + sage: [motzkin_words(n).cardinality() for n in range(8)] + [1, 1, 2, 4, 9, 21, 51, 127] + sage: oeis(_) # optional -- internet + 0: A001006: Motzkin numbers: number of ways of drawing any number + of nonintersecting chords joining n (labeled) points on a circle. + + or Dyck words (see also :class:`DyckWords`), through the bijection + with paths from `(0,0)` to `(n,n)` with left and up steps that remain + below the diagonal:: + + sage: def dyck_words(n): + ....: return IntegerListsLex(length=n, ceiling=range(n+1), min_slope=0) + sage: [dyck_words(n).cardinality() for n in range(8)] + [1, 1, 2, 5, 14, 42, 132, 429] + sage: dyck_words(3).list() + [[0, 1, 2], [0, 1, 1], [0, 0, 2], [0, 0, 1], [0, 0, 0]] + + + .. _IntegerListsLex_finiteness: + + .. RUBRIC:: On finiteness and inverse lexicographic enumeration + + The set of all lists of integers cannot be enumerated in inverse + lexicographic order, since there is no largest list (take `[n]` + for `n` as large as desired):: + + sage: IntegerListsLex().first() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + Here is a variant which could be enumerated in lexicographic order + but not in inverse lexicographic order:: + + sage: L = IntegerListsLex(length=2, ceiling=[Infinity, 0], floor=[0,1]) + sage: for l in L: print l + Traceback (most recent call last): + ... + ValueError: infinite upper bound for values of m + + Even when the sum is specified, it is not necessarily possible to + enumerate *all* elements in inverse lexicographic order. In the + following example, the list ``[1, 1, 1]`` will never appear in the + enumeration:: + + sage: IntegerListsLex(3).first() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + If one wants to proceed anyway, one can sign a waiver by setting + ``check=False`` (again, be warned that some valid lists may never appear):: + + sage: L = IntegerListsLex(3, check=False) + sage: it = iter(L) + sage: [next(it) for i in range(6)] + [[3], [2, 1], [2, 0, 1], [2, 0, 0, 1], [2, 0, 0, 0, 1], [2, 0, 0, 0, 0, 1]] + + In fact, being inverse lexicographically enumerable is almost + equivalent to being finite. The only infinity that can occur would + be from a tail of numbers `0,1` as in the previous example, where + the `1` moves further and further to the right. If there is any + list that is inverse lexicographically smaller than such a + configuration, the iterator would not reach it and hence would not + be considered iterable. Given that the infinite cases are very + specific, at this point only the finite cases are supported + (without signing the waiver). + + The finiteness detection is not complete yet, so some finite cases + may not be supported either, at least not without disabling the + checks. Practical examples of such are welcome. + + .. RUBRIC:: On trailing zeroes, and their caveats + + As mentioned above, when several lists satisfying the constraints + differ only by trailing zeroes, only the shortest one is listed:: + + sage: L = IntegerListsLex(max_length=4, max_part=1) + sage: L.list() + [[1, 1, 1, 1], + [1, 1, 1], + [1, 1, 0, 1], + [1, 1], + [1, 0, 1, 1], + [1, 0, 1], + [1, 0, 0, 1], + [1], + [0, 1, 1, 1], + [0, 1, 1], + [0, 1, 0, 1], + [0, 1], + [0, 0, 1, 1], + [0, 0, 1], + [0, 0, 0, 1], + []] + + and counted:: + + sage: L.cardinality() + 16 + + Still, the others are considered as elements of `L`:: + + sage: L = IntegerListsLex(4,min_length=3,max_length=4) + sage: L.list() + [..., [2, 2, 0], ...] + + sage: [2, 2, 0] in L # in L.list() + True + sage: [2, 2, 0, 0] in L # not in L.list() ! + True + sage: [2, 2, 0, 0, 0] in L + False + + .. RUBRIC:: Specifying functions as input for the floor or ceiling + + We construct all lists of sum `4` and length `4` such that ``l[i] <= i``:: + + sage: list(IntegerListsLex(4, length=4, ceiling=lambda i: i, check=False)) + [[0, 1, 2, 1], [0, 1, 1, 2], [0, 1, 0, 3], [0, 0, 2, 2], [0, 0, 1, 3]] + + .. WARNING:: + + When passing a function as ``floor`` or ``ceiling``, it may + become undecidable to detect improper inverse lexicographic + enumeration. For example, the following example has a finite + enumeration:: + + sage: L = IntegerListsLex(3, floor=lambda i: 1 if i>=2 else 0, check=False) + sage: L.list() + [[3], + [2, 1], + [2, 0, 1], + [1, 2], + [1, 1, 1], + [1, 0, 2], + [1, 0, 1, 1], + [0, 3], + [0, 2, 1], + [0, 1, 2], + [0, 1, 1, 1], + [0, 0, 3], + [0, 0, 2, 1], + [0, 0, 1, 2], + [0, 0, 1, 1, 1]] + + but one cannot decide whether the following has an improper + inverse lexicographic enumeration without computing the floor + all the way to ``Infinity``:: + + sage: L = IntegerListsLex(3, floor=lambda i: 0, check=False) + sage: it = iter(L) + sage: [next(it) for i in range(6)] + [[3], [2, 1], [2, 0, 1], [2, 0, 0, 1], [2, 0, 0, 0, 1], [2, 0, 0, 0, 0, 1]] + + Hence a warning is raised when a function is specified as + input, unless the waiver is signed by setting ``check=False``:: + + sage: L = IntegerListsLex(3, floor=lambda i: 1 if i>=2 else 0) + doctest:... + A function has been given as input of the floor=[...] or ceiling=[...] + arguments of IntegerListsLex. Please see the documentation for the caveats. + If you know what you are doing, you can set check=False to skip this warning. + + Similarly, the algorithm may need to search forever for a + solution when the ceiling is ultimately zero:: + + sage: L = IntegerListsLex(2,ceiling=lambda i:0, check=False) + sage: L.first() # not tested: will hang forever + sage: L = IntegerListsLex(2,ceiling=lambda i:0 if i<20 else 1, check=False) + sage: it = iter(L) + sage: next(it) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] + sage: next(it) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1] + sage: next(it) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1] + + + .. RUBRIC:: Tip: using disjoint union enumerated sets for additional flexibility + + Sometimes, specifying a range for the sum or the length may be too + restrictive. One would want instead to specify a list, or + iterable `L`, of acceptable values. This is easy to achieve using + a :class:`disjoint union of enumerated sets `. + Here we want to accept the values `n=0,2,3`:: + + sage: C = DisjointUnionEnumeratedSets(Family([0,2,3], + ....: lambda n: IntegerListsLex(n, length=2))) + sage: C + Disjoint union of Finite family + {0: Integer lists of sum 0 satisfying certain constraints, + 2: Integer lists of sum 2 satisfying certain constraints, + 3: Integer lists of sum 3 satisfying certain constraints} + sage: C.list() + [[0, 0], + [2, 0], [1, 1], [0, 2], + [3, 0], [2, 1], [1, 2], [0, 3]] + + The price to pay is that the enumeration order is now *graded + lexicographic* instead of lexicographic: first choose the value + according to the order specified by `L`, and use lexicographic + order within each value. Here is we reverse `L`:: + + sage: DisjointUnionEnumeratedSets(Family([3,2,0], + ....: lambda n: IntegerListsLex(n, length=2))).list() + [[3, 0], [2, 1], [1, 2], [0, 3], + [2, 0], [1, 1], [0, 2], + [0, 0]] + + Note that if a given value appears several times, the + corresponding elements will be enumerated several times, which + may, or not, be what one wants:: + + sage: DisjointUnionEnumeratedSets(Family([2,2], + ....: lambda n: IntegerListsLex(n, length=2))).list() + [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] + + Here is a variant where we specify acceptable values for the + length:: + + sage: DisjointUnionEnumeratedSets(Family([0,1,3], + ....: lambda l: IntegerListsLex(2, length=l))).list() + [[2], + [2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + + + This technique can also be useful to obtain a proper enumeration + on infinite sets by using a graded lexicographic enumeration:: + + sage: C = DisjointUnionEnumeratedSets(Family(NN, + ....: lambda n: IntegerListsLex(n, length=2))) + sage: C + Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} + sage: it = iter(C) + sage: [next(it) for i in range(10)] + [[0, 0], + [1, 0], [0, 1], + [2, 0], [1, 1], [0, 2], + [3, 0], [2, 1], [1, 2], [0, 3]] + + + .. RUBRIC:: Specifying how to construct elements + + This is the list of all monomials of degree `4` which divide the + monomial `x^3y^1z^2` (a monomial being identified with its + exponent vector):: + + sage: R. = QQ[] + sage: m = [3,1,2] + sage: def term(exponents): + ....: return x^exponents[0] * y^exponents[1] * z^exponents[2] + sage: list( IntegerListsLex(4, length=len(m), ceiling=m, element_constructor=term) ) + [x^3*y, x^3*z, x^2*y*z, x^2*z^2, x*y*z^2] + + Note the use of the ``element_constructor`` option to specify how + to construct elements from a plain list. + + A variant is to specify a class for the elements. With the default + element constructor, this class should take as input the parent + ``self`` and a list. Here we want the elements to be constructed + in the class :class:`Partition`:: + + sage: IntegerListsLex(3, max_slope=0, element_class=Partition, global_options=Partitions.global_options).list() + doctest:...: DeprecationWarning: the global_options argument is + deprecated since, in general, pickling is broken; + create your own class instead + See http://trac.sagemath.org/15525 for details. + [[3], [2, 1], [1, 1, 1]] + + Note that the :class:`Partition` further assumes the existence of + an attribute ``_global_options`` in the parent, hence the use of the + ``global_options`` parameter. + + .. WARNING:: + + The protocol for specifying the element class and constructor + is subject to changes. + + ALGORITHM: + + The iteration algorithm uses a depth first search through the + prefix tree of the list of integers (see also + :ref:`section-generic-integerlistlex`). While doing so, it does + some lookahead heuristics to attempt to cut dead branches. + + In most practical use cases, most dead branches are cut. Then, + roughly speaking, the time needed to iterate through all the + elements of `S` is proportional to the number of elements, where + the proportion factor is controlled by the length `l` of the + longest element of `S`. In addition, the memory usage is also + controlled by `l`, which is to say negligible in practice. + + Still, there remains much room for efficiency improvements; see + :trac:`18055`, :trac:`18056`. + + .. NOTE:: + + The generation algorithm could in principle be extended to + deal with non-constant slope constraints and with negative + parts. + + TESTS: + + This example from the combinatorics tutorial used to fail before + :trac:`17979` because the floor conditions did not satisfy the + slope conditions:: + + sage: I = IntegerListsLex(16, min_length=2, max_slope=-1, floor=[5,3,3]) + sage: I.list() + [[13, 3], [12, 4], [11, 5], [10, 6], [9, 7], [9, 4, 3], [8, 5, 3], [8, 4, 3, 1], + [7, 6, 3], [7, 5, 4], [7, 5, 3, 1], [7, 4, 3, 2], [6, 5, 4, 1], [6, 5, 3, 2], + [6, 4, 3, 2, 1]] + + :: + + sage: Partitions(2, max_slope=-1, length=2).list() + [] + sage: list(IntegerListsLex(0, floor=ConstantFunction(1), min_slope=0)) + [[]] + sage: list(IntegerListsLex(0, floor=ConstantFunction(1), min_slope=0, max_slope=0)) + [[]] + sage: list(IntegerListsLex(0, max_length=0, floor=ConstantFunction(1), min_slope=0, max_slope=0)) + [[]] + sage: list(IntegerListsLex(0, max_length=0, floor=ConstantFunction(0), min_slope=0, max_slope=0)) + [[]] + sage: list(IntegerListsLex(0, min_part=1, min_slope=0)) + [[]] + sage: list(IntegerListsLex(1, min_part=1, min_slope=0)) + [[1]] + sage: list(IntegerListsLex(0, min_length=1, min_part=1, min_slope=0)) + [] + sage: list(IntegerListsLex(0, min_length=1, min_slope=0)) + [[0]] + sage: list(IntegerListsLex(3, max_length=2)) + [[3], [2, 1], [1, 2], [0, 3]] + sage: partitions = {"min_part": 1, "max_slope": 0} + sage: partitions_min_2 = {"floor": ConstantFunction(2), "max_slope": 0} + sage: compositions = {"min_part": 1} + sage: integer_vectors = lambda l: {"length": l} + sage: lower_monomials = lambda c: {"length": c, "floor": lambda i: c[i]} + sage: upper_monomials = lambda c: {"length": c, "ceiling": lambda i: c[i]} + sage: constraints = { "min_part":1, "min_slope": -1, "max_slope": 0} + sage: list(IntegerListsLex(6, **partitions)) + [[6], + [5, 1], + [4, 2], + [4, 1, 1], + [3, 3], + [3, 2, 1], + [3, 1, 1, 1], + [2, 2, 2], + [2, 2, 1, 1], + [2, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1]] + sage: list(IntegerListsLex(6, **constraints)) + [[6], + [3, 3], + [3, 2, 1], + [2, 2, 2], + [2, 2, 1, 1], + [2, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1]] + sage: list(IntegerListsLex(1, **partitions_min_2)) + [] + sage: list(IntegerListsLex(2, **partitions_min_2)) + [[2]] + sage: list(IntegerListsLex(3, **partitions_min_2)) + [[3]] + sage: list(IntegerListsLex(4, **partitions_min_2)) + [[4], [2, 2]] + sage: list(IntegerListsLex(5, **partitions_min_2)) + [[5], [3, 2]] + sage: list(IntegerListsLex(6, **partitions_min_2)) + [[6], [4, 2], [3, 3], [2, 2, 2]] + sage: list(IntegerListsLex(7, **partitions_min_2)) + [[7], [5, 2], [4, 3], [3, 2, 2]] + sage: list(IntegerListsLex(9, **partitions_min_2)) + [[9], [7, 2], [6, 3], [5, 4], [5, 2, 2], [4, 3, 2], [3, 3, 3], [3, 2, 2, 2]] + sage: list(IntegerListsLex(10, **partitions_min_2)) + [[10], + [8, 2], + [7, 3], + [6, 4], + [6, 2, 2], + [5, 5], + [5, 3, 2], + [4, 4, 2], + [4, 3, 3], + [4, 2, 2, 2], + [3, 3, 2, 2], + [2, 2, 2, 2, 2]] + sage: list(IntegerListsLex(4, **compositions)) + [[4], [3, 1], [2, 2], [2, 1, 1], [1, 3], [1, 2, 1], [1, 1, 2], [1, 1, 1, 1]] + sage: list(IntegerListsLex(6, min_length=1, floor=[7])) + [] + sage: L = IntegerListsLex(10**100,length=1) + sage: L.list() + [[10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]] + + Noted on :trac:`17898`:: + + sage: list(IntegerListsLex(4, min_part=1, length=3, min_slope=1)) + [] + sage: IntegerListsLex(6, ceiling=[4,2], floor=[3,3]).list() + [] + sage: IntegerListsLex(6, min_part=1, max_part=3, max_slope=-4).list() + [] + + Noted in :trac:`17548`, which are now fixed:: + + sage: IntegerListsLex(10, min_part=2, max_slope=-1).list() + [[10], [8, 2], [7, 3], [6, 4], [5, 3, 2]] + sage: IntegerListsLex(5, min_slope=1, floor=[2,1,1], max_part=2).list() + [] + sage: IntegerListsLex(4, min_slope=0, max_slope=0).list() + [[4], [2, 2], [1, 1, 1, 1]] + sage: IntegerListsLex(6, min_slope=-1, max_slope=-1).list() + [[6], [3, 2, 1]] + sage: IntegerListsLex(6, min_length=3, max_length=2, min_part=1).list() + [] + sage: I = IntegerListsLex(3, max_length=2, min_part=1) + sage: I.list() + [[3], [2, 1], [1, 2]] + sage: [1,1,1] in I + False + sage: I=IntegerListsLex(10, ceiling=[4], max_length=1, min_part=1) + sage: I.list() + [] + sage: [4,6] in I + False + sage: I = IntegerListsLex(4, min_slope=1, min_part=1, max_part=2) + sage: I.list() + [] + sage: I = IntegerListsLex(7, min_slope=1, min_part=1, max_part=4) + sage: I.list() + [[3, 4], [1, 2, 4]] + sage: I = IntegerListsLex(4, floor=[2,1], ceiling=[2,2], max_length=2, min_slope=0) + sage: I.list() + [[2, 2]] + sage: I = IntegerListsLex(10, min_part=1, max_slope=-1) + sage: I.list() + [[10], [9, 1], [8, 2], [7, 3], [7, 2, 1], [6, 4], [6, 3, 1], [5, 4, 1], + [5, 3, 2], [4, 3, 2, 1]] + + + .. RUBRIC:: TESTS from comments on :trac:`17979` + + Comment 191:: + + sage: list(IntegerListsLex(1, min_length=2, min_slope=0, max_slope=0)) + [] + + Comment 240:: + + sage: L = IntegerListsLex(min_length=2, max_part=0) + sage: L.list() + [[0, 0]] + + .. RUBRIC:: Tests on the element constructor feature and mutability + + Internally, the iterator works on a single list that is mutated + along the way. Therefore, you need to make sure that the + ``element_constructor`` actually **copies** its input. This example + shows what can go wrong:: + + sage: P = IntegerListsLex(n=3, max_slope=0, min_part=1, element_constructor=lambda x: x) + sage: list(P) + [[], [], []] + + However, specifying ``list()`` as constructor solves this problem:: + + sage: P = IntegerListsLex(n=3, max_slope=0, min_part=1, element_constructor=list) + sage: list(P) + [[3], [2, 1], [1, 1, 1]] + + Same, step by step:: + + sage: it = iter(P) + sage: a = next(it); a + [3] + sage: b = next(it); b + [2, 1] + sage: a + [3] + sage: a is b + False + + Tests from `MuPAD-Combinat `_:: + + sage: IntegerListsLex(7, min_length=2, max_length=6, floor=[0,0,2,0,0,1], ceiling=[3,2,3,2,1,2]).cardinality() + 83 + sage: IntegerListsLex(7, min_length=2, max_length=6, floor=[0,0,2,0,1,1], ceiling=[3,2,3,2,1,2]).cardinality() + 53 + sage: IntegerListsLex(5, min_length=2, max_length=6, floor=[0,0,2,0,0,0], ceiling=[2,2,2,2,2,2]).cardinality() + 30 + sage: IntegerListsLex(5, min_length=2, max_length=6, floor=[0,0,1,1,0,0], ceiling=[2,2,2,2,2,2]).cardinality() + 43 + + sage: IntegerListsLex(0, min_length=0, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).first() + [] + + sage: IntegerListsLex(0, min_length=1, max_length=7, floor=[0,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).first() + [0] + sage: IntegerListsLex(0, min_length=1, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).cardinality() + 0 + + sage: IntegerListsLex(2, min_length=0, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() # Was [1,1], due to slightly different specs + [2] + sage: IntegerListsLex(1, min_length=1, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() + [1] + sage: IntegerListsLex(1, min_length=2, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).cardinality() + 0 + sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() + [1, 1, 0, 0, 0] + sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,0,1], ceiling=[4,3,2,3,2,2,1]).first() + [1, 1, 0, 0, 0] + sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).cardinality() + 0 + + sage: IntegerListsLex(4, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).cardinality() + 0 + sage: IntegerListsLex(5, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() + [2, 1, 2] + sage: IntegerListsLex(6, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() + [3, 1, 2] + sage: IntegerListsLex(12, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() + [3, 1, 2, 3, 2, 1] + sage: IntegerListsLex(13, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() + [3, 1, 2, 3, 2, 2] + sage: IntegerListsLex(14, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).cardinality() + 0 + + This used to hang (see comment 389 and fix in :meth:`Envelope.__init__`):: + + sage: IntegerListsLex(7, max_part=0, ceiling=lambda i:i, check=False).list() + [] + """ + backend_class = IntegerListsBackend_invlex + + __metaclass__ = ClasscallMetaclass + + @staticmethod + def __classcall_private__(cls, n=None, **kwargs): + r""" + Return a disjoint union if ``n`` is a list or iterable. + + TESTS: + + Specifying a list or iterable as argument is deprecated:: + + sage: IntegerListsLex([2,2], length=2).list() + doctest:...: DeprecationWarning: Calling IntegerListsLex with n an iterable is deprecated. Please use DisjointUnionEnumeratedSets or the min_sum and max_sum arguments instead + See http://trac.sagemath.org/17979 for details. + [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] + sage: IntegerListsLex(NN, max_length=3) + Disjoint union of Lazy family (<...>(i))_{i in Non negative integer semiring} + """ + import collections + if isinstance(n, collections.Iterable): + from sage.misc.superseded import deprecation + deprecation(17979, 'Calling IntegerListsLex with n an iterable is deprecated. Please use DisjointUnionEnumeratedSets or the min_sum and max_sum arguments instead') + from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets + from sage.sets.family import Family + return DisjointUnionEnumeratedSets(Family(n, lambda i: IntegerListsLex(i, **kwargs))) + else: + return typecall(cls, n=n, **kwargs) + + +cdef class IntegerListsBackend_invlex(IntegerListsBackend): + """ + Cython back-end of an set of lists of integers with specified + constraints enumerated in inverse lexicographic order. + """ + def __init__(self, *args, check=True, **kwds): + """ + Initialize ``self``. + + TESTS:: + + sage: C = IntegerListsLex(2, length=3) + sage: C == loads(dumps(C)) + True + sage: C.cardinality().parent() is ZZ + True + sage: TestSuite(C).run() + sage: IntegerListsLex(min_part=-1) + Traceback (most recent call last): + ... + NotImplementedError: strictly negative min_part + """ + IntegerListsBackend.__init__(self, *args, **kwds) + + self.check = check + + if self.min_part < 0: + raise NotImplementedError("strictly negative min_part") + + if self.check and ( + self.floor.limit_start() == Infinity or + self.ceiling.limit_start() == Infinity): + from warnings import warn + warn(""" +A function has been given as input of the floor=[...] or ceiling=[...] +arguments of IntegerListsLex. Please see the documentation for the caveats. +If you know what you are doing, you can set check=False to skip this warning.""") + + @cached_method + def _check_finiteness(self): + """ + Check that the constraints define a finite set. + + As mentioned in the description of this class, being finite is + almost equivalent to being inverse lexicographic iterable, + which is what we really care about. + + This set is finite if and only if: + + #. For each `i` such that there exists a list of length at + least `i+1` satisfying the constraints, there exists a + direct or indirect upper bound on the `i`-th part, that + is ``self.ceiling(i)`` is finite. + + #. There exists a global upper bound on the length. + + Failures for 1. are detected and reported later, during the + iteration, namely the first time a prefix including the `i`-th + part is explored. + + This method therefore focuses on 2., namely trying to prove + the existence of an upper bound on the length. It may fail + to do so even when the set is actually finite. + + OUTPUT: + + ``None`` if this method finds a proof that there + exists an upper bound on the length. Otherwise a + ``ValueError`` is raised. + + EXAMPLES:: + + sage: L = IntegerListsLex(4, max_length=4) + sage: L._check_finiteness() + + The following example is infinite:: + + sage: L = IntegerListsLex(4) + sage: L._check_finiteness() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + Indeed:: + + sage: it = iter(IntegerListsLex(4, check=False)) + sage: for _ in range(10): print next(it) + [4] + [3, 1] + [3, 0, 1] + [3, 0, 0, 1] + [3, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 0, 0, 0, 0, 1] + + Unless ``check=False``, :meth:`_check_finiteness` is called as + soon as an iteration is attempted:: + + sage: iter(L) + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + Some other infinite examples:: + + sage: L = IntegerListsLex(ceiling=[0], min_slope=1, max_slope=2) + sage: L.list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + sage: L = IntegerListsLex(ceiling=[0], min_slope=1, max_slope=1) + sage: L.list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + sage: IntegerListsLex(ceiling=[0], min_slope=1, max_slope=1).list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + The following example is actually finite, but not detected as such:: + + sage: IntegerListsLex(7, floor=[4], max_part=4, min_slope=-1).list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + This is sad because the following equivalent example works just fine:: + + sage: IntegerListsLex(7, floor=[4,3], max_part=4, min_slope=-1).list() + [[4, 3]] + + Detecting this properly would require some deeper lookahead, + and the difficulty is to decide how far this lookahead should + search. Until this is fixed, one can disable the checks:: + + sage: IntegerListsLex(7, floor=[4], max_part=4, min_slope=-1, check=False).list() + [[4, 3]] + + If the ceiling or floor is a function, it is much more likely + that a finite set will not be detected as such:: + + sage: IntegerListsLex(ceiling=lambda i: max(3-i,0))._check_finiteness() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + sage: IntegerListsLex(7, ceiling=lambda i:0).list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + The next example shows a case that is finite because we remove + trailing zeroes:: + + sage: list(IntegerListsLex(ceiling=[0], max_slope=0)) + [[]] + sage: L = IntegerListsLex(ceiling=[1], min_slope=1, max_slope=1) + sage: L.list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + In the next examples, there is either no solution, or the region + is bounded:: + + sage: IntegerListsLex(min_sum=10, max_sum=5).list() + [] + sage: IntegerListsLex(max_part=1, min_slope=10).list() + [[1], []] + sage: IntegerListsLex(max_part=100, min_slope=10).first() + [100] + sage: IntegerListsLex(ceiling=[1,Infinity], max_part=2, min_slope=1).list() + [[1, 2], [1], [0, 2], [0, 1, 2], [0, 1], []] + sage: IntegerListsLex(min_sum=1, floor=[1,2], max_part=1).list() + [[1]] + + sage: IntegerListsLex(min_length=2, max_length=1).list() + [] + sage: IntegerListsLex(min_length=-2, max_length=-1).list() + [] + sage: IntegerListsLex(min_length=-1, max_length=-2).list() + [] + sage: IntegerListsLex(min_length=2, max_slope=0, min_slope=1).list() + [] + sage: IntegerListsLex(min_part=2, max_part=1).list() + [[]] + + sage: IntegerListsLex(floor=[0,2], ceiling=[3,1]).list() + [[3], [2], [1], []] + sage: IntegerListsLex(7, ceiling=[2], floor=[4]).list() + [] + sage: IntegerListsLex(7, max_part=0).list() + [] + sage: IntegerListsLex(5, max_part=0, min_slope=0).list() + [] + sage: IntegerListsLex(max_part=0).list() + [[]] + sage: IntegerListsLex(max_sum=1, min_sum=4, min_slope=0).list() + [] + """ + # Trivial cases + if self.max_length < Infinity: + return + if self.max_sum < self.min_sum: + return + if self.min_slope > self.max_slope: + return + if self.max_slope < 0: + return + if self.ceiling.limit() < self.floor.limit(): + return + if self.ceiling.limit() == 0: + # This assumes no trailing zeroes + return + if self.min_slope > 0 and self.ceiling.limit() < Infinity: + return + + # Compute a lower bound on the sum of floor(i) for i=1 to infinity + if self.floor.limit() > 0 or self.min_slope > 0: + floor_sum_lower_bound = Infinity + elif self.floor.limit_start() < Infinity: + floor_sum_lower_bound = sum(self.floor(i) for i in range(self.floor.limit_start())) + else: + floor_sum_lower_bound = 0 + if floor_sum_lower_bound > 0 and self.min_slope >= 0: + floor_sum_lower_bound = Infinity + + if self.max_sum < floor_sum_lower_bound: + return + if self.max_sum == floor_sum_lower_bound and self.max_sum < Infinity: + # This assumes no trailing zeroes + return + + # Variant on ceiling.limit() ==0 where we actually discover that the ceiling limit is 0 + if ( self.max_slope == 0 and + (self.max_sum < Infinity or + (self.ceiling.limit_start() < Infinity and + any(self.ceiling(i) == 0 for i in range(self.ceiling.limit_start()+1))) + ) ): + return + + limit_start = max(self.ceiling.limit_start(), self.floor.limit_start()) + if limit_start < Infinity: + for i in range(limit_start+1): + if self.ceiling(i) < self.floor(i): + return + + raise ValueError("could not prove that the specified constraints yield a finite set") + + def _iter(self): + """ + Return an iterator for the elements of ``self``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: list(C) # indirect doctest + [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + """ + if self.check: + self._check_finiteness() + return IntegerListsLexIter(self) + + +# Constants for IntegerListsLexIter._next_state +LOOKAHEAD = 5 +PUSH = 4 +ME = 3 +DECREASE = 2 +POP = 1 +STOP = 0 + +class IntegerListsLexIter(object): + r""" + Iterator class for IntegerListsLex. + + Let ``T`` be the prefix tree of all lists of nonnegative + integers that satisfy all constraints except possibly for + ``min_length`` and ``min_sum``; let the children of a list + be sorted decreasingly according to their last part. + + The iterator is based on a depth-first search exploration of a + subtree of this tree, trying to cut branches that do not + contain a valid list. Each call of ``next`` iterates through + the nodes of this tree until it finds a valid list to return. + + Here are the attributes describing the current state of the + iterator, and their invariants: + + - ``backend`` -- the :class:`IntegerListsBackend` object this is + iterating on; + + - ``_current_list`` -- the list corresponding to the current + node of the tree; + + - ``_j`` -- the index of the last element of ``_current_list``: + ``self._j == len(self._current_list) - 1``; + + - ``_current_sum`` -- the sum of the parts of ``_current_list``; + + - ``_search_ranges`` -- a list of same length as + ``_current_list``: the range for each part. + + Furthermore, we assume that there is no obvious contradiction + in the contraints: + + - ``self.backend.min_length <= self.backend.max_length``; + - ``self.backend.min_slope <= self.backend.max_slope`` + unless ``self.backend.min_length <= 1``. + + Along this iteration, ``next`` switches between the following + states: + + - LOOKAHEAD: determine whether the current list could be a + prefix of a valid list; + - PUSH: go deeper into the prefix tree by appending the + largest possible part to the current list; + - ME: check whether the current list is valid and if yes return it + - DECREASE: decrease the last part; + - POP: pop the last part of the current list; + - STOP: the iteration is finished. + + The attribute ``_next_state`` contains the next state ``next`` + should enter in. + """ + def __init__(self, backend): + """ + TESTS:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: I = IntegerListsLexIter(C) + sage: I._search_ranges + [] + sage: I._current_list + [] + sage: I._j + -1 + sage: I._current_sum + 0 + """ + self.backend = backend + + self._search_ranges = [] + self._current_list = [] + self._j = -1 # index of last element of _current_list + self._current_sum = 0 # sum of parts in _current_list + + # Make sure that some invariants are respected in the iterator + if (backend.min_length <= backend.max_length and + (backend.min_slope <= backend.max_slope or backend.min_length <= 1)): + self._next_state = PUSH + else: + self._next_state = STOP + + def __iter__(self): + """ + Return ``self`` as per the iterator protocol. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: it = IntegerListsLexIter(C) + sage: it.__iter__() is it + True + """ + return self + + def _push_search(self): + """ + Push search forward, resetting attributes. + + The push may fail if it is discovered that + ``self._current_list`` cannot be extended in a valid way. + + OUTPUT: a boolean: whether the push succeeded + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: I = C._iter() + sage: I._j + -1 + sage: I._search_ranges + [] + sage: I._current_list + [] + sage: I._current_sum + 0 + sage: I._push_search() + True + sage: I._j + 0 + sage: I._search_ranges + [(0, 2)] + sage: I._current_list + [2] + sage: I._current_sum + 2 + sage: I._push_search() + True + sage: I._j + 1 + sage: I._search_ranges + [(0, 2), (0, 0)] + sage: I._current_list + [2, 0] + sage: I._current_sum + 2 + """ + max_sum = self.backend.max_sum + min_length = self.backend.min_length + max_length = self.backend.max_length + if self._j+1 >= max_length: + return False + if self._j+1 >= min_length and self._current_sum == max_sum: + # Cannot add trailing zeroes + return False + + if self._j >= 0: + prev = self._current_list[self._j] + else: + prev = None + interval = self._m_interval(self._j+1, self.backend.max_sum - self._current_sum, prev) + if interval[0] > interval[1]: + return False + + self._j += 1 + m = interval[1] + self._search_ranges.append(interval) + self._current_list.append(m) + self._current_sum += m + return True + + def _pop_search(self): + """ + Go back in search tree. Resetting attributes. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: I = C._iter() + sage: I._push_search() + True + sage: I._j + 0 + sage: I._search_ranges + [(0, 2)] + sage: I._current_sum + 2 + sage: I._current_list + [2] + sage: I._pop_search() + sage: I._j + -1 + sage: I._search_ranges + [] + sage: I._current_sum + 0 + sage: I._current_list + [] + """ + if self._j >= 0: # TODO: get rid of this condition + self._j -= 1 + self._search_ranges.pop() + self._current_sum -= self._current_list[-1] + self._current_list.pop() + + def next(self): + r""" + Return the next element in the iteration. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: I = IntegerListsLexIter(C) + sage: next(I) + [2, 0, 0] + sage: next(I) + [1, 1, 0] + """ + p = self.backend + min_sum = p.min_sum + max_length = p.max_length + search_ranges = self._search_ranges + + while True: + assert self._j == len(self._current_list) - 1 + assert self._j == len(self._search_ranges) - 1 + + # LOOK AHEAD + if self._next_state == LOOKAHEAD: + if self._lookahead(): + self._next_state = PUSH + else: + # We should reuse information about the + # reasons for this failure, to avoid when + # possible retrying with smaller values. + # We just do a special case for now: + if self._j + 1 == max_length and self._current_sum < min_sum: + self._next_state = POP + else: + self._next_state = DECREASE + + # PUSH + if self._next_state == PUSH: + if self._push_search(): + self._next_state = LOOKAHEAD + continue + self._next_state = ME + + # ME + if self._next_state == ME: + if self._j == -1: + self._next_state = STOP + else: + self._next_state = DECREASE + if self._internal_list_valid(): + return self._current_list + + # DECREASE + if self._next_state == DECREASE: + self._current_list[-1] -= 1 + self._current_sum -= 1 + if self._current_list[-1] >= search_ranges[self._j][0]: + self._next_state = LOOKAHEAD + continue + self._next_state = POP + + # POP + if self._next_state == POP: + self._pop_search() + self._next_state = ME + continue + + # STOP + if self._next_state == STOP: + raise StopIteration() + + assert False + + def _internal_list_valid(self): + """ + Return whether the current list in the iteration variable + ``self._current_list`` is a valid list. + + This method checks whether the sum of the parts in + ``self._current_list`` is in the right range, whether its + length is in the required range, and whether there are trailing + zeroes. It does not check all of the necessary conditions to + verify that an arbitrary list satisfies the constraints from + the corresponding ``IntegerListsLex`` object, and should + not be used except internally in the iterator class. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: I = IntegerListsLexIter(C) + sage: I._current_list + [] + sage: I._internal_list_valid() + False + sage: next(I) + [2, 0, 0] + sage: I._current_list + [2, 0, 0] + sage: I._internal_list_valid() + True + """ + p = self.backend + mu = self._current_list + nu = self._current_sum + l = self._j + 1 + return (nu >= p.min_sum and nu <= p.max_sum # Good sum + and l >= p.min_length and l <= p.max_length # Good length + and (l <= max(p.min_length,0) or mu[-1] != 0)) # No trailing zeros + + def _m_interval(self, i, max_sum, prev=None): + r""" + Return coarse lower and upper bounds for the part ``m`` + at position ``i``. + + INPUT: + + - ``i`` -- a nonnegative integer (position) + + - ``max_sum`` -- a nonnegative integer or ``+oo`` + + - ``prev`` -- a nonnegative integer or ``None`` + + Return coarse lower and upper bounds for the value ``m`` + of the part at position ``i`` so that there could exists + some list suffix `v_i,\ldots,v_k` of sum bounded by + ``max_sum`` and satisfying the floor and upper bound + constraints. If ``prev`` is specified, then the slope + conditions between ``v[i-1]=prev`` and ``v[i]=m`` should + also be satisfied. + + Additionally, this raises an error if it can be detected + that some part is neither directly nor indirectly bounded + above, which implies that the constraints possibly do not + allow for an inverse lexicographic iterator. + + OUTPUT: + + A tuple of two integers ``(lower_bound, upper_bound)``. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: I = IntegerListsLexIter(C) + sage: I._m_interval(1,2) + (0, 2) + + The second part is not bounded above, hence we can not + iterate lexicographically through all the elements:: + + sage: IntegerListsLex(ceiling=[2,infinity,3], max_length=3).first() + Traceback (most recent call last): + ... + ValueError: infinite upper bound for values of m + + Same here:: + + sage: IntegerListsLex(ceiling=[2,infinity,2], max_length=3, min_slope=-1).cardinality() + Traceback (most recent call last): + ... + ValueError: infinite upper bound for values of m + + In the following examples however, all parts are + indirectly bounded above:: + + sage: IntegerListsLex(ceiling=[2,infinity,2], length=3, min_slope=-1).cardinality() + 24 + sage: IntegerListsLex(ceiling=[2,infinity,2], max_length=3, max_slope=1).cardinality() + 24 + + sage: IntegerListsLex(max_part=2, max_length=3).cardinality() + 27 + sage: IntegerListsLex(3, max_length=3).cardinality() # parts bounded by n + 10 + sage: IntegerListsLex(max_length=0, min_length=1).list() # no part! + [] + sage: IntegerListsLex(length=0).list() # no part! + [[]] + """ + p = self.backend + + lower_bound = max(0, p.floor(i)) + upper_bound = min(max_sum, p.ceiling(i)) + if prev != None: + lower_bound = max(lower_bound, prev + p.min_slope) + upper_bound = min(upper_bound, prev + p.max_slope) + + ## check for infinite upper bound, in case max_sum is infinite + if p.check and upper_bound == Infinity: + # This assumes that there exists a valid list (which + # is not yet always guaranteed). Then we just + # discovered that part 'i' of this list can be made as + # large as desired, which implies that `self.backend` + # cannot be iterated in inverse lexicographic order + raise ValueError("infinite upper bound for values of m") + + return (lower_bound, upper_bound) + + def _lookahead(self): + r""" + Return whether the current list can possibly be a prefix + of a valid list. + + OUTPUT: + + ``False`` if it is guaranteed that the current list + cannot be a prefix of a valid list and ``True`` otherwise. + + EXAMPLES:: + + sage: it = IntegerListsLex(length=3, min_sum=2, max_sum=2)._iter() + sage: it._current_list = [0,1] # don't do this at home, kids + sage: it._current_sum = 1 + sage: it._j = 1 + sage: it._lookahead() + True + + sage: it = IntegerListsLex(length=3, min_sum=3, max_sum=2)._iter() + sage: it._current_list = [0,1] + sage: it._current_sum = 1 + sage: it._j = 1 + sage: it._lookahead() + False + + sage: it = IntegerListsLex(min_length=2, max_part=0)._iter() + sage: it._current_list = [0] + sage: it._current_sum = 0 + sage: it._j = 0 + sage: it._lookahead() + True + sage: it._current_list = [0, 0] + sage: it._j = 1 + sage: it._lookahead() + True + sage: it._current_list = [0, 0, 0] + sage: it._j = 2 + sage: it._lookahead() + False + + sage: n = 10**100 + sage: it = IntegerListsLex(n, length=1)._iter() + sage: it._current_list = [n-1] + sage: it._current_sum = n-1 + sage: it._j = 0 + sage: it._lookahead() + False + + sage: it = IntegerListsLex(n=3, min_part=2, min_sum=3, max_sum=3)._iter() + sage: it._current_list = [2] + sage: it._current_sum = 2 + sage: it._j = 0 + sage: it._lookahead() + False + + ALGORITHM: + + Let ``j = self._j`` be the position of the last part `m` of + ``self._current_list``. The current algorithm computes, + for `k = j, j+1, \ldots`, a lower bound `l_k` and an upper + bound `u_k` for `v_0+\dots+v_k`, and stops if none of the + invervals `[l_k, u_k]` intersect ``[min_sum, max_sum]``. + + The lower bound `l_k` is given by the area below + `v_0,\dots,v_{j-1}` prolongated by the lower envelope + between `j` and `k` and starting at `m`. The upper bound + `u_k` is given similarly using the upper envelope. + + The complexity of this algorithm is bounded above by + ``O(max_length)``. When ``max_length=oo``, the algorithm + is guaranteed to terminate, unless ``floor`` is a function + which is eventually constant with value `0`, or which + reaches the value `0` while ``max_slope=0``. + + Indeed, the lower bound `l_k` is increasing with `k`; in + fact it is strictly increasing, unless the local lower bound + at `k` is `0`. Furthermore as soon as ``l_k >= min_sum``, + we can conclude; we can also conclude if we know that the + floor is eventually constant with value `0`, or there is a + local lower bound at `k` is `0` and ``max_slope=0``. + + .. RUBRIC:: Room for improvement + + Improved prediction: the lower bound `l_k` does not take + the slope conditions into account, except for those imposed + by the value `m` at `j`. Similarly for `u_k`. + + Improved speed: given that `l_k` is increasing with `k`, + possibly some dichotomy could be used to search for `k`, + with appropriate caching / fast calculation of the partial + sums. Also, some of the information gained at depth `j` + could be reused at depth `j+1`. + + TESTS:: + + sage: it = IntegerListsLex(1, min_length=2, min_slope=0, max_slope=0, min_sum=1, max_sum=1)._iter() + sage: it._current_list = [0] + sage: it._current_sum = 0 + sage: it._j = 0 + sage: it._lookahead() + False + """ + # Check code for various termination conditions. Possible cases: + # 0. interval [lower, upper] intersects interval [min_sum, max_sum] -- terminate True + # 1. lower sum surpasses max_sum -- terminate False + # 2. iteration surpasses max_length -- terminate False + # 3. upper envelope is smaller than lower envelope -- terminate False + # 4. max_slope <= 0 -- terminate False after upper passes 0 + # 5. ceiling_limit == 0 -- terminate False after reaching larger limit point + # 6. (uncomputable) ceiling function == 0 for all but finitely many input values -- terminate False after reaching (unknown) limit point -- currently hangs + + m = self._current_list[-1] + j = self._j + min_sum = self.backend.min_sum - (self._current_sum-m) + max_sum = self.backend.max_sum - (self._current_sum-m) + + if min_sum > max_sum: + return False + + p = self.backend + + # Beware that without slope conditions, the functions below + # currently forget about the value m at k! + lower_envelope = self.backend.floor.adapt(m,j) + upper_envelope = self.backend.ceiling.adapt(m,j) + lower = 0 # The lower bound `l_k` + upper = 0 # The upper bound `u_k` + + assert j >= 0 + # get to smallest valid number of parts + for k in range(j, p.min_length-1): + # We are looking at lists `v_j,...,v_k` + lo = m if k == j else lower_envelope(k) + up = m if k == j else upper_envelope(k) + if lo > up: + return False + lower += lo + upper += up + + if j < p.min_length and min_sum <= upper and lower <= max_sum: + # There could exist a valid list `v_j,\dots,v_{min_length-1}` + return True + + k = max(p.min_length-1,j) + # Check if any of the intervals intersect the target interval + while k < p.max_length: + lo = m if k == j else lower_envelope(k) + up = m if k == j else upper_envelope(k) + if lo > up: + # There exists no valid list of length >= k + return False + lower += lo + upper += up + assert lower <= upper + + if lower > max_sum: + # There cannot exist a valid list `v_j,\dots,v_l` with l>=k + return False + + if ( (p.max_slope <= 0 and up <= 0) + or (p.ceiling.limit() == 0 and k > p.ceiling.limit_start()) ): + # This implies v_l=0 for l>=k: that is we would be generating + # a list with trailing zeroes + return False + + if min_sum <= upper and lower <= max_sum: + # There could exist a valid list `v_j,\dots,v_k` + return True + + k += 1 + + return False + diff --git a/src/sage/combinat/integer_lists/lists.py b/src/sage/combinat/integer_lists/lists.py new file mode 100644 index 00000000000..42ff0dd6214 --- /dev/null +++ b/src/sage/combinat/integer_lists/lists.py @@ -0,0 +1,292 @@ +r""" +Enumerated set of lists of integers with constraints: front-end + +- :class:`IntegerLists`: class which models an enumerated set of lists + of integers with certain constraints. This is a Python front-end + where all user-accessible functionality should be implemented. +""" + +#***************************************************************************** +# Copyright (C) 2015 Bryan Gillespie +# Nicolas M. Thiery +# Anne Schilling +# Jeroen Demeyer +# +# 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 inspect import ismethod +from sage.categories.enumerated_sets import EnumeratedSets +from sage.structure.list_clone import ClonableArray +from sage.structure.parent import Parent +from sage.combinat.integer_lists.base import IntegerListsBackend + + +class IntegerList(ClonableArray): + """ + Element class for :class:`IntegerLists`. + """ + def check(self): + """ + Check to make sure this is a valid element in its + :class:`IntegerLists` parent. + + EXAMPLES:: + + sage: C = IntegerListsLex(4) + sage: C([4]).check() + True + sage: C([5]).check() + False + """ + return self.parent().__contains__(self) + + +class IntegerLists(Parent): + """ + Enumerated set of lists of integers with constraints. + + Currently, this is simply an abstract base class which should not + be used by itself. See :class:`IntegerListsLex` for a class which + can be used by end users. + + ``IntegerLists`` is just a Python front-end, acting as a + :class:`Parent`, supporting element classes and so on. + The attribute ``.backend`` which is an instance of + :class:`sage.combinat.integer_lists.base.IntegerListsBackend` is the + Cython back-end which implements all operations such as iteration. + + The front-end (i.e. this class) and the back-end are supposed to be + orthogonal: there is no imposed correspondence between front-ends + and back-ends. + + For example, the set of partitions of 5 and the set of weakly + decreasing sequences which sum to 5 might be implemented by the + same back-end, but they will be presented to the user by a + different front-end. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import IntegerLists + sage: L = IntegerLists(5) + sage: L + Integer lists of sum 5 satisfying certain constraints + + sage: IntegerListsLex(2, length=3, name="A given name") + A given name + """ + backend = None + backend_class = IntegerListsBackend + + Element = IntegerList + + def __init__(self, *args, **kwds): + """ + Initialize ``self``. + + TESTS:: + + sage: from sage.combinat.integer_lists import IntegerLists + sage: C = IntegerLists(2, length=3) + sage: C == loads(dumps(C)) + True + """ + if "name" in kwds: + self.rename(kwds.pop("name")) + + if "global_options" in kwds: + from sage.misc.superseded import deprecation + deprecation(15525, 'the global_options argument is deprecated since, in general,' + ' pickling is broken; create your own class instead') + self.global_options = kwds.pop("global_options") + + if "element_class" in kwds: + self.Element = kwds.pop("element_class") + + if "element_constructor" in kwds: + element_constructor = kwds.pop("element_constructor") + elif issubclass(self.Element, ClonableArray): + # Not all element classes support check=False + element_constructor = self._element_constructor_nocheck + else: + element_constructor = None # Parent's default + + category = kwds.pop("category", None) + if category is None: + category = EnumeratedSets().Finite() + + # Let self.backend be some IntegerListsBackend + self.backend = self.backend_class(*args, **kwds) + + Parent.__init__(self, element_constructor=element_constructor, + category=category) + + def __eq__(self, other): + r""" + Return whether ``self == other``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: D = IntegerListsLex(2, length=3); L = D.list(); + sage: E = IntegerListsLex(2, min_length=3) + sage: F = IntegerListsLex(2, length=3, element_constructor=list) + sage: G = IntegerListsLex(4, length=3) + sage: C == C + True + sage: C == D + True + sage: C == E + False + sage: C == F + False + sage: C == None + False + sage: C == G + False + + This is a minimal implementation enabling pickling tests. It + is safe, but one would want the two following objects to be + detected as equal:: + + sage: C = IntegerListsLex(2, ceiling=[1,1,1]) + sage: D = IntegerListsLex(2, ceiling=[1,1,1]) + sage: C == D + False + + TESTS: + + This used to fail due to poor equality testing. See + :trac:`17979`, comment 433:: + + sage: DisjointUnionEnumeratedSets(Family([2,2], + ....: lambda n: IntegerListsLex(n, length=2))).list() + [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] + sage: DisjointUnionEnumeratedSets(Family([2,2], + ....: lambda n: IntegerListsLex(n, length=1))).list() + [[2], [2]] + """ + if self.__class__ != other.__class__: + return False + if self.backend != other.backend: + return False + a = self._element_constructor + b = other._element_constructor + if ismethod(a): + a = a.im_func + if ismethod(b): + b = b.im_func + return a == b + + def __ne__(self, other): + r""" + Return whether ``self != other``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: D = IntegerListsLex(2, length=3); L = D.list(); + sage: E = IntegerListsLex(2, max_length=3) + sage: C != D + False + sage: C != E + True + """ + return not self == other + + def __iter__(self): + """ + Return an iterator for the elements of ``self``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: list(C) # indirect doctest + [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + """ + return self._element_iter(self.backend._iter(), self._element_constructor) + + @staticmethod + def _element_iter(itr, constructor): + """ + Given an iterator ``itr`` and an element constructor + ``constructor``, iterate over ``constructor(v)`` where `v` + are the values of ``itr``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: list(C._element_iter(C._iter(), tuple)) + [(2, 0, 0), (1, 1, 0), (1, 0, 1), (0, 2, 0), (0, 1, 1), (0, 0, 2)] + """ + for v in itr: + yield constructor(v) + + def __getattr__(self, name): + """ + Get an attribute of the implementation backend. + + Ideally, this would be done using multiple inheritance, but + Python doesn't support that for built-in types. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: C.min_length + 3 + + TESTS: + + Check that uninitialized instances do not lead to infinite + recursion because there is no ``backend`` attribute:: + + sage: from sage.combinat.integer_lists import IntegerLists + sage: L = IntegerLists.__new__(IntegerLists) + sage: L.foo + Traceback (most recent call last): + ... + AttributeError: 'NoneType' object has no attribute 'foo' + """ + return getattr(self.backend, name) + + def __contains__(self, item): + """ + Return ``True`` if ``item`` meets the constraints imposed by + the arguments. + + EXAMPLES:: + + sage: C = IntegerListsLex(n=2, max_length=3, min_slope=0) + sage: all([l in C for l in C]) + True + """ + return self.backend._contains(item) + + def _element_constructor_nocheck(self, l): + r""" + A variant of the standard element constructor that passes + ``check=False`` to the element class. + + EXAMPLES:: + + sage: L = IntegerListsLex(4, max_slope=0) + sage: L._element_constructor_nocheck([1,2,3]) + [1, 2, 3] + + When relevant, this is assigned to + ``self._element_constructor`` by :meth:`__init__`, to avoid + overhead when constructing elements from trusted data in the + iterator:: + + sage: L._element_constructor + + sage: L._element_constructor([1,2,3]) + [1, 2, 3] + """ + return self.element_class(self, l, check=False) + diff --git a/src/sage/combinat/integer_lists/nn.py b/src/sage/combinat/integer_lists/nn.py new file mode 100644 index 00000000000..24c7e9458fc --- /dev/null +++ b/src/sage/combinat/integer_lists/nn.py @@ -0,0 +1,43 @@ +from sage.sets.family import Family +from sage.combinat.integer_lists import IntegerListsLex +from sage.rings.semirings.non_negative_integer_semiring import NN +from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets + +def IntegerListsNN(**kwds): + """ + Lists of nonnegative integers with constraints. + + This function returns the union of ``IntegerListsLex(n, **kwds)`` + where `n` ranges over all nonnegative integers. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.nn import IntegerListsNN + sage: L = IntegerListsNN(max_length=3, max_slope=-1) + sage: L + Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} + sage: it = iter(L) + sage: for _ in range(20): + ....: print next(it) + [] + [1] + [2] + [3] + [2, 1] + [4] + [3, 1] + [5] + [4, 1] + [3, 2] + [6] + [5, 1] + [4, 2] + [3, 2, 1] + [7] + [6, 1] + [5, 2] + [4, 3] + [4, 2, 1] + [8] + """ + return DisjointUnionEnumeratedSets(Family(NN, lambda i: IntegerListsLex(i, **kwds))) diff --git a/src/sage/combinat/integer_matrices.py b/src/sage/combinat/integer_matrices.py index 15bf04d4964..0983acbf1f4 100644 --- a/src/sage/combinat/integer_matrices.py +++ b/src/sage/combinat/integer_matrices.py @@ -18,7 +18,7 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets -from sage.combinat.integer_list import IntegerListsLex +from sage.combinat.integer_lists import IntegerListsLex from sage.matrix.constructor import matrix from sage.rings.integer_ring import ZZ diff --git a/src/sage/combinat/integer_vector.py b/src/sage/combinat/integer_vector.py index b11ce3136da..6ca77961adc 100644 --- a/src/sage/combinat/integer_vector.py +++ b/src/sage/combinat/integer_vector.py @@ -27,6 +27,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools import misc from __builtin__ import list as builtinlist from sage.categories.enumerated_sets import EnumeratedSets @@ -34,9 +35,8 @@ from sage.rings.integer import Integer from sage.rings.arith import binomial from sage.rings.infinity import PlusInfinity -import cartesian_product import functools -from sage.combinat.integer_list import IntegerListsLex +from sage.combinat.integer_lists import IntegerListsLex def is_gale_ryser(r,s): @@ -114,86 +114,6 @@ def is_gale_ryser(r,s): # same number of 1s domination return len(rstar) <= len(s2) and sum(r2) == sum(s2) and rstar.dominates(s) -def _slider01(A, t, k, p1, p2, fixedcols=[]): - r""" - Assumes `A` is a `(0,1)`-matrix. For each of the - `t` rows with highest row sums, this function - returns a matrix `B` which is the same as `A` except that it - has slid `t` of the `1` in each of these rows of `A` - over towards the `k`-th column. Care must be taken when the - last leading 1 is in column >=k. It avoids those in columns - listed in fixedcols. - - This is a 'private' function for use in gale_ryser_theorem. - - INPUT: - - - ``A`` -- an `m\times n` (0,1) matrix - - ``t``, ``k`` -- integers satisfying `0 < t < m`, `0 < k < n` - - ``fixedcols`` -- those columns (if any) whose entries - aren't permitted to slide - - OUTPUT: - - An `m\times n` (0,1) matrix, which is the same as `A` except - that it has exactly one `1` in `A` slid over to the `k`-th - column. - - EXAMPLES:: - - sage: from sage.combinat.integer_vector import _slider01 - sage: A = matrix([[1,1,1,0],[1,1,1,0],[1,0,0,0],[1,0,0,0]]) - sage: A - [1 1 1 0] - [1 1 1 0] - [1 0 0 0] - [1 0 0 0] - sage: _slider01(A, 1, 3, [3,3,1,1], [3,3,1,1]) - [1 1 0 1] - [1 1 1 0] - [1 0 0 0] - [1 0 0 0] - sage: _slider01(A, 3, 3, [3,3,1,1], [3,3,1,1]) - [1 1 0 1] - [1 0 1 1] - [0 0 0 1] - [1 0 0 0] - - """ - # we assume that the rows of A are arranged so that - # there row sums are decreasing as you go from the - # top row to the bottom row - import copy - from sage.matrix.constructor import matrix - m = len(A.rows()) - rs = [sum(x) for x in A.rows()] - n = len(A.columns()) - cs = [sum(x) for x in A.columns()] - B = [copy.deepcopy(list(A.row(j))) for j in range(m)] - c = 0 # initializing counter - for ii in range(m): - rw = copy.deepcopy(B[ii]) # to make mutable - # now we want to move the rightmost left 1 to the k-th column - fixedcols = [l for l in range(n) if p2[l]==sum(matrix(B).column(l))] - JJ = range(n) - JJ.reverse() - for jj in JJ: - if t==sum(matrix(B).column(k)): - break - if jj=t: next - j=n-1 - else: - next - if c>=t: - break - return matrix(B) - def gale_ryser_theorem(p1, p2, algorithm="gale"): r""" Returns the binary matrix given by the Gale-Ryser theorem. @@ -234,8 +154,8 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): Ryser's Algorithm: - (Ryser [Ryser63]_): The construction of an `m\times n` matrix `A=A_{r,s}`, - due to Ryser, is described as follows. The + (Ryser [Ryser63]_): The construction of an `m\times n` matrix + `A=A_{r,s}`, due to Ryser, is described as follows. The construction works if and only if have `s\preceq r^*`. * Construct the `m\times n` matrix `B` from `r` by defining @@ -255,6 +175,8 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): with a wrong sum in the step below. * Proceed inductively to construct columns `n-1`, ..., `2`, `1`. + Note: when performing the induction on step `k`, we consider + the row sums of the first `k` columns. * Set `A = B`. Return `A`. @@ -282,10 +204,10 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): sage: p1 = [3,3,1,1] sage: p2 = [3,3,1,1] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") - [1 1 0 1] [1 1 1 0] - [0 1 0 0] + [1 1 0 1] [1 0 0 0] + [0 1 0 0] sage: p1 = [4,2,2] sage: p2 = [3,3,1,1] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") @@ -311,19 +233,19 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): sage: from sage.combinat.integer_vector import gale_ryser_theorem sage: gale_ryser_theorem([3,3,0,1,1,0], [3,1,3,1,0], algorithm = "ryser") - [1 0 1 1 0] [1 1 1 0 0] + [1 0 1 1 0] [0 0 0 0 0] - [0 0 1 0 0] [1 0 0 0 0] + [0 0 1 0 0] [0 0 0 0 0] sage: p1 = [3,1,1,1,1]; p2 = [3,2,2,0] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 1 0] - [0 0 1 0] - [0 1 0 0] [1 0 0 0] [1 0 0 0] + [0 1 0 0] + [0 0 1 0] TESTS: @@ -334,20 +256,20 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): checked for correctness.:: sage: def test_algorithm(algorithm, low = 10, high = 50): - ... n,m = randint(low,high), randint(low,high) - ... g = graphs.RandomBipartite(n, m, .3) - ... s1 = sorted(g.degree([(0,i) for i in range(n)]), reverse = True) - ... s2 = sorted(g.degree([(1,i) for i in range(m)]), reverse = True) - ... m = gale_ryser_theorem(s1, s2, algorithm = algorithm) - ... ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True) - ... ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True) - ... if ((ss1 != s1) or (ss2 != s2)): - ... print "Algorithm %s failed with this input:" % algorithm - ... print s1, s2 - - sage: for algorithm in ["gale", "ryser"]: # long time - ... for i in range(50): # long time - ... test_algorithm(algorithm, 3, 10) # long time + ....: n,m = randint(low,high), randint(low,high) + ....: g = graphs.RandomBipartite(n, m, .3) + ....: s1 = sorted(g.degree([(0,i) for i in range(n)]), reverse = True) + ....: s2 = sorted(g.degree([(1,i) for i in range(m)]), reverse = True) + ....: m = gale_ryser_theorem(s1, s2, algorithm = algorithm) + ....: ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True) + ....: ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True) + ....: if ((ss1 != s1) or (ss2 != s2)): + ....: print "Algorithm %s failed with this input:" % algorithm + ....: print s1, s2 + + sage: for algorithm in ["gale", "ryser"]: # long time + ....: for i in range(50): # long time + ....: test_algorithm(algorithm, 3, 10) # long time Null matrix:: @@ -359,14 +281,28 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): [0 0 0 0] [0 0 0 0] [0 0 0 0] - + + Check that :trac:`16638` is fixed:: + + sage: tests = [([4, 3, 3, 2, 1, 1, 1, 1, 0], [6, 5, 1, 1, 1, 1, 1]), + ....: ([4, 4, 3, 3, 1, 1, 0], [5, 5, 2, 2, 1, 1]), + ....: ([4, 4, 3, 2, 1, 1], [5, 5, 1, 1, 1, 1, 1, 0, 0]), + ....: ([3, 3, 3, 3, 2, 1, 1, 1, 0], [7, 6, 2, 1, 1, 0]), + ....: ([3, 3, 3, 1, 1, 0], [4, 4, 1, 1, 1])] + sage: for s1, s2 in tests: + ....: m = gale_ryser_theorem(s1, s2, algorithm="ryser") + ....: ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True) + ....: ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True) + ....: if ((ss1 != s1) or (ss2 != s2)): + ....: print("Error in Ryser algorithm") + ....: print(s1, s2) REFERENCES: .. [Ryser63] H. J. Ryser, Combinatorial Mathematics, - Carus Monographs, MAA, 1963. + Carus Monographs, MAA, 1963. .. [Gale57] D. Gale, A theorem on flows in networks, Pacific J. Math. - 7(1957)1073-1082. + 7(1957)1073-1082. """ from sage.combinat.partition import Partition from sage.matrix.constructor import matrix @@ -380,28 +316,39 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): # Sorts the sequences if they are not, and remembers the permutation # applied tmp = sorted(enumerate(p1), reverse=True, key=lambda x:x[1]) - r = [x[1] for x in tmp if x[1]>0] + r = [x[1] for x in tmp] r_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()] m = len(r) tmp = sorted(enumerate(p2), reverse=True, key=lambda x:x[1]) - s = [x[1] for x in tmp if x[1]>0] + s = [x[1] for x in tmp] s_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()] n = len(s) - A0 = matrix([[1]*r[j]+[0]*(n-r[j]) for j in range(m)]) - - for k in range(1,n+1): - goodcols = [i for i in range(n) if s[i]==sum(A0.column(i))] - if sum(A0.column(n-k)) != s[n-k]: - A0 = _slider01(A0,s[n-k],n-k, p1, p2, goodcols) - - # If we need to add empty rows/columns - if len(p1)!=m: - A0 = A0.stack(matrix([[0]*n]*(len(p1)-m))) - - if len(p2)!=n: - A0 = A0.transpose().stack(matrix([[0]*len(p1)]*(len(p2)-n))).transpose() + # This is the partition equivalent to the sliding algorithm + cols = [] + for t in reversed(s): + c = [0] * m + i = 0 + while t: + k = i + 1 + while k < m and r[i] == r[k]: + k += 1 + if t >= k - i: # == number rows of the same length + for j in range(i, k): + r[j] -= 1 + c[j] = 1 + t -= k - i + else: # Remove the t last rows of that length + for j in range(k-t, k): + r[j] -= 1 + c[j] = 1 + t = 0 + i = k + cols.append(c) + + # We added columns to the back instead of the front + A0 = matrix(list(reversed(cols))).transpose() # Applying the permutations to get a matrix satisfying the # order given by the input @@ -923,34 +870,30 @@ def __init__(self, n, k, constraints, category=None): sage: type(v) - TESTS: - - All the attributes below are private; don't use them! - - :: + TESTS:: - sage: IV._min_length + sage: IV.min_length 3 - sage: IV._max_length + sage: IV.max_length 3 - sage: floor = IV._floor + sage: floor = IV.floor sage: [floor(i) for i in range(1,10)] [0, 0, 0, 0, 0, 0, 0, 0, 0] - sage: ceiling = IV._ceiling + sage: ceiling = IV.ceiling sage: [ceiling(i) for i in range(1,5)] [inf, inf, inf, inf] - sage: IV._min_slope + sage: IV.min_slope 0 - sage: IV._max_slope + sage: IV.max_slope inf sage: IV = IntegerVectors(3, 10, inner=[4,1,3], min_part=2) - sage: floor = IV._floor + sage: floor = IV.floor sage: floor(0), floor(1), floor(2) (4, 2, 3) sage: IV = IntegerVectors(3, 10, outer=[4,1,3], max_part=3) - sage: ceiling = IV._ceiling + sage: ceiling = IV.ceiling sage: ceiling(0), ceiling(1), ceiling(2) (3, 1, 3) """ @@ -1072,7 +1015,7 @@ def next(self, x): [0, 0, 2] """ from sage.combinat.integer_list_old import next - return next(x, self._min_length, self._max_length, self._floor, self._ceiling, self._min_slope, self._max_slope) + return next(x, self.min_length, self.max_length, self.floor, self.ceiling, self.min_slope, self.max_slope) class IntegerVectors_nconstraints(IntegerVectors_nkconstraints): def __init__(self, n, constraints): @@ -1221,7 +1164,7 @@ def __iter__(self): """ for iv in IntegerVectors(self.n, len(self.comp)): blocks = [ IntegerVectors(iv[i], self.comp[i], max_slope=0).list() for i in range(len(self.comp))] - for parts in cartesian_product.CartesianProduct(*blocks): + for parts in itertools.product(*blocks): res = [] for part in parts: res += part diff --git a/src/sage/combinat/k_tableau.py b/src/sage/combinat/k_tableau.py index 12d7a0da215..80850a88a3d 100644 --- a/src/sage/combinat/k_tableau.py +++ b/src/sage/combinat/k_tableau.py @@ -4211,9 +4211,9 @@ def marked_given_unmarked_and_weight_iterator( cls, unmarkedT, k, weight ): if td == {}: # the tableau is empty yield StrongTableau( unmarkedT, k, [] ) else: - allmarkings = cartesian_product.CartesianProduct(*[td[v] for v in td.keys()]) + import itertools dsc = Composition(weight).descents() - for m in allmarkings: + for m in itertools.product(*td.values()): if all(((m[i][1]-m[i][0] 2): + if existence: + return False raise ValueError("The Hadamard matrix of order %s does not exist" % n) if n == 2: - return matrix([[1, 1], [1, -1]]) - if is_even(n): - N = Integer(n / 2) + if existence: + return True + M = matrix([[1, 1], [1, -1]]) elif n == 1: - return matrix([1]) - if is_prime(N - 1) and (N - 1) % 4 == 1: - return hadamard_matrix_paleyII(n) + if existence: + return True + M = matrix([1]) + elif is_prime_power(n//2 - 1) and (n//2 - 1) % 4 == 1: + if existence: + return True + M = hadamard_matrix_paleyII(n) elif n == 4 or n % 8 == 0: - had = hadamard_matrix(Integer(n / 2)) + if existence: + return hadamard_matrix(n//2,existence=True) + had = hadamard_matrix(n//2,check=False) chad1 = matrix([list(r) + list(r) for r in had.rows()]) mhad = (-1) * had R = len(had.rows()) chad2 = matrix([list(had.rows()[i]) + list(mhad.rows()[i]) for i in range(R)]) - return chad1.stack(chad2) - elif is_prime(N - 1) and (N - 1) % 4 == 3: - return hadamard_matrix_paleyI(n) + M = chad1.stack(chad2) + elif is_prime_power(n - 1) and (n - 1) % 4 == 3: + if existence: + return True + M = hadamard_matrix_paleyI(n) else: + if existence: + return Unknown raise ValueError("The Hadamard matrix of order %s is not yet implemented." % n) + if check: + assert is_hadamard_matrix(M, normalized=True) + + return M + def hadamard_matrix_www(url_file, comments=False): """ Pulls file from Sloane's database and returns the corresponding Hadamard diff --git a/src/sage/combinat/ncsf_qsym/generic_basis_code.py b/src/sage/combinat/ncsf_qsym/generic_basis_code.py index e27e00e3ece..ea514d73de7 100644 --- a/src/sage/combinat/ncsf_qsym/generic_basis_code.py +++ b/src/sage/combinat/ncsf_qsym/generic_basis_code.py @@ -978,15 +978,16 @@ def degree(self): sage: S.zero().degree() Traceback (most recent call last): ... - ValueError: The zero element does not have a well-defined degree. + ValueError: the zero element does not have a well-defined degree sage: F = QuasiSymmetricFunctions(QQ).F() sage: F.zero().degree() Traceback (most recent call last): ... - ValueError: The zero element does not have a well-defined degree. + ValueError: the zero element does not have a well-defined degree """ return self.maximal_degree() + class AlgebraMorphism(ModuleMorphismByLinearity): # Find a better name """ A class for algebra morphism defined on a free algebra from the image of the generators diff --git a/src/sage/combinat/ncsf_qsym/ncsf.py b/src/sage/combinat/ncsf_qsym/ncsf.py index d6ba56f6478..fff8dd0393c 100644 --- a/src/sage/combinat/ncsf_qsym/ncsf.py +++ b/src/sage/combinat/ncsf_qsym/ncsf.py @@ -951,8 +951,9 @@ def bernstein_creation_operator(self, n): ....: for i in reversed(xs): ....: res = res.bernstein_creation_operator(i) ....: return res + sage: import itertools sage: all( immaculate_by_bernstein(p) == I.immaculate_function(p) - ....: for p in CartesianProduct(range(-1, 3), range(-1, 3), range(-1, 3)) ) + ....: for p in itertools.product(range(-1, 3), repeat=3)) True Some examples:: diff --git a/src/sage/combinat/ncsf_qsym/tutorial.py b/src/sage/combinat/ncsf_qsym/tutorial.py index 1c987793d64..5cf429b0d17 100644 --- a/src/sage/combinat/ncsf_qsym/tutorial.py +++ b/src/sage/combinat/ncsf_qsym/tutorial.py @@ -92,8 +92,8 @@ sage: y.expand(4) x0*x1^2*x2 + x0*x1^2*x3 + x0*x2^2*x3 + x1*x2^2*x3 -The usual methods on free modules are available such as coefficients, degrees, -and the support:: +The usual methods on free modules are available such as coefficients, +degrees, and the support:: sage: z=3*M[1,2]+M[3]^2; z 3*M[1, 2] + 2*M[3, 3] + M[6] @@ -104,10 +104,10 @@ sage: z.degree() 6 - sage: z.coefficients() - [3, 2, 1] + sage: sorted(z.coefficients()) + [1, 2, 3] - sage: z.monomials() + sage: sorted(z.monomials(), key=lambda x: x.support()) [M[1, 2], M[3, 3], M[6]] sage: z.monomial_coefficients() diff --git a/src/sage/combinat/ordered_tree.py b/src/sage/combinat/ordered_tree.py index a0e39880a95..b69f00686ab 100644 --- a/src/sage/combinat/ordered_tree.py +++ b/src/sage/combinat/ordered_tree.py @@ -14,6 +14,9 @@ # the License, or (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** + +import itertools + from sage.structure.list_clone import ClonableArray, ClonableList from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation @@ -940,7 +943,6 @@ def _element_constructor_(self, *args, **keywords): from sage.misc.lazy_attribute import lazy_attribute from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.combinat.composition import Compositions -from sage.combinat.cartesian_product import CartesianProduct ################################################################# # Enumerated set of binary trees of a given size ################################################################# @@ -1073,7 +1075,7 @@ def __iter__(self): return else: for c in Compositions(self._size - 1): - for lst in CartesianProduct(*[self.__class__(_) for _ in c]): + for lst in itertools.product(*[self.__class__(_) for _ in c]): yield self._element_constructor_(lst) @lazy_attribute diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 76eaa5f17ad..2a9b05a9482 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -311,7 +311,7 @@ from sage.combinat.partitions import number_of_partitions as bober_number_of_partitions from sage.combinat.partitions import ZS1_iterator, ZS1_iterator_nk from sage.combinat.integer_vector import IntegerVectors -from sage.combinat.integer_list import IntegerListsLex +from sage.combinat.integer_lists import IntegerListsLex from sage.combinat.root_system.weyl_group import WeylGroup from sage.combinat.combinatorial_map import combinatorial_map from sage.groups.perm_gps.permgroup import PermutationGroup @@ -4032,7 +4032,8 @@ def remove_horizontal_border_strip(self, k): sage: Partition([5,3,1]).remove_horizontal_border_strip(6).list() [] - The result is returned as an instance of :class:`IntegerListsLex`:: + The result is returned as an instance of + :class:`Partitions_with_constraints`:: sage: Partition([5,3,1]).remove_horizontal_border_strip(5) The subpartitions of [5, 3, 1] obtained by removing an horizontal border strip of length 5 @@ -4048,15 +4049,13 @@ def remove_horizontal_border_strip(self, k): sage: Partition([]).remove_horizontal_border_strip(6).list() [] """ - return IntegerListsLex(n = self.size()-k, - min_length = len(self)-1, - max_length = len(self), - floor = self[1:]+[0], - ceiling = self[:], - max_slope = 0, - element_class = Partition, - global_options = Partitions.global_options, - name = "The subpartitions of %s obtained by removing an horizontal border strip of length %s"%(self,k)) + return Partitions_with_constraints(n = self.size()-k, + min_length = len(self)-1, + max_length = len(self), + floor = self[1:]+[0], + ceiling = self[:], + max_slope = 0, + name = "The subpartitions of {} obtained by removing an horizontal border strip of length {}".format(self,k)) def k_conjugate(self, k): r""" @@ -4636,14 +4635,12 @@ def coloring(i): if directed: from sage.graphs.digraph import DiGraph - G = DiGraph(edges, multiedges=True) - G.add_vertices(T) # Add isolated vertices - self._DDEG = G.copy(immutable=True) + self._DDEG = DiGraph([T, edges], format="vertices_and_edges", + immutable=True, multiedges=True) else: from sage.graphs.graph import Graph - G = Graph(edges, multiedges=True) - G.add_vertices(T) # Add isolated vertices - self._DEG = G.copy(immutable=True) + self._DEG = Graph([T, edges], format="vertices_and_edges", + immutable=True, multiedges=True) return self.dual_equivalence_graph(directed, coloring) ############## @@ -4658,8 +4655,8 @@ class Partitions(UniqueRepresentation, Parent): Valid keywords are: ``starting``, ``ending``, ``min_part``, ``max_part``, ``max_length``, ``min_length``, ``length``, - ``max_slope``, ``min_slope``, ``inner``, ``outer``, and - ``parts_in``. They have the following meanings: + ``max_slope``, ``min_slope``, ``inner``, ``outer``, ``parts_in`` + and ``regular``. They have the following meanings: - ``starting=p`` specifies that the partitions should all be less than or equal to `p` in lex order. This argument cannot be combined @@ -4693,6 +4690,19 @@ class Partitions(UniqueRepresentation, Parent): integers. This argument cannot be combined with any other (see :trac:`15467`). + - ``regular=ell`` specifies that the partitions are `\ell`-regular, + and can only be combined with the ``max_length`` or ``max_part``, but + not both, keywords if `n` is not specified + + The ``max_*`` versions, along with ``inner`` and ``ending``, work + analogously. + + Right now, the ``parts_in``, ``starting``, ``ending``, and ``regular`` + keyword arguments are mutually exclusive, both of each other and of other + keyword arguments. If you specify, say, ``parts_in``, all other + keyword arguments will be ignored; ``starting``, ``ending``, and + ``regular`` work the same way. + EXAMPLES: If no arguments are passed, then the combinatorial class @@ -4769,6 +4779,16 @@ class Partitions(UniqueRepresentation, Parent): sage: Partitions(10, min_part=2, length=3).list() [[6, 2, 2], [5, 3, 2], [4, 4, 2], [4, 3, 3]] + Some examples using the ``regular`` keyword:: + + sage: Partitions(regular=4) + 4-Regular Partitions + sage: Partitions(regular=4, max_length=3) + 4-Regular Partitions with max length 3 + sage: Partitions(regular=4, max_part=3) + 4-Regular 3-Bounded Partitions + sage: Partitions(3, regular=4) + 4-Regular Partitions of the integer 3 Here are some further examples using various constraints:: @@ -4826,7 +4846,7 @@ class Partitions(UniqueRepresentation, Parent): sage: TestSuite(Partitions(0)).run() sage: TestSuite(Partitions(5)).run() - sage: TestSuite(Partitions(5, min_part=2)).run() # Not tested: todo - IntegerListsLex needs to pickle properly + sage: TestSuite(Partitions(5, min_part=2)).run() sage: repr( Partitions(5, min_part=2) ) 'Partitions of the integer 5 satisfying constraints min_part=2' @@ -4940,12 +4960,21 @@ def __classcall_private__(cls, n=None, **kwargs): raise ValueError("n cannot be infinite") if n is None or n is NN or n is NonNegativeIntegers(): if len(kwargs) > 0: - if len(kwargs) == 1 and 'max_part' in kwargs: - return Partitions_all_bounded(kwargs['max_part']) - else: - raise ValueError("the size must be specified with any keyword argument") - else: - return Partitions_all() + if len(kwargs) == 1: + if 'max_part' in kwargs: + return Partitions_all_bounded(kwargs['max_part']) + if 'regular' in kwargs: + return RegularPartitions_all(kwargs['regular']) + elif len(kwargs) == 2: + if 'regular' in kwargs: + if kwargs['regular'] < 2: + raise ValueError("the regularity must be at least 2") + if 'max_part' in kwargs: + return RegularPartitions_bounded(kwargs['regular'], kwargs['max_part']) + if 'max_length' in kwargs: + return RegularPartitions_truncated(kwargs['regular'], kwargs['max_length']) + raise ValueError("the size must be specified with any keyword argument") + return Partitions_all() elif isinstance(n, (int,Integer)): if len(kwargs) == 0: return Partitions_n(n) @@ -4964,11 +4993,13 @@ def __classcall_private__(cls, n=None, **kwargs): "'ending' cannot be combined with anything else.") if 'parts_in' in kwargs: - return Partitions_parts_in(n, kwargs['parts_in']) + return Partitions_parts_in(n, kwargs['parts_in']) elif 'starting' in kwargs: return Partitions_starting(n, kwargs['starting']) elif 'ending' in kwargs: return Partitions_ending(n, kwargs['ending']) + elif 'regular' in kwargs: + return RegularPartitions_n(n, kwargs['regular']) # FIXME: should inherit from IntegerListLex, and implement repr, or _name as a lazy attribute kwargs['name'] = "Partitions of the integer %s satisfying constraints %s"%(n, ", ".join( ["%s=%s"%(key, kwargs[key]) for key in sorted(kwargs.keys())] )) @@ -4995,13 +5026,10 @@ def __classcall_private__(cls, n=None, **kwargs): kwargs['min_length'] = max(len(inner), kwargs.get('min_length',0)) del kwargs['inner'] + return Partitions_with_constraints(n, **kwargs) - kwargs['element_class'] = Partition - kwargs['global_options'] = Partitions.global_options - return IntegerListsLex(n, **kwargs) - else: - raise ValueError("n must be an integer or be equal to one of "+ - "None, NN, NonNegativeIntegers()") + raise ValueError("n must be an integer or be equal to one of " + "None, NN, NonNegativeIntegers()") def __init__(self, is_infinite=False): """ @@ -6614,14 +6642,446 @@ def __setstate__(self, data): [[2, 1], [1, 1, 1]] """ n = data['n'] - self.__class__ = IntegerListsLex + self.__class__ = Partitions_with_constraints constraints = {'max_slope' : 0, - 'min_part' : 1, - 'element_class' : Partition, - 'global_options' : Partitions.global_options} + 'min_part' : 1} constraints.update(data['constraints']) self.__init__(n, **constraints) +class Partitions_with_constraints(IntegerListsLex): + """ + Partitions which satisfy a set of constraints. + + EXAMPLES:: + + sage: P = Partitions(6, inner=[1,1], max_slope=-1) + sage: list(P) + [[5, 1], [4, 2], [3, 2, 1]] + + TESTS:: + + sage: P = Partitions(6, min_part=2, max_slope=-1) + sage: TestSuite(P).run() + + Test that :trac:`15525` is fixed:: + + sage: loads(dumps(P)) == P + True + """ +# def __init__(self, n, **kwargs): +# """ +# Initialize ``self``. +# """ +# IntegerListsLex.__init__(self, n, **kwargs) + + Element = Partition + global_options = PartitionOptions + +###################### +# Regular Partitions # +###################### + +class RegularPartitions(Partitions): + r""" + Base class for `\ell`-regular partitions. + + Let `\ell` be a positive integer. A partition `\lambda` is + `\ell`-*regular* if `m_i < \ell` for all `i`, where `m_i` is the + multiplicity of `i` in `\lambda`. + + .. NOTE:: + + This is conjugate to the notion of `\ell`-*restricted* partitions, + where the difference between any two parts is at most `\ell`. + + INPUT: + + - ``ell`` -- the integer `\ell` + - ``is_infinite`` -- boolean; if the subset of `\ell`-regular + partitions is infinite + """ + def __init__(self, ell, is_infinte=False): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=2) + sage: TestSuite(P).run() + """ + self._ell = ell + Partitions.__init__(self, is_infinte) + + def ell(self): + r""" + Return the value `\ell`. + + EXAMPLES:: + + sage: P = Partitions(regular=2) + sage: P.ell() + 2 + """ + return self._ell + + def __contains__(self, x): + """ + TESTS:: + + sage: P = Partitions(regular=3) + sage: [5] in P + True + sage: [] in P + True + sage: [3, 3, 2, 2] in P + True + sage: [3, 3, 3, 1] in P + False + sage: [4, 0, 0, 0, 0, 0] in P + True + sage: Partition([4,2,2,1]) in P + True + sage: Partition([4,2,2,2]) in P + False + sage: Partition([10,1]) in P + True + """ + if not Partitions.__contains__(self, x): + return False + if isinstance(x, Partition): + return max(x.to_exp(1)) < self._ell + return all(x.count(i) < self._ell for i in set(x) if i > 0) + + def _fast_iterator(self, n, max_part): + """ + A fast (recursive) iterator which returns a list. + + EXAMPLES:: + + sage: P = Partitions(regular=3) + sage: list(P._fast_iterator(5, 5)) + [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] + sage: list(P._fast_iterator(5, 3)) + [[3, 2], [3, 1, 1], [2, 2, 1]] + sage: list(P._fast_iterator(5, 6)) + [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] + """ + if n == 0: + yield [] + return + + if n < max_part: + max_part = n + bdry = self._ell - 1 + + for i in reversed(range(1, max_part+1)): + for p in self._fast_iterator(n-i, i): + if p.count(i) < bdry: + yield [i] + p + +class RegularPartitions_all(RegularPartitions): + r""" + The class of all `\ell`-regular partitions. + + INPUT: + + - ``ell`` -- the integer `\ell` + + .. SEEALSO:: + + :class:`~sage.combinat.partition.RegularPartitions` + """ + def __init__(self, ell): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=4) + sage: TestSuite(P).run() + """ + RegularPartitions.__init__(self, ell, True) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.combinat.partition import RegularPartitions_all + sage: RegularPartitions_all(3) + 3-Regular Partitions + """ + return "{}-Regular Partitions".format(self._ell) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=3) + sage: it = P.__iter__() + sage: [it.next() for x in range(10)] + [[], [1], [2], [1, 1], [3], [2, 1], [4], [3, 1], [2, 2], [2, 1, 1]] + """ + n = 0 + while True: + for p in self._fast_iterator(n, n): + yield self.element_class(self, p) + n += 1 + +class RegularPartitions_truncated(RegularPartitions): + r""" + The class of `\ell`-regular partitions with max length `k`. + + INPUT: + + - ``ell`` -- the integer `\ell` + - ``max_len`` -- integer; the maximum length + + .. SEEALSO:: + + :class:`~sage.combinat.partition.RegularPartitions` + """ + def __init__(self, ell, max_len): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=4, max_length=3) + sage: TestSuite(P).run() + """ + self._max_len = max_len + RegularPartitions.__init__(self, ell, True) + + def max_length(self): + """ + Return the maximum length of the partitions of ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=4, max_length=3) + sage: P.max_length() + 3 + """ + return self._max_len + + def __contains__(self, x): + """ + TESTS:: + + sage: P = Partitions(regular=4, max_length=3) + sage: [3, 3, 3] in P + True + sage: [] in P + True + sage: [4, 2, 1, 1] in P + False + """ + return len(x) <= self._max_len and RegularPartitions.__contains__(self, x) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.combinat.partition import RegularPartitions_truncated + sage: RegularPartitions_truncated(4, 3) + 4-Regular Partitions with max length 3 + """ + return "{}-Regular Partitions with max length {}".format(self._ell, self._max_len) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=3, max_length=2) + sage: it = P.__iter__() + sage: [it.next() for x in range(10)] + [[], [1], [2], [1, 1], [3], [2, 1], [4], [3, 1], [2, 2], [5]] + """ + n = 0 + while True: + for p in self._fast_iterator(n, n): + yield self.element_class(self, p) + n += 1 + + def _fast_iterator(self, n, max_part, depth=0): + """ + A fast (recursive) iterator which returns a list. + + EXAMPLES:: + + sage: P = Partitions(regular=2, max_length=2) + sage: list(P._fast_iterator(5, 5)) + [[5], [4, 1], [3, 2]] + sage: list(P._fast_iterator(5, 3)) + [[3, 2]] + sage: list(P._fast_iterator(5, 6)) + [[5], [4, 1], [3, 2]] + """ + if n == 0 or depth >= self._max_len: + yield [] + return + + # Special case + if depth + 1 == self._max_len: + if max_part >= n: + yield [n] + return + + if n < max_part: + max_part = n + bdry = self._ell - 1 + + for i in reversed(range(1, max_part+1)): + for p in self._fast_iterator(n-i, i, depth+1): + if p.count(i) < bdry: + yield [i] + p + +class RegularPartitions_bounded(RegularPartitions): + r""" + The class of `\ell`-regular `k`-bounded partitions. + + INPUT: + + - ``ell`` -- the integer `\ell` + - ``k`` -- integer; the value `k` + + .. SEEALSO:: + + :class:`~sage.combinat.partition.RegularPartitions` + """ + def __init__(self, ell, k): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=4, max_part=3) + sage: TestSuite(P).run() + """ + self.k = k + RegularPartitions.__init__(self, ell, False) + + def __contains__(self, x): + """ + TESTS:: + + sage: P = Partitions(regular=4, max_part=3) + sage: [3, 3, 3] in P + True + sage: [] in P + True + sage: [4, 2, 1] in P + False + """ + return len(x) == 0 or (x[0] <= self.k and RegularPartitions.__contains__(self, x)) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.combinat.partition import RegularPartitions_bounded + sage: RegularPartitions_bounded(4, 3) + 4-Regular 3-Bounded Partitions + """ + return "{}-Regular {}-Bounded Partitions".format(self._ell, self.k) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=2, max_part=3) + sage: list(P) + [[3, 2, 1], [3, 2], [3, 1], [3], [2, 1], [2], [1], []] + """ + k = self.k + for n in reversed(range(k*(k+1)/2 * self._ell)): + for p in self._fast_iterator(n, k): + yield self.element_class(self, p) + +class RegularPartitions_n(RegularPartitions, Partitions_n): + r""" + The class of `\ell`-regular partitions of `n`. + + INPUT: + + - ``n`` -- the integer `n` to partition + - ``ell`` -- the integer `\ell` + + .. SEEALSO:: + + :class:`~sage.combinat.partition.RegularPartitions` + """ + def __init__(self, n, ell): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(5, regular=3) + sage: TestSuite(P).run() + """ + RegularPartitions.__init__(self, ell) + Partitions_n.__init__(self, n) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.combinat.partition import RegularPartitions_n + sage: RegularPartitions_n(3, 5) + 5-Regular Partitions of the integer 3 + """ + return "{}-Regular Partitions of the integer {}".format(self._ell, self.n) + + def __contains__(self, x): + """ + TESTS:: + + sage: P = Partitions(5, regular=3) + sage: [3, 1, 1] in P + True + sage: [3, 2, 1] in P + False + """ + return RegularPartitions.__contains__(self, x) and sum(x) == self.n + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: P = Partitions(5, regular=3) + sage: list(P) + [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] + """ + for p in self._fast_iterator(self.n, self.n): + yield self.element_class(self, p) + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: P = Partitions(5, regular=3) + sage: P.cardinality() + 5 + sage: P = Partitions(5, regular=6) + sage: P.cardinality() + 7 + sage: P.cardinality() == Partitions(5).cardinality() + True + """ + if self._ell > self.n: + return Partitions_n.cardinality(self) + return ZZ.sum(1 for x in self) ###################### # Ordered Partitions # @@ -7086,7 +7546,7 @@ def number_of_partitions(n, algorithm='default'): [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1], [2, 1, 1, 1], [1, 1, 1, 1, 1]] sage: len(v) 7 - sage: number_of_partitions(5, algorithm='bober') + sage: number_of_partitions(5, algorithm='bober') 7 The input must be a nonnegative integer or a ``ValueError`` is raised. diff --git a/src/sage/combinat/partition_tuple.py b/src/sage/combinat/partition_tuple.py index 6b7fa5e0e87..06ec9991a60 100644 --- a/src/sage/combinat/partition_tuple.py +++ b/src/sage/combinat/partition_tuple.py @@ -256,7 +256,8 @@ class of modules for the algebras, which are generalisations of the Specht # http://www.gnu.org/licenses/ #***************************************************************************** -from cartesian_product import CartesianProduct +import itertools + from combinat import CombinatorialElement from integer_vector import IntegerVectors from partition import Partition, Partitions, Partitions_n, _Partitions @@ -1564,7 +1565,7 @@ def _element_constructor_(self, mu): """ # one way or another these two cases need to be treated separately - if mu==[] or mu==[[]]: + if mu == [] or mu == () or mu == [[]]: return Partition([]) # As partitions are 1-tuples of partitions we need to treat them separately @@ -2051,7 +2052,7 @@ def __iter__(self): """ p = [Partitions(i) for i in range(self.size()+1)] for iv in IntegerVectors(self.size(),self.level()): - for cp in CartesianProduct(*[p[i] for i in iv]): + for cp in itertools.product(*[p[i] for i in iv]): yield self._element_constructor_(cp) diff --git a/src/sage/combinat/perfect_matching.py b/src/sage/combinat/perfect_matching.py index 40852a14e1f..74f0ebe0c59 100644 --- a/src/sage/combinat/perfect_matching.py +++ b/src/sage/combinat/perfect_matching.py @@ -808,6 +808,35 @@ def to_permutation(self): from sage.combinat.permutation import Permutation return Permutation(self.value) + def to_non_crossing_set_partition(self): + r""" + Returns the noncrossing set partition (on half as many elements) + corresponding to the perfect matching if the perfect matching is + noncrossing, and otherwise gives an error. + + OUTPUT: + + The realization of ``self`` as a noncrossing set partition. + + EXAMPLES:: + + sage: PerfectMatching([[1,3], [4,2]]).to_non_crossing_set_partition() + Traceback (most recent call last): + ... + ValueError: matching must be non-crossing + sage: PerfectMatching([[1,4], [3,2]]).to_non_crossing_set_partition() + {{1, 2}} + sage: PerfectMatching([]).to_non_crossing_set_partition() + {} + """ + from sage.combinat.set_partition import SetPartition + if not self.is_non_crossing(): + raise ValueError("matching must be non-crossing") + else: + perm = self.to_permutation() + perm2 = Permutation([(perm[2*i])/2 for i in range(len(perm)/2)]) + return SetPartition(perm2.cycle_tuples()) + class PerfectMatchings(UniqueRepresentation, Parent): r""" diff --git a/src/sage/combinat/posets/__init__.py b/src/sage/combinat/posets/__init__.py index 3e87a7a6012..61b51a2423d 100644 --- a/src/sage/combinat/posets/__init__.py +++ b/src/sage/combinat/posets/__init__.py @@ -15,6 +15,10 @@ - :ref:`sage.combinat.posets.incidence_algebras` +- :ref:`sage.combinat.posets.cartesian_product` + +- :ref:`sage.combinat.posets.moebius_algebra` + - :ref:`sage.combinat.tamari_lattices` - :ref:`sage.combinat.interval_posets` - :ref:`sage.combinat.shard_order` diff --git a/src/sage/combinat/posets/cartesian_product.py b/src/sage/combinat/posets/cartesian_product.py new file mode 100644 index 00000000000..10bb66d5fe6 --- /dev/null +++ b/src/sage/combinat/posets/cartesian_product.py @@ -0,0 +1,505 @@ +""" +Cartesian products of Posets + +AUTHORS: + +- Daniel Krenn (2015) + +""" +#***************************************************************************** +# Copyright (C) 2015 Daniel Krenn +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.sets.cartesian_product import CartesianProduct + + +class CartesianProductPoset(CartesianProduct): + r""" + A class implementing cartesian products of posets (and elements + thereof). Compared to :class:`CartesianProduct` you are able to + specify an order for comparison of the elements. + + INPUT: + + - ``sets`` -- a tuple of parents. + + - ``category`` -- a subcategory of + ``Sets().CartesianProducts() & Posets()``. + + - ``order`` -- a string or function specifying an order less or equal. + It can be one of the following: + + - ``'native'`` -- elements are ordered by their native ordering, + i.e., the order the wrapped elements (tuples) provide. + + - ``'lex'`` -- elements are ordered lexicographically. + + - ``'product'`` -- an element is less or equal to another + element, if less or equal is true for all its components + (cartesian projections). + + - A function which performs the comparison `\leq`. It takes two + input arguments and outputs a boolean. + + Other keyword arguments (``kwargs``) are passed to the constructor + of :class:`CartesianProduct`. + + EXAMPLES:: + + sage: P = Poset((srange(3), lambda left, right: left <= right)) + sage: Cl = cartesian_product((P, P), order='lex') + sage: Cl((1, 1)) <= Cl((2, 0)) + True + sage: Cp = cartesian_product((P, P), order='product') + sage: Cp((1, 1)) <= Cp((2, 0)) + False + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: Cs = cartesian_product((P, P), order=le_sum) + sage: Cs((1, 1)) <= Cs((2, 0)) + True + + TESTS:: + + sage: Cl.category() + Join of Category of finite posets and + Category of Cartesian products of finite enumerated sets + sage: TestSuite(Cl).run() + sage: Cp.category() + Join of Category of finite posets and + Category of Cartesian products of finite enumerated sets + sage: TestSuite(Cp).run() + + .. SEEALSO: + + :class:`CartesianProduct` + """ + + def __init__(self, sets, category, order=None, **kwargs): + r""" + See :class:`CartesianProductPoset` for details. + + TESTS:: + + sage: P = Poset((srange(3), lambda left, right: left <= right)) + sage: C = cartesian_product((P, P), order='notexisting') + Traceback (most recent call last): + ... + ValueError: No order 'notexisting' known. + sage: C = cartesian_product((P, P), category=(Groups(),)) + sage: C.category() + Join of Category of groups and Category of posets + """ + if order is None: + self._le_ = self.le_product + elif isinstance(order, str): + try: + self._le_ = getattr(self, 'le_' + order) + except AttributeError: + raise ValueError("No order '%s' known." % (order,)) + else: + self._le_ = order + + from sage.categories.category import Category + from sage.categories.posets import Posets + if not isinstance(category, tuple): + category = (category,) + category = Category.join(category + (Posets(),)) + super(CartesianProductPoset, self).__init__( + sets, category, **kwargs) + + + def le(self, left, right): + r""" + Test whether ``left`` is less than or equal to ``right``. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the order defined on creation of this + cartesian product. See :class:`CartesianProductPoset`. + + EXAMPLES:: + + sage: P = Posets.ChainPoset(10) + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((P, P), order=le_sum) + sage: C.le(C((1, 6)), C((6, 1))) + True + sage: C.le(C((6, 1)), C((1, 6))) + False + sage: C.le(C((1, 6)), C((6, 6))) + True + sage: C.le(C((6, 6)), C((1, 6))) + False + """ + return self._le_(left, right) + + + def le_lex(self, left, right): + r""" + Test whether ``left`` is lexicographically smaller or equal + to ``right``. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: P = Poset((srange(2), lambda left, right: left <= right)) + sage: Q = cartesian_product((P, P), order='lex') + sage: T = [Q((0, 0)), Q((1, 1)), Q((0, 1)), Q((1, 0))] + sage: for a in T: + ....: for b in T: + ....: assert(Q.le(a, b) == (a <= b)) + ....: print '%s <= %s = %s' % (a, b, a <= b) + (0, 0) <= (0, 0) = True + (0, 0) <= (1, 1) = True + (0, 0) <= (0, 1) = True + (0, 0) <= (1, 0) = True + (1, 1) <= (0, 0) = False + (1, 1) <= (1, 1) = True + (1, 1) <= (0, 1) = False + (1, 1) <= (1, 0) = False + (0, 1) <= (0, 0) = False + (0, 1) <= (1, 1) = True + (0, 1) <= (0, 1) = True + (0, 1) <= (1, 0) = True + (1, 0) <= (0, 0) = False + (1, 0) <= (1, 1) = True + (1, 0) <= (0, 1) = False + (1, 0) <= (1, 0) = True + """ + for l, r, S in \ + zip(left.value, right.value, self.cartesian_factors()): + if l == r: + continue + if S.le(l, r): + return True + if S.le(r, l): + return False + return True # equal + + + def le_product(self, left, right): + r""" + Test whether ``left`` is component-wise smaller or equal + to ``right``. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + The comparison is ``True`` if the result of the + comparision in each component is ``True``. + + EXAMPLES:: + + sage: P = Poset((srange(2), lambda left, right: left <= right)) + sage: Q = cartesian_product((P, P), order='product') + sage: T = [Q((0, 0)), Q((1, 1)), Q((0, 1)), Q((1, 0))] + sage: for a in T: + ....: for b in T: + ....: assert(Q.le(a, b) == (a <= b)) + ....: print '%s <= %s = %s' % (a, b, a <= b) + (0, 0) <= (0, 0) = True + (0, 0) <= (1, 1) = True + (0, 0) <= (0, 1) = True + (0, 0) <= (1, 0) = True + (1, 1) <= (0, 0) = False + (1, 1) <= (1, 1) = True + (1, 1) <= (0, 1) = False + (1, 1) <= (1, 0) = False + (0, 1) <= (0, 0) = False + (0, 1) <= (1, 1) = True + (0, 1) <= (0, 1) = True + (0, 1) <= (1, 0) = False + (1, 0) <= (0, 0) = False + (1, 0) <= (1, 1) = True + (1, 0) <= (0, 1) = False + (1, 0) <= (1, 0) = True + """ + return all( + S.le(l, r) + for l, r, S in + zip(left.value, right.value, self.cartesian_factors())) + + + def le_native(self, left, right): + r""" + Test whether ``left`` is smaller or equal to ``right`` in the order + provided by the elements themselves. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: P = Poset((srange(2), lambda left, right: left <= right)) + sage: Q = cartesian_product((P, P), order='native') + sage: T = [Q((0, 0)), Q((1, 1)), Q((0, 1)), Q((1, 0))] + sage: for a in T: + ....: for b in T: + ....: assert(Q.le(a, b) == (a <= b)) + ....: print '%s <= %s = %s' % (a, b, a <= b) + (0, 0) <= (0, 0) = True + (0, 0) <= (1, 1) = True + (0, 0) <= (0, 1) = True + (0, 0) <= (1, 0) = True + (1, 1) <= (0, 0) = False + (1, 1) <= (1, 1) = True + (1, 1) <= (0, 1) = False + (1, 1) <= (1, 0) = False + (0, 1) <= (0, 0) = False + (0, 1) <= (1, 1) = True + (0, 1) <= (0, 1) = True + (0, 1) <= (1, 0) = True + (1, 0) <= (0, 0) = False + (1, 0) <= (1, 1) = True + (1, 0) <= (0, 1) = False + (1, 0) <= (1, 0) = True + """ + return left.value <= right.value + + + class Element(CartesianProduct.Element): + + def _le_(self, other): + r""" + Return if this element is less or equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method calls :meth:`CartesianProductPoset.le`. Override + it in inherited class to change this. + + It can be assumed that this element and ``other`` have + the same parent. + + TESTS:: + + sage: QQ.CartesianProduct = sage.combinat.posets.cartesian_product.CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) <= C((2, 1/3)) # indirect doctest + True + sage: C((1/3, 2)) <= C((2, 2)) # indirect doctest + True + """ + return self.parent().le(self, other) + + + def __le__(self, other): + r""" + Return if this element is less than or equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the coercion framework to find a + suitable common parent. + + This method can be deleted once :trac:`10130` is fixed and + provides these methods automatically. + + TESTS:: + + sage: from sage.combinat.posets.cartesian_product import CartesianProductPoset + sage: QQ.CartesianProduct = CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) <= C((2, 1/3)) + True + sage: C((1/3, 2)) <= C((2, 2)) + True + + The following example tests that the coercion gets involved in + comparisons; it can be simplified once #18182 is in merged. + :: + + sage: class MyCP(CartesianProductPoset): + ....: def _coerce_map_from_(self, S): + ....: if isinstance(S, self.__class__): + ....: S_factors = S.cartesian_factors() + ....: R_factors = self.cartesian_factors() + ....: if len(S_factors) == len(R_factors): + ....: if all(r.has_coerce_map_from(s) + ....: for r,s in zip(R_factors, S_factors)): + ....: return True + sage: QQ.CartesianProduct = MyCP + sage: A = cartesian_product((QQ, ZZ), order=le_sum) + sage: B = cartesian_product((QQ, QQ), order=le_sum) + sage: A((1/2, 4)) <= B((1/2, 5)) + True + """ + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._le_(other) + + from sage.structure.element import get_coercion_model + import operator + try: + return get_coercion_model().bin_op(self, other, operator.le) + except TypeError: + return False + + + def __ge__(self, other): + r""" + Return if this element is greater than or equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the coercion framework to find a + suitable common parent. + + This method can be deleted once :trac:`10130` is fixed and + provides these methods automatically. + + TESTS:: + + sage: QQ.CartesianProduct = sage.combinat.posets.cartesian_product.CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) >= C((2, 1/3)) + False + sage: C((1/3, 2)) >= C((2, 2)) + False + """ + return other.__le__(self) + + + def __lt__(self, other): + r""" + Return if this element is less than ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the coercion framework to find a + suitable common parent. + + This method can be deleted once :trac:`10130` is fixed and + provides these methods automatically. + + TESTS:: + + sage: QQ.CartesianProduct = sage.combinat.posets.cartesian_product.CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) < C((2, 1/3)) + True + sage: C((1/3, 2)) < C((2, 2)) + True + """ + return not self == other and self.__le__(other) + + + def __gt__(self, other): + r""" + Return if this element is greater than ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the coercion framework to find a + suitable common parent. + + This method can be deleted once :trac:`10130` is fixed and + provides these methods automatically. + + TESTS:: + + sage: QQ.CartesianProduct = sage.combinat.posets.cartesian_product.CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) > C((2, 1/3)) + False + sage: C((1/3, 2)) > C((2, 2)) + False + """ + return not self == other and other.__le__(self) diff --git a/src/sage/combinat/posets/elements.py b/src/sage/combinat/posets/elements.py index b27afd59980..0cf391ef992 100644 --- a/src/sage/combinat/posets/elements.py +++ b/src/sage/combinat/posets/elements.py @@ -51,6 +51,17 @@ def __init__(self, poset, element, vertex): self.element = element self.vertex = vertex + def __hash__(self): + r""" + TESTS:: + + sage: P = Poset([[1,2],[4],[3],[4],[]], facade = False) + sage: e = P(0) + sage: hash(e) + 0 + """ + return hash(self.element) + def _repr_(self): """ TESTS:: diff --git a/src/sage/combinat/posets/hasse_diagram.py b/src/sage/combinat/posets/hasse_diagram.py index ecd358c4abb..b6baeb31ccc 100644 --- a/src/sage/combinat/posets/hasse_diagram.py +++ b/src/sage/combinat/posets/hasse_diagram.py @@ -376,6 +376,8 @@ def is_chain(self): sage: p.is_chain() False """ + if self.cardinality() == 0: + return True return (self.num_edges()+1 == self.num_verts() and # Hasse Diagram is a tree all(d<=1 for d in self.out_degree()) and # max outdegree is <= 1 all(d<=1 for d in self.in_degree())) # max indegree is <= 1 diff --git a/src/sage/combinat/posets/lattices.py b/src/sage/combinat/posets/lattices.py index 7e316701d31..0b985eeb448 100644 --- a/src/sage/combinat/posets/lattices.py +++ b/src/sage/combinat/posets/lattices.py @@ -55,6 +55,7 @@ # # http://www.gnu.org/licenses/ #***************************************************************************** + from sage.categories.finite_lattice_posets import FiniteLatticePosets from sage.combinat.posets.posets import Poset, FinitePoset from sage.combinat.posets.elements import (LatticePosetElement, @@ -1147,6 +1148,37 @@ def frattini_sublattice(self): return LatticePoset(self.subposet([self[x] for x in self._hasse_diagram.frattini_sublattice()])) + def moebius_algebra(self, R): + """ + Return the Mobius algebra of ``self`` over ``R``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: L.moebius_algebra(QQ) + Moebius algebra of Finite lattice containing 16 elements over Rational Field + """ + from sage.combinat.posets.moebius_algebra import MoebiusAlgebra + return MoebiusAlgebra(R, self) + + def quantum_moebius_algebra(self, q=None): + """ + Return the quantum Mobius algebra of ``self`` with parameter ``q``. + + INPUT: + + - ``q`` -- (optional) the deformation parameter `q` + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: L.quantum_moebius_algebra() + Quantum Moebius algebra of Finite lattice containing 16 elements + with q=q over Univariate Laurent Polynomial Ring in q over Integer Ring + """ + from sage.combinat.posets.moebius_algebra import QuantumMoebiusAlgebra + return QuantumMoebiusAlgebra(self, q) + ############################################################################ FiniteMeetSemilattice._dual_class = FiniteJoinSemilattice diff --git a/src/sage/combinat/posets/moebius_algebra.py b/src/sage/combinat/posets/moebius_algebra.py new file mode 100644 index 00000000000..1d57ca89062 --- /dev/null +++ b/src/sage/combinat/posets/moebius_algebra.py @@ -0,0 +1,732 @@ +# -*- coding: utf-8 -*- +r""" +Möbius Algebras +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw , +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# The full text of the GPL is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.misc.bindable_class import BindableClass +from sage.misc.lazy_attribute import lazy_attribute +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.categories.algebras import Algebras +from sage.categories.realizations import Realizations, Category_realization_of_parent +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.combinat.posets.lattices import LatticePoset +from sage.combinat.free_module import CombinatorialFreeModule +from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing +from sage.rings.all import ZZ + +class BasisAbstract(CombinatorialFreeModule, BindableClass): + """ + Abstract base class for a basis. + """ + def __getitem__(self, x): + """ + Return the basis element indexed by ``x``. + + INPUT: + + - ``x`` -- an element of the lattice + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.moebius_algebra(QQ).E() + sage: E[5] + E[5] + sage: C = L.quantum_moebius_algebra().C() + sage: C[5] + C[5] + """ + L = self.realization_of()._lattice + return self.monomial(L(x)) + +class MoebiusAlgebra(Parent, UniqueRepresentation): + r""" + The möbius algebra of a lattice. + + Let `L` be a lattice. The *Möbius algebra* `M_L` was originally + constructed by Solomon and has a natural basis + `\{ E_x \mid x \in L \}` with multiplication given by + `E_x \cdot E_y = E_{x \vee y}`. Moreover this has a basis given by + orthogonal idempotents `\{ I_x \mid x \in L \}` (so + `I_x I_y = \delta_{xy} I_x` where `\delta` is the Kronecker delta) + related to the natural basis by + + .. MATH:: + + I_x = \sum_{y \leq x} \mu_L(y, x) E_x, + + where `\mu_L` is the Möbius function of `L`. + + REFERENCES: + + .. [Greene73] Curtis Greene. + *On the Möbius algebra of a partially ordered set*. + Advances in Mathematics, **10**, 1973. + :doi:`10.1016/0001-8708(73)90106-0`. + + .. [Etienne98] Gwihen Etienne. + *On the Möbius algebra of geometric lattices*. + European Journal of Combinatorics, **19**, 1998. + :doi:`10.1006/eujc.1998.0227`. + """ + def __init__(self, R, L): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: TestSuite(M).run() + """ + if not L.is_lattice(): + raise ValueError("L must be a lattice") + cat = Algebras(R).Commutative().WithBasis() + if L in FiniteEnumeratedSets(): + cat = cat.FiniteDimensional() + self._lattice = L + self._category = cat + Parent.__init__(self, base=R, category=self._category.WithRealizations()) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: L.moebius_algebra(QQ) + Moebius algebra of Finite lattice containing 16 elements over Rational Field + """ + return "Moebius algebra of {} over {}".format(self._lattice, self.base_ring()) + + def a_realization(self): + r""" + Return a particular realization of ``self`` (the `B`-basis). + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: M.a_realization() + Moebius algebra of Finite lattice containing 16 elements + over Rational Field in the natural basis + """ + return self.E() + + def lattice(self): + """ + Return the defining lattice of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: M.lattice() + Finite lattice containing 16 elements + sage: M.lattice() == L + True + """ + return self._lattice + + class E(BasisAbstract): + r""" + The natural basis of a Möbius algebra. + + Let `E_x` and `E_y` be basis elements of `M_L` for some lattice `L`. + Multiplication is given by `E_x E_y = E_{x \vee y}`. + """ + def __init__(self, M, prefix='E'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: TestSuite(M.E()).run() + """ + self._basis_name = "natural" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + @cached_method + def _to_idempotent_basis(self, x): + """ + Convert the element indexed by ``x`` to the idempotent basis. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: E = M.E() + sage: all(E(E._to_idempotent_basis(x)) == E.monomial(x) + ....: for x in E.basis().keys()) + True + """ + M = self.realization_of() + I = M.idempotent() + return I.sum_of_monomials(M._lattice.order_ideal([x])) + + def product_on_basis(self, x, y): + """ + Return the product of basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.moebius_algebra(QQ).E() + sage: E.product_on_basis(5, 14) + E[15] + sage: E.product_on_basis(2, 8) + E[10] + """ + return self.monomial(self.realization_of()._lattice.join(x, y)) + + @cached_method + def one(self): + """ + Return the element ``1`` of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.moebius_algebra(QQ).E() + sage: E.one() + E[0] + """ + elts = self.realization_of()._lattice.minimal_elements() + return self.sum_of_monomials(elts) + + natural = E + + class I(BasisAbstract): + """ + The (orthogonal) idempotent basis of a Möbius algebra. + + Let `I_x` and `I_y` be basis elements of `M_L` for some lattice `L`. + Multiplication is given by `I_x I_y = \delta_{xy} I_x` where + `\delta_{xy}` is the Kronecker delta. + """ + def __init__(self, M, prefix='I'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: TestSuite(M.I()).run() + """ + self._basis_name = "idempotent" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + ## Change of basis: + E = M.E() + self.module_morphism(self._to_natural_basis, + codomain=E, category=self.category(), + triangular='upper', unitriangular=True + ).register_as_coercion() + + E.module_morphism(E._to_idempotent_basis, + codomain=self, category=self.category(), + triangular='upper', unitriangular=True + ).register_as_coercion() + + + @cached_method + def _to_natural_basis(self, x): + """ + Convert the element indexed by ``x`` to the natural basis. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: I = M.I() + sage: all(I(I._to_natural_basis(x)) == I.monomial(x) + ....: for x in I.basis().keys()) + True + """ + M = self.realization_of() + N = M.natural() + mobius = M._lattice.mobius_function + return N.sum_of_terms((y, mobius(y,x)) for y in M._lattice.order_ideal([x])) + + def product_on_basis(self, x, y): + """ + Return the product of basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: I = L.moebius_algebra(QQ).I() + sage: I.product_on_basis(5, 14) + 0 + sage: I.product_on_basis(2, 2) + I[2] + """ + if x == y: + return self.monomial(x) + return self.zero() + + @cached_method + def one(self): + """ + Return the element ``1`` of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: I = L.moebius_algebra(QQ).I() + sage: I.one() + I[0] + I[1] + I[2] + I[3] + I[4] + I[5] + I[6] + I[7] + I[8] + + I[9] + I[10] + I[11] + I[12] + I[13] + I[14] + I[15] + """ + return self.sum_of_monomials(self.realization_of()._lattice) + + def __getitem__(self, x): + """ + Return the basis element indexed by ``x``. + + INPUT: + + - ``x`` -- an element of the lattice + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: I = L.moebius_algebra(QQ).I() + sage: I[5] + I[5] + """ + L = self.realization_of()._lattice + return self.monomial(L(x)) + + idempotent = I + +class QuantumMoebiusAlgebra(Parent, UniqueRepresentation): + r""" + The quantum Möbius algebra of a lattice. + + Let `L` be a lattice, and we define the *quantum Möbius algebra* `M_L(q)` + as the algebra with basis `\{ E_x \mid x \in L \}` with + multiplication given by + + .. MATH:: + + E_x E_y = \sum_{z \geq a \geq x \vee y} \mu_L(a, z) + q^{\operatorname{crk} a} E_z, + + where `\mu_L` is the Möbius function of `L` and `\operatorname{crk}` + is the corank function (i.e., `\operatorname{crk} a = + \operatorname{rank} L - \operatorname{rank}` a). At `q = 1`, this + reduces to the multiplication formula originally given by Solomon. + """ + def __init__(self, L, q=None): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: TestSuite(M).run() # long time + """ + if not L.is_lattice(): + raise ValueError("L must be a lattice") + if q is None: + q = LaurentPolynomialRing(ZZ, 'q').gen() + self._q = q + R = q.parent() + cat = Algebras(R).WithBasis() + if L in FiniteEnumeratedSets(): + cat = cat.Commutative().FiniteDimensional() + self._lattice = L + self._category = cat + Parent.__init__(self, base=R, category=self._category.WithRealizations()) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: L.quantum_moebius_algebra() + Quantum Moebius algebra of Finite lattice containing 16 elements + with q=q over Univariate Laurent Polynomial Ring in q over Integer Ring + """ + return "Quantum Moebius algebra of {} with q={} over {}".format( + self._lattice, self._q, self.base_ring()) + + def a_realization(self): + r""" + Return a particular realization of ``self`` (the `B`-basis). + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: M.a_realization() + Quantum Moebius algebra of Finite lattice containing 16 elements + with q=q over Univariate Laurent Polynomial Ring in q + over Integer Ring in the natural basis + """ + return self.E() + + def lattice(self): + """ + Return the defining lattice of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: M.lattice() + Finite lattice containing 16 elements + sage: M.lattice() == L + True + """ + return self._lattice + + class E(BasisAbstract): + r""" + The natural basis of a quantum Möbius algebra. + + Let `E_x` and `E_y` be basis elements of `M_L` for some lattice `L`. + Multiplication is given by + + .. MATH:: + + E_x E_y = \sum_{z \geq a \geq x \vee y} \mu_L(a, z) + q^{\operatorname{crk} a} E_z, + + where `\mu_L` is the Möbius function of `L` and `\operatorname{crk}` + is the corank function (i.e., `\operatorname{crk} a = + \operatorname{rank} L - \operatorname{rank}` a). + """ + def __init__(self, M, prefix='E'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: TestSuite(M.E()).run() # long time + """ + self._basis_name = "natural" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + def product_on_basis(self, x, y): + """ + Return the product of basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.quantum_moebius_algebra().E() + sage: E.product_on_basis(5, 14) + E[15] + sage: E.product_on_basis(2, 8) + q^2*E[10] + (q-q^2)*E[11] + (q-q^2)*E[14] + (1-2*q+q^2)*E[15] + """ + L = self.realization_of()._lattice + q = self.realization_of()._q + mobius = L.mobius_function + rank = L.rank_function() + R = L.rank() + j = L.join(x,y) + return self.sum_of_terms(( z, mobius(a,z) * q**(R - rank(a)) ) + for z in L.order_filter([j]) + for a in L.closed_interval(j, z)) + + @cached_method + def one(self): + """ + Return the element ``1`` of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.quantum_moebius_algebra().E() + sage: all(E.one() * b == b for b in E.basis()) + True + """ + L = self.realization_of()._lattice + q = self.realization_of()._q + mobius = L.mobius_function + rank = L.rank_function() + R = L.rank() + return self.sum_of_terms((x, mobius(y,x) * q**(rank(y) - R)) + for x in L for y in L.order_ideal([x])) + + natural = E + + class C(BasisAbstract): + r""" + The characteristic basis of a quantum Möbius algebra. + + The characteristic basis `\{ C_x \mid x \in L \}` of `M_L` + for some lattice `L` is defined by + + .. MATH:: + + C_x = \sum_{a \geq x} P(F^x; q) E_a, + + where `F^x = \{ y \in L \mid y \geq x \}` is the principal order + filter of `x` and `P(F^x; q)` is the characteristic polynomial + of the (sub)poset `F^x`. + """ + def __init__(self, M, prefix='C'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(3) + sage: M = L.quantum_moebius_algebra() + sage: TestSuite(M.C()).run() # long time + """ + self._basis_name = "characteristic" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + ## Change of basis: + E = M.E() + phi = self.module_morphism(self._to_natural_basis, + codomain=E, category=self.category(), + triangular='lower', unitriangular=True) + + phi.register_as_coercion() + (~phi).register_as_coercion() + + @cached_method + def _to_natural_basis(self, x): + """ + Convert the element indexed by ``x`` to the natural basis. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).quantum_moebius_algebra() + sage: C = M.C() + sage: all(C(C._to_natural_basis(x)) == C.monomial(x) + ....: for x in C.basis().keys()) + True + """ + M = self.realization_of() + N = M.natural() + q = M._q + R = M.base_ring() + L = M._lattice + poly = lambda x,y: L.subposet(L.closed_interval(x, y)).characteristic_polynomial() + # This is a workaround until #17554 is fixed... + subs = lambda p,q: R.sum( c * q**e for e,c in enumerate(p.list()) ) + # ...at which point, we can do poly(x,y)(q=q) + return N.sum_of_terms((y, subs(poly(x,y), q)) + for y in L.order_filter([x])) + + characteristic_basis = C + + class KL(BasisAbstract): + """ + The Kazhdan-Lusztig basis of a quantum Möbius algebra. + + The Kazhdan-Lusztig basis `\{ B_x \mid x \in L \}` of `M_L` + for some lattice `L` is defined by + + .. MATH:: + + B_x = \sum_{y \geq x} P_{x,y}(q) E_a, + + where `P_{x,y}(q)` is the Kazhdan-Lusztig polynomial of `L`, + following the definition given in [EPW14]_. + + EXAMPLES: + + We construct some examples of Proposition 4.5 of [EPW14]_:: + + sage: M = posets.BooleanLattice(4).quantum_moebius_algebra() + sage: KL = M.KL() + sage: KL[4] * KL[5] + (q^2+q^3)*KL[5] + (q+2*q^2+q^3)*KL[7] + (q+2*q^2+q^3)*KL[13] + + (1+3*q+3*q^2+q^3)*KL[15] + sage: KL[4] * KL[15] + (1+3*q+3*q^2+q^3)*KL[15] + sage: KL[4] * KL[10] + (q+3*q^2+3*q^3+q^4)*KL[14] + (1+4*q+6*q^2+4*q^3+q^4)*KL[15] + """ + def __init__(self, M, prefix='KL'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: TestSuite(M.KL()).run() # long time + """ + self._basis_name = "Kazhdan-Lusztig" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + ## Change of basis: + E = M.E() + phi = self.module_morphism(self._to_natural_basis, + codomain=E, category=self.category(), + triangular='lower', unitriangular=True) + + phi.register_as_coercion() + (~phi).register_as_coercion() + + @cached_method + def _to_natural_basis(self, x): + """ + Convert the element indexed by ``x`` to the natural basis. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).quantum_moebius_algebra() + sage: KL = M.KL() + sage: all(KL(KL._to_natural_basis(x)) == KL.monomial(x) # long time + ....: for x in KL.basis().keys()) + True + """ + M = self.realization_of() + L = M._lattice + E = M.E() + q = M._q + R = M.base_ring() + rank = L.rank_function() + # This is a workaround until #17554 is fixed... + subs = lambda p,q: R.sum( c * q**e for e,c in enumerate(p.list()) ) + return E.sum_of_terms((y, q**(rank(y) - rank(x)) * + subs(L.kazhdan_lusztig_polynomial(x, y), q**-2)) + for y in L.order_filter([x])) + + kazhdan_lusztig = KL + +class MoebiusAlgebraBases(Category_realization_of_parent): + r""" + The category of bases of a Möbius algebra. + + INPUT: + + - ``base`` -- a Möbius algebra + + TESTS:: + + sage: from sage.combinat.posets.moebius_algebra import MoebiusAlgebraBases + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: bases = MoebiusAlgebraBases(M) + sage: M.E() in bases + True + """ + def _repr_(self): + r""" + Return the representation of ``self``. + + EXAMPLES:: + + sage: from sage.combinat.posets.moebius_algebra import MoebiusAlgebraBases + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: MoebiusAlgebraBases(M) + Category of bases of Moebius algebra of Finite lattice + containing 16 elements over Rational Field + """ + return "Category of bases of {}".format(self.base()) + + def super_categories(self): + r""" + The super categories of ``self``. + + EXAMPLES:: + + sage: from sage.combinat.posets.moebius_algebra import MoebiusAlgebraBases + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: bases = MoebiusAlgebraBases(M) + sage: bases.super_categories() + [Category of finite dimensional commutative algebras with basis over Rational Field, + Category of realizations of Moebius algebra of Finite lattice + containing 16 elements over Rational Field] + """ + return [self.base()._category, Realizations(self.base())] + + class ParentMethods: + def _repr_(self): + """ + Text representation of this basis of a Möbius algebra. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: M.E() + Moebius algebra of Finite lattice containing 16 elements + over Rational Field in the natural basis + sage: M.I() + Moebius algebra of Finite lattice containing 16 elements + over Rational Field in the idempotent basis + """ + return "{} in the {} basis".format(self.realization_of(), self._basis_name) + + def product_on_basis(self, x, y): + """ + Return the product of basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: C = L.quantum_moebius_algebra().C() + sage: C.product_on_basis(5, 14) + q^3*C[15] + sage: C.product_on_basis(2, 8) + q^4*C[10] + """ + R = self.realization_of().a_realization() + return self(R(self.monomial(x)) * R(self.monomial(y))) + + @cached_method + def one(self): + """ + Return the element ``1`` of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: C = L.quantum_moebius_algebra().C() + sage: all(C.one() * b == b for b in C.basis()) + True + """ + R = self.realization_of().a_realization() + return self(R.one()) + + class ElementMethods: + pass + diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 58245aa7aca..40ffc5f98b7 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -12,7 +12,7 @@ :class:`FinitePoset` | A class for finite posets :class:`FinitePosets_n` | A class for finite posets up to isomorphism (i.e. unlabeled posets) :meth:`Poset` | Construct a finite poset from various forms of input data. - :meth:`is_poset` | Tests whether a directed graph is acyclic and transitively reduced. + :meth:`is_poset` | Return ``True`` if a directed graph is acyclic and transitively reduced. List of Poset methods --------------------- @@ -65,16 +65,16 @@ :meth:`~FinitePoset.dimension` | Return the dimension of the poset. :meth:`~FinitePoset.has_bottom` | Return ``True`` if the poset has a unique minimal element. :meth:`~FinitePoset.has_top` | Return ``True`` if the poset has a unique maximal element. - :meth:`~FinitePoset.is_bounded` | Return ``True`` if the poset contains a unique maximal element and a unique minimal element, and False otherwise. - :meth:`~FinitePoset.is_chain` | Return ``True`` if the poset is totally ordered, and False otherwise. - :meth:`~FinitePoset.is_connected` | Return ``True`` if the poset is connected, and ``False`` otherwise. - :meth:`~FinitePoset.is_graded` | Return whether this poset is graded. - :meth:`~FinitePoset.is_ranked` | Return whether this poset is ranked. - :meth:`~FinitePoset.is_ranked` | Return ``True`` if the poset is rank symmetric. - :meth:`~FinitePoset.is_incomparable_chain_free` | Return whether the poset is `(m+n)`-free. - :meth:`~FinitePoset.is_slender` | Return whether the poset ``self`` is slender or not. - :meth:`~FinitePoset.is_join_semilattice` | Return ``True`` is the poset has a join operation, and False otherwise. - :meth:`~FinitePoset.is_meet_semilattice` | Return ``True`` if self has a meet operation, and False otherwise. + :meth:`~FinitePoset.is_bounded` | Return ``True`` if the poset has both unique minimal and unique maximal element. + :meth:`~FinitePoset.is_chain` | Return ``True`` if the poset is totally ordered. + :meth:`~FinitePoset.is_connected` | Return ``True`` if the poset is connected. + :meth:`~FinitePoset.is_graded` | Return ``True`` if all maximal chains of the poset has same length. + :meth:`~FinitePoset.is_ranked` | Return ``True`` if the poset has a rank function. + :meth:`~FinitePoset.is_rank_symmetric` | Return ``True`` if the poset is rank symmetric. + :meth:`~FinitePoset.is_incomparable_chain_free` | Return ``True`` if the poset is (m+n)-free. + :meth:`~FinitePoset.is_slender` | Return ``True`` if the poset is slender. + :meth:`~FinitePoset.is_join_semilattice` | Return ``True`` is the poset has a join operation. + :meth:`~FinitePoset.is_meet_semilattice` | Return ``True`` if the poset has a meet operation. **Minimal and maximal elements** @@ -114,11 +114,11 @@ :widths: 30, 70 :delim: | - :meth:`~FinitePoset.is_chain_of_poset` | Return ``True`` if given iterable is a chain of the poset. - :meth:`~FinitePoset.chains` | Return all the chains of ``self``. + :meth:`~FinitePoset.is_chain_of_poset` | Return ``True`` if the given list is a chain of the poset. + :meth:`~FinitePoset.chains` | Return the chains of the poset. :meth:`~FinitePoset.antichains` | Return the antichains of the poset. - :meth:`~FinitePoset.maximal_chains` | Return all maximal chains of the poset. - :meth:`~FinitePoset.maximal_antichains` | Return all maximal antichains of the poset. + :meth:`~FinitePoset.maximal_chains` | Return the maximal chains of the poset. + :meth:`~FinitePoset.maximal_antichains` | Return the maximal antichains of the poset. :meth:`~FinitePoset.antichains_iterator` | Return an iterator over the antichains of the poset. **Drawing** @@ -140,6 +140,7 @@ :delim: | :meth:`~FinitePoset.is_isomorphic` | Return ``True`` if both posets are isomorphic. + :meth:`~FinitePoset.is_induced_subposet` | Return ``True`` if given poset is an induced subposet of this poset. **Polynomials** @@ -190,6 +191,7 @@ :meth:`~FinitePoset.is_linear_extension` | Return whether ``l`` is a linear extension of ``self``. :meth:`~FinitePoset.isomorphic_subposets_iterator` | Return an iterator over the subposets isomorphic to another poset. :meth:`~FinitePoset.isomorphic_subposets` | Return all subposets isomorphic to another poset. + :meth:`~FinitePoset.kazhdan_lusztig_polynomial` | Return the Kazhdan-Lusztig polynomial `P_{x,y}(q)` of ``self``. :meth:`~FinitePoset.lequal_matrix` | Computes the matrix whose ``(i,j)`` entry is 1 if ``self.linear_extension()[i] < self.linear_extension()[j]`` and 0 otherwise. :meth:`~FinitePoset.level_sets` | Return a list l such that l[i+1] is the set of minimal elements of the poset obtained by removing the elements in l[0], l[1], ..., l[i]. :meth:`~FinitePoset.linear_extension` | Return a linear extension of this poset. @@ -229,6 +231,7 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute from sage.misc.misc_c import prod +from sage.functions.other import floor from sage.categories.category import Category from sage.categories.sets_cat import Sets from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets @@ -240,6 +243,7 @@ from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.rings.polynomial.polynomial_ring import polygen +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.graphs.digraph import DiGraph from sage.graphs.digraph_generators import digraphs from sage.combinat.posets.hasse_diagram import HasseDiagram @@ -1236,9 +1240,9 @@ def hasse_diagram(self, wrapped = True): sage: P = Poset((divisors(15), attrcall("divides")), facade = False) sage: H = P.hasse_diagram() sage: H.vertices() - [1, 5, 3, 15] + [1, 3, 5, 15] sage: H.edges() - [(1, 3, None), (1, 5, None), (5, 15, None), (3, 15, None)] + [(1, 3, None), (1, 5, None), (3, 15, None), (5, 15, None)] sage: H.set_latex_options(format="dot2tex") # optional - dot2tex sage: view(H, tight_page=True) # optional - dot2tex, not tested (opens external window) """ @@ -1947,53 +1951,60 @@ def relations_number(self): intervals_number = relations_number intervals_iterator = relations_iterator - def is_incomparable_chain_free(self, m, n = None): + def is_incomparable_chain_free(self, m, n=None): r""" - Return ``True`` if the poset is `(m+n)`-free (that is, there is no pair - of incomparable chains of lengths `m` and `n`), and ``False`` if not. + Return ``True`` if the poset is `(m+n)`-free, and ``False`` otherwise. - If ``m`` is a tuple of pairs of chain lengths, returns ``True`` if the poset - does not contain a pair of incomparable chains whose lengths comprise - one of the chain pairs, and ``False`` if not. - A poset is `(m+n)`-free if it contains no induced subposet that is - isomorphic to the poset consisting of two disjoint chains of lengths - `m` and `n`. See, for example, Exercise 15 in Chapter 3 of - [EnumComb1]_. + A poset is `(m+n)`-free if there is no incomparable chains of + lengths `m` and `n`. Three cases have special name: + + - ''interval order'' is `(2+2)`-free + - ''semiorder'' (or ''unit interval order'') is `(1+3)`-free and + `(2+2)`-free + - ''weak order'' is `(1+2)`-free. INPUT: - - ``m`` - tuple of pairs of nonnegative integers - - ``m``, ``n`` - nonnegative integers + - ``m``, ``n`` - positive integers + + It is also possible to give a list of integer pairs as argument. + See below for an example. EXAMPLES:: - sage: P = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: P.is_incomparable_chain_free(1, 1) - False - sage: P.is_incomparable_chain_free(2, 1) + sage: B3 = Posets.BooleanLattice(3) + sage: B3.is_incomparable_chain_free(1, 3) True + sage: B3.is_incomparable_chain_free(2, 2) + False - :: - - sage: P = Poset(((0, 1, 2, 3, 4), ((0, 1), (1, 2), (0, 3), (4, 2)))) - sage: P.is_incomparable_chain_free(((3, 1), (2, 2))) + sage: IP6 = Posets.IntegerPartitions(6) + sage: IP6.is_incomparable_chain_free(1, 3) + False + sage: IP6.is_incomparable_chain_free(2, 2) True - :: + A list of pairs as an argument:: - sage: P = Poset((("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"), (("d", "a"), ("e", "a"), ("f", "a"), ("g", "a"), ("h", "b"), ("f", "b"), ("h", "c"), ("g", "c"), ("h", "d"), ("i", "d"), ("h", "e"), ("i", "e"), ("j", "f"), ("i", "f"), ("j", "g"), ("i", "g"), ("j", "h")))) - sage: P.is_incomparable_chain_free(3, 1) - True - sage: P.is_incomparable_chain_free(2, 2) + sage: B3.is_incomparable_chain_free([[1, 3], [2, 2]]) False - :: + We show how to get an incomparable chain pair:: - sage: [len([p for p in Posets(n) if p.is_incomparable_chain_free(((3, 1), (2, 2)))]) for n in range(6)] # long time - [1, 1, 2, 5, 14, 42] + sage: P = Posets.PentagonPoset() + sage: chains_1_2 = Poset({0:[], 1:[2]}) + sage: incomps = P.isomorphic_subposets(chains_1_2)[0] + sage: sorted(incomps.list()), incomps.cover_relations() + ([1, 2, 3], [[2, 3]]) TESTS:: + sage: Poset().is_incomparable_chain_free(1,1) # Test empty poset + True + + sage: [len([p for p in Posets(n) if p.is_incomparable_chain_free(((3, 1), (2, 2)))]) for n in range(6)] # long time + [1, 1, 2, 5, 14, 42] + sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) sage: Q.is_incomparable_chain_free(2, 20/10) True @@ -2004,7 +2015,7 @@ def is_incomparable_chain_free(self, m, n = None): sage: Q.is_incomparable_chain_free(2, -1) Traceback (most recent call last): ... - ValueError: 2 and -1 must be nonnegative integers. + ValueError: 2 and -1 must be positive integers. sage: P = Poset(((0, 1, 2, 3, 4), ((0, 1), (1, 2), (0, 3), (4, 2)))) sage: P.is_incomparable_chain_free((3, 1)) Traceback (most recent call last): @@ -2051,8 +2062,8 @@ def is_incomparable_chain_free(self, m, n = None): m, n = Integer(m), Integer(n) except TypeError: raise TypeError('%s and %s must be integers.' % (m, n)) - if m < 0 or n < 0: - raise ValueError("%s and %s must be nonnegative integers." % (m, n)) + if m < 1 or n < 1: + raise ValueError("%s and %s must be positive integers." % (m, n)) twochains = digraphs.TransitiveTournament(m) + digraphs.TransitiveTournament(n) return self._hasse_diagram.transitive_closure().subgraph_search(twochains, induced = True) is None @@ -2241,7 +2252,7 @@ def bottom(self): sage: Q.bottom() 0 - .. SEEALSO:: :meth:`has_bottom`, :meth:`top`. + .. SEEALSO:: :meth:`has_bottom`, :meth:`top` """ hasse_bot = self._hasse_diagram.bottom() if hasse_bot is None: @@ -2256,14 +2267,19 @@ def has_bottom(self): EXAMPLES:: - sage: P = Poset({0:[3],1:[3],2:[3],3:[4],4:[]}) + sage: P = Poset({0:[3], 1:[3], 2:[3], 3:[4], 4:[]}) sage: P.has_bottom() False - sage: Q = Poset({0:[1],1:[]}) + sage: Q = Poset({0:[1], 1:[]}) sage: Q.has_bottom() True - .. SEEALSO:: :meth:`bottom`, :meth:`has_top`. + .. SEEALSO:: :meth:`bottom`, :meth:`has_top`, :meth:`is_bounded` + + TESTS:: + + sage: Poset().has_top() # Test empty poset + False """ return self._hasse_diagram.has_bottom() @@ -2280,7 +2296,7 @@ def top(self): sage: Q.top() 1 - .. SEEALSO:: :meth:`has_top`, :meth:`bottom`. + .. SEEALSO:: :meth:`has_top`, :meth:`bottom` TESTS:: @@ -2303,14 +2319,19 @@ def has_top(self): EXAMPLES:: - sage: P = Poset({0:[3],1:[3],2:[3],3:[4,5],4:[],5:[]}) + sage: P = Poset({0:[3], 1:[3], 2:[3], 3:[4, 5], 4:[], 5:[]}) sage: P.has_top() False - sage: Q = Poset({0:[1],1:[]}) + sage: Q = Poset({0:[3], 1:[3], 2:[3], 3:[4], 4:[]}) sage: Q.has_top() True - .. SEEALSO:: :meth:`top`, :meth:`has_bottom`. + .. SEEALSO:: :meth:`top`, :meth:`has_bottom`, :meth:`is_bounded` + + TESTS:: + + sage: Poset().has_top() # Test empty poset + False """ return self._hasse_diagram.has_top() @@ -2368,127 +2389,143 @@ def has_isomorphic_subposet(self, other): def is_bounded(self): """ - Return ``True`` if the poset ``self`` is bounded, and ``False`` - otherwise. + Return ``True`` if the poset is bounded, and ``False`` otherwise. - We call a poset bounded if it contains a unique maximal element + A poset is bounded if it contains both a unique maximal element and a unique minimal element. EXAMPLES:: - sage: P = Poset({0:[3],1:[3],2:[3],3:[4,5],4:[],5:[]}) + sage: P = Poset({0:[3], 1:[3], 2:[3], 3:[4, 5], 4:[], 5:[]}) sage: P.is_bounded() False - sage: Q = Poset({0:[1],1:[]}) + sage: Q = Posets.DiamondPoset(5) sage: Q.is_bounded() True + + .. SEEALSO:: :meth:`has_bottom`, :meth:`has_top` + + TESTS:: + + sage: Poset().is_bounded() # Test empty poset + False + sage: Poset({1:[]}).is_bounded() # Here top == bottom + True + sage: ( len([P for P in Posets(5) if P.is_bounded()]) == + ....: Posets(3).cardinality() ) + True """ return self._hasse_diagram.is_bounded() def is_chain(self): """ - Returns True if the poset is totally ordered, and False otherwise. + Return ``True`` if the poset is totally ordered ("chain"), and + ``False`` otherwise. EXAMPLES:: - sage: L = Poset({0:[1],1:[2],2:[3],3:[4]}) - sage: L.is_chain() + sage: I = Poset({0:[1], 1:[2], 2:[3], 3:[4]}) + sage: I.is_chain() True - sage: V = Poset({0:[1,2]}) + + sage: II = Poset({0:[1], 2:[3]}) + sage: II.is_chain() + False + + sage: V = Poset({0:[1, 2]}) sage: V.is_chain() False + + TESTS:: + + sage: [len([P for P in Posets(n) if P.is_chain()]) for n in range(5)] + [1, 1, 1, 1, 1] """ return self._hasse_diagram.is_chain() - def is_chain_of_poset(self, o, ordered=False): + def is_chain_of_poset(self, elms, ordered=False): """ - Return whether an iterable ``o`` is a chain of ``self``, - including a check for ``o`` being ordered from smallest - to largest element if the keyword ``ordered`` is set to - ``True``. + Return ``True`` if `elms` is a chain of the poset, and ``False`` otherwise. INPUT: - - ``o`` -- an iterable (e. g., list, set, or tuple) - containing some elements of ``self`` - - - ``ordered`` -- a Boolean (default: ``False``) which - decides whether the notion of a chain includes being - ordered + - ``elms`` -- a list or other iterable containing some elements + of the poset - OUTPUT: - - If ``ordered`` is set to ``False``, the truth value of - the following assertion is returned: The subset of ``self`` - formed by the elements of ``o`` is a chain in ``self``. - - If ``ordered`` is set to ``True``, the truth value of - the following assertion is returned: Every element of the - list ``o`` is (strictly!) smaller than its successor in - ``self``. (This makes no sense if ``ordered`` is a set.) + - ``ordered`` -- a Boolean. If ``True``, then return ``True`` + only if elements in `elms` are strictly increasing in the + poset; this makes no sense if `elms` is a set. If ``False`` + (the default), then elements can be repeated and be in any + order. EXAMPLES:: sage: P = Poset((divisors(12), attrcall("divides"))) sage: sorted(P.list()) [1, 2, 3, 4, 6, 12] - sage: P.is_chain_of_poset([2, 4]) - True - sage: P.is_chain_of_poset([12, 6]) + sage: P.is_chain_of_poset([12, 3]) True - sage: P.is_chain_of_poset([12, 6], ordered=True) + sage: P.is_chain_of_poset({3, 4, 12}) False - sage: P.is_chain_of_poset([6, 12], ordered=True) - True - sage: P.is_chain_of_poset(()) - True - sage: P.is_chain_of_poset((), ordered=True) - True - sage: P.is_chain_of_poset((3, 4, 12)) - False - sage: P.is_chain_of_poset((3, 6, 12, 1)) - True - sage: P.is_chain_of_poset((3, 6, 12, 1), ordered=True) + sage: P.is_chain_of_poset([12, 3], ordered=True) False - sage: P.is_chain_of_poset((3, 6, 12), ordered=True) - True sage: P.is_chain_of_poset((1, 1, 3)) True sage: P.is_chain_of_poset((1, 1, 3), ordered=True) False sage: P.is_chain_of_poset((1, 3), ordered=True) True - sage: P.is_chain_of_poset((6, 1, 1, 3)) + + TESTS:: + + sage: P = Posets.BooleanLattice(4) + sage: P.is_chain_of_poset([]) True - sage: P.is_chain_of_poset((2, 1, 1, 3)) + sage: P.is_chain_of_poset((1,3,7,15,14)) False + sage: P.is_chain_of_poset({10}) + True + sage: P.is_chain_of_poset([32]) + Traceback (most recent call last): + ... + ValueError: element (=32) not in poset """ if ordered: - sorted_o = o + sorted_o = elms return all(self.lt(a, b) for a, b in zip(sorted_o, sorted_o[1:])) else: # _element_to_vertex can be assumed to be a linear extension # of the poset according to the documentation of class # HasseDiagram. - sorted_o = sorted(o, key=self._element_to_vertex) + sorted_o = sorted(elms, key=self._element_to_vertex) return all(self.le(a, b) for a, b in zip(sorted_o, sorted_o[1:])) def is_connected(self): """ Return ``True`` if the poset is connected, and ``False`` otherwise. - Poset is not connected if it can be divided to disjoint parts + A poset is connected if it's Hasse diagram is connected. + + If a poset is not connected, then it can be divided to parts `S_1` and `S_2` so that every element of `S_1` is incomparable to every element of `S_2`. EXAMPLES:: - sage: P=Poset({1:[2,3], 3:[4,5]}) + sage: P = Poset({1:[2, 3], 3:[4, 5]}) sage: P.is_connected() True - sage: P=Poset({1:[2,3], 3:[4,5], 6:[7,8]}) + + sage: P = Poset({1:[2, 3], 3:[4, 5], 6:[7, 8]}) sage: P.is_connected() False + + .. SEEALSO:: :meth:`connected_components` + + TESTS:: + + sage: Poset().is_connected() # Test empty poset + True """ return self._hasse_diagram.is_connected() @@ -2821,80 +2858,72 @@ def rank(self, element=None): def is_ranked(self): r""" - Returns whether this poset is ranked. + Return ``True`` if the poset is ranked, and ``False`` otherwise. - A poset is *ranked* if it admits a rank function. For more information - about the rank function, see :meth:`~sage.combinat.posets.hasse_diagram.HasseDiagram.rank_function`. + A poset is ranked if there is a function `r` from poset elements + to integers so that `r(x)=r(y)+1` when `x` covers `y`. - .. SEEALSO:: :meth:`is_graded`. + Informally said a ranked poset can be "levelized": every element is + on a "level", and every cover relation goes only one level up. EXAMPLES:: - sage: P = Poset([[1],[2],[3],[4],[]]) + sage: P = Poset( ([1, 2, 3, 4], [[1, 2], [2, 4], [3, 4]] )) sage: P.is_ranked() True - sage: Q = Poset([[1,5],[2,6],[3],[4],[],[6,3],[4]]) - sage: Q.is_ranked() - False - sage: P = Poset( ([1,2,3,4],[[1,2],[2,4],[3,4]] )) + + sage: P = Poset([[1, 5], [2, 6], [3], [4],[], [6, 3], [4]]) sage: P.is_ranked() + False + + .. SEEALSO:: :meth:`rank_function`, :meth:`rank`, :meth:`is_graded` + + TESTS:: + + sage: Poset().is_ranked() # Test empty poset True """ return bool(self.rank_function()) def is_graded(self): r""" - Returns whether this poset is graded. + Return ``True`` if the poset is graded, and ``False`` otherwise. - A poset is *graded* if all its maximal chains have the same length. - There are various competing definitions for graded posets (see - :wikipedia:`Graded_poset`). This definition is from section 3.1 of - Richard Stanley's *Enumerative Combinatorics, Vol. 1* [EnumComb1]_. + A poset is graded if all its maximal chains have the same length. - Note that every graded poset is ranked, but the converse is not - true. + There are various competing definitions for graded + posets (see :wikipedia:`Graded_poset`). This definition is from + section 3.1 of Richard Stanley's *Enumerative Combinatorics, + Vol. 1* [EnumComb1]_. - .. SEEALSO:: :meth:`is_ranked` + Every graded poset is ranked. The converse is true + for bounded posets, including lattices. EXAMPLES:: - sage: P = Poset([[1],[2],[3],[4],[]]) + sage: P = Posets.PentagonPoset() # Not even ranked sage: P.is_graded() - True - sage: Q = Poset([[1,5],[2,6],[3],[4],[],[6,3],[4]]) - sage: Q.is_graded() False - sage: P = Poset( ([1,2,3,4],[[1,2],[2,4],[3,4]] )) + + sage: P = Poset({1:[2, 3], 3:[4]}) # Ranked, but not graded sage: P.is_graded() False - sage: P = Poset({1: [2, 3], 4: [5]}) + + sage: P = Poset({1:[3, 4], 2:[3, 4], 5:[6]}) sage: P.is_graded() True - sage: P = Poset({1: [2, 3], 3: [4]}) - sage: P.is_graded() - False - sage: P = Poset({1: [2, 3], 4: []}) + + sage: P = Poset([[1], [2], [], [4], []]) sage: P.is_graded() False - sage: P = Posets.BooleanLattice(4) - sage: P.is_graded() - True - sage: P = RootSystem(['D',4]).root_poset() - sage: P.is_graded() - True - sage: P = Poset({}) - sage: P.is_graded() - True - TESTS: + .. SEEALSO:: :meth:`is_ranked`, :meth:`level_sets` - Here we test that the empty poset is graded:: + TESTS:: - sage: Poset([[],[]]).is_graded() + sage: Poset().is_graded() # Test empty poset True """ - # The code below is not really optimized, but beats looking at - # every maximal chain... if len(self) <= 2: return True # Let's work with the Hasse diagram in order to avoid some @@ -3163,21 +3192,29 @@ def meet_matrix(self): def is_meet_semilattice(self): r""" - Returns True if self has a meet operation, and False otherwise. + Return ``True`` if the poset has a meet operation, and + ``False`` otherwise. - EXAMPLES:: + A meet is the greatest lower bound for given elements, if it exists. - sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]], facade = False) - sage: P.is_meet_semilattice() - True + EXAMPLES:: - sage: P = Poset([[1,2],[3],[3],[]]) + sage: P = Poset({1:[2, 3, 4], 2:[5, 6], 3:[6], 4:[6, 7]}) sage: P.is_meet_semilattice() True - sage: P = Poset({0:[2,3],1:[2,3]}) - sage: P.is_meet_semilattice() + sage: Q = P.dual() + sage: Q.is_meet_semilattice() False + + .. SEEALSO:: :meth:`is_join_semilattice`, :meth:`~sage.categories.finite_posets.FinitePosets.ParentMethods.is_lattice` + + TESTS:: + + sage: Poset().is_meet_semilattice() # Test empty lattice + True + sage: len([P for P in Posets(4) if P.is_meet_semilattice()]) + 5 """ return self._hasse_diagram.is_meet_semilattice() @@ -3194,22 +3231,32 @@ def join_matrix(self): def is_join_semilattice(self): """ - Returns True if the poset has a join operation, and False + Return ``True`` if the poset has a join operation, and ``False`` otherwise. + A join is the least upper bound for given elements, if it exists. + EXAMPLES:: - sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]]) + sage: P = Poset([[1,3,2], [4], [4,5,6], [6], [7], [7], [7], []]) sage: P.is_join_semilattice() True - sage: P = Poset([[1,2],[3],[3],[]]) - sage: P.is_join_semilattice() - True + Elements 3 and 4 have no common upper bound at all; elements + 1 and 2 have upper bounds 3 and 4, but no least upper bound:: - sage: P = Poset({0:[2,3],1:[2,3]}) + sage: P = Poset({1:[3, 4], 2:[3, 4]}) sage: P.is_join_semilattice() False + + .. SEEALSO:: :meth:`is_meet_semilattice`, :meth:`~sage.categories.finite_posets.FinitePosets.ParentMethods.is_lattice` + + TESTS:: + + sage: Poset().is_join_semilattice() # Test empty lattice + True + sage: len([P for P in Posets(4) if P.is_join_semilattice()]) + 5 """ return self._hasse_diagram.is_join_semilattice() @@ -3309,17 +3356,22 @@ def isomorphic_subposets(self, other): def antichains(self, element_constructor = __builtin__.list): """ - Returns the antichains of the poset. + Return the antichains of the poset. + + An *antichain* of a poset is a set of elements of the + poset that are pairwise incomparable. INPUT: - ``element_constructor`` -- a function taking an iterable as - argument (default: list) + argument (default: ``list``) - OUTPUT: an enumerated set + OUTPUT: - An *antichain* of a poset is a collection of elements of the - poset that are pairwise incomparable. + The enumerated set (of type + :class:`~sage.combinat.subsets_pairwise.PairwiseCompatibleSubsets`) + of all antichains of the poset, each of which is given as an + ``element_constructor.`` EXAMPLES:: @@ -3331,10 +3383,12 @@ def antichains(self, element_constructor = __builtin__.list): 8 sage: A[3] [1, 2] - sage: list(Posets.AntichainPoset(3).antichains()) - [[], [2], [2, 1], [2, 1, 0], [2, 0], [1], [1, 0], [0]] - sage: list(Posets.ChainPoset(3).antichains()) - [[], [0], [1], [2]] + + To get the antichains as, say, sets, one may use the + ``element_constructor`` option:: + + sage: list(Posets.ChainPoset(3).antichains(element_constructor=set)) + [set(), {0}, {1}, {2}] To get the antichains of a given size one can currently use:: @@ -3345,12 +3399,6 @@ def antichains(self, element_constructor = __builtin__.list): sage: A.subset(size = 2) # todo: not implemented - To get the antichains as, say, sets, one may use the - ``element_constructor`` option:: - - sage: list(Posets.ChainPoset(3).antichains(element_constructor = set)) - [set(), {0}, {1}, {2}] - .. NOTE:: Internally, this uses @@ -3366,7 +3414,7 @@ def antichains(self, element_constructor = __builtin__.list): On the other hand, this returns a full featured enumerated set, with containment testing, etc. - .. seealso:: :meth:`maximal_antichains` + .. seealso:: :meth:`maximal_antichains`, :meth:`chains` """ vertex_to_element = self._vertex_to_element @@ -3378,12 +3426,14 @@ def f(antichain): def antichains_iterator(self): """ - Returns an iterator over the antichains of the poset. + Return an iterator over the antichains of the poset. EXAMPLES:: - sage: Posets.PentagonPoset().antichains_iterator() + sage: it = Posets.PentagonPoset().antichains_iterator(); it + sage: it.next(), it.next() + ([], [4]) .. SEEALSO:: :meth:`antichains` """ @@ -3395,8 +3445,9 @@ def width(self): r""" Return the width of the poset (the size of its longest antichain). - It is computed through a matching in a bipartite graph. See - :wikipedia:`Dilworth's_theorem` for more information. + It is computed through a matching in a bipartite graph; see + :wikipedia:`Dilworth's_theorem` for more information. The width is + also called Dilworth number. EXAMPLES:: @@ -3444,7 +3495,7 @@ def dilworth_decomposition(self): union of chains. According to Dilworth's theorem, the number of chains is equal to - `\alpha` (the posets' width). + `\alpha` (the posets' width). EXAMPLES:: @@ -3489,51 +3540,47 @@ def dilworth_decomposition(self): def chains(self, element_constructor=__builtin__.list, exclude=None): """ - Return all the chains of ``self``. + Return the chains of the poset. + + A *chain* of a poset is a set of elements of the poset + that are pairwise comparable. INPUT: - ``element_constructor`` -- a function taking an iterable as - argument (default: ``list``) + argument (default: ``list``) - ``exclude`` -- elements of the poset to be excluded (default: ``None``) OUTPUT: - The enumerated set of all chains of ``self``, each of which - is given as an ``element_constructor``. - - A *chain* of a poset is a set of elements of the poset - that are pairwise comparable. + The enumerated set (of type + :class:`~sage.combinat.subsets_pairwise.PairwiseCompatibleSubsets`) + of all chains of the poset, each of which is given as an + ``element_constructor``. EXAMPLES:: - sage: A = Posets.PentagonPoset().chains(); A + sage: C = Posets.PentagonPoset().chains(); C Set of chains of Finite lattice containing 5 elements - sage: list(A) + sage: list(C) [[], [0], [0, 1], [0, 1, 4], [0, 2], [0, 2, 3], [0, 2, 3, 4], [0, 2, 4], [0, 3], [0, 3, 4], [0, 4], [1], [1, 4], [2], [2, 3], [2, 3, 4], [2, 4], [3], [3, 4], [4]] - To get the chains of a given size one can currently use:: - - sage: list(A.elements_of_depth_iterator(2)) - [[0, 1], [0, 2], [0, 3], [0, 4], [1, 4], [2, 3], [2, 4], [3, 4]] - - For bounded posets, one can exclude the bounds as follows:: - - sage: P = Posets.DiamondPoset(5) - sage: list(P.chains(exclude=[0, 4])) - [[], [1], [2], [3]] - - Another example of exclusion of vertices:: + Exclusion of vertices, tuple (instead of list) as constructor:: sage: P = Poset({1: [2, 3], 2: [4], 3: [4, 5]}) sage: list(P.chains(element_constructor=tuple, exclude=[3])) [(), (1,), (1, 2), (1, 2, 4), (1, 4), (1, 5), (2,), (2, 4), (4,), (5,)] + To get the chains of a given size one can currently use:: + + sage: list(C.elements_of_depth_iterator(2)) + [[0, 1], [0, 2], [0, 3], [0, 4], [1, 4], [2, 3], [2, 4], [3, 4]] + Eventually the following syntax will be accepted:: - sage: A.subset(size = 2) # todo: not implemented + sage: C.subset(size = 2) # todo: not implemented .. SEEALSO:: :meth:`maximal_chains`, :meth:`antichains` """ @@ -4456,9 +4503,57 @@ def incomparability_graph(self): G.rename('Incomparability graph on %s vertices' % self.cardinality()) return G + def linear_extensions_graph(self): + r""" + Return the linear extensions graph of the poset. + + Vertices of the graph are linear extensions of the poset. + Two vertices are connected by an edge if the linear extensions + differ by only one adjacent transposition. + + EXAMPLES:: + + sage: P = Poset({1:[3,4],2:[4]}) + sage: G = P.linear_extensions_graph(); G + Graph on 5 vertices + sage: G.degree_sequence() + [3, 2, 2, 2, 1] + + sage: chevron = Poset({1:[2,6], 2:[3], 4:[3,5], 6:[5]}) + sage: G = chevron.linear_extensions_graph(); G + Graph on 22 vertices + sage: G.size() + 36 + + TESTS:: + + sage: Poset().linear_extensions_graph() + Graph on 1 vertex + + sage: A4 = Posets.AntichainPoset(4) + sage: G = A4.linear_extensions_graph() + sage: G.is_regular() + True + """ + from sage.graphs.graph import Graph + # Direct implementation, no optimizations + L = self.linear_extensions() + G = Graph() + G.add_vertices(L) + for i in range(len(L)): + for j in range(i): + tmp = map(lambda x,y: x != y, L[i], L[j]) + if tmp.count(True) == 2 and tmp[tmp.index(True)+1]: + G.add_edge(L[i], L[j]) + return G + def maximal_antichains(self): """ - Return all maximal antichains of the poset. + Return the maximal antichains of the poset. + + An antichain `a` of poset `P` is *maximal* if there is + no element `e \in P \setminus a` such that `a \cup \{e\}` + is an antichain. EXAMPLES:: @@ -4469,7 +4564,7 @@ def maximal_antichains(self): sage: Posets.PentagonPoset().maximal_antichains() [[0], [1, 2], [1, 3], [4]] - .. seealso:: :meth:`maximal_chains`, :meth:`antichains` + .. seealso:: :meth:`antichains`, :meth:`maximal_chains` """ # Maximal antichains are maximum cliques on incomparability graph. return self.incomparability_graph().cliques_maximal() @@ -5245,7 +5340,8 @@ def evacuation(self): def is_rank_symmetric(self): """ - Return ``True`` if the poset is rank symmetric, and ``False`` otherwise. + Return ``True`` if the poset is rank symmetric, and ``False`` + otherwise. The poset is expected to be graded and connected. @@ -5255,12 +5351,12 @@ def is_rank_symmetric(self): EXAMPLES:: - sage: P = Poset({1:[2], 2:[3,4], 3:[5], 4:[5]}) - sage: P.is_rank_symmetric() - False - sage: P = Poset({1:[3,4,5], 2:[3,4,5], 3:[6], 4:[7], 5:[7]}) + sage: P = Poset({1:[3, 4, 5], 2:[3, 4, 5], 3:[6], 4:[7], 5:[7]}) sage: P.is_rank_symmetric() True + sage: P = Poset({1:[2], 2:[3, 4], 3:[5], 4:[5]}) + sage: P.is_rank_symmetric() + False TESTS:: @@ -5297,21 +5393,26 @@ def is_slender(self): EXAMPLES:: - sage: P = Poset(([1,2,3,4],[[1,2],[1,3],[2,4],[3,4]]), facade = True) + sage: P = Poset(([1, 2, 3, 4], [[1, 2], [1, 3], [2, 4], [3, 4]])) sage: P.is_slender() True - sage: P = Poset(([1,2,3,4,5],[[1,2],[1,3],[1,4],[2,5],[3,5],[4,5]]), facade = True) + sage: P = Poset(([1,2,3,4,5],[[1,2],[1,3],[1,4],[2,5],[3,5],[4,5]])) sage: P.is_slender() False - sage: W = WeylGroup(['A',2]) + sage: W = WeylGroup(['A', 2]) sage: G = W.bruhat_poset() sage: G.is_slender() True - sage: W = WeylGroup(['A',3]) + sage: W = WeylGroup(['A', 3]) sage: G = W.bruhat_poset() sage: G.is_slender() True + + TESTS:: + + sage: Poset().is_slender() # Test empty poset + True """ for x in self: d = {} @@ -5652,6 +5753,195 @@ def incidence_algebra(self, R, prefix='I'): from sage.combinat.posets.incidence_algebras import IncidenceAlgebra return IncidenceAlgebra(R, self, prefix) + @cached_method(key=lambda self,x,y,l: (x,y)) + def _kl_poly(self, x=None, y=None, canonical_labels=None): + r""" + Cached Kazhdan-Lusztig polynomial of ``self`` for generic `q`. + + .. SEEALSO:: + + :meth:`kazhdan_lusztig_polynomial` + + EXAMPLES:: + + sage: L = posets.SymmetricGroupWeakOrderPoset(4) + sage: L._kl_poly() + 1 + sage: x = '2314' + sage: y = '3421' + sage: L._kl_poly(x, y) + -q + 1 + + AUTHORS: + + - Travis Scrimshaw (27-12-2014) + """ + R = PolynomialRing(ZZ, 'q') + q = R.gen(0) + + # Handle some special cases + if self.cardinality() == 0: + return q.parent().zero() + if not self.rank(): + return q.parent().one() + + if canonical_labels is None: + canonical_labels = x is None and y is None + + if x is not None or y is not None: + if x == y: + return q.parent().one() + if x is None: + x = self.minimal_elements()[0] + if y is None: + y = self.maximal_elements()[0] + if not self.le(x, y): + return q.parent().zero() + P = self.subposet(self.interval(x, y)) + return P.kazhdan_lusztig_polynomial(q=q, canonical_labels=canonical_labels) + + min_elt = self.minimal_elements()[0] + if canonical_labels: + sublat = lambda P: self.subposet(P).canonical_label() + else: + sublat = lambda P: self.subposet(P) + poly = -sum(sublat(self.order_ideal([x])).characteristic_polynomial() + * sublat(self.order_filter([x])).kazhdan_lusztig_polynomial() + for x in self if x != min_elt) + tr = floor(self.rank()/2) + 1 + ret = poly.truncate(tr) + return ret(q=q) + + def kazhdan_lusztig_polynomial(self, x=None, y=None, q=None, canonical_labels=None): + r""" + Return the Kazhdan-Lusztig polynomial `P_{x,y}(q)` of ``self``. + + We follow the definition given in [EPW14]_. Let `G` denote a + graded poset with unique minimal and maximal elements and `\chi_G` + denote the characteristic polynomial of `G`. Let `I_x` and `F^x` + denote the principal order ideal and filter of `x` respectively. + Define the *Kazhdan-Lusztig polynomial* of `G` as the unique + polynomial `P_G(q)` satisfying the following: + + 1. If `\operatorname{rank} G = 0`, then `P_G(q) = 1`. + 2. If `\operatorname{rank} G > 0`, then `\deg P_G(q) < + \frac{1}{2} \operatorname{rank} G`. + 3. We have + + .. MATH:: + + q^{\operatorname{rank} G} P_G(q^{-1}) + = \sum_{x \in G} \chi_{I_x}(q) P_{F^x}(q). + + We then extend this to `P_{x,y}(q)` by considering the subposet + corresponding to the (closed) interval `[x, y]`. We also + define `P_{\emptyset}(q) = 0` (so if `x \not\leq y`, + then `P_{x,y}(q) = 0`). + + INPUT: + + - ``q`` -- (default: `q \in \ZZ[q]`) the indeterminate `q` + - ``x`` -- (default: the minimal element) the element `x` + - ``y`` -- (default: the maximal element) the element `y` + - ``canonical_labels`` -- (optional) for subposets, use the + canonical labeling (this can limit recursive calls for posets + with large amounts of symmetry, but producing the labeling + takes time); if not specified, this is ``True`` if ``x`` + and ``y`` are both not specified and ``False`` otherwise + + EXAMPLES:: + + sage: L = posets.BooleanLattice(3) + sage: L.kazhdan_lusztig_polynomial() + 1 + + :: + + sage: L = posets.SymmetricGroupWeakOrderPoset(4) + sage: L.kazhdan_lusztig_polynomial() + 1 + sage: x = '2314' + sage: y = '3421' + sage: L.kazhdan_lusztig_polynomial(x, y) + -q + 1 + sage: L.kazhdan_lusztig_polynomial(x, y, var('t')) + -t + 1 + + REFERENCES: + + .. [EPW14] Ben Elias, Nicholas Proudfoot, and Max Wakefield. + *The Kazhdan-Lusztig polynomial of a matroid*. 2014. + :arxiv:`1412.7408`. + + AUTHORS: + + - Travis Scrimshaw (27-12-2014) + """ + if not self.is_ranked(): + raise ValueError("poset is not ranked") + if q is None: + q = PolynomialRing(ZZ, 'q').gen(0) + poly = self._kl_poly(x, y, canonical_labels) + return poly(q=q) + + def is_induced_subposet(self, other): + r""" + Return ``True`` if the poset is an induced subposet of ``other``, and + ``False`` otherwise. + + A poset `P` is an induced subposet of `Q` if every element + of `P` is an element of `Q`, and `x \le_P y` iff `x \le_Q y`. + Note that "induced" here has somewhat different meaning compared + to that of graphs. + + INPUT: + + - ``other``, a poset. + + .. NOTE:: + + This method does not check whether the poset is a + *isomorphic* (i.e., up to relabeling) subposet of ``other``, + but only if ``other`` directly contains the poset as an + induced subposet. For isomorphic subposets see + :meth:`has_isomorphic_subposet`. + + EXAMPLES:: + + sage: P = Poset({1:[2, 3]}) + sage: Q = Poset({1:[2, 4], 2:[3]}) + sage: P.is_induced_subposet(Q) + False + sage: R = Poset({0:[1], 1:[3, 4], 3:[5], 4:[2]}) + sage: P.is_induced_subposet(R) + True + + TESTS:: + + sage: P = Poset({2:[1]}) + sage: Poset().is_induced_subposet(P) + True + sage: Poset().is_induced_subposet(Poset()) + True + sage: P.is_induced_subposet(Poset()) + False + + Bad input:: + + sage: Poset().is_induced_subposet('junk') + Traceback (most recent call last): + ... + AttributeError: 'str' object has no attribute 'subposet' + """ + if (not self._is_facade or + (isinstance(other, FinitePoset) and not other._is_facade)): + raise TypeError('the function is not defined on non-facade posets') + # TODO: When we have decided if + # Poset({'x':[42]}) == LatticePoset({'x':[42]}) + # or not, either remove this note or remove .hasse_diagram() below. + return (set(self).issubset(set(other)) and + other.subposet(self).hasse_diagram() == self.hasse_diagram()) + FinitePoset._dual_class = FinitePoset ##### Posets ##### @@ -5771,13 +6061,13 @@ def cardinality(self, from_iterator=False): def is_poset(dig): r""" - Tests whether a directed graph is acyclic and transitively - reduced. + Return ``True`` if a directed graph is acyclic and transitively + reduced, and ``False`` otherwise. EXAMPLES:: sage: from sage.combinat.posets.posets import is_poset - sage: dig = DiGraph({0:[2,3], 1:[3,4,5], 2:[5], 3:[5], 4:[5]}) + sage: dig = DiGraph({0:[2, 3], 1:[3, 4, 5], 2:[5], 3:[5], 4:[5]}) sage: is_poset(dig) False sage: is_poset(dig.transitive_reduction()) diff --git a/src/sage/combinat/quickref.py b/src/sage/combinat/quickref.py index f939e8ba945..64d9cd70fac 100644 --- a/src/sage/combinat/quickref.py +++ b/src/sage/combinat/quickref.py @@ -27,7 +27,7 @@ Constructions and Species:: - sage: for (p, s) in CartesianProduct(P, S): print p, s # not tested + sage: for (p, s) in cartesian_product([P,S]): print p, s # not tested sage: DisjointUnionEnumeratedSets(Family(lambda n: IntegerVectors(n, 3), NonNegativeIntegers)) # not tested Words:: diff --git a/src/sage/combinat/rigged_configurations/bij_abstract_class.py b/src/sage/combinat/rigged_configurations/bij_abstract_class.py index cb745eb36a8..8a12d5ce409 100644 --- a/src/sage/combinat/rigged_configurations/bij_abstract_class.py +++ b/src/sage/combinat/rigged_configurations/bij_abstract_class.py @@ -432,7 +432,7 @@ def run(self, verbose=False, build_graph=False): self._graph.pop(0) # Remove the dummy at the start from sage.graphs.digraph import DiGraph from sage.graphs.dot2tex_utils import have_dot2tex - self._graph = DiGraph(self._graph) + self._graph = DiGraph(self._graph, format="list_of_edges") if have_dot2tex(): self._graph.set_latex_options(format="dot2tex", edge_labels=True) diff --git a/src/sage/combinat/rigged_configurations/bij_type_D.py b/src/sage/combinat/rigged_configurations/bij_type_D.py index a191ed3e0df..5b660866e24 100644 --- a/src/sage/combinat/rigged_configurations/bij_type_D.py +++ b/src/sage/combinat/rigged_configurations/bij_type_D.py @@ -542,7 +542,7 @@ def run(self, verbose=False, build_graph=False): self._graph.pop(0) # Remove the dummy at the start from sage.graphs.digraph import DiGraph from sage.graphs.dot2tex_utils import have_dot2tex - self._graph = DiGraph(self._graph) + self._graph = DiGraph(self._graph, format="list_of_edges") if have_dot2tex(): self._graph.set_latex_options(format="dot2tex", edge_labels=True) diff --git a/src/sage/combinat/rigged_configurations/kleber_tree.py b/src/sage/combinat/rigged_configurations/kleber_tree.py index 1a059859c79..031c85902b3 100644 --- a/src/sage/combinat/rigged_configurations/kleber_tree.py +++ b/src/sage/combinat/rigged_configurations/kleber_tree.py @@ -24,30 +24,30 @@ sage: from sage.combinat.rigged_configurations.kleber_tree import KleberTree sage: KT = KleberTree(['A', 3, 1], [[3,2], [2,1], [1,1], [1,1]]) - sage: for x in set(KT.list()): x - Kleber tree node with weight [1, 0, 3] and upwards edge root [1, 1, 0] - Kleber tree node with weight [0, 2, 2] and upwards edge root [1, 0, 0] - Kleber tree node with weight [2, 1, 2] and upwards edge root [0, 0, 0] - Kleber tree node with weight [2, 0, 0] and upwards edge root [0, 1, 1] - Kleber tree node with weight [0, 0, 2] and upwards edge root [1, 1, 0] - Kleber tree node with weight [0, 1, 0] and upwards edge root [0, 0, 1] - Kleber tree node with weight [3, 0, 1] and upwards edge root [0, 1, 1] - Kleber tree node with weight [0, 1, 0] and upwards edge root [1, 1, 1] - Kleber tree node with weight [1, 1, 1] and upwards edge root [1, 1, 1] - Kleber tree node with weight [0, 0, 2] and upwards edge root [2, 2, 1] + sage: sorted((x.weight.to_vector(), x.up_root.to_vector()) for x in KT.list()) + [((0, 0, 2), (1, 1, 0)), + ((0, 0, 2), (2, 2, 1)), + ((0, 1, 0), (0, 0, 1)), + ((0, 1, 0), (1, 1, 1)), + ((0, 2, 2), (1, 0, 0)), + ((1, 0, 3), (1, 1, 0)), + ((1, 1, 1), (1, 1, 1)), + ((2, 0, 0), (0, 1, 1)), + ((2, 1, 2), (0, 0, 0)), + ((3, 0, 1), (0, 1, 1))] sage: KT = KleberTree(['A', 7, 1], [[3,2], [2,1], [1,1]]) sage: KT Kleber tree of Cartan type ['A', 7, 1] and B = ((3, 2), (2, 1), (1, 1)) - sage: for x in set(KT.list()): x - Kleber tree node with weight [1, 0, 1, 0, 1, 0, 0] and upwards edge root [1, 2, 2, 1, 0, 0, 0] - Kleber tree node with weight [0, 0, 1, 0, 0, 1, 0] and upwards edge root [2, 3, 3, 2, 1, 0, 0] - Kleber tree node with weight [1, 1, 2, 0, 0, 0, 0] and upwards edge root [0, 0, 0, 0, 0, 0, 0] - Kleber tree node with weight [2, 0, 1, 1, 0, 0, 0] and upwards edge root [0, 1, 1, 0, 0, 0, 0] - Kleber tree node with weight [1, 0, 0, 2, 0, 0, 0] and upwards edge root [0, 1, 1, 0, 0, 0, 0] - Kleber tree node with weight [0, 0, 3, 0, 0, 0, 0] and upwards edge root [1, 1, 0, 0, 0, 0, 0] - Kleber tree node with weight [0, 0, 0, 1, 1, 0, 0] and upwards edge root [1, 1, 1, 0, 0, 0, 0] - Kleber tree node with weight [0, 1, 1, 1, 0, 0, 0] and upwards edge root [1, 1, 1, 0, 0, 0, 0] + sage: sorted((x.weight.to_vector(), x.up_root.to_vector()) for x in KT.list()) + [((0, 0, 0, 1, 1, 0, 0), (1, 1, 1, 0, 0, 0, 0)), + ((0, 0, 1, 0, 0, 1, 0), (2, 3, 3, 2, 1, 0, 0)), + ((0, 0, 3, 0, 0, 0, 0), (1, 1, 0, 0, 0, 0, 0)), + ((0, 1, 1, 1, 0, 0, 0), (1, 1, 1, 0, 0, 0, 0)), + ((1, 0, 0, 2, 0, 0, 0), (0, 1, 1, 0, 0, 0, 0)), + ((1, 0, 1, 0, 1, 0, 0), (1, 2, 2, 1, 0, 0, 0)), + ((1, 1, 2, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0)), + ((2, 0, 1, 1, 0, 0, 0), (0, 1, 1, 0, 0, 0, 0))] """ #***************************************************************************** @@ -65,6 +65,8 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools + from sage.misc.lazy_attribute import lazy_attribute from sage.misc.cachefunc import cached_method from sage.misc.latex import latex @@ -77,7 +79,6 @@ from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.combinat.root_system.cartan_type import CartanType -from sage.combinat.cartesian_product import CartesianProduct from sage.graphs.digraph import DiGraph from sage.graphs.dot2tex_utils import have_dot2tex @@ -347,6 +348,22 @@ def multiplicity(self): return mult + def __hash__(self): + r""" + TESTS:: + + sage: from sage.combinat.rigged_configurations.kleber_tree import KleberTree + sage: RS = RootSystem(['A', 2]) + sage: WS = RS.weight_space() + sage: R = RS.root_space() + sage: KT = KleberTree(['A', 2, 1], [[1,1]]) + sage: n = KT(WS.sum_of_terms([(1,5), (2,2)]), R.zero()) + sage: hash(n) + -603608031356818252 # 64-bit + -1956156236 # 32-bit + """ + return hash(self.depth) ^ hash(self.weight) + def __cmp__(self, rhs): r""" Check whether two nodes are equal. @@ -830,7 +847,9 @@ def _children_iter(self, node): L = [range(val + 1) for val in node.up_root.to_vector()] - for root in CartesianProduct(*L).list()[1:]: # First element is the zero element + it = itertools.product(*L) + it.next() # First element is the zero element + for root in it: # Convert the list to an honest root in the root space converted_root = RS.sum_of_terms([[I[i], val] for i, val in enumerate(root)]) diff --git a/src/sage/combinat/rigged_configurations/kr_tableaux.py b/src/sage/combinat/rigged_configurations/kr_tableaux.py index 1637d297003..c059ef3ea85 100644 --- a/src/sage/combinat/rigged_configurations/kr_tableaux.py +++ b/src/sage/combinat/rigged_configurations/kr_tableaux.py @@ -308,20 +308,16 @@ def __iter__(self): EXAMPLES:: - sage: KR = crystals.KirillovReshetikhin(['A', 3, 1], 2, 1, model='KR') - sage: g = KR.__iter__() - sage: next(g) - [[1], [2]] - sage: next(g) - [[1], [3]] - sage: next(g) - [[2], [3]] + sage: KR = crystals.KirillovReshetikhin(['A', 5, 2], 2, 1, model='KR') + sage: L = [x for x in KR] + sage: len(L) + 15 """ index_set = self._cartan_type.classical().index_set() from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet return RecursivelyEnumeratedSet(self.module_generators, lambda x: [x.f(i) for i in index_set], - structure=None).naive_search_iterator() + structure='graded').breadth_first_search_iterator() def module_generator(self, i=None, **options): r""" @@ -1322,7 +1318,7 @@ def e(self, i): sage: KRT.module_generators[0].e(0) [[-2, 1], [-1, -1]] """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): ret = self.to_kirillov_reshetikhin_crystal().e0() if ret is None: return None @@ -1343,7 +1339,7 @@ def f(self, i): sage: KRT.module_generators[0].f(0) [[1, 1], [2, -1]] """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): ret = self.to_kirillov_reshetikhin_crystal().f0() if ret is None: return None @@ -1365,7 +1361,7 @@ def epsilon(self, i): sage: KRT.module_generators[0].epsilon(0) 2 """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.to_kirillov_reshetikhin_crystal().epsilon0() return TensorProductOfRegularCrystalsElement.epsilon(self, i) @@ -1383,7 +1379,7 @@ def phi(self, i): sage: KRT.module_generators[0].phi(0) 2 """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.to_kirillov_reshetikhin_crystal().phi0() return TensorProductOfRegularCrystalsElement.phi(self, i) @@ -1481,7 +1477,8 @@ def e(self, i): [[1], [3], [-4], [-2]] sage: KRT(-1, -4, 3, 2).e(3) """ - if i == 0: # Only need to do it once since we pull to the KR crystal + if i == self.parent()._cartan_type.special_node(): + # Only need to do it once since we pull to the KR crystal return KirillovReshetikhinTableauxElement.e(self, i) half = KirillovReshetikhinTableauxElement.e(self, i) @@ -1500,7 +1497,8 @@ def f(self, i): sage: KRT(-1, -4, 3, 2).f(3) [[2], [4], [-3], [-1]] """ - if i == 0: # Only need to do it once since we pull to the KR crystal + if i == self.parent()._cartan_type.special_node(): + # Only need to do it once since we pull to the KR crystal return KirillovReshetikhinTableauxElement.f(self, i) half = KirillovReshetikhinTableauxElement.f(self, i) @@ -1521,7 +1519,8 @@ def epsilon(self, i): sage: KRT(-1, -4, 3, 2).epsilon(3) 0 """ - if i == 0: # Don't need to half it since we pull to the KR crystal + if i == self.parent()._cartan_type.special_node(): + # Don't need to half it since we pull to the KR crystal return KirillovReshetikhinTableauxElement.epsilon(self, i) return KirillovReshetikhinTableauxElement.epsilon(self, i) // 2 @@ -1537,7 +1536,8 @@ def phi(self, i): sage: KRT(-1, -4, 3, 2).phi(3) 1 """ - if i == 0: # Don't need to half it since we pull to the KR crystal + if i == self.parent()._cartan_type.special_node(): + # Don't need to half it since we pull to the KR crystal return KirillovReshetikhinTableauxElement.phi(self, i) return KirillovReshetikhinTableauxElement.phi(self, i) // 2 diff --git a/src/sage/combinat/rigged_configurations/rigged_configuration_element.py b/src/sage/combinat/rigged_configurations/rigged_configuration_element.py index a41ef5774d4..bbfe85b94a7 100644 --- a/src/sage/combinat/rigged_configurations/rigged_configuration_element.py +++ b/src/sage/combinat/rigged_configurations/rigged_configuration_element.py @@ -1805,6 +1805,125 @@ def left_box(self, return_b=False): delta = left_box + def left_column_box(self): + r""" + Return the image of ``self`` under the left column box splitting + map `\gamma`. + + Consider the map `\gamma : RC(B^{r,1} \otimes B) \to RC(B^{1,1} + \otimes B^{r-1,1} \otimes B)` for `r > 1`, which is a natural strict + classical crystal injection. On rigged configurations, the map + `\gamma` adds a singular string of length `1` to `\nu^{(a)}`. + + We can extend `\gamma` when the left-most factor is not a single + column by precomposing with a :meth:`left_split()`. + + EXAMPLES:: + + sage: RC = RiggedConfigurations(['C',3,1], [[3,1], [2,1]]) + sage: mg = RC.module_generators[-1] + sage: ascii_art(mg) + 0[ ]0 0[ ][ ]0 0[ ]0 + 0[ ]0 0[ ]0 + sage: ascii_art(mg.left_column_box()) + 0[ ]0 0[ ][ ]0 0[ ]0 + 0[ ]0 0[ ]0 0[ ]0 + 0[ ]0 + + sage: RC = RiggedConfigurations(['C',3,1], [[2,1], [1,1], [3,1]]) + sage: mg = RC.module_generators[7] + sage: ascii_art(mg) + 1[ ]0 0[ ][ ]0 0[ ]0 + 0[ ]0 0[ ]0 + sage: ascii_art(mg.left_column_box()) + 1[ ]1 0[ ][ ]0 0[ ]0 + 1[ ]0 0[ ]0 0[ ]0 + """ + P = self.parent() + r = P.dims[0][0] + if r == 1: + raise ValueError("cannot split a single box") + ct = P.cartan_type() + if ct.type() == 'D': + if P.dims[0][0] >= ct.rank() - 2: + raise ValueError("only for non-spinor cases") + elif ct.type() == 'B' or ct.dual().type() == 'B': + if P.dims[0][0] == ct.rank() - 1: + raise ValueError("only for non-spinor cases") + + if P.dims[0][1] > 1: + return self.left_split().left_column_box() + + B = [[1,1], [r-1,1]] + B.extend(P.dims[1:]) + from sage.combinat.rigged_configurations.rigged_configurations import RiggedConfigurations + RC = RiggedConfigurations(P._cartan_type, B) + parts = [x._clone() for x in self] # Make a deep copy + for nu in parts[:r-1]: + nu._list.append(1) + for a, nu in enumerate(parts[:r-1]): + vac_num = RC._calc_vacancy_number(parts, a, 1) + i = nu._list.index(1) + nu.vacancy_numbers.insert(i, vac_num) + nu.rigging.insert(i, vac_num) + return RC(*parts) + + def right_column_box(self): + r""" + Return the image of ``self`` under the right column box splitting + map `\gamma^*`. + + Consider the map `\gamma^* : RC(B \otimes B^{r,1}) \to RC(B \otimes + B^{r-1,1} \otimes B^{1,1})` for `r > 1`, which is a natural strict + classical crystal injection. On rigged configurations, the map + `\gamma` adds a string of length `1` with rigging 0 to `\nu^{(a)}` + for all `a < r` to a classically highest weight element and extended + as a classical crystal morphism. + + We can extend `\gamma^*` when the right-most factor is not a single + column by precomposing with a :meth:`right_split()`. + + EXAMPLES:: + + sage: RC = RiggedConfigurations(['C',3,1], [[2,1], [1,1], [3,1]]) + sage: mg = RC.module_generators[7] + sage: ascii_art(mg) + 1[ ]0 0[ ][ ]0 0[ ]0 + 0[ ]0 0[ ]0 + sage: ascii_art(mg.right_column_box()) + 1[ ]0 0[ ][ ]0 0[ ]0 + 1[ ]0 0[ ]0 0[ ]0 + 0[ ]0 + """ + P = self.parent() + r = P.dims[-1][0] + if r == 1: + raise ValueError("cannot split a single box") + ct = P.cartan_type() + if ct.type() == 'D': + if P.dims[-1][0] >= ct.rank() - 2: + raise ValueError("only for non-spinor cases") + elif ct.type() == 'B' or ct.dual().type() == 'B': + if P.dims[-1][0] == ct.rank() - 1: + raise ValueError("only for non-spinor cases") + + if P.dims[-1][1] > 1: + return self.right_split().right_column_box() + + rc, e_string = self.to_highest_weight(P.cartan_type().classical().index_set()) + + B = P.dims[:-1] + ([r-1,1], [1,1]) + from sage.combinat.rigged_configurations.rigged_configurations import RiggedConfigurations + RC = RiggedConfigurations(P._cartan_type, B) + parts = [x._clone() for x in rc] # Make a deep copy + for nu in parts[:r-1]: + nu._list.append(1) + for a, nu in enumerate(parts[:r-1]): + vac_num = RC._calc_vacancy_number(parts, a, -1) + nu.vacancy_numbers.append(vac_num) + nu.rigging.append(0) + return RC(*parts).f_string(reversed(e_string)) + def complement_rigging(self, reverse_factors=False): r""" Apply the complement rigging morphism `\theta` to ``self``. diff --git a/src/sage/combinat/rigged_configurations/rigged_configurations.py b/src/sage/combinat/rigged_configurations/rigged_configurations.py index 86f5c38263d..190a57e9944 100644 --- a/src/sage/combinat/rigged_configurations/rigged_configurations.py +++ b/src/sage/combinat/rigged_configurations/rigged_configurations.py @@ -21,6 +21,8 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools + from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute from sage.structure.global_options import GlobalOptions @@ -32,7 +34,6 @@ from sage.categories.finite_crystals import FiniteCrystals from sage.categories.regular_crystals import RegularCrystals from sage.combinat.root_system.cartan_type import CartanType -from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.rigged_configurations.kleber_tree import KleberTree, VirtualKleberTree from sage.combinat.rigged_configurations.rigged_configuration_element import ( RiggedConfigurationElement, KRRCSimplyLacedElement, KRRCNonSimplyLacedElement, @@ -480,29 +481,15 @@ def __iter__(self): EXAMPLES:: sage: RC = RiggedConfigurations(['A', 3, 1], [[2,1], [1,1]]) - sage: g = RC.__iter__() - sage: next(g) - - (/) - - (/) - - (/) - - sage: next(g) # random - - 0[ ]0 - - 0[ ]0 - - (/) - + sage: L = [x for x in RC] + sage: len(L) + 24 """ index_set = self._cartan_type.classical().index_set() from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet return RecursivelyEnumeratedSet(self.module_generators, lambda x: [x.f(i) for i in index_set], - structure=None).naive_search_iterator() + structure='graded').breadth_first_search_iterator() @lazy_attribute def module_generators(self): @@ -602,10 +589,9 @@ def module_generators(self): L2 = [] for block in blocks[-1]: L2.append(IterableFunctionCall(self._block_iterator, block)) - L.append(CartesianProduct(*L2)) + L.append(itertools.product(*L2)) - # TODO Find a more efficient method without appealing to the CartesianProduct - C = CartesianProduct(*L) + C = itertools.product(*L) for curBlocks in C: module_gens.append( self.element_class(self, KT_constructor=[shapes[:], self._blocks_to_values(curBlocks[:]), @@ -1279,10 +1265,9 @@ def module_generators(self): L2 = [] for block in blocks[-1]: L2.append(IterableFunctionCall(self._block_iterator, block)) - L.append(CartesianProduct(*L2)) + L.append(itertools.product(*L2)) - # TODO Find a more efficient method without appealing to the CartesianProduct - C = CartesianProduct(*L) + C = itertools.product(*L) for cur_blocks in C: module_gens.append( self.element_class(self, KT_constructor=[shapes[:], self._blocks_to_values(cur_blocks[:]), vac_nums[:]]) ) @@ -1768,7 +1753,7 @@ def module_generators(self): L2 = [] for block in blocks[-1]: L2.append(IterableFunctionCall(self._block_iterator, block)) - L.append(CartesianProduct(*L2)) + L.append(itertools.product(*L2)) # Special case for the final tableau partition = base[-1] @@ -1799,10 +1784,9 @@ def module_generators(self): L2.append(IterableFunctionCall(self._block_iterator_n_odd, block)) else: L2.append(IterableFunctionCall(self._block_iterator, block)) - L.append(CartesianProduct(*L2)) + L.append(itertools.product(*L2)) - # TODO Find a more efficient method without appealing to the CartesianProduct - C = CartesianProduct(*L) + C = itertools.product(*L) for curBlocks in C: module_gens.append( self.element_class(self, KT_constructor=[shapes[:], self._blocks_to_values(curBlocks[:]), vac_nums[:]]) ) diff --git a/src/sage/combinat/root_system/__init__.py b/src/sage/combinat/root_system/__init__.py index f4cfb1b9a9a..6a9be3b1b0c 100644 --- a/src/sage/combinat/root_system/__init__.py +++ b/src/sage/combinat/root_system/__init__.py @@ -35,6 +35,7 @@ - :ref:`sage.combinat.root_system.dynkin_diagram` - :ref:`sage.combinat.root_system.cartan_matrix` - :ref:`sage.combinat.root_system.coxeter_matrix` +- :ref:`sage.combinat.root_system.coxeter_type` Root systems ------------ diff --git a/src/sage/combinat/root_system/all.py b/src/sage/combinat/root_system/all.py index f3b45cbe666..91e894eee4c 100644 --- a/src/sage/combinat/root_system/all.py +++ b/src/sage/combinat/root_system/all.py @@ -6,8 +6,9 @@ from cartan_type import CartanType from dynkin_diagram import DynkinDiagram -from cartan_matrix import CartanMatrix, cartan_matrix -from coxeter_matrix import coxeter_matrix +from cartan_matrix import CartanMatrix +from coxeter_matrix import CoxeterMatrix, coxeter_matrix +from coxeter_type import CoxeterType from root_system import RootSystem, WeylDim from weyl_group import WeylGroup, WeylGroupElement lazy_import('sage.combinat.root_system.extended_affine_weyl_group', 'ExtendedAffineWeylGroup') diff --git a/src/sage/combinat/root_system/cartan_matrix.py b/src/sage/combinat/root_system/cartan_matrix.py index 28e29fca36c..6d152d57376 100644 --- a/src/sage/combinat/root_system/cartan_matrix.py +++ b/src/sage/combinat/root_system/cartan_matrix.py @@ -35,6 +35,7 @@ from sage.combinat.root_system.cartan_type import CartanType, CartanType_abstract from sage.combinat.root_system.root_system import RootSystem from sage.sets.family import Family +from sage.graphs.digraph import DiGraph class CartanMatrix(Matrix_integer_sparse, CartanType_abstract): r""" @@ -197,9 +198,10 @@ class CartanMatrix(Matrix_integer_sparse, CartanType_abstract): __metaclass__ = InheritComparisonClasscallMetaclass @staticmethod - def __classcall_private__(cls, *args, **kwds): + def __classcall_private__(cls, data=None, index_set=None, + cartan_type=None, cartan_type_check=True): """ - Normalize input so we can inherit from spare integer matrix. + Normalize input so we can inherit from sparse integer matrix. .. NOTE:: @@ -227,30 +229,31 @@ def __classcall_private__(cls, *args, **kwds): ('a', 'b') """ # Special case with 0 args and kwds has Cartan type - if "cartan_type" in kwds and len(args) == 0: - args = (CartanType(kwds["cartan_type"]),) - if len(args) == 0: + if cartan_type is not None and data is None: + data = CartanType(cartan_type) + + if data is None: data = [] n = 0 index_set = tuple() cartan_type = None subdivisions = None - elif len(args) == 4 and isinstance(args[0], MatrixSpace): # For pickling - return typecall(cls, args[0], args[1], args[2], args[3]) - elif isinstance(args[0], CartanMatrix): - return args[0] + elif isinstance(data, CartanMatrix): + if index_set is not None: + d = {a: index_set[i] for i,a in enumerate(data.index_set())} + return data.relabel(d) + return data else: - cartan_type = None dynkin_diagram = None subdivisions = None from sage.combinat.root_system.dynkin_diagram import DynkinDiagram_class - if isinstance(args[0], DynkinDiagram_class): - dynkin_diagram = args[0] - cartan_type = args[0]._cartan_type + if isinstance(data, DynkinDiagram_class): + dynkin_diagram = data + cartan_type = data._cartan_type else: try: - cartan_type = CartanType(args[0]) + cartan_type = CartanType(data) dynkin_diagram = cartan_type.dynkin_diagram() except (TypeError, ValueError): pass @@ -258,54 +261,74 @@ def __classcall_private__(cls, *args, **kwds): if dynkin_diagram is not None: n = dynkin_diagram.rank() index_set = dynkin_diagram.index_set() - reverse = dict((index_set[i], i) for i in range(len(index_set))) + reverse = {a: i for i,a in enumerate(index_set)} data = {(i, i): 2 for i in range(n)} for (i,j,l) in dynkin_diagram.edge_iterator(): data[(reverse[j], reverse[i])] = -l else: - M = matrix(args[0]) + M = matrix(data) if not is_generalized_cartan_matrix(M): raise ValueError("the input matrix is not a generalized Cartan matrix") n = M.ncols() - if "cartan_type" in kwds: - cartan_type = CartanType(kwds["cartan_type"]) - elif n == 1: - cartan_type = CartanType(['A', 1]) - elif kwds.get("cartan_type_check", True): - cartan_type = find_cartan_type_from_matrix(M) data = M.dict() subdivisions = M._subdivisions - if len(args) == 1: - if cartan_type is not None: - index_set = tuple(cartan_type.index_set()) - elif dynkin_diagram is None: - index_set = tuple(range(n)) - elif len(args) == 2: - index_set = tuple(args[1]) - if len(index_set) != n and len(set(index_set)) != n: - raise ValueError("the given index set is not valid") + if index_set is None: + index_set = tuple(range(n)) else: - raise ValueError("too many arguments") + index_set = tuple(index_set) - mat = typecall(cls, MatrixSpace(ZZ, n, sparse=True), data, cartan_type, index_set) + if len(index_set) != n and len(set(index_set)) != n: + raise ValueError("the given index set is not valid") + + # We can do the Cartan type initialization later as this is not + # a unqiue representation + mat = typecall(cls, MatrixSpace(ZZ, n, sparse=True), data, False, False) + # FIXME: We have to initialize the CartanMatrix part separately because + # of the __cinit__ of the matrix. We should get rid of this workaround + mat._CM_init(cartan_type, index_set, cartan_type_check) mat._subdivisions = subdivisions return mat - def __init__(self, parent, data, cartan_type, index_set): + def _CM_init(self, cartan_type, index_set, cartan_type_check): """ - Initialize ``self``. + Initialize ``self`` as a Cartan matrix. TESTS:: - sage: C = CartanMatrix(['A',1,1]) + sage: C = CartanMatrix(['A',1,1]) # indirect doctest sage: TestSuite(C).run(skip=["_test_category", "_test_change_ring"]) """ - Matrix_integer_sparse.__init__(self, parent, data, False, False) - self._cartan_type = cartan_type self._index_set = index_set self.set_immutable() + if cartan_type is not None: + cartan_type = CartanType(cartan_type) + elif self.nrows() == 1: + cartan_type = CartanType(['A', 1]) + elif cartan_type_check: + # Placeholder so we don't have to reimplement creating a + # Dynkin diagram from a Cartan matrix + self._cartan_type = None + cartan_type = find_cartan_type_from_matrix(self) + + self._cartan_type = cartan_type + + def __reduce__(self): + """ + Used for pickling. + + TESTS:: + + sage: CM = CartanMatrix(['A',4]) + sage: x = loads(dumps(CM)) + sage: x._index_set + (1, 2, 3, 4) + """ + if self._cartan_type: + return (CartanMatrix, (self._cartan_type,)) + return (CartanMatrix, (self.dynkin_diagram(),)) + def root_system(self): """ Return the root system corresponding to ``self``. @@ -462,14 +485,17 @@ def subtype(self, index_set): EXAMPLES:: sage: C = CartanMatrix(['F',4]) - sage: C.subtype([1,2,3]) + sage: S = C.subtype([1,2,3]) + sage: S [ 2 -1 0] [-1 2 -1] [ 0 -2 2] + sage: S.index_set() + (1, 2, 3) """ ind = self.index_set() I = [ind.index(i) for i in index_set] - return CartanMatrix(self.matrix_from_rows_and_columns(I, I)) + return CartanMatrix(self.matrix_from_rows_and_columns(I, I), index_set) def rank(self): r""" @@ -891,88 +917,84 @@ def is_generalized_cartan_matrix(M): return True def find_cartan_type_from_matrix(CM): - """ - Find a Cartan type by direct comparison of matrices given from the - generalized Cartan matrix ``CM`` and return ``None`` if not found. + r""" + Find a Cartan type by direct comparison of Dynkin diagrams given from + the generalized Cartan matrix ``CM`` and return ``None`` if not found. INPUT: - - ``CM`` -- A generalized Cartan matrix + - ``CM`` -- a generalized Cartan matrix EXAMPLES:: sage: from sage.combinat.root_system.cartan_matrix import find_cartan_type_from_matrix - sage: M = matrix([[2,-1,-1], [-1,2,-1], [-1,-1,2]]) - sage: find_cartan_type_from_matrix(M) + sage: CM = CartanMatrix([[2,-1,-1], [-1,2,-1], [-1,-1,2]]) + sage: find_cartan_type_from_matrix(CM) ['A', 2, 1] - sage: M = matrix([[2,-1,0], [-1,2,-2], [0,-1,2]]) - sage: find_cartan_type_from_matrix(M) - ['C', 3] - sage: M = matrix([[2,-1,-2], [-1,2,-1], [-2,-1,2]]) - sage: find_cartan_type_from_matrix(M) + sage: CM = CartanMatrix([[2,-1,0], [-1,2,-2], [0,-1,2]]) + sage: find_cartan_type_from_matrix(CM) + ['C', 3] relabelled by {1: 0, 2: 1, 3: 2} + sage: CM = CartanMatrix([[2,-1,-2], [-1,2,-1], [-2,-1,2]]) + sage: find_cartan_type_from_matrix(CM) """ - n = CM.ncols() - # Build the list to test based upon rank - if n == 1: - return CartanType(['A', 1]) - - test = [['A', n]] - if n >= 2: - if n == 2: - test += [['G',2], ['A',2,2]] - test += [['B',n], ['A',n-1,1]] - if n >= 3: - if n == 3: - test += [['G',2,1], ['D',4,3]] - test += [['C',n], ['BC',n-1,2], ['C',n-1,1]] - if n >= 4: - if n == 4: - test += [['F',4], ['G',2,1], ['D',4,3]] - test += [['D',n], ['B',n-1,1]] - if n == 5: - test += [['F',4,1], ['D',n-1,1]] - elif n == 6: - test.append(['E',6]) - elif n == 7: - test += [['E',7], ['E',6,1]] - elif n == 8: - test += [['E',8], ['E',7,1]] - elif n == 9: - test.append(['E',8,1]) - - # Test every possible Cartan type and its dual - for x in test: - ct = CartanType(x) - if ct.cartan_matrix() == CM: - return ct - if ct == ct.dual(): + types = [] + for S in CM.dynkin_diagram().connected_components_subgraphs(): + S = DiGraph(S) # We need a simple digraph here + n = S.num_verts() + # Build the list to test based upon rank + if n == 1: + types.append(CartanType(['A', 1])) continue - ct = ct.dual() - if ct.cartan_matrix() == CM: - return ct - return None - -def cartan_matrix(t): - """ - Return the Cartan matrix of type `t`. - - .. NOTE:: - - This function is deprecated in favor of - ``CartanMatrix(...)``, to avoid polluting the - global namespace. - EXAMPLES:: - - sage: cartan_matrix(['A', 4]) - doctest:...: DeprecationWarning: cartan_matrix() is deprecated. Use CartanMatrix() instead - See http://trac.sagemath.org/14137 for details. - [ 2 -1 0 0] - [-1 2 -1 0] - [ 0 -1 2 -1] - [ 0 0 -1 2] - """ - from sage.misc.superseded import deprecation - deprecation(14137, 'cartan_matrix() is deprecated. Use CartanMatrix() instead') - return CartanMatrix(t) + test = [['A', n]] + if n >= 2: + if n == 2: + test += [['G',2], ['A',2,2]] + test += [['B',n], ['A',n-1,1]] + if n >= 3: + if n == 3: + test.append(['G',2,1]) + test += [['C',n], ['BC',n-1,2], ['C',n-1,1]] + if n >= 4: + if n == 4: + test.append(['F',4]) + test += [['D',n], ['B',n-1,1]] + if n >= 5: + if n == 5: + test.append(['F',4,1]) + test.append(['D',n-1,1]) + if n == 6: + test.append(['E',6]) + elif n == 7: + test += [['E',7], ['E',6,1]] + elif n == 8: + test += [['E',8], ['E',7,1]] + elif n == 9: + test.append(['E',8,1]) + + # Test every possible Cartan type and its dual + found = False + for x in test: + ct = CartanType(x) + T = DiGraph(ct.dynkin_diagram()) # We need a simple digraph here + iso, match = T.is_isomorphic(S, certify=True, edge_labels=True) + if iso: + types.append(ct.relabel(match)) + found = True + break + + if ct == ct.dual(): + continue # self-dual, so nothing more to test + + ct = ct.dual() + T = DiGraph(ct.dynkin_diagram()) # We need a simple digraph here + iso, match = T.is_isomorphic(S, certify=True, edge_labels=True) + if iso: + types.append(ct.relabel(match)) + found = True + break + if not found: + return None + + return CartanType(types) diff --git a/src/sage/combinat/root_system/cartan_type.py b/src/sage/combinat/root_system/cartan_type.py index 8514ccec51c..17fbc7cd24c 100644 --- a/src/sage/combinat/root_system/cartan_type.py +++ b/src/sage/combinat/root_system/cartan_type.py @@ -279,9 +279,9 @@ .. rubric:: Affine Cartan types -For affine types, we use the usual conventions for affine Coxeter groups: each affine type -is either untwisted (that is arise from the natural affinisation -of a finite Cartan type):: +For affine types, we use the usual conventions for affine Coxeter +groups: each affine type is either untwisted (that is arise from the +natural affinisation of a finite Cartan type):: sage: CartanType(["A", 4, 1]).dynkin_diagram() 0 @@ -451,9 +451,7 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.global_options import GlobalOptions from sage.sets.family import Family -from sage.misc.superseded import deprecated_function_alias from sage.misc.decorators import rename_keyword -from sage.misc.misc import union from __builtin__ import sorted # TODO: @@ -605,6 +603,16 @@ def __call__(self, *args): True sage: CartanType('A2') ['A', 2] + + Check that we can pass any Cartan type as a single element list:: + + sage: CT = CartanType(['A2', 'A2', 'A2']) + sage: CartanType([CT]) + A2xA2xA2 + + sage: CT = CartanType('A2').relabel({1:-1, 2:-2}) + sage: CartanType([CT]) + ['A', 2] relabelled by {1: -1, 2: -2} """ if len(args) == 1: t = args[0] @@ -618,6 +626,10 @@ def __call__(self, *args): if len(t) == 1: # Fix for trac #13774 t = t[0] + # We need to make another check + if isinstance(t, CartanType_abstract): + return t + if isinstance(t, str): if "x" in t: import type_reducible @@ -1085,16 +1097,20 @@ def coxeter_matrix(self): [2 3 1 3] [2 2 3 1] """ - from sage.matrix.constructor import matrix - from sage.rings.all import ZZ - index_set = self.index_set() - reverse = dict((index_set[i], i) for i in range(len(index_set))) - m = matrix(ZZ,len(index_set), lambda i,j: 1 if i==j else 2) - for (i,j,l) in self.coxeter_diagram().edge_iterator(): - m[reverse[i], reverse[j]] = l - m[reverse[j], reverse[i]] = l - m.set_immutable() - return m + from sage.combinat.root_system.coxeter_matrix import CoxeterMatrix + return CoxeterMatrix(self) + + def coxeter_type(self): + """ + Return the Coxeter type for ``self``. + + EXAMPLES:: + + sage: CartanType(['A', 4]).coxeter_type() + Coxeter type of ['A', 4] + """ + from sage.combinat.root_system.coxeter_type import CoxeterType + return CoxeterType(self) def dual(self): """ @@ -1305,20 +1321,9 @@ def is_crystallographic(self): [['E', 6], True], [['E', 7], True], [['E', 8], True], [['F', 4], True], [['G', 2], True], [['I', 5], False], [['H', 3], False], [['H', 4], False]] - - TESTS:: - - sage: all(t.is_crystallographic() for t in CartanType.samples(affine=True)) - True - sage: t = CartanType(['A',3]); t.is_crystalographic() - doctest:...: DeprecationWarning: is_crystalographic is deprecated. Please use is_crystallographic instead. - See http://trac.sagemath.org/14673 for details. - True """ return False - is_crystalographic = deprecated_function_alias(14673, is_crystallographic) - def is_simply_laced(self): """ Return whether this Cartan type is simply laced. @@ -1597,18 +1602,9 @@ def is_crystallographic(self): sage: CartanType(['A', 3, 1]).is_crystallographic() True - - TESTS:: - - sage: t = CartanType(['A',3]); t.is_crystalographic() - doctest:...: DeprecationWarning: is_crystalographic is deprecated. Please use is_crystallographic instead. - See http://trac.sagemath.org/14673 for details. - True """ return True - is_crystalographic = deprecated_function_alias(14673, is_crystallographic) - @cached_method def symmetrizer(self): """ diff --git a/src/sage/combinat/root_system/coxeter_group.py b/src/sage/combinat/root_system/coxeter_group.py index 015f2c1abcb..b1d4aa5b2a6 100644 --- a/src/sage/combinat/root_system/coxeter_group.py +++ b/src/sage/combinat/root_system/coxeter_group.py @@ -16,7 +16,6 @@ from sage.groups.perm_gps.permgroup_element import PermutationGroupElement from sage.combinat.root_system.weyl_group import WeylGroup from sage.structure.unique_representation import UniqueRepresentation -from sage.structure.parent import Parent from sage.combinat.root_system.cartan_type import CartanType from sage.groups.perm_gps.permgroup import PermutationGroup_generic diff --git a/src/sage/combinat/root_system/coxeter_matrix.py b/src/sage/combinat/root_system/coxeter_matrix.py index 499f861f5a8..7344c72e159 100644 --- a/src/sage/combinat/root_system/coxeter_matrix.py +++ b/src/sage/combinat/root_system/coxeter_matrix.py @@ -1,8 +1,10 @@ """ -Coxeter matrices +Coxeter Matrices """ #***************************************************************************** # Copyright (C) 2007 Mike Hansen , +# 2015 Travis Scrimshaw +# 2015 Jean-Philippe Labbe # # Distributed under the terms of the GNU General Public License (GPL) # @@ -15,64 +17,63 @@ # # http://www.gnu.org/licenses/ #***************************************************************************** -from cartan_type import CartanType -from sage.matrix.all import MatrixSpace -from sage.rings.all import ZZ -def coxeter_matrix_as_function(t): - """ - Returns the Coxeter matrix, as a function +from sage.misc.cachefunc import cached_method +from sage.matrix.constructor import matrix +from sage.matrix.matrix_space import MatrixSpace +from sage.misc.classcall_metaclass import ClasscallMetaclass, typecall +from sage.matrix.matrix_generic_dense import Matrix_generic_dense +from sage.graphs.graph import Graph +from sage.rings.all import ZZ, QQ, RR +from sage.rings.infinity import infinity +from sage.combinat.root_system.cartan_type import CartanType +from sage.combinat.root_system.coxeter_type import CoxeterType - INPUT: +class CoxeterMatrix(CoxeterType): + r""" + A Coxeter matrix. - - ``t`` -- a Cartan type + A Coxeter matrix `M = (m_{ij})_{i,j \in I}` is a matrix encoding + a Coxeter system `(W, S)`, where the relations are given by + `(s_i s_j)^{m_{ij}}`. Thus `M` is symmetric and has entries + in `\{1, 2, 3, \ldots, \infty\}` with `m_{ij} = 1` if and only + if `i = j`. - EXAMPLES:: + We represent `m_{ij} = \infty` by any number `m_{ij} \leq -1`. In + particular, we can construct a bilinear form `B = (b_{ij})_{i,j \in I}` + from `M` by - sage: from sage.combinat.root_system.coxeter_matrix import coxeter_matrix_as_function - sage: f = coxeter_matrix_as_function(['A',4]) - sage: matrix([[f(i,j) for j in range(1,5)] for i in range(1,5)]) - [1 3 2 2] - [3 1 3 2] - [2 3 1 3] - [2 2 3 1] - """ - t = CartanType(t) - m = t.coxeter_matrix() - index_set = t.index_set() - reverse = dict((index_set[i], i) for i in range(len(index_set))) - return lambda i,j: m[reverse[i], reverse[j]] + .. MATH:: -def coxeter_matrix(t): - """ - Returns the Coxeter matrix of type t. + b_{ij} = \begin{cases} + m_{ij} & m_{ij} < 0\ (\text{i.e., } m_{ij} = \infty), \\ + -\cos\left( \frac{\pi}{m_{ij}} \right) & \text{otherwise}. + \end{cases} EXAMPLES:: - sage: coxeter_matrix(['A', 4]) + sage: CoxeterMatrix(['A', 4]) [1 3 2 2] [3 1 3 2] [2 3 1 3] [2 2 3 1] - sage: coxeter_matrix(['B', 4]) + sage: CoxeterMatrix(['B', 4]) [1 3 2 2] [3 1 3 2] [2 3 1 4] [2 2 4 1] - sage: coxeter_matrix(['C', 4]) + sage: CoxeterMatrix(['C', 4]) [1 3 2 2] [3 1 3 2] [2 3 1 4] [2 2 4 1] - sage: coxeter_matrix(['D', 4]) + sage: CoxeterMatrix(['D', 4]) [1 3 2 2] [3 1 3 3] [2 3 1 2] [2 3 2 1] - :: - - sage: coxeter_matrix(['E', 6]) + sage: CoxeterMatrix(['E', 6]) [1 2 3 2 2 2] [2 1 2 3 2 2] [3 2 1 3 2 2] @@ -80,18 +81,1120 @@ def coxeter_matrix(t): [2 2 2 3 1 3] [2 2 2 2 3 1] - :: - - sage: coxeter_matrix(['F', 4]) + sage: CoxeterMatrix(['F', 4]) [1 3 2 2] [3 1 4 2] [2 4 1 3] [2 2 3 1] - :: - - sage: coxeter_matrix(['G', 2]) + sage: CoxeterMatrix(['G', 2]) [1 6] [6 1] + + By default, entries representing `\infty` are given by `-1` + in the Coxeter matrix:: + + sage: G = Graph([(0,1,None), (1,2,4), (0,2,oo)]) + sage: CoxeterMatrix(G) + [ 1 3 -1] + [ 3 1 4] + [-1 4 1] + + It is possible to give a number `\leq -1` to represent an infinite label:: + + sage: CoxeterMatrix([[1,-1],[-1,1]]) + [ 1 -1] + [-1 1] + sage: CoxeterMatrix([[1,-3/2],[-3/2,1]]) + [ 1 -3/2] + [-3/2 1] + """ + __metaclass__ = ClasscallMetaclass + + @staticmethod + def __classcall_private__(cls, data=None, index_set=None, coxeter_type=None, + cartan_type=None, coxeter_type_check=True): + r""" + A Coxeter matrix can we created via a graph, a Coxeter type, or + a matrix. + + .. NOTE:: + + To disable the Coxeter type check, use the optional argument + ``coxeter_type_check = False``. + + EXAMPLES:: + + sage: C = CoxeterMatrix(['A',1,1],['a','b']) + sage: C2 = CoxeterMatrix([[1, -1], [-1, 1]]) + sage: C3 = CoxeterMatrix(matrix([[1, -1], [-1, 1]]), [0, 1]) + sage: C == C2 and C == C3 + True + + Check with `\infty` because of the hack of using `-1` to represent + `\infty` in the Coxeter matrix:: + + sage: G = Graph([(0, 1, 3), (1, 2, oo)]) + sage: W1 = CoxeterMatrix([[1, 3, 2], [3, 1, -1], [2, -1, 1]]) + sage: W2 = CoxeterMatrix(G) + sage: W1 == W2 + True + sage: CoxeterMatrix(W1.coxeter_graph()) == W1 + True + + The base ring of the matrix depends on the entries given:: + + sage: CoxeterMatrix([[1,-1],[-1,1]])._matrix.base_ring() + Integer Ring + sage: CoxeterMatrix([[1,-3/2],[-3/2,1]])._matrix.base_ring() + Rational Field + sage: CoxeterMatrix([[1,-1.5],[-1.5,1]])._matrix.base_ring() + Real Field with 53 bits of precision + """ + if not data: + if coxeter_type: + data = CoxeterType(coxeter_type) + elif cartan_type: + data = CoxeterType(CartanType(cartan_type)) + + # Special cases with no arguments passed + if not data: + data = [] + n = 0 + index_set = tuple() + coxeter_type = None + base_ring = ZZ + mat = typecall(cls, MatrixSpace(base_ring, n, sparse=False), data, coxeter_type, index_set) + mat._subdivisions = None + + return mat + + if isinstance(data, CoxeterMatrix): # Initiate from itself + return data + + # Initiate from a graph: + # TODO: Check if a CoxeterDiagram once implemented + if isinstance(data, Graph): + return cls._from_graph(data, coxeter_type_check) + + # Get the Coxeter type + coxeter_type = None + from sage.combinat.root_system.cartan_type import CartanType_abstract + if isinstance(data, CartanType_abstract): + coxeter_type = data.coxeter_type() + else: + try: + coxeter_type = CoxeterType(data) + except (TypeError, ValueError, NotImplementedError): + pass + + # Initiate from a Coxeter type + if coxeter_type: + return cls._from_coxetertype(coxeter_type) + + # TODO:: remove when oo is possible in matrices. + n = len(data[0]) + data = [x if x != infinity else -1 for r in data for x in r] + data = matrix(n, n, data) + # until here + + # Get the index set + if index_set: + index_set = tuple(index_set) + else: + index_set = tuple(range(1,n+1)) + if len(set(index_set)) != n: + raise ValueError("the given index set is not valid") + + return cls._from_matrix(data, coxeter_type, index_set, coxeter_type_check) + + def __init__(self, parent, data, coxeter_type, index_set): + """ + Initialize ``self``. + + TESTS:: + + sage: C = CoxeterMatrix(['A', 2, 1]) + sage: TestSuite(C).run(skip=["_test_category", "_test_change_ring"]) + """ + self._matrix = Matrix_generic_dense(parent, data, False, True) + self._matrix.set_immutable() + + if self._matrix.base_ring() not in [ZZ, QQ]: + self._is_cyclotomic = False + else: + self._is_cyclotomic = True + self._coxeter_type = coxeter_type + + if self._coxeter_type is not None: + if self._coxeter_type.is_finite(): + self._is_finite = True + self._is_affine = False + elif self._coxeter_type.is_affine(): + self._is_finite = False + self._is_affine = True + else: + self._is_finite = False + self._is_affine = False + else: + self._is_finite = False + self._is_affine = False + + self._index_set = index_set + self._rank = self._matrix.nrows() + + self._dict = {(self._index_set[i], self._index_set[j]): self._matrix[i, j] + for i in range(self._rank) for j in range(self._rank)} + + for i,key in enumerate(self._index_set): + self._dict[key] = {key2: self._matrix[i,j] + for j,key2 in enumerate(self._index_set)} + + @classmethod + def _from_matrix(cls, data, coxeter_type, index_set, coxeter_type_check): + """ + Initiate the Coxeter matrix from a matrix. + + TESTS:: + + sage: CM = CoxeterMatrix([[1,2],[2,1]]); CM + [1 2] + [2 1] + sage: CM = CoxeterMatrix([[1,-1],[-1,1]]); CM + [ 1 -1] + [-1 1] + sage: CM = CoxeterMatrix([[1,-1.5],[-1.5,1]]); CM + [ 1.00000000000000 -1.50000000000000] + [-1.50000000000000 1.00000000000000] + sage: CM = CoxeterMatrix([[1,-3/2],[-3/2,1]]); CM + [ 1 -3/2] + [-3/2 1] + sage: CM = CoxeterMatrix([[1,-3/2,5],[-3/2,1,-1],[5,-1,1]]); CM + [ 1 -3/2 5] + [-3/2 1 -1] + [ 5 -1 1] + sage: CM = CoxeterMatrix([[1,-3/2,5],[-3/2,1,oo],[5,oo,1]]); CM + [ 1 -3/2 5] + [-3/2 1 -1] + [ 5 -1 1] + """ + # Check that the data is valid + check_coxeter_matrix(data) + + M = matrix(data) + n = M.ncols() + + base_ring = M.base_ring() + + if not coxeter_type: + if n == 1: + coxeter_type = CoxeterType(['A', 1]) + elif coxeter_type_check: + coxeter_type = recognize_coxeter_type_from_matrix(M, index_set) + else: + coxeter_type = None + + raw_data = M.list() + + mat = typecall(cls, MatrixSpace(base_ring, n, sparse=False), raw_data, + coxeter_type, index_set) + mat._subdivisions = M._subdivisions + + return mat + + @classmethod + def _from_graph(cls, graph, coxeter_type_check): + """ + Initiate the Coxeter matrix from a graph. + + TESTS:: + + sage: CoxeterMatrix(CoxeterMatrix(['A',4,1]).coxeter_graph()) + [1 3 2 2 3] + [3 1 3 2 2] + [2 3 1 3 2] + [2 2 3 1 3] + [3 2 2 3 1] + sage: CoxeterMatrix(CoxeterMatrix(['B',4,1]).coxeter_graph()) + [1 2 3 2 2] + [2 1 3 2 2] + [3 3 1 3 2] + [2 2 3 1 4] + [2 2 2 4 1] + sage: CoxeterMatrix(CoxeterMatrix(['F',4]).coxeter_graph()) + [1 3 2 2] + [3 1 4 2] + [2 4 1 3] + [2 2 3 1] + + sage: G=Graph() + sage: G.add_edge([0,1,oo]) + sage: CoxeterMatrix(G) + [ 1 -1] + [-1 1] + sage: H = Graph() + sage: H.add_edge([0,1,-1.5]) + sage: CoxeterMatrix(H) + [ 1.00000000000000 -1.50000000000000] + [-1.50000000000000 1.00000000000000] + """ + verts = sorted(graph.vertices()) + index_set = tuple(verts) + n = len(index_set) + + # Setup the basis matrix as all 2 except 1 on the diagonal + data = [] + for i in range(n): + data += [[]] + for j in range(n): + if i == j: + data[-1] += [ZZ.one()] + else: + data[-1] += [2] + + for e in graph.edges(): + label = e[2] + if label is None: + label = 3 + elif label == infinity: + label = -1 + elif label not in ZZ and label > -1: + raise ValueError("invalid Coxeter graph label") + elif label == 0 or label == 1: + raise ValueError("invalid Coxeter graph label") + i = verts.index(e[0]) + j = verts.index(e[1]) + data[j][i] = data[i][j] = label + + return cls._from_matrix(data, None, index_set, coxeter_type_check) + + @classmethod + def _from_coxetertype(cls, coxeter_type): + """ + Initiate the Coxeter matrix from a Coxeter type. + + TESTS:: + + sage: CoxeterMatrix(['A',4]).coxeter_type() + Coxeter type of ['A', 4] + sage: CoxeterMatrix(['A',4,1]).coxeter_type() + Coxeter type of ['A', 4, 1] + sage: CoxeterMatrix(['D',4,1]).coxeter_type() + Coxeter type of ['D', 4, 1] + """ + index_set = coxeter_type.index_set() + n = len(index_set) + reverse = {index_set[i]: i for i in range(n)} + data = [[1 if i == j else 2 for j in range(n)] for i in range(n)] + for (i, j, l) in coxeter_type.coxeter_graph().edge_iterator(): + if l == infinity: + l = -1 + data[reverse[i]][reverse[j]] = l + data[reverse[j]][reverse[i]] = l + + return cls._from_matrix(data, coxeter_type, index_set, False) + + @classmethod + def samples(self, finite=None, affine=None, crystallographic=None, higher_rank=None): + """ + Return a sample of the available Coxeter types. + + INPUT: + + - ``finite`` -- (default: ``None``) a boolean or ``None`` + + - ``affine`` -- (default: ``None``) a boolean or ``None`` + + - ``crystallographic`` -- (default: ``None``) a boolean or ``None`` + + - ``higher_rank`` -- (default: ``None``) a boolean or ``None`` + + The sample contains all the exceptional finite and affine + Coxeter types, as well as typical representatives of the + infinite families. + + Here the ``higher_rank`` term denotes non-finite, non-affine, + Coxeter groups (including hyperbolic types). + + .. TODO:: Implement the hyperbolic and compact hyperbolic in the samples. + + EXAMPLES:: + + sage: [CM.coxeter_type() for CM in CoxeterMatrix.samples()] + [ + Coxeter type of ['A', 1], Coxeter type of ['A', 5], + + Coxeter type of ['B', 5], Coxeter type of ['D', 4], + + Coxeter type of ['D', 5], Coxeter type of ['E', 6], + + Coxeter type of ['E', 7], Coxeter type of ['E', 8], + + Coxeter type of ['F', 4], Coxeter type of ['H', 3], + + Coxeter type of ['H', 4], Coxeter type of ['I', 10], + + Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + + [ 1 -1 -1] + [-1 1 -1] + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1], [-1 -1 1], + + [ 1 -2 3 2] + [1 2 3] [-2 1 2 3] + [2 1 7] [ 3 2 1 -8] + [3 7 1], [ 2 3 -8 1] + ] + + The finite, affine and crystallographic options allow + respectively for restricting to (non) finite, (non) affine, + and (non) crystallographic Cartan types:: + + sage: [CM.coxeter_type() for CM in CoxeterMatrix.samples(finite=True)] + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 5], Coxeter type of ['D', 4], + Coxeter type of ['D', 5], Coxeter type of ['E', 6], + Coxeter type of ['E', 7], Coxeter type of ['E', 8], + Coxeter type of ['F', 4], Coxeter type of ['H', 3], + Coxeter type of ['H', 4], Coxeter type of ['I', 10]] + + sage: [CM.coxeter_type() for CM in CoxeterMatrix.samples(affine=True)] + [Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1]] + + sage: [CM.coxeter_type() for CM in CoxeterMatrix.samples(crystallographic=True)] + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 5], Coxeter type of ['D', 4], + Coxeter type of ['D', 5], Coxeter type of ['E', 6], + Coxeter type of ['E', 7], Coxeter type of ['E', 8], + Coxeter type of ['F', 4], Coxeter type of ['A', 2, 1], + Coxeter type of ['B', 5, 1], Coxeter type of ['C', 5, 1], + Coxeter type of ['D', 5, 1], Coxeter type of ['E', 6, 1], + Coxeter type of ['E', 7, 1], Coxeter type of ['E', 8, 1], + Coxeter type of ['F', 4, 1], Coxeter type of ['G', 2, 1]] + + sage: CoxeterMatrix.samples(crystallographic=False) + [ + [1 3 2 2] + [1 3 2] [3 1 3 2] [ 1 -1 -1] [1 2 3] + [3 1 5] [2 3 1 5] [ 1 10] [ 1 -1] [-1 1 -1] [2 1 7] + [2 5 1], [2 2 5 1], [10 1], [-1 1], [-1 -1 1], [3 7 1], + + [ 1 -2 3 2] + [-2 1 2 3] + [ 3 2 1 -8] + [ 2 3 -8 1] + ] + + .. TODO:: add some reducible Coxeter types (suggestions?) + + TESTS:: + + sage: for ct in CoxeterMatrix.samples(): TestSuite(ct).run() + """ + result = self._samples() + if crystallographic is not None: + result = [t for t in result if t.is_crystallographic() == crystallographic] + if finite is not None: + result = [t for t in result if t.is_finite() == finite] + if affine is not None: + result = [t for t in result if t.is_affine() == affine] + if higher_rank is not None: + result = [t for t in result if not t.is_affine() and not t.is_finite()] + return result + + @cached_method + def _samples(self): + """ + Return a sample of all implemented Coxeter types. + + .. NOTE:: This is intended to be used through :meth:`samples`. + + EXAMPLES:: + + sage: [CM.coxeter_type() for CM in CoxeterMatrix._samples()] + [ + Coxeter type of ['A', 1], Coxeter type of ['A', 5], + + Coxeter type of ['B', 5], Coxeter type of ['D', 4], + + Coxeter type of ['D', 5], Coxeter type of ['E', 6], + + Coxeter type of ['E', 7], Coxeter type of ['E', 8], + + Coxeter type of ['F', 4], Coxeter type of ['H', 3], + + Coxeter type of ['H', 4], Coxeter type of ['I', 10], + + Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + + [ 1 -1 -1] + [-1 1 -1] + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1], [-1 -1 1], + + [ 1 -2 3 2] + [1 2 3] [-2 1 2 3] + [2 1 7] [ 3 2 1 -8] + [3 7 1], [ 2 3 -8 1] + ] + """ + finite = [CoxeterMatrix(t) for t in [['A', 1], ['A', 5], ['B', 5], + ['D', 4], ['D', 5], ['E', 6], ['E', 7], + ['E', 8], ['F', 4], ['H', 3], ['H', 4], + ['I', 10]]] + + affine = [CoxeterMatrix(t) for t in [['A', 2, 1], ['B', 5, 1], + ['C', 5, 1], ['D', 5, 1], ['E', 6, 1], + ['E', 7, 1], ['E', 8, 1], ['F', 4, 1], + ['G', 2, 1], ['A', 1, 1]]] + + higher_matrices = [[[1, -1, -1], [-1, 1, -1], [-1, -1, 1]], + [[1, 2, 3], [2, 1, 7], [3, 7, 1]], + [[1, -2, 3, 2], [-2, 1, 2, 3], [3, 2, 1, -8], [2, 3, -8, 1]]] + + higher = [CoxeterMatrix(m) for m in higher_matrices] + + return finite + affine + higher + + def relabel(self, relabelling): + """ + Return a relabelled copy of this Coxeter matrix. + + INPUT: + + - ``relabelling`` -- a function (or dictionary) + + OUTPUT: + + an isomorphic Coxeter type obtained by relabelling the nodes of + the Coxeter graph. Namely, the node with label ``i`` is + relabelled ``f(i)`` (or, by ``f[i]`` if ``f`` is a dictionary). + + EXAMPLES:: + + sage: CoxeterMatrix(['F',4]).relabel({ 1:2, 2:3, 3:4, 4:1}) + [1 4 2 3] + [4 1 3 2] + [2 3 1 2] + [3 2 2 1] + sage: CoxeterMatrix(['F',4]).relabel(lambda x: x+1 if x<4 else 1) + [1 4 2 3] + [4 1 3 2] + [2 3 1 2] + [3 2 2 1] + """ + if isinstance(relabelling, dict): + data = [[self[relabelling[i]][relabelling[j]] + for j in self.index_set()] for i in self.index_set()] + else: + data = [[self[relabelling(i)][relabelling(j)] + for j in self.index_set()] for i in self.index_set()] + + return CoxeterMatrix(data) + + def __reduce__(self): + """ + Used for pickling. + + TESTS:: + + sage: C = CoxeterMatrix(['A',4]) + sage: M = loads(dumps(C)) + sage: M._index_set + (1, 2, 3, 4) + """ + if self._coxeter_type: + return (CoxeterMatrix, (self._coxeter_type,)) + return (CoxeterMatrix, (self._matrix, self._index_set)) + + def _repr_(self): + """ + String representation of the Coxeter matrix. + + EXAMPLES:: + + sage: CM = CoxeterMatrix(['A',3]); CM + [1 3 2] + [3 1 3] + [2 3 1] + sage: CM = CoxeterMatrix([[1,-3/2],[-3/2,1]]); CM + [ 1 -3/2] + [-3/2 1] + """ + return self._matrix.__repr__() + + def _repr_option(self, key): + """ + Metadata about the :meth:`_repr_` output. + + See :meth:`sage.structure.parent._repr_option` for details. + + EXAMPLES:: + + sage: CM = CoxeterMatrix(['A',3]) + sage: CM._repr_option('ascii_art') + True + """ + if key == 'ascii_art' or key == 'element_ascii_art': + return self._matrix.nrows() > 1 + return super(CoxeterMatrix, self)._repr_option(key) + + def _latex_(self): + r""" + Latex representation of the Coxeter matrix. + + EXAMPLES:: + + sage: CM = CoxeterMatrix(['A',3]) + sage: latex(CM) + \left(\begin{array}{rrr} + 1 & 3 & 2 \\ + 3 & 1 & 3 \\ + 2 & 3 & 1 + \end{array}\right) + """ + return self._matrix._latex_() + + + def __iter__(self): + """ + Return an iterator for the rows of the Coxeter matrix. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,8],[8,1]]) + sage: CM.__iter__().next() + (1, 8) + """ + return self._matrix.__iter__() + + def __getitem__(self, key): + """ + Return a dictionary of labels adjacent to a node or + the label of an edge in the Coxeter graph. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,-2],[-2,1]]) + sage: CM = CoxeterMatrix([[1,-2],[-2,1]], ['a','b']) + sage: CM['a'] + {'a': 1, 'b': -2} + sage: CM['b'] + {'a': -2, 'b': 1} + sage: CM['a','b'] + -2 + sage: CM['a','a'] + 1 + """ + return self._dict[key] + + def __hash__(self): + r""" + Return hash of the Coxeter matrix. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,-2],[-2,1]],['a','b']) + sage: CM.__hash__() + 1 + sage: CM = CoxeterMatrix([[1,-3],[-3,1]],['1','2']) + sage: CM.__hash__() + 4 + """ + return self._matrix.__hash__() + + def __eq__(self, other): + r""" + Return if ``self`` and ``other`` are equal, ``False`` otherwise. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,-2],[-2,1]],['a','b']) + sage: CM.__hash__() + 1 + sage: CM = CoxeterMatrix([[1,-3],[-3,1]],['1','2']) + sage: CM.__hash__() + 4 + """ + return self._matrix.__eq__(other._matrix) + + def _matrix_(self, R=None): + """ + Return ``self`` as a matrix over the ring ``R``. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,-3],[-3,1]]) + sage: matrix(CM) + [ 1 -3] + [-3 1] + sage: matrix(CM,RR) + [ 1.00000000000000 -3.00000000000000] + [-3.00000000000000 1.00000000000000] + """ + if R is not None: + return self._matrix.change_ring(R) + else: + return self._matrix + + ########################################################################## + # Coxeter type methods + + def index_set(self): + """ + Return the index set of ``self``. + + EXAMPLES:: + + sage: C = CoxeterMatrix(['A',1,1]) + sage: C.index_set() + (0, 1) + sage: C = CoxeterMatrix(['E',6]) + sage: C.index_set() + (1, 2, 3, 4, 5, 6) + """ + return self._index_set + + def coxeter_type(self): + """ + Return the Coxeter type of ``self`` or ``self`` if unknown. + + EXAMPLES:: + + sage: C = CoxeterMatrix(['A',4,1]) + sage: C.coxeter_type() + Coxeter type of ['A', 4, 1] + + If the Coxeter type is unknown:: + + sage: C = CoxeterMatrix([[1,3,4], [3,1,-1], [4,-1,1]]) + sage: C.coxeter_type() + [ 1 3 4] + [ 3 1 -1] + [ 4 -1 1] + """ + if self._coxeter_type is None: + return self + return self._coxeter_type + + def rank(self): + r""" + Return the rank of ``self``. + + EXAMPLES:: + + sage: CoxeterMatrix(['C',3]).rank() + 3 + sage: CoxeterMatrix(["A2","B2","F4"]).rank() + 8 + """ + return self._rank + + def coxeter_matrix(self): + r""" + Return the Coxeter matrix of ``self``. + + EXAMPLES:: + + sage: CoxeterMatrix(['C',3]).coxeter_matrix() + [1 3 2] + [3 1 4] + [2 4 1] + """ + return self + + def bilinear_form(self): + r""" + Return the bilinear form of ``self``. + + EXAMPLES:: + + sage: CoxeterType(['A', 2, 1]).bilinear_form() + [ 1 -1/2 -1/2] + [-1/2 1 -1/2] + [-1/2 -1/2 1] + sage: CoxeterType(['H', 3]).bilinear_form() + [ 1 -1/2 0] + [ -1/2 1 1/2*E(5)^2 + 1/2*E(5)^3] + [ 0 1/2*E(5)^2 + 1/2*E(5)^3 1] + sage: C = CoxeterMatrix([[1,-1,-1],[-1,1,-1],[-1,-1,1]]) + sage: C.bilinear_form() + [ 1 -1 -1] + [-1 1 -1] + [-1 -1 1] + """ + return CoxeterType.bilinear_form(self) + + @cached_method + def coxeter_graph(self): + """ + Return the Coxeter graph of ``self``. + + EXAMPLES:: + + sage: C = CoxeterMatrix(['A',3]) + sage: C.coxeter_graph() + Graph on 3 vertices + + sage: C = CoxeterMatrix([['A',3],['A',1]]) + sage: C.coxeter_graph() + Graph on 4 vertices + """ + n = self.rank() + I = self.index_set() + val = lambda x: infinity if x == -1 else x + G = Graph([(I[i], I[j], val((self._matrix)[i, j])) + for i in range(n) for j in range(i) + if self._matrix[i, j] not in [1, 2]]) + G.add_vertices(I) + return G.copy(immutable = True) + + def is_simply_laced(self): + """ + Return if ``self`` is simply-laced. + + A Coxeter matrix is simply-laced if all non-diagonal entries are + either 2 or 3. + + EXAMPLES:: + + sage: cm = CoxeterMatrix([[1,3,3,3], [3,1,3,3], [3,3,1,3], [3,3,3,1]]) + sage: cm.is_simply_laced() + True + """ + # We include 1 in this list to account for the diagonal + L = [1, 2, 3] + return all(x in L for row in self for x in row) + + def is_crystallographic(self): + """ + Return if ``self`` is crystallographic. + + A Coxeter matrix is crystallographic if all non-diagonal entries + are either 2, 4, or 6. + + EXAMPLES:: + + sage: CoxeterMatrix(['F',4]).is_crystallographic() + True + sage: CoxeterMatrix(['H',3]).is_crystallographic() + False + """ + # We include 1 in this list to account for the diagonal + L = [1, 2, 3, 4, 6] + return all(x in L for row in self for x in row) + + def is_finite(self): + """ + Return if ``self`` is a finite type or ``False`` if unknown. + + EXAMPLES:: + + sage: M = CoxeterMatrix(['C',4]) + sage: M.is_finite() + True + sage: M = CoxeterMatrix(['D',4,1]) + sage: M.is_finite() + False + sage: M = CoxeterMatrix([[1, -1], [-1, 1]]) + sage: M.is_finite() + False + """ + return self._is_finite + + def is_affine(self): + """ + Return if ``self`` is an affine type or ``False`` if unknown. + + EXAMPLES:: + + sage: M = CoxeterMatrix(['C',4]) + sage: M.is_affine() + False + sage: M = CoxeterMatrix(['D',4,1]) + sage: M.is_affine() + True + sage: M = CoxeterMatrix([[1, 3],[3,1]]) + sage: M.is_affine() + False + sage: M = CoxeterMatrix([[1, -1, 7], [-1, 1, 3], [7, 3, 1]]) + sage: M.is_affine() + False + """ + return self._is_affine + + +##################################################################### +## Type check functions + +def recognize_coxeter_type_from_matrix(coxeter_matrix, index_set): """ - return CartanType(t).coxeter_matrix() + Return the Coxeter type of ``coxeter_matrix`` if known, + otherwise return ``None``. + + EXAMPLES: + + Some infinite ones:: + + sage: C = CoxeterMatrix([[1,3,2],[3,1,-1],[2,-1,1]]) + sage: C.is_finite() # indirect doctest + False + sage: C = CoxeterMatrix([[1,-1,-1],[-1,1,-1],[-1,-1,1]]) + sage: C.is_finite() # indirect doctest + False + + Some finite ones:: + + sage: m = matrix(CoxeterMatrix(['D', 4])) + sage: CoxeterMatrix(m).is_finite() # indirect doctest + True + sage: m = matrix(CoxeterMatrix(['H', 4])) + sage: CoxeterMatrix(m).is_finite() # indirect doctest + True + + sage: CoxeterMatrix(CoxeterType(['A',10]).coxeter_graph()).coxeter_type() + Coxeter type of ['A', 10] + sage: CoxeterMatrix(CoxeterType(['B',10]).coxeter_graph()).coxeter_type() + Coxeter type of ['B', 10] + sage: CoxeterMatrix(CoxeterType(['C',10]).coxeter_graph()).coxeter_type() + Coxeter type of ['B', 10] + sage: CoxeterMatrix(CoxeterType(['D',10]).coxeter_graph()).coxeter_type() + Coxeter type of ['D', 10] + sage: CoxeterMatrix(CoxeterType(['E',6]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 6] + sage: CoxeterMatrix(CoxeterType(['E',7]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 7] + sage: CoxeterMatrix(CoxeterType(['E',8]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 8] + sage: CoxeterMatrix(CoxeterType(['F',4]).coxeter_graph()).coxeter_type() + Coxeter type of ['F', 4] + sage: CoxeterMatrix(CoxeterType(['G',2]).coxeter_graph()).coxeter_type() + Coxeter type of ['G', 2] + sage: CoxeterMatrix(CoxeterType(['H',3]).coxeter_graph()).coxeter_type() + Coxeter type of ['H', 3] + sage: CoxeterMatrix(CoxeterType(['H',4]).coxeter_graph()).coxeter_type() + Coxeter type of ['H', 4] + sage: CoxeterMatrix(CoxeterType(['I',100]).coxeter_graph()).coxeter_type() + Coxeter type of ['I', 100] + + Some affine graphs:: + + sage: CoxeterMatrix(CoxeterType(['A',1,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['A', 1, 1] + sage: CoxeterMatrix(CoxeterType(['A',10,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['A', 10, 1] + sage: CoxeterMatrix(CoxeterType(['B',10,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['B', 10, 1] + sage: CoxeterMatrix(CoxeterType(['C',10,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['C', 10, 1] + sage: CoxeterMatrix(CoxeterType(['D',10,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['D', 10, 1] + sage: CoxeterMatrix(CoxeterType(['E',6,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 6, 1] + sage: CoxeterMatrix(CoxeterType(['E',7,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 7, 1] + sage: CoxeterMatrix(CoxeterType(['E',8,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 8, 1] + sage: CoxeterMatrix(CoxeterType(['F',4,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['F', 4, 1] + sage: CoxeterMatrix(CoxeterType(['G',2,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['G', 2, 1] + + TESTS: + + Check that we detect relabellings:: + + sage: M = CoxeterMatrix([[1,2,3],[2,1,6],[3,6,1]], index_set=['a', 'b', 'c']) + sage: M.coxeter_type() + Coxeter type of ['G', 2, 1] relabelled by {0: 'a', 1: 'b', 2: 'c'} + + sage: from sage.combinat.root_system.coxeter_matrix import recognize_coxeter_type_from_matrix + sage: for C in CoxeterMatrix.samples(): + ....: relabelling_perm = Permutations(C.index_set()).random_element() + ....: relabelling_dict = {C.index_set()[i]: relabelling_perm[i] for i in range(C.rank())} + ....: relabeled_matrix = C.relabel(relabelling_dict)._matrix + ....: recognized_type = recognize_coxeter_type_from_matrix(relabeled_matrix, relabelling_perm) + ....: if C.is_finite() or C.is_affine(): + ....: assert recognized_type == C.coxeter_type() + """ + # First, we build the Coxeter graph of the group without the edge labels + n = ZZ(coxeter_matrix.nrows()) + G = Graph([[index_set[i], index_set[j], coxeter_matrix[i, j]] + for i in range(n) for j in range(i,n) + if coxeter_matrix[i, j] not in [1, 2]]) + G.add_vertices(index_set) + + types = [] + for S in G.connected_components_subgraphs(): + r = S.num_verts() + # Handle the special cases first + if r == 1: + types.append(CoxeterType(['A',1]).relabel({1: S.vertices()[0]})) + continue + if r == 2: # Type B2, G2, or I_2(p) + e = S.edge_labels()[0] + if e == 3: # Can't be 2 because it is connected + ct = CoxeterType(['B',2]) + elif e == 4: + ct = CoxeterType(['G',2]) + elif e > 0 and e < float('inf'): # Remaining non-affine types + ct = CoxeterType(['I',e]) + else: # Otherwise it is infinite dihedral group Z_2 \ast Z_2 + ct = CoxeterType(['A',1,1]) + if not ct.is_affine(): + types.append(ct.relabel({1: S.vertices()[0], 2: S.vertices()[1]})) + else: + types.append(ct.relabel({0: S.vertices()[0], 1: S.vertices()[1]})) + continue + + test = [['A',r], ['B',r], ['A',r-1,1]] + if r >= 3: + if r == 3: + test += [['G',2,1], ['H',3]] + test.append(['C',r-1,1]) + if r >= 4: + if r == 4: + test += [['F',4], ['H',4]] + test += [['D',r], ['B',r-1,1]] + if r >= 5: + if r == 5: + test.append(['F',4,1]) + test.append(['D',r-1,1]) + if r == 6: + test.append(['E',6]) + elif r == 7: + test += [['E',7], ['E',6,1]] + elif r == 8: + test += [['E',8], ['E',7,1]] + elif r == 9: + test.append(['E',8,1]) + + found = False + for ct in test: + ct = CoxeterType(ct) + T = ct.coxeter_graph() + iso, match = T.is_isomorphic(S, certify=True, edge_labels=True) + if iso: + types.append(ct.relabel(match)) + found = True + break + if not found: + return None + + return CoxeterType(types) + +##################################################################### +## Other functions + +def check_coxeter_matrix(m): + """ + Check if ``m`` represents a generalized Coxeter matrix and raise + and error if not. + + EXAMPLES:: + + sage: from sage.combinat.root_system.coxeter_matrix import check_coxeter_matrix + sage: m = matrix([[1,3,2],[3,1,-1],[2,-1,1]]) + sage: check_coxeter_matrix(m) + + sage: m = matrix([[1,3],[3,1],[2,-1]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: not a square matrix + + sage: m = matrix([[1,3,2],[3,1,-1],[2,-1,2]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: the matrix diagonal is not all 1 + + sage: m = matrix([[1,3,3],[3,1,-1],[2,-1,1]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: the matrix is not symmetric + + sage: m = matrix([[1,3,1/2],[3,1,-1],[1/2,-1,1]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: invalid Coxeter label 1/2 + + sage: m = matrix([[1,3,1],[3,1,-1],[1,-1,1]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: invalid Coxeter label 1 + """ + mat = matrix(m) + if not mat.is_square(): + raise ValueError("not a square matrix") + for i, row in enumerate(m): + if mat[i, i] != 1: + raise ValueError("the matrix diagonal is not all 1") + for j, val in enumerate(row[i+1:]): + if val != m[j+i+1][i]: + raise ValueError("the matrix is not symmetric") + if val not in ZZ: + if val > -1 and val in RR and val != infinity: + raise ValueError("invalid Coxeter label {}".format(val)) + else: + if val == 1 or val == 0: + raise ValueError("invalid Coxeter label {}".format(val)) + +def coxeter_matrix_as_function(t): + """ + Return the Coxeter matrix, as a function. + + INPUT: + + - ``t`` -- a Cartan type + + EXAMPLES:: + + sage: from sage.combinat.root_system.coxeter_matrix import coxeter_matrix_as_function + sage: f = coxeter_matrix_as_function(['A',4]) + sage: matrix([[f(i,j) for j in range(1,5)] for i in range(1,5)]) + [1 3 2 2] + [3 1 3 2] + [2 3 1 3] + [2 2 3 1] + """ + t = CartanType(t) + m = t.coxeter_matrix() + return lambda i, j: m[i, j] + +def coxeter_matrix(t): + """ + This was deprecated in :trac:`17798` for :class:`CartanMatrix`. + + EXAMPLES:: + + sage: coxeter_matrix(['A', 4]) + doctest:...: DeprecationWarning: coxeter_matrix() is deprecated. Use CoxeterMatrix() instead + See http://trac.sagemath.org/17798 for details. + [1 3 2 2] + [3 1 3 2] + [2 3 1 3] + [2 2 3 1] + """ + from sage.misc.superseded import deprecation + deprecation(17798, 'coxeter_matrix() is deprecated. Use CoxeterMatrix() instead') + return CoxeterMatrix(t) + diff --git a/src/sage/combinat/root_system/coxeter_type.py b/src/sage/combinat/root_system/coxeter_type.py new file mode 100644 index 00000000000..ecbd5ab6a20 --- /dev/null +++ b/src/sage/combinat/root_system/coxeter_type.py @@ -0,0 +1,576 @@ +""" +Coxeter Types +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw , +# 2015 Jean-Philippe Labbe , +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# The full text of the GPL is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.misc.classcall_metaclass import ClasscallMetaclass +from sage.combinat.root_system.cartan_type import CartanType +from sage.matrix.all import MatrixSpace +from sage.symbolic.ring import SR +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.sage_object import SageObject + + +class CoxeterType(SageObject): + """ + Abstract class for Coxeter types. + """ + __metaclass__ = ClasscallMetaclass + + @staticmethod + def __classcall_private__(cls, *x): + """ + Parse input ``x``. + + EXAMPLES:: + + sage: CoxeterType(['A',3]) + Coxeter type of ['A', 3] + """ + if len(x) == 1: + x = x[0] + + if isinstance(x, CoxeterType): + return x + + try: + return CoxeterTypeFromCartanType(CartanType(x)) + except (ValueError, TypeError): + pass + + if len(x) == 1: # In case the input is similar to CoxeterType([['A',2]]) + return CoxeterType(x[0]) + + raise NotImplementedError("Coxeter types not from Cartan types not yet implemented") + + @classmethod + def samples(self, finite=None, affine=None, crystallographic=None): + """ + Return a sample of the available Coxeter types. + + INPUT: + + - ``finite`` -- a boolean or ``None`` (default: ``None``) + + - ``affine`` -- a boolean or ``None`` (default: ``None``) + + - ``crystallographic`` -- a boolean or ``None`` (default: ``None``) + + The sample contains all the exceptional finite and affine + Coxeter types, as well as typical representatives of the + infinite families. + + EXAMPLES:: + + sage: CoxeterType.samples() + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 1], Coxeter type of ['B', 5], + Coxeter type of ['C', 1], Coxeter type of ['C', 5], + Coxeter type of ['D', 4], Coxeter type of ['D', 5], + Coxeter type of ['E', 6], Coxeter type of ['E', 7], + Coxeter type of ['E', 8], Coxeter type of ['F', 4], + Coxeter type of ['H', 3], Coxeter type of ['H', 4], + Coxeter type of ['I', 10], Coxeter type of ['A', 2, 1], + Coxeter type of ['B', 5, 1], Coxeter type of ['C', 5, 1], + Coxeter type of ['D', 5, 1], Coxeter type of ['E', 6, 1], + Coxeter type of ['E', 7, 1], Coxeter type of ['E', 8, 1], + Coxeter type of ['F', 4, 1], Coxeter type of ['G', 2, 1], + Coxeter type of ['A', 1, 1]] + + The finite, affine and crystallographic options allow + respectively for restricting to (non) finite, (non) affine, + and (non) crystallographic Cartan types:: + + sage: CoxeterType.samples(finite=True) + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 1], Coxeter type of ['B', 5], + Coxeter type of ['C', 1], Coxeter type of ['C', 5], + Coxeter type of ['D', 4], Coxeter type of ['D', 5], + Coxeter type of ['E', 6], Coxeter type of ['E', 7], + Coxeter type of ['E', 8], Coxeter type of ['F', 4], + Coxeter type of ['H', 3], Coxeter type of ['H', 4], + Coxeter type of ['I', 10]] + + sage: CoxeterType.samples(affine=True) + [Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1]] + + sage: CoxeterType.samples(crystallographic=True) + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 1], Coxeter type of ['B', 5], + Coxeter type of ['C', 1], Coxeter type of ['C', 5], + Coxeter type of ['D', 4], Coxeter type of ['D', 5], + Coxeter type of ['E', 6], Coxeter type of ['E', 7], + Coxeter type of ['E', 8], Coxeter type of ['F', 4], + Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1]] + + sage: CoxeterType.samples(crystallographic=False) + [Coxeter type of ['H', 3], + Coxeter type of ['H', 4], + Coxeter type of ['I', 10]] + + .. TODO:: add some reducible Coxeter types (suggestions?) + + TESTS:: + + sage: for ct in CoxeterType.samples(): TestSuite(ct).run() + """ + result = self._samples() + if crystallographic is not None: + result = [t for t in result if t.is_crystallographic() == crystallographic] + if finite is not None: + result = [t for t in result if t.is_finite() == finite] + if affine is not None: + result = [t for t in result if t.is_affine() == affine] + return result + + @cached_method + def _samples(self): + """ + Return a sample of all implemented Coxeter types. + + .. NOTE:: + + This is intended to be used through :meth:`samples`. + + EXAMPLES:: + + sage: CoxeterType._samples() + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 1], Coxeter type of ['B', 5], + Coxeter type of ['C', 1], Coxeter type of ['C', 5], + Coxeter type of ['D', 4], Coxeter type of ['D', 5], + Coxeter type of ['E', 6], Coxeter type of ['E', 7], + Coxeter type of ['E', 8], Coxeter type of ['F', 4], + Coxeter type of ['H', 3], Coxeter type of ['H', 4], + Coxeter type of ['I', 10], Coxeter type of ['A', 2, 1], + Coxeter type of ['B', 5, 1], Coxeter type of ['C', 5, 1], + Coxeter type of ['D', 5, 1], Coxeter type of ['E', 6, 1], + Coxeter type of ['E', 7, 1], Coxeter type of ['E', 8, 1], + Coxeter type of ['F', 4, 1], Coxeter type of ['G', 2, 1], + Coxeter type of ['A', 1, 1]] + """ + finite = [CoxeterType(t) for t in [['A', 1], ['A', 5], ['B', 1], ['B', 5], + ['C', 1], ['C', 5], ['D', 4], ['D', 5], + ['E', 6], ['E', 7], ['E', 8], ['F', 4], + ['H', 3], ['H', 4], ['I', 10]]] + + affine = [CoxeterType(t) for t in ['A', 2, 1], ['B', 5, 1], + ['C', 5, 1], ['D', 5, 1], ['E', 6, 1], + ['E', 7, 1], ['E', 8, 1], ['F', 4, 1], + ['G', 2, 1], ['A', 1, 1]] + + return finite + affine + + @abstract_method + def rank(self): + """ + Return the rank of ``self``. + + This is the number of nodes of the associated Coxeter graph. + + EXAMPLES:: + + sage: CoxeterType(['A', 4]).rank() + 4 + sage: CoxeterType(['A', 7, 2]).rank() + 5 + sage: CoxeterType(['I', 8]).rank() + 2 + """ + + @abstract_method + def index_set(self): + """ + Return the index set for ``self``. + + This is the list of the nodes of the associated Coxeter graph. + + EXAMPLES:: + + sage: CoxeterType(['A', 3, 1]).index_set() + (0, 1, 2, 3) + sage: CoxeterType(['D', 4]).index_set() + (1, 2, 3, 4) + sage: CoxeterType(['A', 7, 2]).index_set() + (0, 1, 2, 3, 4) + sage: CoxeterType(['A', 7, 2]).index_set() + (0, 1, 2, 3, 4) + sage: CoxeterType(['A', 6, 2]).index_set() + (0, 1, 2, 3) + sage: CoxeterType(['D', 6, 2]).index_set() + (0, 1, 2, 3, 4, 5) + sage: CoxeterType(['E', 6, 1]).index_set() + (0, 1, 2, 3, 4, 5, 6) + sage: CoxeterType(['E', 6, 2]).index_set() + (0, 1, 2, 3, 4) + sage: CoxeterType(['A', 2, 2]).index_set() + (0, 1) + sage: CoxeterType(['G', 2, 1]).index_set() + (0, 1, 2) + sage: CoxeterType(['F', 4, 1]).index_set() + (0, 1, 2, 3, 4) + """ + + @abstract_method + def coxeter_matrix(self): + """ + Return the Coxeter matrix associated to ``self``. + + EXAMPLES:: + + sage: CoxeterType(['A', 3]).coxeter_matrix() + [1 3 2] + [3 1 3] + [2 3 1] + sage: CoxeterType(['A', 3, 1]).coxeter_matrix() + [1 3 2 3] + [3 1 3 2] + [2 3 1 3] + [3 2 3 1] + """ + + @abstract_method + def coxeter_graph(self): + """ + Return the Coxeter graph associated to ``self``. + + EXAMPLES:: + + sage: CoxeterType(['A', 3]).coxeter_graph() + Graph on 3 vertices + sage: CoxeterType(['A', 3, 1]).coxeter_graph() + Graph on 4 vertices + """ + + @abstract_method + def is_finite(self): + """ + Return whether ``self`` is finite. + + EXAMPLES:: + + sage: CoxeterType(['A',4]).is_finite() + True + sage: CoxeterType(['A',4, 1]).is_finite() + False + """ + + @abstract_method + def is_affine(self): + """ + Return whether ``self`` is affine. + + EXAMPLES:: + + sage: CoxeterType(['A', 3]).is_affine() + False + sage: CoxeterType(['A', 3, 1]).is_affine() + True + """ + + def is_crystallographic(self): + """ + Return whether ``self`` is crystallographic. + + This returns ``False`` by default. Derived class should override this + appropriately. + + EXAMPLES:: + + sage: [ [t, t.is_crystallographic() ] for t in CartanType.samples(finite=True) ] + [[['A', 1], True], [['A', 5], True], + [['B', 1], True], [['B', 5], True], + [['C', 1], True], [['C', 5], True], + [['D', 2], True], [['D', 3], True], [['D', 5], True], + [['E', 6], True], [['E', 7], True], [['E', 8], True], + [['F', 4], True], [['G', 2], True], + [['I', 5], False], [['H', 3], False], [['H', 4], False]] + """ + return False + + def is_simply_laced(self): + """ + Return whether ``self`` is simply laced. + + This returns ``False`` by default. Derived class should override this + appropriately. + + EXAMPLES:: + + sage: [ [t, t.is_simply_laced() ] for t in CartanType.samples() ] + [[['A', 1], True], [['A', 5], True], + [['B', 1], True], [['B', 5], False], + [['C', 1], True], [['C', 5], False], + [['D', 2], True], [['D', 3], True], [['D', 5], True], + [['E', 6], True], [['E', 7], True], [['E', 8], True], + [['F', 4], False], [['G', 2], False], + [['I', 5], False], [['H', 3], False], [['H', 4], False], + [['A', 1, 1], False], [['A', 5, 1], True], + [['B', 1, 1], False], [['B', 5, 1], False], + [['C', 1, 1], False], [['C', 5, 1], False], + [['D', 3, 1], True], [['D', 5, 1], True], + [['E', 6, 1], True], [['E', 7, 1], True], [['E', 8, 1], True], + [['F', 4, 1], False], [['G', 2, 1], False], + [['BC', 1, 2], False], [['BC', 5, 2], False], + [['B', 5, 1]^*, False], [['C', 4, 1]^*, False], + [['F', 4, 1]^*, False], [['G', 2, 1]^*, False], + [['BC', 1, 2]^*, False], [['BC', 5, 2]^*, False]] + """ + return False + + @cached_method + def bilinear_form(self, R=None): + """ + Return the bilinear form over ``R`` associated to ``self``. + + INPUT: + + - ``R`` -- (default: universal cyclotomic field) a ring used to + compute the bilinear form + + EXAMPLES:: + + sage: CoxeterType(['A', 2, 1]).bilinear_form() + [ 1 -1/2 -1/2] + [-1/2 1 -1/2] + [-1/2 -1/2 1] + sage: CoxeterType(['H', 3]).bilinear_form() + [ 1 -1/2 0] + [ -1/2 1 1/2*E(5)^2 + 1/2*E(5)^3] + [ 0 1/2*E(5)^2 + 1/2*E(5)^3 1] + sage: C = CoxeterMatrix([[1,-1,-1],[-1,1,-1],[-1,-1,1]]) + sage: C.bilinear_form() + [ 1 -1 -1] + [-1 1 -1] + [-1 -1 1] + """ + + n = self.rank() + mat = self.coxeter_matrix()._matrix + base_ring = mat.base_ring() + + from sage.rings.universal_cyclotomic_field import UniversalCyclotomicField + UCF = UniversalCyclotomicField() + if UCF.has_coerce_map_from(base_ring): + R = UCF + else: + R = base_ring + # Compute the matrix with entries `- \cos( \pi / m_{ij} )`. + if R is UCF: + val = lambda x: (R.gen(2*x) + ~R.gen(2*x)) / R(-2) if x > -1 else R.one()*x + else: + from sage.functions.trig import cos + from sage.symbolic.constants import pi + val = lambda x: -R(cos(pi / SR(x))) if x > -1 else x + + MS = MatrixSpace(R, n, sparse=True) + MC = MS._get_matrix_class() + + bilinear = MC(MS, entries={(i, j): val(mat[i, j]) + for i in range(n) for j in range(n) + if mat[i, j] != 2}, + coerce=True, copy=True) + bilinear.set_immutable() + return bilinear + + +class CoxeterTypeFromCartanType(CoxeterType, UniqueRepresentation): + """ + A Coxeter type associated to a Cartan type. + """ + @staticmethod + def __classcall_private__(cls, cartan_type): + """ + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: from sage.combinat.root_system.coxeter_type import CoxeterTypeFromCartanType + sage: C1 = CoxeterTypeFromCartanType(['A',3]) + sage: C2 = CoxeterTypeFromCartanType(CartanType(['A',3])) + sage: C1 is C2 + True + """ + return super(CoxeterTypeFromCartanType, cls).__classcall__(cls, + CartanType(cartan_type)) + + def __init__(self, cartan_type): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['A',3]) + sage: TestSuite(C).run() + sage: C = CoxeterType(['H',4]) + sage: TestSuite(C).run() + sage: C = CoxeterType(['C',3,1]) + sage: TestSuite(C).run() + """ + self._cartan_type = cartan_type + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: CoxeterType(['A',3]) + Coxeter type of ['A', 3] + """ + return "Coxeter type of {}".format(self._cartan_type) + + def coxeter_matrix(self): + """ + Return the Coxeter matrix associated to ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['H',3]) + sage: C.coxeter_matrix() + [1 3 2] + [3 1 5] + [2 5 1] + """ + return self._cartan_type.coxeter_matrix() + + def coxeter_graph(self): + """ + Return the Coxeter graph of ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['H',3]) + sage: C.coxeter_graph().edges() + [(1, 2, 3), (2, 3, 5)] + """ + return self._cartan_type.coxeter_diagram() + + def cartan_type(self): + """ + Return the Cartan type used to construct ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['C',3]) + sage: C.cartan_type() + ['C', 3] + """ + return self._cartan_type + + def rank(self): + """ + Return the rank of ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['I', 16]) + sage: C.rank() + 2 + """ + return self._cartan_type.rank() + + def index_set(self): + """ + Return the index set of ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['A', 4]) + sage: C.index_set() + (1, 2, 3, 4) + """ + return self._cartan_type.index_set() + + def is_finite(self): + """ + Return if ``self`` is a finite type. + + EXAMPLES:: + + sage: C = CoxeterType(['E', 6]) + sage: C.is_finite() + True + """ + return self._cartan_type.is_finite() + + def is_affine(self): + """ + Return if ``self`` is an affine type. + + EXAMPLES:: + + sage: C = CoxeterType(['F', 4, 1]) + sage: C.is_affine() + True + """ + return self._cartan_type.is_affine() + + def is_crystallographic(self): + """ + Return if ``self`` is crystallographic. + + EXAMPLES:: + + sage: C = CoxeterType(['C', 3]) + sage: C.is_crystallographic() + True + + sage: C = CoxeterType(['H', 3]) + sage: C.is_crystallographic() + False + """ + return self._cartan_type.is_crystallographic() + + def is_simply_laced(self): + """ + Return if ``self`` is simply-laced. + + EXAMPLES:: + + sage: C = CoxeterType(['A', 5]) + sage: C.is_simply_laced() + True + + sage: C = CoxeterType(['B', 3]) + sage: C.is_simply_laced() + False + """ + return self._cartan_type.is_simply_laced() + + def relabel(self, relabelling): + """ + Return a relabelled copy of ``self``. + + EXAMPLES:: + + sage: ct = CoxeterType(['A',2]) + sage: ct.relabel({1:-1, 2:-2}) + Coxeter type of ['A', 2] relabelled by {1: -1, 2: -2} + """ + return CoxeterType(self._cartan_type.relabel(relabelling)) + diff --git a/src/sage/combinat/root_system/dynkin_diagram.py b/src/sage/combinat/root_system/dynkin_diagram.py index ee900688c47..bebe22fabbf 100644 --- a/src/sage/combinat/root_system/dynkin_diagram.py +++ b/src/sage/combinat/root_system/dynkin_diagram.py @@ -32,7 +32,6 @@ from sage.graphs.digraph import DiGraph from sage.combinat.root_system.cartan_type import CartanType, CartanType_abstract from sage.combinat.root_system.cartan_matrix import CartanMatrix -from sage.misc.superseded import deprecated_function_alias def DynkinDiagram(*args, **kwds): r""" @@ -564,9 +563,7 @@ def subtype(self, index_set): 0 1 2 3 BC3~ sage: D.subtype([1,2,3]) - O---O=<=O - 1 2 3 - C3 + Dynkin diagram of rank 3 """ return self.cartan_matrix().subtype(index_set).dynkin_diagram() @@ -626,15 +623,11 @@ def is_crystallographic(self): TESTS:: - sage: CartanType(['G',2]).dynkin_diagram().is_crystalographic() - doctest:...: DeprecationWarning: is_crystalographic is deprecated. Please use is_crystallographic instead. - See http://trac.sagemath.org/14673 for details. + sage: CartanType(['G',2]).dynkin_diagram().is_crystallographic() True """ return True - is_crystalographic = deprecated_function_alias(14673, is_crystallographic) - def symmetrizer(self): """ Return the symmetrizer of the corresponding Cartan matrix. diff --git a/src/sage/combinat/root_system/integrable_representations.py b/src/sage/combinat/root_system/integrable_representations.py index ab8842f370f..14e3386db3d 100644 --- a/src/sage/combinat/root_system/integrable_representations.py +++ b/src/sage/combinat/root_system/integrable_representations.py @@ -5,6 +5,7 @@ #***************************************************************************** # Copyright (C) 2014, 2105 Daniel Bump # Travis Scrimshaw +# Valentin Buciumas # # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ @@ -42,6 +43,9 @@ class IntegrableRepresentation(UniqueRepresentation, CategoryObject): .. [KacPeterson] Kac and Peterson. *Infinite-dimensional Lie algebras, theta functions and modular forms*. Adv. in Math. 53 (1984), no. 2, 125-264. + + .. [Carter] Carter, *Lie algebras of finite and affine type*. Cambridge + University Press, 2005 If `\Lambda` is a dominant integral weight for an affine root system, there exists a unique integrable representation `V=V_\Lambda` of highest @@ -147,6 +151,31 @@ class IntegrableRepresentation(UniqueRepresentation, CategoryObject): Lambda[0] + Lambda[2] - delta: 1 5 18 55 149 372 872 1941 4141 8523 17005 33019 2*Lambda[1] - delta: 1 4 15 44 122 304 721 1612 3469 7176 14414 28124 2*Lambda[2] - 2*delta: 2 7 26 72 194 467 1084 2367 5010 10191 20198 38907 + + Examples for twisted affine types:: + + sage: Lambda = RootSystem(["A",2,2]).weight_lattice(extended=True).fundamental_weights() + sage: IntegrableRepresentation(Lambda[0]).strings() + {Lambda[0]: [1, 1, 2, 3, 5, 7, 11, 15, 22, 30, 42, 56]} + sage: Lambda = RootSystem(['G',2,1]).dual.weight_lattice(extended=true).fundamental_weights() + sage: V = IntegrableRepresentation(Lambda[0]+Lambda[1]+Lambda[2]) + sage: V.print_strings() # long time + 6*Lambdacheck[0]: 4 28 100 320 944 2460 6064 14300 31968 69020 144676 293916 + 4*Lambdacheck[0] + Lambdacheck[2]: 4 22 84 276 800 2124 5288 12470 28116 61056 128304 261972 + 3*Lambdacheck[0] + Lambdacheck[1]: 2 16 58 192 588 1568 3952 9520 21644 47456 100906 207536 + Lambdacheck[0] + Lambdacheck[1] + Lambdacheck[2]: 1 6 26 94 294 832 2184 5388 12634 28390 61488 128976 + 2*Lambdacheck[1] - deltacheck: 2 8 32 120 354 980 2576 6244 14498 32480 69776 145528 + 2*Lambdacheck[0] + 2*Lambdacheck[2]: 2 12 48 164 492 1344 3428 8256 18960 41844 89208 184512 + 3*Lambdacheck[2] - deltacheck: 4 16 60 208 592 1584 4032 9552 21728 47776 101068 207888 + sage: Lambda = RootSystem(['A',6,2]).weight_lattice(extended=true).fundamental_weights() + sage: V = IntegrableRepresentation(Lambda[0]+2*Lambda[1]) + sage: V.print_strings() # long time + 5*Lambda[0]: 3 42 378 2508 13707 64650 272211 1045470 3721815 12425064 39254163 118191378 + 3*Lambda[0] + Lambda[2]: 1 23 234 1690 9689 47313 204247 800029 2893198 9786257 31262198 95035357 + Lambda[0] + 2*Lambda[1]: 1 14 154 1160 6920 34756 153523 612354 2248318 7702198 24875351 76341630 + Lambda[0] + Lambda[1] + Lambda[3] - 2*delta: 6 87 751 4779 25060 113971 464842 1736620 6034717 19723537 61152367 181068152 + Lambda[0] + 2*Lambda[2] - 2*delta: 3 54 499 3349 18166 84836 353092 1341250 4725259 15625727 48938396 146190544 + Lambda[0] + 2*Lambda[3] - 4*delta: 15 195 1539 9186 45804 200073 789201 2866560 9723582 31120281 94724550 275919741 """ def __init__(self, Lam): """ @@ -160,16 +189,16 @@ def __init__(self, Lam): """ CategoryObject.__init__(self, base=ZZ, category=Modules(ZZ)) - if not Lam.parent().cartan_type().is_affine() or not Lam.parent()._extended: - raise ValueError("the parent of %s must be an extended affine root lattice"%Lam) self._Lam = Lam self._P = Lam.parent() self._Q = self._P.root_system.root_lattice() + # Store some extra simple computations that appear in tight loops + self._Lam_rho = self._Lam + self._P.rho() + self._cartan_matrix = self._P.root_system.cartan_matrix() self._cartan_type = self._P.root_system.cartan_type() - if not self._cartan_type.is_untwisted_affine(): - raise NotImplementedError("integrable representations are only implemented for untwisted affine types") + self._classical_rank = self._cartan_type.classical().rank() self._index_set = self._P.index_set() self._index_set_classical = self._cartan_type.classical().index_set() @@ -186,11 +215,14 @@ def __init__(self, Lam): self._a = self._cartan_type.a() # This is not cached self._ac = self._cartan_type.dual().a() # This is not cached self._eps = {i: self._a[i] / self._ac[i] for i in self._index_set} - self._coxeter_number = sum(self._a) - self._dual_coxeter_number = sum(self._ac) E = Matrix.diagonal([self._eps[i] for i in self._index_set_classical]) self._ip = (self._cartan_type.classical().cartan_matrix()*E).inverse() + # Extra data for the twisted cases + if not self._cartan_type.is_untwisted_affine(): + self._classical_short_roots = frozenset(al for al in self._classical_roots + if self._inner_qq(al,al) == 2) + def highest_weight(self): """ Returns the highest weight of ``self``. @@ -243,6 +275,7 @@ def level(self): """ return ZZ(self._inner_pq(self._Lam, self._Q.null_root())) + @cached_method def coxeter_number(self): """ Return the Coxeter number of the Cartan type of ``self``. @@ -257,8 +290,9 @@ def coxeter_number(self): sage: V.coxeter_number() 12 """ - return self._coxeter_number + return sum(self._a) + @cached_method def dual_coxeter_number(self): r""" Return the dual Coxeter number of the Cartan type of ``self``. @@ -273,7 +307,7 @@ def dual_coxeter_number(self): sage: V.dual_coxeter_number() 9 """ - return self._dual_coxeter_number + return sum(self._ac) def _repr_(self): """ @@ -570,8 +604,8 @@ def to_dominant(self, n): def _freudenthal_roots_imaginary(self, nu): r""" - Return the set of imaginary roots `\alpha \in \Delta^+` in ``self`` - such that `\nu - \alpha \in Q^+`. + Iterate over the set of imaginary roots `\alpha \in \Delta^+` + in ``self`` such that `\nu - \alpha \in Q^+`. INPUT: @@ -581,20 +615,24 @@ def _freudenthal_roots_imaginary(self, nu): sage: Lambda = RootSystem(['B',3,1]).weight_lattice(extended=true).fundamental_weights() sage: V = IntegrableRepresentation(Lambda[0]+Lambda[1]+Lambda[3]) - sage: [V._freudenthal_roots_imaginary(V.highest_weight() - mw) + sage: [list(V._freudenthal_roots_imaginary(V.highest_weight() - mw)) ....: for mw in V.dominant_maximal_weights()] [[], [], [], [], []] """ l = self._from_weight_helper(nu) kp = min(l[i] // self._a[i] for i in self._index_set) delta = self._Q.null_root() - return [u * delta for u in range(1, kp+1)] + for u in range(1, kp+1): + yield u * delta def _freudenthal_roots_real(self, nu): r""" - Return the set of real positive roots `\alpha \in \Delta^+` in - ``self`` such that `\nu - \alpha \in Q^+`. - + Iterate over the set of real positive roots `\alpha \in \Delta^+` + in ``self`` such that `\nu - \alpha \in Q^+`. + + See [Kac]_ Proposition 6.3 for the way to compute the set of real + roots for twisted affine case. + INPUT: - ``nu`` -- an element in `Q` @@ -604,7 +642,7 @@ def _freudenthal_roots_real(self, nu): sage: Lambda = RootSystem(['B',3,1]).weight_lattice(extended=true).fundamental_weights() sage: V = IntegrableRepresentation(Lambda[0]+Lambda[1]+Lambda[3]) sage: mw = V.dominant_maximal_weights()[0] - sage: V._freudenthal_roots_real(V.highest_weight() - mw) + sage: list(V._freudenthal_roots_real(V.highest_weight() - mw)) [alpha[1], alpha[2], alpha[3], @@ -612,14 +650,66 @@ def _freudenthal_roots_real(self, nu): alpha[2] + alpha[3], alpha[1] + alpha[2] + alpha[3]] """ - ret = [] for al in self._classical_positive_roots: - if all(x >= 0 for x in self._from_weight_helper(nu-al)): - ret.append(al) - for al in self._classical_roots: - for ir in self._freudenthal_roots_imaginary(nu-al): - ret.append(al+ir) - return ret + if min(self._from_weight_helper(nu-al)) >= 0: + yield al + + if self._cartan_type.is_untwisted_affine(): + # untwisted case + for al in self._classical_roots: + for ir in self._freudenthal_roots_imaginary(nu-al): + yield al + ir + + elif self._cartan_type.type() == 'BC': + #case A^2_{2l} + # We have to keep track of the roots we have visted for this case + ret = set(self._classical_positive_roots) + for al in self._classical_roots: + if al in self._classical_short_roots: + for ir in self._freudenthal_roots_imaginary(nu-al): + ret.add(al + ir) + yield al + ir + else: + fri = list(self._freudenthal_roots_imaginary(nu-al)) + friset = set(fri) + for ir in fri: + if 2*ir in friset: + ret.add(al + 2*ir) + yield al + 2*ir + alpha = self._Q.simple_roots() + fri = list(self._freudenthal_roots_imaginary(2*nu-al)) + for ir in fri[::2]: + rt = sum( val // 2 * alpha[i] for i,val in + enumerate(self._from_weight_helper(al+ir)) ) + if rt not in ret: + ret.add(rt) + yield rt + + elif self._cartan_type.dual().type() == 'G': + # case D^3_4 in the Kac notation + for al in self._classical_roots: + if al in self._classical_short_roots: + for ir in self._freudenthal_roots_imaginary(nu-al): + yield al + ir + else: + fri = list(self._freudenthal_roots_imaginary(nu-al)) + friset = set(fri) + for ir in fri: + if 3*ir in friset: + yield al + 3*ir + + elif self._cartan_type.dual().type() in ['B','C','F']: + #case A^2_{2l-1} or case D^2_{l+1} or case E^2_6: + for al in self._classical_roots: + if al in self._classical_short_roots: + for ir in self._freudenthal_roots_imaginary(nu-al): + yield al + ir + else: + fri = list(self._freudenthal_roots_imaginary(nu-al)) + friset = set(fri) + for ir in fri: + if 2*ir in friset: + yield al + 2*ir def _freudenthal_accum(self, nu, al): """ @@ -640,21 +730,25 @@ def _freudenthal_accum(self, nu, al): n_shift = self._from_weight_helper(al) ip_shift = self._inner_qq(al, al) - while all(val >= 0 for val in n): + while min(n) >= 0: # Change in data by adding ``al`` to our current weight ip += ip_shift for i,val in enumerate(n_shift): n[i] -= val # Compute the multiplicity - mk = self.m(tuple(n)) - ret += 2*mk*ip + ret += 2 * self.m(tuple(n)) * ip return ret def _m_freudenthal(self, n): - """ + r""" Compute the weight multiplicity using the Freudenthal multiplicity formula in ``self``. + The multiplicities of the imaginary roots for the twisted + affine case are different than those for the untwisted case. + See [Carter]_ Corollary 18.10 for general type and Corollary + 18.15 for `A^2_{2l}` + EXAMPLES:: sage: Lambda = RootSystem(['B',3,1]).weight_lattice(extended=true).fundamental_weights() @@ -670,12 +764,48 @@ def _m_freudenthal(self, n): I = self._index_set al = self._Q._from_dict({I[i]: val for i,val in enumerate(n) if val}, remove_zeros=False) - den = 2*self._inner_pq(self._Lam+self._P.rho(), al) - self._inner_qq(al, al) - num = 0 - for al in self._freudenthal_roots_real(self._Lam - mu): - num += self._freudenthal_accum(mu, al) - for al in self._freudenthal_roots_imaginary(self._Lam - mu): - num += self._classical_rank * self._freudenthal_accum(mu, al) + cr = self._classical_rank + num = sum(self._freudenthal_accum(mu, alr) + for alr in self._freudenthal_roots_real(self._Lam - mu)) + + if self._cartan_type.is_untwisted_affine(): + num += sum(cr * self._freudenthal_accum(mu, alr) + for alr in self._freudenthal_roots_imaginary(self._Lam - mu)) + + elif self._cartan_type.dual().type() == 'B': # A_{2n-1}^{(2)} + val = 1 + for rt in self._freudenthal_roots_imaginary(self._Lam - mu): + # k-th element (starting from 1) is k*delta + num += (cr - val) * self._freudenthal_accum(mu, rt) + val = 1 - val + + elif self._cartan_type.type() == 'BC': # A_{2n}^{(2)} + num += sum(cr * self._freudenthal_accum(mu, alr) + for alr in self._freudenthal_roots_imaginary(self._Lam - mu)) + + elif self._cartan_type.dual() == 'C': # D_{n+1}^{(2)} + val = 1 + for rt in self._freudenthal_roots_imaginary(self._Lam - mu): + # k-th element (starting from 1) is k*delta + num += (cr - (cr - 1)*val) * self._freudenthal_accum(mu, rt) + val = 1 - val + + elif self._cartan_type.dual().type() == 'F': # E_6^{(2)} + val = 1 + for rt in self._freudenthal_roots_imaginary(self._Lam - mu): + # k-th element (starting from 1) is k*delta + num += (4 - 2*val) * self._freudenthal_accum(mu, rt) + val = 1 - val + + elif self._cartan_type.dual().type() == 'G': # D_4^{(3)} (or dual of G_2^{(1)}) + for k,rt in enumerate(self._freudenthal_roots_imaginary(self._Lam - mu)): + # k-th element (starting from 1) is k*delta + if (k+1) % 3 == 0: + num += 2 * self._freudenthal_accum(mu, rt) + else: + num += self._freudenthal_accum(mu, rt) + + den = 2*self._inner_pq(self._Lam_rho, al) - self._inner_qq(al, al) try: return ZZ(num / den) except TypeError: @@ -872,9 +1002,9 @@ def modular_characteristic(self, mu=None): else: n = self.from_weight(mu) k = self.level() - hd = self._dual_coxeter_number + hd = self.dual_coxeter_number() rho = self._P.rho() - m_Lambda = self._inner_pp(self._Lam+rho, self._Lam+rho) / (2*(k+hd)) \ + m_Lambda = self._inner_pp(self._Lam_rho, self._Lam_rho) / (2*(k+hd)) \ - self._inner_pp(rho, rho) / (2*hd) if n is None: return m_Lambda diff --git a/src/sage/combinat/root_system/plot.py b/src/sage/combinat/root_system/plot.py index cec3c3ff33c..8305963e7b4 100644 --- a/src/sage/combinat/root_system/plot.py +++ b/src/sage/combinat/root_system/plot.py @@ -416,7 +416,7 @@ :: sage: L = RootSystem(["A",3,1]).ambient_space() - sage: alcoves = CartesianProduct([0,1],[0,1],[0,1]) + sage: alcoves = cartesian_product([[0,1],[0,1],[0,1]]) sage: color = lambda i: "black" if i==0 else None sage: L.plot_alcoves(alcoves=alcoves, color=color, bounding_box=10,wireframe=True).show(frame=False) # long time @@ -671,7 +671,7 @@ def __init__(self, space, # Bounding box from sage.rings.real_mpfr import RR from sage.geometry.polyhedron.all import Polyhedron - from sage.combinat.cartesian_product import CartesianProduct + from itertools import product if bounding_box in RR: bounding_box = [[-bounding_box,bounding_box]] * self.dimension else: @@ -679,7 +679,7 @@ def __init__(self, space, raise TypeError("bounding_box argument doesn't match with the plot dimension") elif not all(len(b)==2 for b in bounding_box): raise TypeError("Invalid bounding box %s"%bounding_box) - self.bounding_box = Polyhedron(vertices=CartesianProduct(*bounding_box)) + self.bounding_box = Polyhedron(vertices=product(*bounding_box)) @cached_method def in_bounding_box(self, x): diff --git a/src/sage/combinat/root_system/root_lattice_realizations.py b/src/sage/combinat/root_system/root_lattice_realizations.py index 047a674a397..3df1e443fef 100644 --- a/src/sage/combinat/root_system/root_lattice_realizations.py +++ b/src/sage/combinat/root_system/root_lattice_realizations.py @@ -794,7 +794,7 @@ def positive_real_roots(self): if not self.cartan_type().is_affine(): raise NotImplementedError("only implemented for finite and affine Cartan types") - from sage.combinat.cartesian_product import CartesianProduct + from sage.categories.cartesian_product import cartesian_product from sage.combinat.root_system.root_system import RootSystem from sage.sets.positive_integers import PositiveIntegers from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets @@ -813,19 +813,19 @@ def lift(x): # Add all of the delta shifts delta = self.null_root() if self.cartan_type().is_untwisted_affine(): - C = CartesianProduct(PositiveIntegers(), Q.roots()) + C = cartesian_product([PositiveIntegers(), Q.roots()]) F = Family(C, lambda x: lift(x[1]) + x[0]*delta) D = DisjointUnionEnumeratedSets([P, F]) elif self.cartan_type().type() == 'BC' or self.cartan_type().dual().type() == 'BC': - Cs = CartesianProduct(PositiveIntegers(), Q.short_roots()) - Cl = CartesianProduct(PositiveIntegers(), Q.long_roots()) + Cs = cartesian_product([PositiveIntegers(), Q.short_roots()]) + Cl = cartesian_product([PositiveIntegers(), Q.long_roots()]) Fs = Family(Cl, lambda x: (lift(x[1]) + (2*x[0]-1)*delta) / 2) Fm = Family(Cs, lambda x: lift(x[1]) + x[0]*delta) Fl = Family(Cl, lambda x: lift(x[1]) + 2*x[0]*delta) D = DisjointUnionEnumeratedSets([P, Fs, Fm, Fl]) else: # Other twisted types - Cs = CartesianProduct(PositiveIntegers(), Q.short_roots()) - Cl = CartesianProduct(PositiveIntegers(), Q.long_roots()) + Cs = cartesian_product([PositiveIntegers(), Q.short_roots()]) + Cl = cartesian_product([PositiveIntegers(), Q.long_roots()]) Fs = Family(Cs, lambda x: lift(x[1]) + x[0]*delta) if self.cartan_type().dual() == 'G': # D_4^3 k = 3 diff --git a/src/sage/combinat/root_system/type_I.py b/src/sage/combinat/root_system/type_I.py index d97f9cd3d6f..bcbcb8dbfbf 100644 --- a/src/sage/combinat/root_system/type_I.py +++ b/src/sage/combinat/root_system/type_I.py @@ -22,7 +22,7 @@ def __init__(self, n): sage: ct.rank() 2 sage: ct.index_set() - [1, 2] + (1, 2) sage: ct.is_irreducible() True @@ -60,9 +60,9 @@ def index_set(self): EXAMPLES:: sage: CartanType(['I', 5]).index_set() - [1, 2] + (1, 2) """ - return [1, 2] + return (1, 2) def coxeter_diagram(self): """ @@ -94,3 +94,4 @@ def coxeter_number(self): 12 """ return self.n + diff --git a/src/sage/combinat/set_partition.py b/src/sage/combinat/set_partition.py index 383c3b9aa2d..b61d29cb9ee 100644 --- a/src/sage/combinat/set_partition.py +++ b/src/sage/combinat/set_partition.py @@ -38,7 +38,6 @@ from sage.rings.infinity import infinity from sage.rings.integer import Integer -from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.misc import IterableFunctionCall from sage.combinat.combinatorial_map import combinatorial_map import sage.combinat.subset as subset @@ -845,7 +844,7 @@ def refinements(self): [{}] """ L = [SetPartitions(part) for part in self] - return [SetPartition(sum(map(list, x), [])) for x in CartesianProduct(*L)] + return [SetPartition(sum(map(list, x), [])) for x in itertools.product(*L)] def coarsenings(self): """ @@ -1118,7 +1117,7 @@ def _iterator_part(self, part): for b in blocs: lb = [IterableFunctionCall(_listbloc, nonzero[i][0], nonzero[i][1], b[i]) for i in range(len(nonzero))] - for x in itertools.imap(lambda x: _union(x), CartesianProduct( *lb )): + for x in itertools.imap(lambda x: _union(x), itertools.product( *lb )): yield x def is_less_than(self, s, t): diff --git a/src/sage/combinat/sf/macdonald.py b/src/sage/combinat/sf/macdonald.py index 21e989c24a0..f7472b4aa42 100644 --- a/src/sage/combinat/sf/macdonald.py +++ b/src/sage/combinat/sf/macdonald.py @@ -1391,7 +1391,7 @@ def _m_to_self( self, f ): mu_to_H = lambda mu: self._self_to_m(self(mu)).theta_qt(q=self.t, t=0) out = {} while not g.is_zero(): - sprt = g.support() + sprt = sorted(g.support()) Hmu = mu_to_H(sprt[-1]) fl_sprt = fl(sprt[-1]) out[fl_sprt] = self._base(g.coefficient(sprt[-1]) / Hmu.coefficient(sprt[-1])) @@ -1625,7 +1625,7 @@ def _m_to_self( self, f ): g = f.omega_qt(q=subsval, t=0) out = {} while not g.is_zero(): - sprt = g.support() + sprt = sorted(g.support()) Htmu = self._self_to_m(self(fl(sprt[-1]))).omega_qt(q=subsval, t=0) out[fl(sprt[-1])] = self._base(g.coefficient(sprt[-1]) / Htmu.coefficient(sprt[-1])) g -= out[fl(sprt[-1])] * Htmu diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index c99b2bd525f..e82edcf7bb7 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -2095,8 +2095,8 @@ def transition_matrix(self, basis, n): sage: a = s([3,1])+5*s([1,1,1,1])-s([4]) sage: a 5*s[1, 1, 1, 1] + s[3, 1] - s[4] - sage: mon = a.support() - sage: coeffs = a.coefficients() + sage: mon = sorted(a.support()) + sage: coeffs = [a[i] for i in mon] sage: coeffs [5, 1, -1] sage: mon diff --git a/src/sage/combinat/sidon_sets.py b/src/sage/combinat/sidon_sets.py index 47a0055ec81..d595cb2b554 100644 --- a/src/sage/combinat/sidon_sets.py +++ b/src/sage/combinat/sidon_sets.py @@ -46,12 +46,12 @@ def sidon_sets(N, g = 1): sage: S.cardinality() 8 sage: S.category() - Category of sets + Category of finite sets sage: sid = S.an_element() sage: sid {2} sage: sid.category() - Category of sets + Category of finite sets TESTS:: diff --git a/src/sage/combinat/similarity_class_type.py b/src/sage/combinat/similarity_class_type.py index 9cbfc8b1774..2231480778b 100644 --- a/src/sage/combinat/similarity_class_type.py +++ b/src/sage/combinat/similarity_class_type.py @@ -176,12 +176,12 @@ class type, it is also possible to compute the number of classes of that type #***************************************************************************** from operator import mul -from itertools import chain +from itertools import chain, product from sage.misc.all import prod from sage.functions.all import factorial from sage.rings.arith import moebius from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass -from sage.structure.element import Element +from sage.structure.element import Element, parent from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets @@ -189,7 +189,6 @@ class type, it is also possible to compute the number of classes of that type from sage.combinat.partition import Partitions, Partition from sage.rings.all import ZZ, QQ, FractionField, divisors from sage.misc.cachefunc import cached_in_parent_method, cached_function -from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.misc import IterableFunctionCall from functools import reduce @@ -400,6 +399,25 @@ def __repr__(self): """ return "%s"%([self._deg, self._par]) + def __hash__(self): + r""" + TESTS:: + + sage: PT1 = PrimarySimilarityClassType(2, [3, 2, 1]) + sage: PT2 = PrimarySimilarityClassType(3, [3, 2, 1]) + sage: PT3 = PrimarySimilarityClassType(2, [4, 2, 1]) + sage: hash(PT1) + 5050909583595644741 # 64-bit + 1658169157 # 32-bit + sage: hash(PT2) + 5050909583595644740 # 64-bit + 1658169156 # 32-bit + sage: hash(PT3) + 6312110366011971308 # 64-bit + 1429493484 # 32-bit + """ + return hash(self._deg) ^ hash(tuple(self._par)) + def __eq__(self, other): """ Check equality. @@ -420,9 +438,25 @@ def __eq__(self, other): sage: PT1 == PT5 False """ - if isinstance(other, PrimarySimilarityClassType): - return self.degree() == other.degree() and self.partition() == other.partition() - return False + return isinstance(other, PrimarySimilarityClassType) and \ + self.degree() == other.degree() and \ + self.partition() == other.partition() + + def __ne__(self, other): + r""" + TESTS:: + + sage: PT1 = PrimarySimilarityClassType(2, [3, 2, 1]) + sage: PT2 = PrimarySimilarityClassType(2, Partition([3, 2, 1])) + sage: PT1 != PT2 + False + sage: PT3 = PrimarySimilarityClassType(3, [3, 2, 1]) + sage: PT1 != PT3 + True + """ + return not isinstance(other, PrimarySimilarityClassType) or \ + self.degree() != other.degree() or \ + self.partition() != other.partition() def size(self): """ @@ -1520,7 +1554,7 @@ def ext_orbit_centralizers(input_data, q = None, selftranspose = False): yield (item[0].substitute(q = q**tau.degree()), item[1].substitute(q = q**tau.degree())) elif case == 'sim': tau = data - for item in CartesianProduct(*[IterableFunctionCall(lambda x: ext_orbit_centralizers(x, q = q, selftranspose = selftranspose), PT) for PT in tau]): + for item in product(*[IterableFunctionCall(lambda x: ext_orbit_centralizers(x, q = q, selftranspose = selftranspose), PT) for PT in tau]): size = prod([list(entry)[0] for entry in item]) freq = prod([list(entry)[1] for entry in item]) yield(size, freq) diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index 84ac40d262d..cb1b6a308bc 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -40,7 +40,8 @@ from sage.structure.list_clone import ClonableList from sage.combinat.partition import Partition -from sage.combinat.tableau import Tableau, TableauOptions +from sage.combinat.tableau import (Tableau, TableauOptions, + StandardTableau, SemistandardTableau) from sage.combinat.skew_partition import SkewPartition, SkewPartitions from sage.combinat.integer_vector import IntegerVectors from sage.combinat.words.words import Words @@ -407,9 +408,9 @@ def conjugate(self): def to_word_by_row(self): """ Return a word obtained from a row reading of ``self``. - Specifically, this is the word obtained by concatenating the - rows from the bottommost one (in English notation) to the - topmost one. + + This is the word obtained by concatenating the rows from + the bottommost one (in English notation) to the topmost one. EXAMPLES:: @@ -434,19 +435,16 @@ def to_word_by_row(self): sage: SkewTableau([]).to_word_by_row() word: """ - word = [] - for row in self: - word = list(row) + word - - return Words("positive integers")([i for i in word if i is not None]) + word = [x for row in reversed(self) for x in row if x is not None] + return Words("positive integers")(word) def to_word_by_column(self): """ Return the word obtained from a column reading of the skew tableau. - Specifically, this is the word obtained by concatenating the - columns from the rightmost one (in English notation) to the - leftmost one. + + This is the word obtained by concatenating the columns from + the rightmost one (in English notation) to the leftmost one. EXAMPLES:: @@ -778,11 +776,11 @@ def to_chain(self, max_entry=None): def slide(self, corner=None): """ - Apply a jeu-de-taquin slide to ``self`` on the specified corner and - returns the new tableau. If no corner is given an arbitrary corner - is chosen. + Apply a jeu-de-taquin slide to ``self`` on the specified inner corner and + return the resulting tableau. If no corner is given, an arbitrary inner + corner is chosen. - See [FW]_ p12-13. + See [Fulton97]_ p12-13. EXAMPLES:: @@ -854,38 +852,74 @@ def slide(self, corner=None): return SkewTableau(new_st) - def rectify(self): + def rectify(self, algorithm=None): """ - Return a :class:`Tableau` formed by applying the jeu de taquin - process to ``self``. See page 15 of [FW]_. + Return a :class:`StandardTableau`, :class:`SemistandardTableau`, + or just :class:`Tableau` formed by applying the jeu de taquin + process to ``self``. - REFERENCES: + See page 15 of [Fulton97]_. - .. [FW] William Fulton, - *Young Tableaux*, - Cambridge University Press 1997. + INPUT: + + - ``algorithm`` -- optional: if set to ``'jdt'``, rectifies by jeu de + taquin; if set to ``'schensted'``, rectifies by Schensted insertion + of the reading word; otherwise, guesses which will be faster. EXAMPLES:: - sage: s = SkewTableau([[None,1],[2,3]]) - sage: s.rectify() + sage: S = SkewTableau([[None,1],[2,3]]) + sage: S.rectify() [[1, 3], [2]] - sage: SkewTableau([[None, None, None, 4],[None,None,1,6],[None,None,5],[2,3]]).rectify() + sage: T = SkewTableau([[None, None, None, 4],[None,None,1,6],[None,None,5],[2,3]]) + sage: T.rectify() + [[1, 3, 4, 6], [2, 5]] + sage: T.rectify(algorithm='jdt') [[1, 3, 4, 6], [2, 5]] + sage: T.rectify(algorithm='schensted') + [[1, 3, 4, 6], [2, 5]] + sage: T.rectify(algorithm='spaghetti') + Traceback (most recent call last): + ... + ValueError: algorithm must be 'jdt', 'schensted', or None TESTS:: - sage: s + sage: S [[None, 1], [2, 3]] - """ - rect = copy.deepcopy(self) - inner_corners = rect.inner_shape().corners() + sage: T + [[None, None, None, 4], [None, None, 1, 6], [None, None, 5], [2, 3]] + + REFERENCES: - while len(inner_corners) > 0: - rect = rect.slide() - inner_corners = rect.inner_shape().corners() + .. [Fulton97] William Fulton, *Young Tableaux*, + Cambridge University Press 1997. + """ + mu_size = self.inner_shape().size() - return rect.to_tableau() + # Roughly, use jdt with a small inner shape, Schensted with a large one + if algorithm is None: + la = self.outer_shape() + la_size = la.size() + if mu_size ** 2 < len(la) * (la_size - mu_size): + algorithm = 'jdt' + else: + algorithm = 'schensted' + + if algorithm == 'jdt': + rect = self + for i in range(mu_size): + rect = rect.slide() + elif algorithm == 'schensted': + w = [x for row in reversed(self) for x in row if x is not None] + rect = Tableau([]).insert_word(w) + else: + raise ValueError("algorithm must be 'jdt', 'schensted', or None") + if self in StandardSkewTableaux(): + return StandardTableau(rect[:]) + if self in SemistandardSkewTableaux(): + return SemistandardTableau(rect[:]) + return Tableau(rect) def standardization(self, check=True): r""" @@ -1773,9 +1807,10 @@ def cardinality(self): def __iter__(self): """ - An iterator for all the standard skew tableaux with shape of the - skew partition ``skp``. The standard skew tableaux are ordered - lexicographically by the word obtained from their row reading. + An iterator for all the standard skew tableaux whose shape is + the skew partition ``skp``. The standard skew tableaux are + ordered lexicographically by the word obtained from their row + reading. EXAMPLES:: diff --git a/src/sage/combinat/species/composition_species.py b/src/sage/combinat/species/composition_species.py index cbc23c9e43f..88e5544305f 100644 --- a/src/sage/combinat/species/composition_species.py +++ b/src/sage/combinat/species/composition_species.py @@ -42,7 +42,7 @@ def __repr__(self): sage: E = species.SetSpecies(); C = species.CycleSpecies() sage: L = E(C) sage: L.structures(['a','b','c']).random_element() - F-structure: {{'a', 'b', 'c'}}; G-structures: [('a', 'b', 'c')] + F-structure: {{'a', 'b', 'c'}}; G-structures: (('a', 'b', 'c'),) """ f, gs = self._list return "F-structure: %s; G-structures: %s"%(repr(f), repr(gs)) @@ -56,9 +56,9 @@ def transport(self, perm): sage: L = E(C) sage: S = L.structures(['a','b','c']).list() sage: a = S[2]; a - F-structure: {{'a', 'c'}, {'b'}}; G-structures: [('a', 'c'), ('b')] + F-structure: {{'a', 'c'}, {'b'}}; G-structures: (('a', 'c'), ('b')) sage: a.transport(p) - F-structure: {{'a', 'b'}, {'c'}}; G-structures: [('a', 'c'), ('b')] + F-structure: {{'a', 'b'}, {'c'}}; G-structures: (('a', 'c'), ('b')) """ f, gs = self._list pi = self._partition.transport(perm) @@ -75,7 +75,7 @@ def change_labels(self, labels): sage: L = E(C) sage: S = L.structures(['a','b','c']).list() sage: a = S[2]; a - F-structure: {{'a', 'c'}, {'b'}}; G-structures: [('a', 'c'), ('b')] + F-structure: {{'a', 'c'}, {'b'}}; G-structures: (('a', 'c'), ('b')) sage: a.change_labels([1,2,3]) F-structure: {{1, 3}, {2}}; G-structures: [(1, 3), (2)] """ @@ -126,12 +126,12 @@ def _structures(self, structure_class, labels): sage: E = species.SetSpecies(); C = species.CycleSpecies() sage: L = E(C) sage: L.structures(['a','b','c']).list() - [F-structure: {{'a', 'b', 'c'}}; G-structures: [('a', 'b', 'c')], - F-structure: {{'a', 'b', 'c'}}; G-structures: [('a', 'c', 'b')], - F-structure: {{'a', 'c'}, {'b'}}; G-structures: [('a', 'c'), ('b')], - F-structure: {{'a', 'b'}, {'c'}}; G-structures: [('a', 'b'), ('c')], - F-structure: {{'b', 'c'}, {'a'}}; G-structures: [('b', 'c'), ('a')], - F-structure: {{'a'}, {'b'}, {'c'}}; G-structures: [('a'), ('b'), ('c')]] + [F-structure: {{'a', 'b', 'c'}}; G-structures: (('a', 'b', 'c'),), + F-structure: {{'a', 'b', 'c'}}; G-structures: (('a', 'c', 'b'),), + F-structure: {{'a', 'c'}, {'b'}}; G-structures: (('a', 'c'), ('b')), + F-structure: {{'a', 'b'}, {'c'}}; G-structures: (('a', 'b'), ('c')), + F-structure: {{'b', 'c'}, {'a'}}; G-structures: (('b', 'c'), ('a')), + F-structure: {{'a'}, {'b'}, {'c'}}; G-structures: (('a'), ('b'), ('c'))] TESTS:: @@ -152,16 +152,16 @@ def _structures(self, structure_class, labels): sage: [g._list for g in gs] [[1, 2], [1]] """ - from sage.combinat.cartesian_product import CartesianProduct + from itertools import product P = PartitionSpecies() for pi in P.structures(labels): #The labels of the G-structures will be just be the things #in labels - gs = CartesianProduct(*[self._G.structures(part.labels()) for part in pi]) + gs = product(*[self._G.structures(part.labels()) for part in pi]) #The labels of the F-structure will be set objects fs = self._F.structures(list(pi)) - for f, gg in CartesianProduct(fs, gs): + for f, gg in product(fs, gs): yield structure_class(self, labels, pi, f, gg) def _isotypes(self, structure_class, labels): diff --git a/src/sage/combinat/tableau.py b/src/sage/combinat/tableau.py index 24227877be1..2dfe0da2d39 100644 --- a/src/sage/combinat/tableau.py +++ b/src/sage/combinat/tableau.py @@ -2845,7 +2845,7 @@ def row_stabilizer(self): k = self.size() gens = [range(1, k+1)] for row in self: - for j in range(0, len(row)-1): + for j in range(len(row)-1): gens.append( (row[j], row[j+1]) ) return PermutationGroup( gens ) diff --git a/src/sage/combinat/tutorial.py b/src/sage/combinat/tutorial.py index 40156bb03a9..03b7058fc92 100644 --- a/src/sage/combinat/tutorial.py +++ b/src/sage/combinat/tutorial.py @@ -61,8 +61,8 @@ sage: Suits = Set(["Hearts", "Diamonds", "Spades", "Clubs"]) sage: Values = Set([2, 3, 4, 5, 6, 7, 8, 9, 10, - ... "Jack", "Queen", "King", "Ace"]) - sage: Cards = CartesianProduct(Values, Suits) + ....: "Jack", "Queen", "King", "Ace"]) + sage: Cards = cartesian_product([Values, Suits]) There are `4` suits and `13` possible values, and therefore `4\times 13=52` cards in the poker deck:: @@ -77,21 +77,7 @@ Draw a card at random:: sage: Cards.random_element() # random - [6, 'Clubs'] - -A small technical digression is necessary here. The elements of a -Cartesian product are returned in the form of lists:: - - sage: type(Cards.random_element()) - - -A ``Python`` list not being immutable, it cannot be an element of a set, which -would cause us a problem later. We will therefore redefine our -Cartesian product so that its elements are represented by tuples:: - - sage: Cards = CartesianProduct(Values, Suits).map(tuple) - sage: Cards.an_element() - ('King', 'Hearts') + (6, 'Clubs') Now we can define a set of cards:: @@ -136,7 +122,7 @@ We will construct the set of all flushes, so as to determine how many there are:: - sage: Flushes = CartesianProduct(Subsets(Values, 5), Suits) + sage: Flushes = cartesian_product([Subsets(Values, 5), Suits]) sage: Flushes.cardinality() 5148 @@ -157,7 +143,7 @@ function tests whether a given hand is a flush or not:: sage: def is_flush(hand): - ... return len(set(suit for (val, suit) in hand)) == 1 + ....: return len(set(suit for (val, suit) in hand)) == 1 We now draw 10000 hands at random, and count the number of flushes obtained (this takes about 10 seconds):: @@ -165,9 +151,9 @@ sage: n = 10000 sage: nflush = 0 sage: for i in range(n): # long time - ... hand = Hands.random_element() - ... if is_flush(hand): - ... nflush += 1 + ....: hand = Hands.random_element() + ....: if is_flush(hand): + ....: nflush += 1 sage: print n, nflush # random 10000 18 @@ -647,7 +633,7 @@ {2, 4} but this should be used with care because some sets have a -natural indexing other than by `(0,\dots)`. +natural indexing other than by `(0, 1, \dots)`. Conversely, one can calculate the position of an object in this order:: @@ -668,7 +654,7 @@ which is roughly `2\cdot 10^{19728}`:: - sage: n.ndigits() # long time + sage: n.ndigits() 19729 or ask for its `237102124`-th element:: @@ -1029,7 +1015,7 @@ comprehensions provide a much pleasanter syntax:: sage: for s in Subsets(3): - ... print s + ....: print s {} {1} {2} @@ -1095,7 +1081,7 @@ sage: def mersenne(p): return 2^p -1 sage: [ is_prime(p) - ... for p in range(1000) if is_prime(mersenne(p)) ] + ....: for p in range(1000) if is_prime(mersenne(p)) ] [True, True, True, True, True, True, True, True, True, True, True, True, True, True] @@ -1107,24 +1093,24 @@ difference in the length of the calculations:: sage: all( is_prime(mersenne(p)) - ... for p in range(1000) if is_prime(p) ) + ....: for p in range(1000) if is_prime(p) ) False sage: all( [ is_prime(mersenne(p)) - ... for p in range(1000) if is_prime(p)] ) + ....: for p in range(1000) if is_prime(p)] ) False We now try to find the smallest counter-example. In order to do this, we use the ``Sage`` function ``exists``:: sage: exists( (p for p in range(1000) if is_prime(p)), - ... lambda p: not is_prime(mersenne(p)) ) + ....: lambda p: not is_prime(mersenne(p)) ) (True, 11) Alternatively, we could construct an interator on the counter-examples:: sage: counter_examples = \ - ... (p for p in range(1000) - ... if is_prime(p) and not is_prime(mersenne(p))) + ....: (p for p in range(1000) + ....: if is_prime(p) and not is_prime(mersenne(p))) sage: next(counter_examples) 11 sage: next(counter_examples) @@ -1138,10 +1124,10 @@ sage: cubes = [t**3 for t in range(-999,1000)] sage: exists([(x,y) for x in cubes for y in cubes], # long time (3s, 2012) - ... lambda (x,y): x+y == 218) + ....: lambda (x,y): x+y == 218) (True, (-125, 343)) sage: exists(((x,y) for x in cubes for y in cubes), # long time (2s, 2012) - ... lambda (x,y): x+y == 218) + ....: lambda (x,y): x+y == 218) (True, (-125, 343)) Which of the last two is more economical in terms of time? In terms @@ -1236,7 +1222,7 @@ :: sage: counter_examples = (p for p in Primes() - ... if not is_prime(mersenne(p))) + ....: if not is_prime(mersenne(p))) sage: for p in counter_examples: print p # not tested 11 23 @@ -1278,23 +1264,23 @@ apply a function to all the elements:: sage: list(itertools.imap(lambda z: z.cycle_type(), - ... Permutations(3))) + ....: Permutations(3))) [[1, 1, 1], [2, 1], [2, 1], [3], [3], [2, 1]] or select the elements satisfying a certain condition:: sage: list(itertools.ifilter(lambda z: z.has_pattern([1,2]), - ... Permutations(3))) + ....: Permutations(3))) [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2]] In all these situations, ``attrcall`` can be an advantageous alternative to creating an anonymous function:: sage: list(itertools.imap(lambda z: z.cycle_type(), - ... Permutations(3))) + ....: Permutations(3))) [[1, 1, 1], [2, 1], [2, 1], [3], [3], [2, 1]] sage: list(itertools.imap(attrcall("cycle_type"), - ... Permutations(3))) + ....: Permutations(3))) [[1, 1, 1], [2, 1], [2, 1], [3], [3], [2, 1]] Implementation of new interators @@ -1304,8 +1290,8 @@ instead of ``return`` in a function:: sage: def f(n): - ... for i in range(n): - ... yield i + ....: for i in range(n): + ....: yield i After the ``yield``, execution is not halted, but only suspended, ready to be continued from the same point. The result of the function is @@ -1338,12 +1324,12 @@ generate all words of a given length on a given alphabet:: sage: def words(alphabet,l): - ... if l == 0: - ... yield [] - ... else: - ... for word in words(alphabet, l-1): - ... for l in alphabet: - ... yield word + [l] + ....: if l == 0: + ....: yield [] + ....: else: + ....: for word in words(alphabet, l-1): + ....: for l in alphabet: + ....: yield word + [l] sage: [ w for w in words(['a','b'], 3) ] [['a', 'a', 'a'], ['a', 'a', 'b'], ['a', 'b', 'a'], ['a', 'b', 'b'], ['b', 'a', 'a'], ['b', 'a', 'b'], @@ -1367,13 +1353,13 @@ `w_1` and `w_2` are Dyck words:: sage: def dyck_words(l): - ... if l==0: - ... yield '' - ... else: - ... for k in range(l): - ... for w1 in dyck_words(k): - ... for w2 in dyck_words(l-k-1): - ... yield '('+w1+')'+w2 + ....: if l==0: + ....: yield '' + ....: else: + ....: for k in range(l): + ....: for w1 in dyck_words(k): + ....: for w2 in dyck_words(l-k-1): + ....: yield '('+w1+')'+w2 Here are all the Dyck words of length `4`:: @@ -1423,27 +1409,29 @@ Consider a large Cartesian product:: - sage: C = CartesianProduct(Compositions(8), Permutations(20)); C - Cartesian product of Compositions of 8, Standard permutations of 20 + sage: C = cartesian_product([Compositions(8), Permutations(20)]); C + The cartesian product of (Compositions of 8, Standard permutations of 20) sage: C.cardinality() 311411457046609920000 -Clearly, it is impractical to construct the list of all the elements of -this Cartesian product. For the moment, the contruction -``CartesianProduct`` ignores the algebraic properties of its arguments. -This is partially corrected in Sage 4.4.4, with the construction -``cartesian_product``. Eventually, these two constructions will be merged -and, in the following example, `H` will be equipped with the -usual combinatorial operations and also its structure as a product -group:: +Clearly, it is impractical to construct the list of all the elements of this +Cartesian product! And, in the following example, `H` is equipped with the +usual combinatorial operations and also its structure as a product group:: sage: G = DihedralGroup(4) sage: H = cartesian_product([G,G]) + sage: H in Groups() + True + sage: t = H.an_element() + sage: t + ((1,2,3,4), (1,2,3,4)) + sage: t*t + ((1,3)(2,4), (1,3)(2,4)) We now construct the union of two existing disjoint sets:: sage: C = DisjointUnionEnumeratedSets( - ... [ Compositions(4), Permutations(3)] ) + ....: [ Compositions(4), Permutations(3)] ) sage: C Disjoint union of Family (Compositions of 4, Standard permutations of 3) @@ -1482,7 +1470,7 @@ necessary to interrupt it at some point:: sage: for p in U: # not tested - ... print p + ....: print p [] [1] [1, 2] @@ -1539,21 +1527,21 @@ and length `3`, with parts bounded below by `2`, `4` and `2` respectively:: - sage: IntegerVectors(10, 3, min_part = 2, max_part = 5, - ... inner = [2, 4, 2]).list() + sage: IntegerVectors(10, 3, min_part=2, max_part=5, + ....: inner=[2, 4, 2]).list() [[4, 4, 2], [3, 5, 2], [3, 4, 3], [2, 5, 3], [2, 4, 4]] The compositions of `5` with each part at most `3`, and with length `2` or `3`:: - sage: Compositions(5, max_part = 3, - ... min_length = 2, max_length = 3).list() + sage: Compositions(5, max_part=3, + ....: min_length=2, max_length=3).list() [[3, 2], [3, 1, 1], [2, 3], [2, 2, 1], [2, 1, 2], [1, 3, 1], [1, 2, 2], [1, 1, 3]] The strictly decreasing partitions of `5`:: - sage: Partitions(5, max_slope = -1).list() + sage: Partitions(5, max_slope=-1).list() [[5], [4, 1], [3, 2]] These sets share the same underlying algorithmic structure, implemented @@ -1565,16 +1553,16 @@ examples:: sage: IntegerListsLex(10, length=3, - ... min_part = 2, max_part = 5, - ... floor = [2, 4, 2]).list() + ....: min_part=2, max_part=5, + ....: floor=[2, 4, 2]).list() [[4, 4, 2], [3, 5, 2], [3, 4, 3], [2, 5, 3], [2, 4, 4]] - sage: IntegerListsLex(5, min_part = 1, max_part = 3, - ... min_length = 2, max_length = 3).list() + sage: IntegerListsLex(5, min_part=1, max_part=3, + ....: min_length=2, max_length=3).list() [[3, 2], [3, 1, 1], [2, 3], [2, 2, 1], [2, 1, 2], [1, 3, 1], [1, 2, 2], [1, 1, 3]] - sage: IntegerListsLex(5, min_part = 1, max_slope = -1).list() + sage: IntegerListsLex(5, min_part=1, max_slope=-1).list() [[5], [4, 1], [3, 2]] sage: list(Compositions(5, max_length=2)) @@ -1842,7 +1830,7 @@ selecting only the children which are planar:: sage: [len(list(graphs(n, property = lambda G: G.is_planar()))) - ... for n in range(7)] + ....: for n in range(7)] [1, 1, 2, 4, 11, 33, 142] In a similar fashion, one can generate any family of graphs closed diff --git a/src/sage/combinat/words/word_char.pyx b/src/sage/combinat/words/word_char.pyx index bef4c943c05..0c55949c019 100644 --- a/src/sage/combinat/words/word_char.pyx +++ b/src/sage/combinat/words/word_char.pyx @@ -25,6 +25,8 @@ from cpython.number cimport PyIndex_Check, PyNumber_Check from cpython.sequence cimport PySequence_Check from cpython.slice cimport PySlice_Check, PySlice_GetIndicesEx +import itertools + # the maximum value of a size_t cdef size_t SIZE_T_MAX = -( 1) @@ -550,7 +552,6 @@ cdef class WordDatatype_char(WordDatatype): return w._new_c(data, new_length, None) - @cython.boundscheck(False) def has_prefix(self, other): r""" Test whether ``other`` is a prefix of ``self``. @@ -573,6 +574,22 @@ cdef class WordDatatype_char(WordDatatype): True sage: w.has_prefix(w[1:]) False + + TESTS: + + :trac:`19322`:: + + sage: W = Words([0,1,2]) + sage: w = W([0,1,0,2]) + sage: w.has_prefix(words.FibonacciWord()) + False + + sage: w.has_prefix([0,1,0,2,0]) + False + sage: w.has_prefix([0,1,0,2]) + True + sage: w.has_prefix([0,1,0]) + True """ cdef size_t i cdef WordDatatype_char w @@ -582,15 +599,16 @@ cdef class WordDatatype_char(WordDatatype): w = other if w._length > self._length: return False - return memcmp(self._data, w._data, w._length) == 0 + return memcmp(self._data, w._data, w._length * sizeof(unsigned char)) == 0 elif PySequence_Check(other): # python level - if len(other) > self._length: + from sage.combinat.words.infinite_word import InfiniteWord_class + if isinstance(other, InfiniteWord_class) or len(other) > len(self): return False for i in range(len(other)): - if other[i] != self._data[i]: + if other[i] != self[i]: return False return True @@ -638,3 +656,151 @@ cdef class WordDatatype_char(WordDatatype): return memcmp(self._data, self._data + l, l * sizeof(unsigned char)) == 0 + + def longest_common_prefix(self, other): + r""" + Return the longest common prefix of this word and ``other``. + + EXAMPLES:: + + sage: W = Words([0,1,2]) + sage: W([0,1,0,2]).longest_common_prefix([0,1]) + word: 01 + sage: u = W([0,1,0,0,1]) + sage: v = W([0,1,0,2]) + sage: u.longest_common_prefix(v) + word: 010 + sage: v.longest_common_prefix(u) + word: 010 + + Using infinite words is also possible (and the return type is also a + of the same type as ``self``):: + + sage: W([0,1,0,0]).longest_common_prefix(words.FibonacciWord()) + word: 0100 + sage: type(_) + + + An example of an intensive usage:: + + sage: W = Words([0,1]) + sage: w = words.FibonacciWord() + sage: w = W(list(w[:5000])) + sage: L = [[len(w[n:].longest_common_prefix(w[n+fibonacci(i):])) + ....: for i in range(5,15)] for n in range(1,1000)] + sage: for n,l in enumerate(L): + ....: if l.count(0) > 4: print n+1,l + 375 [0, 13, 0, 34, 0, 89, 0, 233, 0, 233] + 376 [0, 12, 0, 33, 0, 88, 0, 232, 0, 232] + 608 [8, 0, 21, 0, 55, 0, 144, 0, 377, 0] + 609 [7, 0, 20, 0, 54, 0, 143, 0, 376, 0] + 985 [0, 13, 0, 34, 0, 89, 0, 233, 0, 610] + 986 [0, 12, 0, 33, 0, 88, 0, 232, 0, 609] + + TESTS:: + + sage: W = Words([0,1,2]) + sage: w = W([0,2,1,0,0,1]) + sage: w.longest_common_prefix(0) + Traceback (most recent call last): + ... + TypeError: unsupported input 0 + """ + cdef WordDatatype_char w + cdef size_t i + cdef size_t m + + if isinstance(other, WordDatatype_char): + # C level + # (this can be much faster if we allow to compare larger memory + # zones) + w = other + m = min(self._length, w._length) + for i in range(m): + if self._data[i] != w._data[i]: + break + else: + if self._length <= w._length: + return self + else: + return other + + return self._new_c(self._data, i, self) + + elif PySequence_Check(other): + # Python level + # we avoid to call len(other) since it might be an infinite word + for i,a in enumerate(itertools.islice(other, self._length)): + if self._data[i] != a: + break + else: + i += 1 + + return self._new_c(self._data, i, self) + + raise TypeError("unsupported input {}".format(other)) + + def longest_common_suffix(self, other): + r""" + Return the longest common suffix between this word and ``other``. + + EXAMPLES:: + + sage: W = Words([0,1,2]) + sage: W([0,1,0,2]).longest_common_suffix([2,0,2]) + word: 02 + sage: u = W([0,1,0,0,1]) + sage: v = W([1,2,0,0,1]) + sage: u.longest_common_suffix(v) + word: 001 + sage: v.longest_common_suffix(u) + word: 001 + + TESTS:: + + sage: W = Words([0,1,2]) + sage: w = W([0,2,1,0,0,1]) + sage: w.longest_common_suffix(0) + Traceback (most recent call last): + ... + TypeError: unsupported input 0 + """ + cdef WordDatatype_char w + cdef size_t i + cdef size_t m + cdef size_t lo + + if isinstance(other, WordDatatype_char): + # C level + # (this can be much faster if we could compare larger memory + # zones) + w = other + m = min(self._length, w._length) + for i in range(m): + if self._data[self._length-i-1] != w._data[w._length-i-1]: + break + else: + if self._length <= w._length: + return self + else: + return other + + return self._new_c(self._data+self._length-i, i, self) + + elif PySequence_Check(other): + # Python level + lo = len(other) + m = min(self._length, lo) + for i in range(m): + if self._data[self._length-i-1] != other[lo-i-1]: + break + else: + if self._length == m: + return self + else: + i += 1 + + return self._new_c(self._data+self._length-i, i, self) + + raise TypeError("unsupported input {}".format(other)) + diff --git a/src/sage/combinat/words/words.py b/src/sage/combinat/words/words.py index e2e272dd347..a1bb2e3be03 100644 --- a/src/sage/combinat/words/words.py +++ b/src/sage/combinat/words/words.py @@ -1376,11 +1376,10 @@ def iter_morphisms(self, arg=None, codomain=None, min_length=1): # create an iterable of compositions (all "compositions" if arg is # None, or [arg] otherwise) if arg is None: - # TODO in #17927: use IntegerVectors(length=n, min_part=min_length) - from sage.combinat.integer_list import IntegerListsNN + from sage.combinat.integer_lists.nn import IntegerListsNN compositions = IntegerListsNN(length=n, min_part=min_length) elif isinstance(arg, tuple): - from sage.combinat.integer_list import IntegerListsLex + from sage.combinat.integer_lists import IntegerListsLex a, b = arg compositions = IntegerListsLex(min_sum=a, max_sum=b-1, length=n, min_part=min_length) diff --git a/src/sage/data_structures/mutable_poset.py b/src/sage/data_structures/mutable_poset.py new file mode 100644 index 00000000000..2f30ccb4535 --- /dev/null +++ b/src/sage/data_structures/mutable_poset.py @@ -0,0 +1,3522 @@ +r""" +Mutable Poset + +This module provides a class representing a finite partially ordered +set (poset) for the purpose of being used as a data structure. Thus +the posets introduced in this module are mutable, i.e., elements can +be added and removed from a poset at any time. + +To get in touch with Sage's "usual" posets, start with the page +:mod:`Posets ` in the reference manual. + + +.. _mutable_poset_examples: + +Examples +======== + +First Steps +----------- + +We start by creating an empty poset. This is simply done by + +:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P + poset() + +A poset should contain elements, thus let us add them with + +:: + + sage: P.add(42) + sage: P.add(7) + sage: P.add(13) + sage: P.add(3) + +Let us look at the poset again:: + + sage: P + poset(3, 7, 13, 42) + +We see that they elements are sorted using `\leq` which exists on the +integers `\ZZ`. Since this is even a total order, we could have used a +more efficient data structure. Alternativly, we can write +:: + + sage: MP([42, 7, 13, 3]) + poset(3, 7, 13, 42) + +to add several elements at once on construction. + + +A less boring Example +--------------------- + +Let us continue with a less boring example. We define the class + +:: + + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + +It is equipped with a `\leq`-operation such that `a \leq b` if all +entries of `a` are at most the corresponding entry of `b`. For +example, we have + +:: + + sage: a = T((1,1)) + sage: b = T((2,1)) + sage: c = T((1,2)) + sage: a <= b, a <= c, b <= c + (True, True, False) + +The last comparison gives ``False``, since the comparison of the +first component checks whether `2 \leq 1`. + +Now, let us add such elements to a poset:: + + sage: Q = MP([T((1, 1)), T((3, 3)), T((4, 1)), + ....: T((3, 2)), T((2, 3)), T((2, 2))]); Q + poset((1, 1), (2, 2), (2, 3), (3, 2), (3, 3), (4, 1)) + +In the representation above, the elements are sorted topologically, +smallest first. This does not (directly) show more structural +information. We can overcome this and display a "wiring layout" by +typing:: + + sage: print Q.repr_full(reverse=True) + poset((3, 3), (2, 3), (3, 2), (2, 2), (4, 1), (1, 1)) + +-- oo + | +-- no successors + | +-- predecessors: (3, 3), (4, 1) + +-- (3, 3) + | +-- successors: oo + | +-- predecessors: (2, 3), (3, 2) + +-- (2, 3) + | +-- successors: (3, 3) + | +-- predecessors: (2, 2) + +-- (3, 2) + | +-- successors: (3, 3) + | +-- predecessors: (2, 2) + +-- (2, 2) + | +-- successors: (2, 3), (3, 2) + | +-- predecessors: (1, 1) + +-- (4, 1) + | +-- successors: oo + | +-- predecessors: (1, 1) + +-- (1, 1) + | +-- successors: (2, 2), (4, 1) + | +-- predecessors: null + +-- null + | +-- successors: (1, 1) + | +-- no predecessors + +Note that we use ``reverse=True`` to let the elements appear from +largest (on the top) to smallest (on the bottom). + +If you look at the output above, you'll see two additional elements, +namely ``oo`` (`\infty`) and ``null`` (`\emptyset`). So what are these +strange animals? The answer is simple and maybe you can guess it +already. The `\infty`-element is larger than every other element, +therefore a successor of the maximal elements in the poset. Similarly, +the `\emptyset`-element is smaller than any other element, therefore a +predecessor of the poset's minimal elements. Both do not have to scare +us; they are just there and sometimes useful. + + +AUTHORS: + +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Daniel Krenn is supported by the Austrian Science Fund (FWF): P 24644-N26. + +Classes and their Methods +========================= +""" +#***************************************************************************** +# Copyright (C) 2015 Daniel Krenn +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.structure.sage_object import SageObject + + +class MutablePosetShell(SageObject): + r""" + A shell for an element of a :class:`mutable poset `. + + INPUT: + + - ``poset`` -- the poset to which this shell belongs. + + - ``element`` -- the element which should be + contained/encapsulated in this shell. + + OUTPUT: + + A shell for the given element. + + .. NOTE:: + + If the :meth:`element` of a shell is ``None``, then this + element is considered as "special" (see :meth:`is_special`). + There are two special elements, namely + + - a ``'null'`` (an element smaller than each other element; + it has no predecessors) and + - an ``'oo'`` (an element larger than each other element; + it has no successors). + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(66) + sage: P + poset(66) + sage: s = P.shell(66) + sage: type(s) + + + .. SEEALSO:: + + :class:`MutablePoset` + """ + def __init__(self, poset, element): + r""" + See :class:`MutablePosetShell` for details. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: MutablePosetShell(P, (1, 2)) + (1, 2) + """ + self._poset_ = poset + self._element_ = element + self._key_ = self.poset.get_key(element) + self._predecessors_ = set() + self._successors_ = set() + super(MutablePosetShell, self).__init__() + + + @property + def poset(self): + r""" + The poset to which this shell belongs. + + .. SEEALSO:: + + :class:`MutablePoset` + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: e = MutablePosetShell(P, (1, 2)) + sage: e.poset is P + True + """ + return self._poset_ + + + @property + def element(self): + r""" + The element contained in this shell. + + .. SEEALSO:: + + :meth:`key`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: e = MutablePosetShell(P, (1, 2)) + sage: e.element + (1, 2) + """ + return self._element_ + + + @property + def key(self): + r""" + The key of the element contained in this shell. + + The key of an element is determined by the mutable poset (the + parent) via the ``key``-function (see construction of a + :class:`MutablePoset`). + + .. SEEALSO:: + + :meth:`element`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: P = MP() + sage: e = MutablePosetShell(P, (1, 2)) + sage: e.key + (1, 2) + sage: Q = MP(key=lambda k: k[0]) + sage: f = MutablePosetShell(Q, (1, 2)) + sage: f.key + 1 + + Test the caching of the key:: + + sage: def k(k): + ....: print 'key %s' % (k,) + ....: return k + sage: R = MP(key=k) + sage: h = MutablePosetShell(R, (1, 2)) + key (1, 2) + sage: h.key; h.key + (1, 2) + (1, 2) + """ + return self._key_ + + + def predecessors(self, reverse=False): + r""" + Return the predecessors of this shell. + + INPUT: + + - ``reverse`` -- (default: ``False``) if set, then return + successors instead. + + OUTPUT: + + A set. + + .. SEEALSO:: + + :meth:`successors`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: e = MutablePosetShell(P, (1, 2)) + sage: e.predecessors() + set() + """ + if reverse: + return self._successors_ + return self._predecessors_ + + + def successors(self, reverse=False): + r""" + Return the successors of this shell. + + INPUT: + + - ``reverse`` -- (default: ``False``) if set, then return + predecessors instead. + + OUTPUT: + + A set. + + .. SEEALSO:: + + :meth:`predecessors`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: e = MutablePosetShell(P, (1, 2)) + sage: e.successors() + set() + """ + if reverse: + return self._predecessors_ + return self._successors_ + + + def is_special(self): + r""" + Return whether this shell contains either the null-element, i.e., the + element smaller than any possible other element or the + infinity-element, i.e., the element larger than any possible + other element. + + INPUT: + + Nothing. + + OUTPUT: + + ``True`` or ``False``. + + .. SEEALSO:: + + :meth:`is_null`, + :meth:`is_oo`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.null.is_special() + True + sage: P.oo.is_special() + True + """ + return self.element is None + + + def is_null(self): + r""" + Return whether this shell contains the null-element, i.e., the element + smaller than any possible other element. + + OUTPUT: + + ``True`` or ``False``. + + .. SEEALSO:: + + :meth:`is_special`, + :meth:`is_oo`, + :meth:`MutablePoset.null`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.null.is_null() + True + sage: P.oo.is_null() + False + """ + return self.element is None and not self.predecessors() + + + def is_oo(self): + r""" + Return whether this shell contains the infinity-element, i.e., the element + larger than any possible other element. + + OUTPUT: + + ``True`` or ``False``. + + .. SEEALSO:: + + :meth:`is_null`, + :meth:`is_special`, + :meth:`MutablePoset.oo`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.null.is_oo() + False + sage: P.oo.is_oo() + True + """ + return self.element is None and not self.successors() + + + def _repr_(self): + r""" + Return the representation of this shell. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + .. NOTE:: + + If the :meth:`element` of this shell is not ``None``, + this method returns the respective representation string. + Otherwise, ``'null'`` or ``'oo'`` are returned, + depending on the non-existence of predecessors or + successors, respectively. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: repr(MutablePosetShell(P, (1, 2))) # indirect doctest + '(1, 2)' + sage: repr(P.null) # indirect doctest + 'null' + sage: repr(P.oo) # indirect doctest + 'oo' + """ + if self.is_null(): + return 'null' + elif self.is_oo(): + return 'oo' + else: + return repr(self.element) + + + def __hash__(self): + r""" + Return the hash of this shell. + + INPUT: + + Nothing. + + OUTPUT: + + A hash value. + + This returns the hash value of the key of the element + contained in this shell. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: hash(MutablePosetShell(P, (1, 2))) == hash((1, 2)) + True + """ + return hash(self.key) + + + def le(self, other, reverse=False): + r""" + Return whether this shell is less than or equal to ``other``. + + INPUT: + + - ``other`` -- a shell. + + - ``reverse`` -- (default: ``False``) if set, then return + whether this shell is greater than or equal to ``other``. + + OUTPUT: + + ``True`` or ``False``. + + .. NOTE:: + + The comparison of the shells is based on the comparison + of the keys of the elements contained in the shells, + except for special shells (see :class:`MutablePosetShell`). + + .. SEEALSO:: + + :meth:`eq`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: e = MutablePosetShell(P, (1, 2)) + sage: z = P.null + sage: oo = P.oo + sage: z <= e # indirect doctest + True + sage: e <= oo # indirect doctest + True + sage: z <= oo # indirect doctest + True + sage: oo <= z # indirect doctest + False + sage: oo <= e # indirect doctest + False + sage: e <= z # indirect doctest + False + sage: z <= z # indirect doctest + True + sage: oo <= oo # indirect doctest + True + sage: e <= e # indirect doctest + True + + :: + + sage: z.le(e, reverse=True) + False + sage: e.le(oo, reverse=True) + False + sage: z.le(oo, reverse=True) + False + sage: oo.le(z, reverse=True) + True + sage: oo.le(e, reverse=True) + True + sage: e.le(z, reverse=True) + True + sage: z.le(z, reverse=True) + True + sage: oo.le(oo, reverse=True) + True + sage: e.le(e, reverse=True) + True + """ + if reverse: + return other.le(self, reverse=False) + + if self.element is None: + if not self._predecessors_: + # null on the left + return True + else: + # oo on the left + if other.element is None: + # null or oo on the right + return not other._successors_ + else: + # not null, not oo on the right + return False + elif other.element is None: + # null/oo on the right + return not other._successors_ + + return self.key <= other.key + + + __le__ = le + + + def eq(self, other): + r""" + Return whether this shell is equal to ``other``. + + INPUT: + + - ``other`` -- a shell. + + OUTPUT: + + ``True`` or ``False``. + + .. NOTE:: + + This method compares the keys of the elements contained + in the (non-special) shells. In particular, + elements/shells with the same key are considered as equal. + + .. SEEALSO:: + + :meth:`le`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: e = MutablePosetShell(P, (1, 2)) + sage: f = MutablePosetShell(P, (2, 1)) + sage: z = P.null + sage: oo = P.oo + sage: z == z + True + sage: oo == oo + True + sage: e == e + True + sage: e == f + False + sage: z == e + False + sage: e == oo + False + sage: oo == z + False + + Comparing elements in different mutable posets is possible; their + shells are equal if their elements are:: + + sage: S = MP([42]); s = S.shell(42) + sage: T = MP([42]); t = T.shell(42) + sage: s == t + True + sage: S.oo == T.oo + True + """ + if self.element is None and other.element is None: + return self.is_null() == other.is_null() + return self.key == other.key + + + __eq__ = eq + + + def _copy_all_linked_(self, memo, poset, mapping): + r""" + Return a copy of this shell. All shells linked to this shell + are copied as well. + + This is a helper function for :meth:`MutablePoset.copy`. + + INPUT: + + - ``memo`` -- a dictionary which assigns to the id of the + calling shell to a copy of it. + + - ``poset`` -- the poset to which the newly created shells + belongs. Note that the elements are not inserted into + ``poset``; this is done in the calling method + :meth:`MutablePoset._copy_shells_`. + + - ``mapping`` -- a function which is applied on each of the elements. + + OUTPUT: + + A new shell. + + .. SEEALSO:: + + :meth:`MutablePoset.copy`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: Q = MP() + sage: memo = {} + sage: z = P.null._copy_all_linked_(memo, Q, lambda e: e) + sage: z.poset is Q + True + sage: oo = z.successors().pop() + sage: oo.is_oo() + True + + Note that :meth:`_copy_all_linked_` does not change the mutable + poset ``Q`` (this is done in the calling method + :meth:`MutablePoset._copy_shells_`). Thus we have + :: + + sage: oo is Q.oo + False + """ + try: + return memo[id(self)] + except KeyError: + pass + + new = self.__class__(poset, mapping(self.element) + if self.element is not None else None) + memo[id(self)] = new + + for reverse in (False, True): + for e in self.successors(reverse): + new.successors(reverse).add(e._copy_all_linked_(memo, poset, mapping)) + + return new + + + def lower_covers(self, shell, reverse=False): + r""" + Return the lower covers of the specified ``shell``; + the search is started at this (``self``) shell. + + A lower cover of `x` is an element `y` of the poset + such that `y < x` and there is no element `z` of the poset + so that `y < z < x`. + + INPUT: + + - ``shell`` -- the shell for which to find the covering shells. + There is no restriction of ``shell`` being contained in the poset. + If ``shell`` is contained in the poset, then use the more efficient + methods :meth:`predecessors` and :meth:`successors`. + + - ``reverse`` -- (default: ``False``) if set, then find + the upper covers (see also :meth:`upper_covers`) + instead of the lower covers. + + OUTPUT: + + A set of :class:`shells `. + + .. NOTE:: + + Suppose ``reverse`` is ``False``. This method starts at + the calling shell (``self``) and searches towards ``'oo'``. + Thus, only shells which are (not necessarily + direct) successors of this shell are considered. + + If ``reverse`` is ``True``, then the reverse direction is + taken. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: e = P.shell(T((2, 2))); e + (2, 2) + sage: sorted(P.null.lower_covers(e), + ....: key=lambda c: repr(c.element)) + [(1, 2), (2, 1)] + sage: set(_) == e.predecessors() + True + sage: sorted(P.oo.upper_covers(e), + ....: key=lambda c: repr(c.element)) + [(4, 4)] + sage: set(_) == e.successors() + True + + :: + + sage: Q = MP([T((3, 2))]) + sage: f = next(Q.shells()) + sage: sorted(P.null.lower_covers(f), + ....: key=lambda c: repr(c.element)) + [(2, 2)] + sage: sorted(P.oo.upper_covers(f), + ....: key=lambda c: repr(c.element)) + [(4, 4)] + + .. SEEALSO:: + + :meth:`upper_covers`, + :meth:`predecessors`, + :meth:`successors`, + :class:`MutablePoset`. + """ + if self == shell: + return set() + covers = set().union(*(e.lower_covers(shell, reverse) + for e in self.successors(reverse) + if e.le(shell, reverse))) + return covers or set([self]) + + + def upper_covers(self, shell, reverse=False): + r""" + Return the upper covers of the specified ``shell``; + the search is started at this (``self``) shell. + + An upper cover of `x` is an element `y` of the poset + such that `x < y` and there is no element `z` of the poset + so that `x < z < y`. + + INPUT: + + - ``shell`` -- the shell for which to find the covering shells. + There is no restriction of ``shell`` being contained in the poset. + If ``shell`` is contained in the poset, then use the more efficient + methods :meth:`predecessors` and :meth:`successors`. + + - ``reverse`` -- (default: ``False``) if set, then find + the lower covers (see also :meth:`lower_covers`) + instead of the upper covers. + + OUTPUT: + + A set of :class:`shells `. + + .. NOTE:: + + Suppose ``reverse`` is ``False``. This method starts at + the calling shell (``self``) and searches towards ``'null'``. + Thus, only shells which are (not necessarily + direct) predecessors of this shell are considered. + + If ``reverse`` is ``True``, then the reverse direction is + taken. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: e = P.shell(T((2, 2))); e + (2, 2) + sage: sorted(P.null.lower_covers(e), + ....: key=lambda c: repr(c.element)) + [(1, 2), (2, 1)] + sage: set(_) == e.predecessors() + True + sage: sorted(P.oo.upper_covers(e), + ....: key=lambda c: repr(c.element)) + [(4, 4)] + sage: set(_) == e.successors() + True + + :: + + sage: Q = MP([T((3, 2))]) + sage: f = next(Q.shells()) + sage: sorted(P.null.lower_covers(f), + ....: key=lambda c: repr(c.element)) + [(2, 2)] + sage: sorted(P.oo.upper_covers(f), + ....: key=lambda c: repr(c.element)) + [(4, 4)] + + .. SEEALSO:: + + :meth:`predecessors`, + :meth:`successors`, + :class:`MutablePoset`. + """ + return self.lower_covers(shell, not reverse) + + + def _iter_depth_first_visit_(self, marked, + reverse=False, key=None, + condition=None): + r""" + Return an iterator over all shells in depth first order. + + This is a helper function for :meth:`iter_depth_first`. + + INPUT: + + - ``marked`` -- a set in which marked shells are stored. + + - ``reverse`` -- (default: ``False``) if set, reverses the + order, i.e., ``False`` searches towards ``'oo'`` and + ``True`` searches towards ``'null'``. + + - ``key`` -- (default: ``None``) a function used for sorting + the direct successors of a shell (used in case of a + tie). If this is ``None``, no sorting occurs. + + - ``condition`` -- (default: ``None``) a function mapping a + shell to ``True`` (include in iteration) or ``False`` (do + not include). ``None`` is equivalent to a function returning + always ``True``. Note that the iteration does not go beyond a + not included shell. + + OUTPUT: + + An iterator. + + .. NOTE:: + + The depth first search starts at this (``self``) shell. Thus + only this shell and shells greater than (in case of + ``reverse=False``) this shell are visited. + + .. SEEALSO:: + + :meth:`iter_depth_first`, + :meth:`iter_topological`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(42) + sage: P.add(5) + sage: marked = set() + sage: list(P.oo._iter_depth_first_visit_(marked, reverse=True)) + [oo, 42, 5, null] + """ + if (condition is not None and + not self.is_special() and not condition(self)): + return + if self in marked: + return + marked.add(self) + yield self + S = self.successors(reverse) + if key is not None: + S = sorted(S, key=key) + for shell in S: + for e in shell._iter_depth_first_visit_(marked, reverse, + key, condition): + yield e + + + def iter_depth_first(self, reverse=False, key=None, condition=None): + r""" + Iterate over all shells in depth first order. + + INPUT: + + - ``reverse`` -- (default: ``False``) if set, reverses the + order, i.e., ``False`` searches towards ``'oo'`` and + ``True`` searches towards ``'null'``. + + - ``key`` -- (default: ``None``) a function used for sorting + the direct successors of a shell (used in case of a + tie). If this is ``None``, no sorting occurs. + + - ``condition`` -- (default: ``None``) a function mapping a + shell to ``True`` (include in iteration) or ``False`` (do + not include). ``None`` is equivalent to a function returning + always ``True``. Note that the iteration does not go beyond a + not included shell. + + OUTPUT: + + An iterator. + + .. NOTE:: + + The depth first search starts at this (``self``) shell. Thus + only this shell and shells greater than (in case of + ``reverse=False``) this shell are visited. + + ALGORITHM: + + See :wikipedia:`Depth-first_search`. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: list(P.null.iter_depth_first(reverse=False, key=repr)) + [null, (1, 1), (1, 2), (1, 3), (4, 4), oo, (2, 2), (2, 1)] + sage: list(P.oo.iter_depth_first(reverse=True, key=repr)) + [oo, (4, 4), (1, 3), (1, 2), (1, 1), null, (2, 2), (2, 1)] + sage: list(P.null.iter_depth_first( + ....: condition=lambda s: s.element[0] == 1)) + [null, (1, 1), (1, 2), (1, 3)] + + .. SEEALSO:: + + :meth:`iter_topological`, + :class:`MutablePoset`. + """ + marked = set() + return self._iter_depth_first_visit_(marked, reverse, key, condition) + + + def _iter_topological_visit_(self, marked, + reverse=False, key=None, + condition=None): + r""" + Return an iterator over all shells in topological order. + + This is a helper function for :meth:`iter_topological`. + + INPUT: + + - ``marked`` -- a set in which marked shells are stored. + + - ``reverse`` -- (default: ``False``) if set, reverses the + order, i.e., ``False`` searches towards ``'oo'`` and + ``True`` searches towards ``'null'``. + + - ``key`` -- (default: ``None``) a function used for sorting + the direct predecessors of a shell (used in case of a + tie). If this is ``None``, no sorting occurs. + + - ``condition`` -- (default: ``None``) a function mapping a + shell to ``True`` (include in iteration) or ``False`` (do + not include). ``None`` is equivalent to a function returning + always ``True``. Note that the iteration does not go beyond a + not included shell. + + OUTPUT: + + An iterator. + + .. NOTE:: + + The topological search will only find shells smaller than + (in case of ``reverse=False``) + or equal to this (``self``) shell. This is in contrast to + :meth:`iter_depth_first`. + + .. SEEALSO:: + + :meth:`iter_depth_first`, + :meth:`iter_topological`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(42) + sage: P.add(5) + sage: marked = set() + sage: list(P.null._iter_topological_visit_(marked, reverse=True)) + [oo, 42, 5, null] + """ + if (condition is not None and + not self.is_special() and not condition(self)): + return + if self in marked: + return + marked.add(self) + S = self.predecessors(reverse) + if key is not None: + S = sorted(S, key=key) + for shell in S: + for e in shell._iter_topological_visit_(marked, reverse, + key, condition): + yield e + yield self + + + def iter_topological(self, reverse=False, key=None, condition=None): + r""" + Iterate over all shells in topological order. + + INPUT: + + - ``reverse`` -- (default: ``False``) if set, reverses the + order, i.e., ``False`` searches towards ``'oo'`` and + ``True`` searches towards ``'null'``. + + - ``key`` -- (default: ``None``) a function used for sorting + the direct predeccessors of a shell (used in case of a + tie). If this is ``None``, no sorting occurs. + + - ``condition`` -- (default: ``None``) a function mapping a + shell to ``True`` (include in iteration) or ``False`` (do + not include). ``None`` is equivalent to a function returning + always ``True``. Note that the iteration does not go beyond a + not included shell. + + OUTPUT: + + An iterator. + + .. NOTE:: + + The topological search will only find shells smaller than + (in case of ``reverse=False``) + or equal to this (``self``) shell. This is in contrast to + :meth:`iter_depth_first`. + + ALGORITHM: + + Here a simplified version of the algorithm found in [T1976]_ + and [CLRS2001]_ is used. See also + :wikipedia:`Topological_sorting`. + + .. [T1976] Robert E. Tarjan, *Edge-disjoint spanning trees and + depth-first search*, Acta Informatica 6 (2), 1976, 171-185, + :doi:`10.1007/BF00268499`. + + .. [CLRS2001] Thomas H. Cormen, Charles E. Leiserson, Ronald + L. Rivest and Clifford Stein, *Section 22.4: Topological + sort*, Introduction to Algorithms (2nd ed.), MIT Press and + McGraw-Hill, 2001, 549-552, ISBN 0-262-03293-7. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + + :: + + sage: for e in P.shells_topological(include_special=True, + ....: reverse=True): + ....: print e + ....: print list(e.iter_topological(reverse=True, key=repr)) + oo + [oo] + (4, 4) + [oo, (4, 4)] + (1, 3) + [oo, (4, 4), (1, 3)] + (2, 2) + [oo, (4, 4), (2, 2)] + (1, 2) + [oo, (4, 4), (1, 3), (2, 2), (1, 2)] + (2, 1) + [oo, (4, 4), (2, 2), (2, 1)] + (1, 1) + [oo, (4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1)] + null + [oo, (4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1), null] + + :: + + sage: for e in P.shells_topological(include_special=True, + ....: reverse=True): + ....: print e + ....: print list(e.iter_topological(reverse=False, key=repr)) + oo + [null, (1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (4, 4), oo] + (4, 4) + [null, (1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (4, 4)] + (1, 3) + [null, (1, 1), (1, 2), (1, 3)] + (2, 2) + [null, (1, 1), (1, 2), (2, 1), (2, 2)] + (1, 2) + [null, (1, 1), (1, 2)] + (2, 1) + [null, (1, 1), (2, 1)] + (1, 1) + [null, (1, 1)] + null + [null] + + :: + + sage: list(P.null.iter_topological( + ....: reverse=True, condition=lambda s: s.element[0] == 1)) + [(1, 3), (1, 2), (1, 1), null] + + .. SEEALSO:: + + :meth:`iter_depth_first`, + :meth:`MutablePoset.shells_topological`, + :meth:`MutablePoset.elements_topological`, + :meth:`MutablePoset.keys_topological`, + :class:`MutablePoset`. + """ + marked = set() + return self._iter_topological_visit_(marked, reverse, key, condition) + + + def merge(self, element, check=True, delete=True): + r""" + Merge the given element with the element contained in this + shell. + + INPUT: + + - ``element`` -- an element (of the poset). + + - ``check`` -- (default: ``True``) if set, then the + ``can_merge``-function of :class:`MutablePoset` determines + whether the merge is possible. ``can_merge`` is ``None`` means + that this check is always passed. + + - ``delete`` -- (default: ``True``) if set, then ``element`` + is removed from the poset after the merge. + + OUTPUT: + + Nothing. + + .. NOTE:: + + This operation depends on the parameters ``merge`` and + ``can_merge`` of the :class:`MutablePoset` this shell is + contained in. These parameters are defined when the poset + is constructed. + + .. NOTE:: + + If the ``merge`` function returns ``None``, then this shell + is removed from the poset. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: def add(left, right): + ....: return (left[0], ''.join(sorted(left[1] + right[1]))) + sage: def can_add(left, right): + ....: return left[0] <= right[0] + sage: P = MP([(1, 'a'), (3, 'b'), (2, 'c'), (4, 'd')], + ....: key=lambda c: c[0], merge=add, can_merge=can_add) + sage: P + poset((1, 'a'), (2, 'c'), (3, 'b'), (4, 'd')) + sage: P.shell(2).merge((3, 'b')) + sage: P + poset((1, 'a'), (2, 'bc'), (4, 'd')) + + .. SEEALSO:: + + :meth:`MutablePoset.merge`, + :class:`MutablePoset`. + + TESTS:: + + sage: MP([2], merge=operator.add, + ....: can_merge=lambda _, __: False).shell(2).merge(1) + Traceback (most recent call last): + ... + RuntimeError: Cannot merge 2 with 1. + """ + poset = self.poset + if poset._merge_ is None: + # poset._merge_ is None means no merge (poset._merge_ simply + # returns its first input argument). + return + self_element = self.element + if check: + if not poset._can_merge_(self_element, element): + raise RuntimeError('Cannot merge %s with %s.' % + (self_element, element)) + new = poset._merge_(self_element, element) + if new is None: + poset.discard(poset.get_key(self.element)) + else: + self._element_ = new + if delete: + poset.remove(poset.get_key(element)) + + +# ***************************************************************************** + + +def is_MutablePoset(P): + r""" + Test whether ``P`` inherits from :class:`MutablePoset`. + + .. SEEALSO:: + + :class:`MutablePoset` + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: from sage.data_structures.mutable_poset import is_MutablePoset + sage: P = MP() + sage: is_MutablePoset(P) + True + """ + return isinstance(P, MutablePoset) + + +class MutablePoset(SageObject): + r""" + A data structure that models a mutable poset (partially ordered + set). + + INPUT: + + - ``data`` -- data from which to construct the poset. It can be + any of the following: + + #. ``None`` (default), in which case an empty poset is created, + + #. a :class:`MutablePoset`, which will be copied during creation, + + #. an iterable, whose elements will be in the poset. + + - ``key`` -- a function which maps elements to keys. If ``None`` + (default), this is the identity, i.e., keys are equal to their + elements. + + Two elements with the same keys are considered as equal; so only + one of these two elements can be in the poset. + + This ``key`` is not used for sorting (in contrast to + sorting-functions, e.g. ``sorted``). + + - ``merge`` -- a function which merges its second argument (an + element) to its first (again an element) and returns the result + (as an element). If the return value is ``None``, the element is + removed from the poset. + + This hook is called by :meth:`merge`. Moreover it is used during + :meth:`add` when an element (more precisely its key) is already + in this poset. + + ``merge`` is ``None`` (default) is equivalent to ``merge`` + returning its first argument. Note that it is not allowed that the + key of the returning element differs from the key of the first + input parameter. This means ``merge`` must not change the + position of the element in the poset. + + - ``can_merge`` -- a function which checks whether its second argument + can be merged to its first. + + This hook is called by :meth:`merge`. Moreover it is used during + :meth:`add` when an element (more precisely its key) is already + in this poset. + + ``can_merge`` is ``None`` (default) is equivalent to ``can_merge`` + returning ``True`` in all cases. + + OUTPUT: + + A mutable poset. + + You can find a short introduction and examples + :mod:`here `. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + + We illustrate the different input formats + + #. No input:: + + sage: A = MP(); A + poset() + + #. A :class:`MutablePoset`:: + + sage: B = MP(A); B + poset() + sage: B.add(42) + sage: C = MP(B); C + poset(42) + + #. An iterable:: + + sage: C = MP([5, 3, 11]); C + poset(3, 5, 11) + + .. SEEALSO:: + + :class:`MutablePosetShell`. + """ + def __init__(self, data=None, key=None, merge=None, can_merge=None): + r""" + See :class:`MutablePoset` for details. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: MP() + poset() + + :: + + sage: P = MP() + sage: P.add(42) + sage: MP(P) + poset(42) + + :: + + sage: MP([3, 5, 7]) + poset(3, 5, 7) + + :: + + sage: MP(33) + Traceback (most recent call last): + ... + TypeError: 33 is not iterable; do not know what to do with it. + """ + if is_MutablePoset(data): + if key is not None: + raise TypeError('Cannot use key when data is a poset.') + self._copy_shells_(data, lambda e: e) + + else: + self.clear() + + if key is None: + self._key_ = lambda k: k + else: + self._key_ = key + + self._merge_ = merge + if can_merge is None: + self._can_merge_ = lambda _, __: True + else: + self._can_merge_ = can_merge + + if data is not None: + try: + it = iter(data) + except TypeError: + raise TypeError('%s is not iterable; do not know what to ' + 'do with it.' % (data,)) + self.union_update(it) + super(MutablePoset, self).__init__() + + + def clear(self): + r""" + Remove all elements from this poset. + + INPUT: + + Nothing. + + OUTPUT: + + Nothing. + + .. SEEALSO:: + + :meth:`discard`, + :meth:`pop`, + :meth:`remove`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(42); P + poset(42) + sage: P.clear() + sage: print P.repr_full() + poset() + +-- null + | +-- no predecessors + | +-- successors: oo + +-- oo + | +-- predecessors: null + | +-- no successors + """ + self._null_ = MutablePosetShell(self, None) + self._oo_ = MutablePosetShell(self, None) + self._null_.successors().add(self._oo_) + self._oo_.predecessors().add(self._null_) + self._shells_ = {} + + + def __len__(self): + r""" + Return the number of elements contained in this poset. + + INPUT: + + Nothing. + + OUTPUT: + + An integer. + + .. NOTE:: + + The special elements ``'null'`` and ``'oo'`` are not counted. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: len(P) # indirect doctest + 0 + sage: bool(P) + False + sage: P.add(42) + sage: len(P) + 1 + sage: bool(P) + True + """ + return len(self._shells_) + + + @property + def null(self): + r""" + The shell `\emptyset` whose element is smaller than any + other element. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: z = P.null; z + null + sage: z.is_null() + True + + .. SEEALSO:: + + :meth:`oo`, + :meth:`MutablePosetShell.is_null`, + :meth:`MutablePosetShell.is_special`. + """ + return self._null_ + + + @property + def oo(self): + r""" + The shell `\infty` whose element is larger than any other + element. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: oo = P.oo; oo + oo + sage: oo.is_oo() + True + + .. SEEALSO:: + + :meth:`null`, + :meth:`MutablePosetShell.is_oo`, + :meth:`MutablePosetShell.is_special`. + """ + return self._oo_ + + + def shell(self, key): + r""" + Return the shell of the element corresponding to ``key``. + + INPUT: + + ``key`` -- the key of an object. + + OUTPUT: + + An instance of :class:`MutablePosetShell`. + + .. NOTE:: + + Each element is contained/encapsulated in a shell inside + the poset. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(42) + sage: e = P.shell(42); e + 42 + sage: type(e) + + + .. SEEALSO:: + + :meth:`element`, + :meth:`get_key`. + """ + return self._shells_[key] + + + def element(self, key): + r""" + Return the element corresponding to ``key``. + + INPUT: + + ``key`` -- the key of an object. + + OUTPUT: + + An object. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(42) + sage: e = P.element(42); e + 42 + sage: type(e) + + + .. SEEALSO:: + + :meth:`shell`, + :meth:`get_key`. + """ + return self.shell(key).element + + + def get_key(self, element): + r""" + Return the key corresponding to the given element. + + INPUT: + + - ``element`` -- an object. + + OUTPUT: + + An object (the key of ``element``). + + .. SEEALSO:: + + :meth:`element`, + :meth:`shell`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.get_key(None) is None + True + sage: P.get_key((1, 2)) + (1, 2) + sage: Q = MP(key=lambda k: k[0]) + sage: Q.get_key((1, 2)) + 1 + """ + if element is None: + return None + return self._key_(element) + + + def _copy_shells_(self, other, mapping): + r""" + Copy shells from another poset. + + INPUT: + + - ``other`` -- the mutable poset from which the shells + should be copied to this poset. + + - ``mapping`` -- a function that is applied to each element. + + OUTPUT: + + Nothing. + + .. SEEALSO:: + + :meth:`copy` + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP() + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2))]) + sage: Q = MP() + sage: Q._copy_shells_(P, lambda e: e) + sage: P.repr_full() == Q.repr_full() + True + """ + from copy import copy + self._key_ = copy(other._key_) + self._merge_ = copy(other._merge_) + self._can_merge_ = copy(other._can_merge_) + memo = {} + self._null_ = other._null_._copy_all_linked_(memo, self, mapping) + self._oo_ = memo[id(other._oo_)] + self._shells_ = dict((f.key, f) for f in + iter(memo[id(e)] for e in + other._shells_.itervalues())) + + + def copy(self, mapping=None): + r""" + Create a shallow copy. + + INPUT: + + - ``mapping`` -- a function which is applied on each of the elements. + + OUTPUT: + + A poset with the same content as ``self``. + + .. SEEALSO:: + + :meth:`map`, + :meth:`mapped`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2))]) + sage: Q = copy(P) # indirect doctest + sage: P.repr_full() == Q.repr_full() + True + """ + if mapping is None: + mapping = lambda element: element + new = self.__class__() + new._copy_shells_(self, mapping) + return new + + + __copy__ = copy + + + def shells(self, include_special=False): + r""" + Return an iterator over all shells. + + INPUT: + + - ``include_special`` -- (default: ``False``) if set, then + including shells containing a smallest element (`\emptyset`) + and a largest element (`\infty`). + + OUTPUT: + + An iterator. + + .. NOTE:: + + Each element is contained/encapsulated in a shell inside + the poset. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: tuple(P.shells()) + () + sage: tuple(P.shells(include_special=True)) + (null, oo) + + .. SEEALSO:: + + :meth:`shells_topological`, + :meth:`elements`, + :meth:`elements_topological`, + :meth:`keys`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. + """ + if include_special: + yield self.null + for e in self._shells_.itervalues(): + yield e + if include_special: + yield self.oo + + + def shells_topological(self, include_special=False, + reverse=False, key=None): + r""" + Return an iterator over all shells in topological order. + + INPUT: + + - ``include_special`` -- (default: ``False``) if set, then + including shells containing a smallest element (`\emptyset`) + and a largest element (`\infty`). + + - ``reverse`` -- (default: ``False``) -- if set, reverses the + order, i.e., ``False`` gives smallest elements first, + ``True`` gives largest first. + + - ``key`` -- (default: ``None``) a function used for sorting + the direct successors of a shell (used in case of a tie). If + this is ``None``, then the successors are sorted according + to their representation strings. + + OUTPUT: + + An iterator. + + .. NOTE:: + + Each element is contained/encapsulated in a shell inside + the poset. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: list(P.shells_topological()) + [(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (4, 4)] + sage: list(P.shells_topological(reverse=True)) + [(4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1)] + sage: list(P.shells_topological(include_special=True)) + [null, (1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (4, 4), oo] + sage: list(P.shells_topological( + ....: include_special=True, reverse=True)) + [oo, (4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1), null] + + .. SEEALSO:: + + :meth:`shells`, + :meth:`elements`, + :meth:`elements_topological`, + :meth:`keys`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. + """ + if key is None: + key = repr + shell = self.oo if not reverse else self.null + return iter(e for e in shell.iter_topological(reverse, key) + if include_special or not e.is_special()) + + + def elements(self, **kwargs): + r""" + Return an iterator over all elements. + + INPUT: + + - ``kwargs`` -- arguments are passed to :meth:`shells`. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]) + sage: [(v, type(v)) for v in sorted(P.elements())] + [(3, ), + (7, ), + (42, )] + + Note that + + :: + + sage: it = iter(P) + sage: sorted(it) + [3, 7, 42] + + returns all elements as well. + + .. SEEALSO:: + + :meth:`shells`, + :meth:`shells_topological`, + :meth:`elements_topological`, + :meth:`keys`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. + """ + for shell in self.shells(**kwargs): + yield shell.element + + + __iter__ = elements + + + def elements_topological(self, **kwargs): + r""" + Return an iterator over all elements in topological order. + + INPUT: + + - ``kwargs`` -- arguments are passed to :meth:`shells_topological`. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: [(v, type(v)) for v in P.elements_topological()] + [((1, 1), ), + ((1, 2), ), + ((1, 3), ), + ((2, 1), ), + ((2, 2), ), + ((4, 4), )] + + .. SEEALSO:: + + :meth:`shells`, + :meth:`shells_topological`, + :meth:`elements`, + :meth:`keys`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. + """ + for shell in self.shells_topological(**kwargs): + yield shell.element + + + def keys(self, **kwargs): + r""" + Return an iterator over all keys of the elements. + + INPUT: + + - ``kwargs`` -- arguments are passed to :meth:`shells`. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7], key=lambda c: -c) + sage: [(v, type(v)) for v in sorted(P.keys())] + [(-42, ), + (-7, ), + (-3, )] + + sage: [(v, type(v)) for v in sorted(P.elements())] + [(3, ), + (7, ), + (42, )] + + sage: [(v, type(v)) for v in sorted(P.shells(), + ....: key=lambda c: c.element)] + [(3, ), + (7, ), + (42, )] + + .. SEEALSO:: + + :meth:`shells`, + :meth:`shells_topological`, + :meth:`elements`, + :meth:`elements_topological`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. + """ + for shell in self.shells(**kwargs): + yield shell.key + + + def keys_topological(self, **kwargs): + r""" + Return an iterator over all keys of the elements in + topological order. + + INPUT: + + - ``kwargs`` -- arguments are passed to :meth:`shells_topological`. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([(1, 1), (2, 1), (4, 4)], + ....: key=lambda c: c[0]) + sage: [(v, type(v)) for v in P.keys_topological()] + [(1, ), + (2, ), + (4, )] + sage: [(v, type(v)) for v in P.elements_topological()] + [((1, 1), ), + ((2, 1), ), + ((4, 4), )] + sage: [(v, type(v)) for v in P.shells_topological()] + [((1, 1), ), + ((2, 1), ), + ((4, 4), )] + + .. SEEALSO:: + + :meth:`shells`, + :meth:`shells_topological`, + :meth:`elements`, + :meth:`elements_topological`, + :meth:`keys`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. + """ + for shell in self.shells_topological(**kwargs): + yield shell.key + + + def repr(self, include_special=False, reverse=False): + r""" + Return a representation of the poset. + + INPUT: + + - ``include_special`` -- (default: ``False``) a boolean + indicating whether to include the special elements + ``'null'`` and ``'oo'`` or not. + + - ``reverse`` -- (default: ``False``) a boolean. If set, then + largest elements are displayed first. + + OUTPUT: + + A string. + + .. SEEALSO:: + + :meth:`repr_full` + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: print MP().repr() + poset() + """ + s = 'poset(' + s += ', '.join(repr(shell) for shell in + self.shells_topological(include_special, reverse)) + s += ')' + return s + + + def repr_full(self, reverse=False): + r""" + Return a representation with ordering details of the poset. + + INPUT: + + - ``reverse`` -- (default: ``False``) a boolean. If set, then + largest elements are displayed first. + + OUTPUT: + + A string. + + .. SEEALSO:: + + :meth:`repr` + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: print MP().repr_full(reverse=True) + poset() + +-- oo + | +-- no successors + | +-- predecessors: null + +-- null + | +-- successors: oo + | +-- no predecessors + """ + sortedshells = tuple( + self.shells_topological(include_special=True, reverse=reverse)) + strings = [self.repr(include_special=False, reverse=reverse)] + for shell in sortedshells: + strings.append('+-- ' + repr(shell)) + for rev in (not reverse, reverse): + what = 'successors' if not rev else 'predecessors' + if shell.successors(rev): + s = '| +-- ' + what + ': ' + s += ', '.join(repr(e) for e in + sortedshells if e in shell.successors(rev)) + else: + s = '| +-- no ' + what + strings.append(s) + return '\n'.join(strings) + + + _repr_ = repr + + + def contains(self, key): + r""" + Test whether ``key`` is encapsulated by one of the poset's elements. + + INPUT: + + - ``key`` -- an object. + + OUTPUT: + + ``True`` or ``False``. + + .. SEEALSO:: + + :meth:`shells`, + :meth:`elements`, + :meth:`keys`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP() + sage: P.add(T((1, 1))) + sage: T((1, 1)) in P # indirect doctest + True + sage: T((1, 2)) in P # indirect doctest + False + """ + return key in self._shells_ + + + __contains__ = contains + + + def add(self, element): + r""" + Add the given object as element to the poset. + + INPUT: + + - ``element`` -- an object (hashable and supporting comparison + with the operator ``<=``). + + OUTPUT: + + Nothing. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2))]) + sage: print P.repr_full(reverse=True) + poset((4, 4), (1, 3), (1, 2), (2, 1), (1, 1)) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4) + +-- (4, 4) + | +-- successors: oo + | +-- predecessors: (1, 3), (2, 1) + +-- (1, 3) + | +-- successors: (4, 4) + | +-- predecessors: (1, 2) + +-- (1, 2) + | +-- successors: (1, 3) + | +-- predecessors: (1, 1) + +-- (2, 1) + | +-- successors: (4, 4) + | +-- predecessors: (1, 1) + +-- (1, 1) + | +-- successors: (1, 2), (2, 1) + | +-- predecessors: null + +-- null + | +-- successors: (1, 1) + | +-- no predecessors + sage: P.add(T((2, 2))) + sage: reprP = P.repr_full(reverse=True); print reprP + poset((4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1)) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4) + +-- (4, 4) + | +-- successors: oo + | +-- predecessors: (1, 3), (2, 2) + +-- (1, 3) + | +-- successors: (4, 4) + | +-- predecessors: (1, 2) + +-- (2, 2) + | +-- successors: (4, 4) + | +-- predecessors: (1, 2), (2, 1) + +-- (1, 2) + | +-- successors: (1, 3), (2, 2) + | +-- predecessors: (1, 1) + +-- (2, 1) + | +-- successors: (2, 2) + | +-- predecessors: (1, 1) + +-- (1, 1) + | +-- successors: (1, 2), (2, 1) + | +-- predecessors: null + +-- null + | +-- successors: (1, 1) + | +-- no predecessors + + When adding an element which is already in the poset, nothing happens:: + + sage: e = T((2, 2)) + sage: P.add(e) + sage: P.repr_full(reverse=True) == reprP + True + + We can influence the behavior when an element with existing key + is to be inserted in the poset. For example, we can perform an + addition on some argument of the elements:: + + sage: def add(left, right): + ....: return (left[0], ''.join(sorted(left[1] + right[1]))) + sage: A = MP(key=lambda k: k[0], merge=add) + sage: A.add((3, 'a')) + sage: A + poset((3, 'a')) + sage: A.add((3, 'b')) + sage: A + poset((3, 'ab')) + + We can also deal with cancellations. If the return value of + our hook-function is ``None``, then the element is removed out of + the poset:: + + sage: def add_None(left, right): + ....: s = left[1] + right[1] + ....: if s == 0: + ....: return None + ....: return (left[0], s) + sage: B = MP(key=lambda k: k[0], + ....: merge=add_None) + sage: B.add((7, 42)) + sage: B.add((7, -42)) + sage: B + poset() + + .. SEEALSO:: + + :meth:`discard`, + :meth:`pop`, + :meth:`remove`. + + TESTS:: + + sage: R = MP([(1, 1, 42), (1, 3, 42), (2, 1, 7), + ....: (4, 4, 42), (1, 2, 7), (2, 2, 7)], + ....: key=lambda k: T(k[2:3])) + sage: print R.repr_full(reverse=True) + poset((1, 1, 42), (2, 1, 7)) + +-- oo + | +-- no successors + | +-- predecessors: (1, 1, 42) + +-- (1, 1, 42) + | +-- successors: oo + | +-- predecessors: (2, 1, 7) + +-- (2, 1, 7) + | +-- successors: (1, 1, 42) + | +-- predecessors: null + +-- null + | +-- successors: (2, 1, 7) + | +-- no predecessors + + :: + + sage: P = MP() + sage: P.add(None) + Traceback (most recent call last): + ... + ValueError: None is not an allowed element. + """ + if element is None: + raise ValueError('None is not an allowed element.') + key = self.get_key(element) + + if key in self._shells_: + if self._merge_ is not None: + self.shell(key).merge(element, delete=False) + return + + new = MutablePosetShell(self, element) + new._predecessors_ = self.null.lower_covers(new) + new._successors_ = self.oo.upper_covers(new) + + for s in new.predecessors(): + for l in s.successors().intersection(new.successors()): + l.predecessors().remove(s) + s.successors().remove(l) + s.successors().add(new) + for l in new.successors(): + l.predecessors().add(new) + + self._shells_[key] = new + + + def remove(self, key, raise_key_error=True): + r""" + Remove the given object from the poset. + + INPUT: + + - ``key`` -- the key of an object. + + - ``raise_key_error`` -- (default: ``True``) switch raising + ``KeyError`` on and off. + + OUTPUT: + + Nothing. + + If the element is not a member and ``raise_key_error`` is set + (default), raise a ``KeyError``. + + .. NOTE:: + + As with Python's ``set``, the methods :meth:`remove` + and :meth:`discard` only differ in their behavior when an + element is not contained in the poset: :meth:`remove` + raises a ``KeyError`` whereas :meth:`discard` does not + raise any exception. + + This default behavior can be overridden with the + ``raise_key_error`` parameter. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: print P.repr_full(reverse=True) + poset((4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1)) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4) + +-- (4, 4) + | +-- successors: oo + | +-- predecessors: (1, 3), (2, 2) + +-- (1, 3) + | +-- successors: (4, 4) + | +-- predecessors: (1, 2) + +-- (2, 2) + | +-- successors: (4, 4) + | +-- predecessors: (1, 2), (2, 1) + +-- (1, 2) + | +-- successors: (1, 3), (2, 2) + | +-- predecessors: (1, 1) + +-- (2, 1) + | +-- successors: (2, 2) + | +-- predecessors: (1, 1) + +-- (1, 1) + | +-- successors: (1, 2), (2, 1) + | +-- predecessors: null + +-- null + | +-- successors: (1, 1) + | +-- no predecessors + sage: P.remove(T((1, 2))) + sage: print P.repr_full(reverse=True) + poset((4, 4), (1, 3), (2, 2), (2, 1), (1, 1)) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4) + +-- (4, 4) + | +-- successors: oo + | +-- predecessors: (1, 3), (2, 2) + +-- (1, 3) + | +-- successors: (4, 4) + | +-- predecessors: (1, 1) + +-- (2, 2) + | +-- successors: (4, 4) + | +-- predecessors: (2, 1) + +-- (2, 1) + | +-- successors: (2, 2) + | +-- predecessors: (1, 1) + +-- (1, 1) + | +-- successors: (1, 3), (2, 1) + | +-- predecessors: null + +-- null + | +-- successors: (1, 1) + | +-- no predecessors + + .. SEEALSO:: + + :meth:`add`, + :meth:`clear`, + :meth:`discard`, + :meth:`pop`. + + TESTS:: + + sage: Q = MP([(1, 1, 42), (1, 3, 42), (2, 1, 7), + ....: (4, 4, 42), (1, 2, 7), (2, 2, 7)], + ....: key=lambda k: T(k[0:2])) + sage: print Q.repr_full(reverse=True) + poset((4, 4, 42), (1, 3, 42), (2, 2, 7), + (1, 2, 7), (2, 1, 7), (1, 1, 42)) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4, 42) + +-- (4, 4, 42) + | +-- successors: oo + | +-- predecessors: (1, 3, 42), (2, 2, 7) + +-- (1, 3, 42) + | +-- successors: (4, 4, 42) + | +-- predecessors: (1, 2, 7) + +-- (2, 2, 7) + | +-- successors: (4, 4, 42) + | +-- predecessors: (1, 2, 7), (2, 1, 7) + +-- (1, 2, 7) + | +-- successors: (1, 3, 42), (2, 2, 7) + | +-- predecessors: (1, 1, 42) + +-- (2, 1, 7) + | +-- successors: (2, 2, 7) + | +-- predecessors: (1, 1, 42) + +-- (1, 1, 42) + | +-- successors: (1, 2, 7), (2, 1, 7) + | +-- predecessors: null + +-- null + | +-- successors: (1, 1, 42) + | +-- no predecessors + sage: Q.remove((1,1)) + sage: print Q.repr_full(reverse=True) + poset((4, 4, 42), (1, 3, 42), (2, 2, 7), (1, 2, 7), (2, 1, 7)) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4, 42) + +-- (4, 4, 42) + | +-- successors: oo + | +-- predecessors: (1, 3, 42), (2, 2, 7) + +-- (1, 3, 42) + | +-- successors: (4, 4, 42) + | +-- predecessors: (1, 2, 7) + +-- (2, 2, 7) + | +-- successors: (4, 4, 42) + | +-- predecessors: (1, 2, 7), (2, 1, 7) + +-- (1, 2, 7) + | +-- successors: (1, 3, 42), (2, 2, 7) + | +-- predecessors: null + +-- (2, 1, 7) + | +-- successors: (2, 2, 7) + | +-- predecessors: null + +-- null + | +-- successors: (1, 2, 7), (2, 1, 7) + | +-- no predecessors + + :: + + sage: P = MP() + sage: P.remove(None) + Traceback (most recent call last): + ... + ValueError: None is not an allowed key. + """ + if key is None: + raise ValueError('None is not an allowed key.') + + try: + shell = self._shells_[key] + except KeyError: + if not raise_key_error: + return + raise KeyError('Key %s is not contained in this poset.' % (key,)) + + for reverse in (False, True): + for p in shell.predecessors(reverse): + S = p.successors(reverse) + S.remove(shell) + D = set(s for s in p.iter_depth_first(reverse) + if s in shell.successors(reverse)) + S.update(shell.successors(reverse)) + S.difference_update(D) + del self._shells_[key] + + + def discard(self, key, raise_key_error=False): + r""" + Remove the given object from the poset. + + INPUT: + + - ``key`` -- the key of an object. + + - ``raise_key_error`` -- (default: ``False``) switch raising + ``KeyError`` on and off. + + OUTPUT: + + Nothing. + + If the element is not a member and ``raise_key_error`` is set + (not default), raise a ``KeyError``. + + .. NOTE:: + + As with Python's ``set``, the methods :meth:`remove` + and :meth:`discard` only differ in their behavior when an + element is not contained in the poset: :meth:`remove` + raises a ``KeyError`` whereas :meth:`discard` does not + raise any exception. + + This default behavior can be overridden with the + ``raise_key_error`` parameter. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: P.discard(T((1, 2))) + sage: P.remove(T((1, 2))) + Traceback (most recent call last): + ... + KeyError: 'Key (1, 2) is not contained in this poset.' + sage: P.discard(T((1, 2))) + + .. SEEALSO:: + + :meth:`add`, + :meth:`clear`, + :meth:`remove`, + :meth:`pop`. + """ + return self.remove(key, raise_key_error) + + + def pop(self, **kwargs): + r""" + Remove and return an arbitrary poset element. + + INPUT: + + - ``kwargs`` -- arguments are passed to :meth:`shells_topological`. + + OUTPUT: + + An object. + + .. NOTE:: + + The special elements ``'null'`` and ``'oo'`` cannot be popped. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(3) + sage: P + poset(3) + sage: P.pop() + 3 + sage: P + poset() + sage: P.pop() + Traceback (most recent call last): + ... + KeyError: 'pop from an empty poset' + + .. SEEALSO:: + + :meth:`add`, + :meth:`clear`, + :meth:`discard`, + :meth:`remove`. + """ + kwargs['include_special'] = False + + try: + shell = next(self.shells_topological(**kwargs)) + except StopIteration: + raise KeyError('pop from an empty poset') + self.remove(shell.key) + return shell.element + + + def union(self, *other): + r""" + Return the union of the given posets as a new poset + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). + + OUTPUT: + + A poset. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. + + Due to keys and a ``merge`` function (see :class:`MutablePoset`) + this operation might not be commutative. + + .. TODO:: + + Use the already existing information in the other poset to speed + up this function. (At the moment each element of the other poset + is inserted one by one and without using this information.) + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.union(Q) + poset(3, 4, 7, 8, 42) + + .. SEEALSO:: + + :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + + TESTS:: + + sage: P.union(P, Q, Q, P) + poset(3, 4, 7, 8, 42) + """ + new = self.copy() + new.update(*other) + return new + + + def union_update(self, *other): + r""" + Update this poset with the union of itself and another poset. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). + + OUTPUT: + + Nothing. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal; + ``A.union_update(B)`` and ``B.union_update(A)`` might + result in different posets. + + .. TODO:: + + Use the already existing information in the other poset to speed + up this function. (At the moment each element of the other poset + is inserted one by one and without using this information.) + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.union_update(Q) + sage: P + poset(3, 4, 7, 8, 42) + + .. SEEALSO:: + + :meth:`union`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + + TESTS:: + + sage: Q.update(P) + sage: Q + poset(3, 4, 7, 8, 42) + """ + for o in other: + try: + it = o.elements() + except AttributeError: + it = iter(o) + for element in it: + self.add(element) + + + update = union_update # as in a Python set + r""" + Alias of :meth:`union_update`. + """ + + + def difference(self, *other): + r""" + Return a new poset where all elements of this poset, which are + contained in one of the other given posets, are removed. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). + + OUTPUT: + + A poset. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.difference(Q) + poset(3, 7) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + + TESTS:: + + sage: P.difference(Q, Q) + poset(3, 7) + sage: P.difference(P) + poset() + sage: P.difference(Q, P) + poset() + """ + new = self.copy() + new.difference_update(*other) + return new + + + def difference_update(self, *other): + r""" + Remove all elements of another poset from this poset. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). + + OUTPUT: + + Nothing. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.difference_update(Q) + sage: P + poset(3, 7) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + """ + for o in other: + try: + it = o.keys() + except AttributeError: + it = iter(o) + for key in it: + self.discard(key) + + + def intersection(self, *other): + r""" + Return the intersection of the given posets as a new poset + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). + + OUTPUT: + + A poset. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.intersection(Q) + poset(42) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + + TESTS:: + + sage: P.intersection(P, Q, Q, P) + poset(42) + """ + new = self.copy() + new.intersection_update(*other) + return new + + + def intersection_update(self, *other): + r""" + Update this poset with the intersection of itself and another poset. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). + + OUTPUT: + + Nothing. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal; + ``A.intersection_update(B)`` and ``B.intersection_update(A)`` might + result in different posets. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.intersection_update(Q) + sage: P + poset(42) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + """ + keys = tuple(self.keys()) + for key in keys: + if any(key not in o for o in other): + self.discard(key) + + + def symmetric_difference(self, other): + r""" + Return the symmetric difference of two posets as a new poset. + + INPUT: + + - ``other`` -- a poset. + + OUTPUT: + + A poset. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.symmetric_difference(Q) + poset(3, 4, 7, 8) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + """ + new = self.copy() + new.symmetric_difference_update(other) + return new + + + def symmetric_difference_update(self, other): + r""" + Update this poset with the symmetric difference of itself and + another poset. + + INPUT: + + - ``other`` -- a poset. + + OUTPUT: + + Nothing. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal; + ``A.symmetric_difference_update(B)`` and + ``B.symmetric_difference_update(A)`` might + result in different posets. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.symmetric_difference_update(Q) + sage: P + poset(3, 4, 7, 8) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + """ + T = other.difference(self) + self.difference_update(other) + self.union_update(T) + + + def is_disjoint(self, other): + r""" + Return whether another poset is disjoint to this poset. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + + OUTPUT: + + Nothing. + + .. NOTE:: + + If this poset uses a ``key``-function, then all + comparisons are performed on the keys of the elements (and + not on the elements themselves). + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.is_disjoint(Q) + False + sage: P.is_disjoint(Q.difference(P)) + True + + .. SEEALSO:: + + :meth:`is_subset`, + :meth:`is_superset`, + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`. + """ + return all(key not in other for key in self.keys()) + + + isdisjoint = is_disjoint # as in a Python set + r""" + Alias of :meth:`is_disjoint`. + """ + + + def is_subset(self, other): + r""" + Return whether another poset contains this poset, i.e., whether this poset + is a subset of the other poset. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + + OUTPUT: + + Nothing. + + .. NOTE:: + + If this poset uses a ``key``-function, then all + comparisons are performed on the keys of the elements (and + not on the elements themselves). + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.is_subset(Q) + False + sage: Q.is_subset(P) + False + sage: P.is_subset(P) + True + sage: P.is_subset(P.union(Q)) + True + + .. SEEALSO:: + + :meth:`is_disjoint`, + :meth:`is_superset`, + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`. + """ + return all(key in other for key in self.keys()) + + + issubset = is_subset # as in a Python set + r""" + Alias of :meth:`is_subset`. + """ + + + def is_superset(self, other): + r""" + Return whether this poset contains another poset, i.e., whether this poset + is a superset of the other poset. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + + OUTPUT: + + Nothing. + + .. NOTE:: + + If this poset uses a ``key``-function, then all + comparisons are performed on the keys of the elements (and + not on the elements themselves). + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.is_superset(Q) + False + sage: Q.is_superset(P) + False + sage: P.is_superset(P) + True + sage: P.union(Q).is_superset(P) + True + + .. SEEALSO:: + + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`. + """ + try: + it = other.keys() + except AttributeError: + it = iter(other) + return all(key in self for key in it) + + + issuperset = is_superset # as in a Python set + r""" + Alias of :meth:`is_superset`. + """ + + + def merge(self, key=None, reverse=False): + r""" + Merge the given element with its successors/predecessors. + + INPUT: + + - ``key`` -- the key specifying an element or ``None`` + (default), in which case this method is called on each + element in this poset. + + - ``reverse`` -- (default: ``False``) specifies which + direction to go first: + ``False`` searches towards ``'oo'`` and + ``True`` searches towards ``'null'``. + When ``key=None``, then this also + specifies which elements are merged first. + + OUTPUT: + + Nothing. + + This method tests all (not necessarily direct) successors and + predecessors of the given element whether they can be merged with + the element itself. This is done by the ``can_merge``-function + of :class:`MutablePoset`. If this merge is possible, then it + is performed by calling :class:`MutablePoset`'s + ``merge``-function and the corresponding successor/predecessor + is removed from the poset. + + .. NOTE:: + + ``can_merge`` is applied in the sense of the condition of + depth first iteration, i.e., once ``can_merge`` fails, + the successors/predecessors are no longer tested. + + .. NOTE:: + + The motivation for such a merge behavior comes from + asymptotic expansions: `O(n^3)` merges with, for + example, `3n^2` or `O(n)` to `O(n^3)` (as `n` tends to + `\infty`; see :wikipedia:`Big_O_notation`). + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: key = lambda t: T(t[0:2]) + sage: def add(left, right): + ....: return (left[0], left[1], + ....: ''.join(sorted(left[2] + right[2]))) + sage: def can_add(left, right): + ....: return key(left) >= key(right) + sage: P = MP([(1, 1, 'a'), (1, 3, 'b'), (2, 1, 'c'), + ....: (4, 4, 'd'), (1, 2, 'e'), (2, 2, 'f')], + ....: key=key, merge=add, can_merge=can_add) + sage: Q = copy(P) + sage: Q.merge(T((1, 3))) + sage: print Q.repr_full(reverse=True) + poset((4, 4, 'd'), (1, 3, 'abe'), (2, 2, 'f'), (2, 1, 'c')) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4, 'd') + +-- (4, 4, 'd') + | +-- successors: oo + | +-- predecessors: (1, 3, 'abe'), (2, 2, 'f') + +-- (1, 3, 'abe') + | +-- successors: (4, 4, 'd') + | +-- predecessors: null + +-- (2, 2, 'f') + | +-- successors: (4, 4, 'd') + | +-- predecessors: (2, 1, 'c') + +-- (2, 1, 'c') + | +-- successors: (2, 2, 'f') + | +-- predecessors: null + +-- null + | +-- successors: (1, 3, 'abe'), (2, 1, 'c') + | +-- no predecessors + sage: for k in P.keys(): + ....: Q = copy(P) + ....: Q.merge(k) + ....: print 'merging %s: %s' % (k, Q) + merging (1, 2): poset((1, 2, 'ae'), (1, 3, 'b'), + (2, 1, 'c'), (2, 2, 'f'), (4, 4, 'd')) + merging (1, 3): poset((1, 3, 'abe'), (2, 1, 'c'), + (2, 2, 'f'), (4, 4, 'd')) + merging (4, 4): poset((4, 4, 'abcdef')) + merging (2, 1): poset((1, 2, 'e'), (1, 3, 'b'), + (2, 1, 'ac'), (2, 2, 'f'), (4, 4, 'd')) + merging (2, 2): poset((1, 3, 'b'), (2, 2, 'acef'), (4, 4, 'd')) + merging (1, 1): poset((1, 1, 'a'), (1, 2, 'e'), (1, 3, 'b'), + (2, 1, 'c'), (2, 2, 'f'), (4, 4, 'd')) + sage: Q = copy(P) + sage: Q.merge(); Q + poset((4, 4, 'abcdef')) + + .. SEEALSO:: + + :meth:`MutablePosetShell.merge` + + TESTS:: + + sage: copy(P).merge(reverse=False) == copy(P).merge(reverse=True) + True + + :: + + sage: P = MP(srange(4), + ....: merge=lambda l, r: l, can_merge=lambda l, r: l >= r); P + poset(0, 1, 2, 3) + sage: Q = P.copy() + sage: Q.merge(reverse=True); Q + poset(3) + sage: R = P.mapped(lambda x: x+1) + sage: R.merge(reverse=True); R + poset(4) + + :: + + sage: P = MP(srange(4), + ....: merge=lambda l, r: r, can_merge=lambda l, r: l < r) + sage: P.merge() + Traceback (most recent call last): + ... + RuntimeError: Stopping merge before started; + the can_merge-function is not reflexive. + """ + if key is None: + for shell in tuple(self.shells_topological(reverse=reverse)): + if shell.key in self._shells_: + self.merge(key=shell.key) + return + + shell = self.shell(key) + def can_merge(other): + return self._can_merge_(shell.element, other.element) + for rev in (reverse, not reverse): + to_merge = shell.iter_depth_first( + reverse=rev, condition=can_merge) + try: + next(to_merge) + except StopIteration: + raise RuntimeError('Stopping merge before started; the ' + 'can_merge-function is not reflexive.') + for m in tuple(to_merge): + if m.is_special(): + continue + shell.merge(m.element, check=False, delete=True) + + + def maximal_elements(self): + r""" + Return an iterator over the maximal elements of this poset. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((1, 2)), T((2, 2))]) + sage: list(P.maximal_elements()) + [(1, 3), (2, 2)] + + .. SEEALSO:: + + :meth:`minimal_elements` + """ + return iter(shell.element + for shell in self.oo.predecessors() + if not shell.is_special()) + + + def minimal_elements(self): + r""" + Return an iterator over the minimal elements of this poset. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: list(P.minimal_elements()) + [(1, 2), (2, 1)] + + .. SEEALSO:: + + :meth:`maximal_elements` + """ + return iter(shell.element + for shell in self.null.successors() + if not shell.is_special()) + + + def map(self, function, topological=False, reverse=False): + r""" + Apply the given ``function`` to each element of this poset. + + INPUT: + + - ``function`` -- a function mapping an existing element to + a new element. + + - ``topological`` -- (default: ``False``) if set, then the + mapping is done in topological order, otherwise unordered. + + - ``reverse`` -- is passed on to topological ordering. + + OUTPUT: + + Nothing. + + .. NOTE:: + + Since this method works inplace, it is not allowed that + ``function`` alters the key of an element. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))], + ....: key=lambda e: e[:2]) + sage: P.map(lambda e: e + (sum(e),)) + sage: P + poset((1, 2, 3), (1, 3, 4), (2, 1, 3), (2, 2, 4), (4, 4, 8)) + + .. SEEALSO:: + + :meth:`copy`, + :meth:`mapped`. + """ + shells = self.shells_topological(reverse=reverse) \ + if topological else self.shells() + for shell in shells: + shell._element_ = function(shell._element_) + + + def mapped(self, function): + r""" + Return a poset where on each element the given ``function`` + was applied. + + INPUT: + + - ``function`` -- a function mapping an existing element to + a new element. + + - ``topological`` -- (default: ``False``) if set, then the + mapping is done in topological order, otherwise unordered. + + - ``reverse`` -- is passed on to topological ordering. + + OUTPUT: + + A :class:`MutablePoset`. + + .. NOTE:: + + ``function`` is not allowed to change the order of the keys, + but changing the keys themselves is allowed (in contrast + to :meth:`map`). + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: P.mapped(lambda e: str(e)) + poset('(1, 2)', '(1, 3)', '(2, 1)', '(2, 2)', '(4, 4)') + + .. SEEALSO:: + + :meth:`copy`, + :meth:`map`. + """ + return self.copy(mapping=function) + + +# ***************************************************************************** diff --git a/src/sage/databases/findstat.py b/src/sage/databases/findstat.py index c831644c4ca..61dfb90dab1 100644 --- a/src/sage/databases/findstat.py +++ b/src/sage/databases/findstat.py @@ -458,6 +458,15 @@ def __call__(self, query, function=None, depth=2, max_values=FINDSTAT_MAX_VALUES Traceback (most recent call last): ... ValueError: The given arguments, Permutations and 1, cannot be used for a FindStat search. + + sage: from sage.databases.findstat import FindStatCollection + sage: findstat(FindStatCollection("Permutations"), lambda pi: pi.length()) # optional -- internet,random + 0: (St000018: The [[/Permutations/Inversions|number of inversions]] of a permutation., [], 200) + 1: (St000004: The [[/Permutations/Descents-Major|major index]] of a permutation., [Mp00062: inversion-number to major-index bijection], 200) + 2: (St000067: The inversion number of the alternating sign matrix., [Mp00063: to alternating sign matrix], 152) + 3: (St000246: The [[/Permutations/Inversions|number of non-inversions]] of a permutation., [Mp00064: reverse], 200) + 4: (St000008: The major index of the composition., [Mp00062: inversion-number to major-index bijection, Mp00071: descent composition], 200) + """ try: depth = int(depth) @@ -526,7 +535,9 @@ def __call__(self, query, function=None, depth=2, max_values=FINDSTAT_MAX_VALUES else: if callable(function): - if not isinstance(query, FindStatCollection): + if isinstance(query, FindStatCollection): + collection = query + else: collection = FindStatCollection(query) first_terms = collection.first_terms(function, max_values=max_values) data = [([key], [value]) for (key, value) in first_terms] diff --git a/src/sage/dev/git_interface.py b/src/sage/dev/git_interface.py index 8f8148987b9..3cebbe75805 100644 --- a/src/sage/dev/git_interface.py +++ b/src/sage/dev/git_interface.py @@ -211,16 +211,7 @@ def _execute(self, cmd, *args, **kwds): sage: git._execute('status',foo=True) # --foo is not a valid parameter Traceback (most recent call last): ... - GitError: git returned with non-zero exit code (129) for - "git -c user.email=doc@test.test -c user.name=doctest status --foo". - output to stderr: error: unknown option `foo' - usage: git status [options] [--] ... - - -v, --verbose be verbose - -s, --short show status concisely - -b, --branch show branch information - --porcelain machine-readable output - ... + GitError: git returned with non-zero exit code ... """ exit_code, stdout, stderr, cmd = self._run_git(cmd, args, kwds) if exit_code: @@ -251,16 +242,7 @@ def _execute_silent(self, cmd, *args, **kwds): sage: git._execute_silent('status',foo=True) # --foo is not a valid parameter Traceback (most recent call last): ... - GitError: git returned with non-zero exit code (129) for - "git -c user.email=doc@test.test -c user.name=doctest status --foo". - output to stderr: error: unknown option `foo' - usage: git status [options] [--] ... - - -v, --verbose be verbose - -s, --short show status concisely - -b, --branch show branch information - --porcelain machine-readable output - ... + GitError: git returned with non-zero exit code ... """ exit_code, stdout, stderr, cmd = self._run_git(cmd, args, kwds) if exit_code: @@ -546,9 +528,7 @@ def get_state(self): sage: git._execute_supersilent('rebase', 'branch2') Traceback (most recent call last): ... - GitError: git returned with non-zero exit code (1) for - "git -c user.email=doc@test.test -c user.name=doctest rebase branch2". - ... + GitError: git returned with non-zero exit code ... sage: git.get_state() ('rebase',) sage: git.super_silent.rebase(abort=True) diff --git a/src/sage/dynamics/interval_exchanges/template.py b/src/sage/dynamics/interval_exchanges/template.py index ded494ef6d0..8d9ee2fd7f9 100644 --- a/src/sage/dynamics/interval_exchanges/template.py +++ b/src/sage/dynamics/interval_exchanges/template.py @@ -251,7 +251,7 @@ class Permutation(SageObject): r""" Template for all permutations. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -790,7 +790,7 @@ class PermutationIET(Permutation): """ Template for permutation from Interval Exchange Transformation. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1582,7 +1582,7 @@ class PermutationLI(Permutation): r""" Template for quadratic permutation. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1857,7 +1857,7 @@ class FlippedPermutation(Permutation): r""" Template for flipped generalized permutations. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1916,7 +1916,7 @@ class FlippedPermutationIET(FlippedPermutation, PermutationIET): r""" Template for flipped Abelian permutations. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1945,7 +1945,7 @@ class FlippedPermutationLI(FlippedPermutation, PermutationLI): r""" Template for flipped quadratic permutations. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1981,7 +1981,7 @@ class RauzyDiagram(SageObject): r""" Template for Rauzy diagrams. - .. warning: + .. WARNING:: Internal class! Do not use directly! @@ -2600,27 +2600,30 @@ def __init__(self, p, top_bottom_inversion=False, symmetric=False): r""" - self._succ contains successors - self._pred contains predecessors + - ``self._succ`` contains successors - self._element_class is the class of elements of self - self._element is an instance of this class (hence contains the alphabet, - the representation mode, ...). It is used to store data about property - of permutations and also as a fast iterator. + - ``self._pred`` contains predecessors - INPUT: + - ``self._element_class`` is the class of elements of ``self`` - - ``right_induction`` - boolean or 'top' or 'bottom': consider the - right induction + - ``self._element`` is an instance of this class (hence + contains the alphabet, the representation mode, ...). It is + used to store data about property of permutations and also as + a fast iterator. - - ``left_induction`` - boolean or 'top' or 'bottom': consider the - left induction + INPUT: + + - ``right_induction`` - boolean or 'top' or 'bottom': consider the + right induction + + - ``left_induction`` - boolean or 'top' or 'bottom': consider the + left induction - - ``left_right_inversion`` - consider the left right inversion + - ``left_right_inversion`` - consider the left right inversion - - ``top_bottom_inversion`` - consider the top bottom inversion + - ``top_bottom_inversion`` - consider the top bottom inversion - - ``symmetric`` - consider the symmetric + - ``symmetric`` - consider the symmetric TESTS:: @@ -3529,7 +3532,7 @@ class FlippedRauzyDiagram(RauzyDiagram): r""" Template for flipped Rauzy diagrams. - .. warning: + .. WARNING:: Internal class! Do not use directly! diff --git a/src/sage/functions/log.py b/src/sage/functions/log.py index ac1f2e0fd00..a25b85f5976 100644 --- a/src/sage/functions/log.py +++ b/src/sage/functions/log.py @@ -565,8 +565,10 @@ def __init__(self): 0.567143290409784 """ BuiltinFunction.__init__(self, "lambert_w", nargs=2, - conversions={'mathematica':'ProductLog', - 'maple':'LambertW'}) + conversions={'mathematica': 'ProductLog', + 'maple': 'LambertW', + 'matlab': 'lambertw', + 'maxima': 'generalized_lambert_w'}) def __call__(self, *args, **kwds): r""" @@ -718,21 +720,25 @@ def _maxima_init_evaled_(self, n, z): sage: lambert_w(0, x)._maxima_() lambert_w(_SAGE_VAR_x) sage: lambert_w(1, x)._maxima_() - Traceback (most recent call last): - ... - NotImplementedError: Non-principal branch lambert_w[1](x) is not implemented in Maxima + generalized_lambert_w(1,_SAGE_VAR_x) + + TESTS:: + + sage: lambert_w(x)._maxima_()._sage_() + lambert_w(x) + sage: lambert_w(2, x)._maxima_()._sage_() + lambert_w(2, x) """ + if isinstance(z, str): + maxima_z = z + elif hasattr(z, '_maxima_init_'): + maxima_z = z._maxima_init_() + else: + maxima_z = str(z) if n == 0: - if isinstance(z,str): - maxima_z=z - elif hasattr(z,'_maxima_init_'): - maxima_z=z._maxima_init_() - else: - maxima_z=str(z) return "lambert_w(%s)" % maxima_z else: - raise NotImplementedError("Non-principal branch lambert_w[%s](%s) is not implemented in Maxima" % (n, z)) - + return "generalized_lambert_w(%s,%s)" % (n, maxima_z) def _print_(self, n, z): """ @@ -759,15 +765,17 @@ def _print_latex_(self, n, z): EXAMPLES:: sage: latex(lambert_w(1)) - \operatorname{W_0}(1) + \operatorname{W}({1}) sage: latex(lambert_w(0,x)) - \operatorname{W_0}(x) + \operatorname{W}({x}) sage: latex(lambert_w(1,x)) - \operatorname{W_{1}}(x) + \operatorname{W_{1}}({x}) + sage: latex(lambert_w(1,x+exp(x))) + \operatorname{W_{1}}({x + e^{x}}) """ if n == 0: - return r"\operatorname{W_0}(%s)" % z + return r"\operatorname{W}({%s})" % z._latex_() else: - return r"\operatorname{W_{%s}}(%s)" % (n, z) + return r"\operatorname{W_{%s}}({%s})" % (n, z._latex_()) lambert_w = Function_lambert_w() diff --git a/src/sage/functions/other.py b/src/sage/functions/other.py index 1dbe2c36b01..8cb74664a95 100644 --- a/src/sage/functions/other.py +++ b/src/sage/functions/other.py @@ -441,6 +441,8 @@ def __call__(self, x, maximum_bits=20000): 100000000000000000000000000000000000000000000000000 sage: ceil(int(10^50)) 100000000000000000000000000000000000000000000000000 + sage: ceil((1725033*pi - 5419351)/(25510582*pi - 80143857)) + -2 """ try: return x.ceil() @@ -453,39 +455,29 @@ def __call__(self, x, maximum_bits=20000): import numpy return numpy.ceil(x) - x_original = x - from sage.rings.all import RealIntervalField - # If x can be coerced into a real interval, then we should - # try increasing the number of bits of precision until - # we get the ceiling at each of the endpoints is the same. - # The precision will continue to be increased up to maximum_bits - # of precision at which point it will raise a value error. + bits = 53 + while bits < maximum_bits: + try: + x_interval = RealIntervalField(bits)(x) + except TypeError: + # If we cannot compute a numerical enclosure, leave the + # expression unevaluated. + return BuiltinFunction.__call__(self, SR(x)) + try: + return x_interval.unique_ceil() + except ValueError: + bits *= 2 + try: - x_interval = RealIntervalField(bits)(x) - upper_ceil = x_interval.upper().ceil() - lower_ceil = x_interval.lower().ceil() + return ceil(SR(x).full_simplify().canonicalize_radical()) + except ValueError: + pass - while upper_ceil != lower_ceil and bits < maximum_bits: - bits += 100 - x_interval = RealIntervalField(bits)(x) - upper_ceil = x_interval.upper().ceil() - lower_ceil = x_interval.lower().ceil() + raise ValueError("computing ceil(%s) requires more than %s bits of precision (increase maximum_bits to proceed)"%(x, maximum_bits)) - if bits < maximum_bits: - return lower_ceil - else: - try: - return ceil(SR(x).full_simplify().canonicalize_radical()) - except ValueError: - pass - raise ValueError("x (= %s) requires more than %s bits of precision to compute its ceiling"%(x, maximum_bits)) - except TypeError: - # If x cannot be coerced into a RealField, then - # it should be left as a symbolic expression. - return BuiltinFunction.__call__(self, SR(x_original)) def _eval_(self, x): """ @@ -612,6 +604,8 @@ def __call__(self, x, maximum_bits=20000): 99999999999999999999999999999999999999999999999999 sage: floor(int(10^50)) 100000000000000000000000000000000000000000000000000 + sage: floor((1725033*pi - 5419351)/(25510582*pi - 80143857)) + -3 """ try: return x.floor() @@ -624,40 +618,27 @@ def __call__(self, x, maximum_bits=20000): import numpy return numpy.floor(x) - x_original = x - from sage.rings.all import RealIntervalField - # If x can be coerced into a real interval, then we should - # try increasing the number of bits of precision until - # we get the floor at each of the endpoints is the same. - # The precision will continue to be increased up to maximum_bits - # of precision at which point it will raise a value error. bits = 53 - try: - x_interval = RealIntervalField(bits)(x) - upper_floor = x_interval.upper().floor() - lower_floor = x_interval.lower().floor() - - while upper_floor != lower_floor and bits < maximum_bits: - bits += 100 + while bits < maximum_bits: + try: x_interval = RealIntervalField(bits)(x) - upper_floor = x_interval.upper().floor() - lower_floor = x_interval.lower().floor() + except TypeError: + # If we cannot compute a numerical enclosure, leave the + # expression unevaluated. + return BuiltinFunction.__call__(self, SR(x)) + try: + return x_interval.unique_floor() + except ValueError: + bits *= 2 - if bits < maximum_bits: - return lower_floor - else: - try: - return floor(SR(x).full_simplify().canonicalize_radical()) - except ValueError: - pass - raise ValueError("x (= %s) requires more than %s bits of precision to compute its floor"%(x, maximum_bits)) - - except TypeError: - # If x cannot be coerced into a RealField, then - # it should be left as a symbolic expression. - return BuiltinFunction.__call__(self, SR(x_original)) + try: + return floor(SR(x).full_simplify().canonicalize_radical()) + except ValueError: + pass + + raise ValueError("computing floor(%s) requires more than %s bits of precision (increase maximum_bits to proceed)"%(x, maximum_bits)) def _eval_(self, x): """ diff --git a/src/sage/game_theory/normal_form_game.py b/src/sage/game_theory/normal_form_game.py index 695647aed3e..827363f716c 100644 --- a/src/sage/game_theory/normal_form_game.py +++ b/src/sage/game_theory/normal_form_game.py @@ -606,7 +606,6 @@ from collections import MutableMapping from itertools import product from parser import Parser -from sage.combinat.cartesian_product import CartesianProduct from sage.misc.latex import latex from sage.misc.misc import powerset from sage.rings.all import QQ @@ -1549,7 +1548,7 @@ def _solve_enumeration(self, maximization=True): powerset(range(player.num_strategies))] for player in self.players] - potential_support_pairs = [pair for pair in CartesianProduct(*potential_supports) if len(pair[0]) == len(pair[1])] + potential_support_pairs = [pair for pair in product(*potential_supports) if len(pair[0]) == len(pair[1])] equilibria = [] for pair in potential_support_pairs: @@ -2027,7 +2026,7 @@ def is_degenerate(self, certificate=False): potential_supports] potential_support_pairs = [pair for pair in - CartesianProduct(*potential_supports) if + product(*potential_supports) if len(pair[0]) != len(pair[1])] # Sort so that solve small linear systems first diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index 936b8c434ee..68fd8ba37cc 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -207,10 +207,10 @@ is_ToricLatticeQuotient from sage.geometry.toric_plotter import ToricPlotter, label_list from sage.graphs.digraph import DiGraph -from sage.matrix.all import matrix +from sage.matrix.all import matrix, MatrixSpace from sage.misc.all import cached_method, flatten, latex from sage.misc.superseded import deprecation -from sage.modules.all import span, vector +from sage.modules.all import span, vector, VectorSpace from sage.rings.all import QQ, RR, ZZ, gcd from sage.structure.all import SageObject, parent from sage.libs.ppl import C_Polyhedron, Generator_System, Constraint_System, \ @@ -4348,6 +4348,254 @@ def lineality(self): """ return self.linear_subspace().dimension() + @cached_method + def discrete_complementarity_set(self): + r""" + Compute a discrete complementarity set of this cone. + + A discrete complementarity set of a cone is the set of all + orthogonal pairs `(x,s)` where `x` is in some fixed generating + set of the cone, and `s` is in some fixed generating set of its + dual. The generators chosen for this cone and its dual are + simply their :meth:`~IntegralRayCollection.rays`. + + OUTPUT: + + A tuple of pairs `(x,s)` such that, + + * `x` and `s` are nonzero. + * `x` and `s` are orthogonal. + * `x` is one of this cone's :meth:`~IntegralRayCollection.rays`. + * `s` is one of the :meth:`~IntegralRayCollection.rays` of this + cone's :meth:`dual`. + + REFERENCES: + + .. [Orlitzky] M. Orlitzky. The Lyapunov rank of an improper cone. + http://www.optimization-online.org/DB_HTML/2015/10/5135.html + + EXAMPLES: + + Pairs of standard basis elements form a discrete complementarity + set for the nonnegative orthant:: + + sage: K = Cone([(1,0),(0,1)]) + sage: K.discrete_complementarity_set() + ((N(1, 0), M(0, 1)), (N(0, 1), M(1, 0))) + + If a cone consists of a single ray, then the second components + of a discrete complementarity set for that cone should generate + the orthogonal complement of the ray:: + + sage: K = Cone([(1,0)]) + sage: K.discrete_complementarity_set() + ((N(1, 0), M(0, 1)), (N(1, 0), M(0, -1))) + sage: K = Cone([(1,0,0)]) + sage: K.discrete_complementarity_set() + ((N(1, 0, 0), M(0, 1, 0)), + (N(1, 0, 0), M(0, -1, 0)), + (N(1, 0, 0), M(0, 0, 1)), + (N(1, 0, 0), M(0, 0, -1))) + + When a cone is the entire space, its dual is the trivial cone, + so the only discrete complementarity set for it is empty:: + + sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)]) + sage: K.is_full_space() + True + sage: K.discrete_complementarity_set() + () + + Likewise for trivial cones, whose duals are the entire space:: + + sage: L = ToricLattice(0) + sage: K = Cone([], ToricLattice(0)) + sage: K.discrete_complementarity_set() + () + + TESTS: + + A discrete complementarity set for the dual can be obtained by + switching components in a discrete complementarity set of the + original cone:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=6) + sage: dcs_dual = K.dual().discrete_complementarity_set() + sage: expected = tuple( (x,s) for (s,x) in dcs_dual ) + sage: actual = K.discrete_complementarity_set() + sage: sorted(actual) == sorted(expected) + True + + The pairs in a discrete complementarity set are in fact + complementary:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=6) + sage: dcs = K.discrete_complementarity_set() + sage: sum([ x.inner_product(s).abs() for (x,s) in dcs ]) + 0 + """ + # Return an immutable tuple instead of a mutable list because + # the result will be cached. + return tuple( (x,s) for x in self + for s in self.dual() + if x.inner_product(s) == 0 ) + + def lyapunov_like_basis(self): + r""" + Compute a basis of Lyapunov-like transformations on this cone. + + A linear transformation `L` is said to be Lyapunov-like on this + cone if `L(x)` and `s` are orthogonal for every pair `(x,s)` in + its :meth:`discrete_complementarity_set`. The set of all such + transformations forms a vector space, namely the Lie algebra of + the automorphism group of this cone. + + OUTPUT: + + A list of matrices forming a basis for the space of all + Lyapunov-like transformations on this cone. + + REFERENCES: + + M. Orlitzky. The Lyapunov rank of an improper cone. + http://www.optimization-online.org/DB_HTML/2015/10/5135.html + + .. [Rudolf] G. Rudolf, N. Noyan, D. Papp, and F. Alizadeh. + Bilinear optimality constraints for the cone of positive + polynomials. Mathematical Programming, Series B, 129 (2011) 5-31. + + EXAMPLES: + + Every transformation is Lyapunov-like on the trivial cone:: + + sage: K = Cone([(0,0)]) + sage: M = MatrixSpace(K.lattice().base_field(), K.lattice_dim()) + sage: M.basis() == K.lyapunov_like_basis() + True + + And by duality, every transformation is Lyapunov-like on the + ambient space:: + + sage: K = Cone([(1,0), (-1,0), (0,1), (0,-1)]) + sage: K.is_full_space() + True + sage: M = MatrixSpace(K.lattice().base_field(), K.lattice_dim()) + sage: M.basis() == K.lyapunov_like_basis() + True + + However, in a trivial space, there are no non-trivial linear maps, + so there can be no Lyapunov-like basis:: + + sage: L = ToricLattice(0) + sage: K = Cone([], lattice=L) + sage: K.lyapunov_like_basis() + [] + + The Lyapunov-like transformations on the nonnegative orthant are + diagonal matrices:: + + sage: K = Cone([(1,)]) + sage: K.lyapunov_like_basis() + [[1]] + + sage: K = Cone([(1,0),(0,1)]) + sage: K.lyapunov_like_basis() + [ + [1 0] [0 0] + [0 0], [0 1] + ] + + sage: K = Cone([(1,0,0),(0,1,0),(0,0,1)]) + sage: K.lyapunov_like_basis() + [ + [1 0 0] [0 0 0] [0 0 0] + [0 0 0] [0 1 0] [0 0 0] + [0 0 0], [0 0 0], [0 0 1] + ] + + Only the identity matrix is Lyapunov-like on the pyramids + defined by the one- and infinity-norms [Rudolf]_:: + + sage: l31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)]) + sage: l31.lyapunov_like_basis() + [ + [1 0 0] + [0 1 0] + [0 0 1] + ] + + sage: l3infty = Cone([(0,1,1), (1,0,1), (0,-1,1), (-1,0,1)]) + sage: l3infty.lyapunov_like_basis() + [ + [1 0 0] + [0 1 0] + [0 0 1] + ] + + TESTS: + + The vectors `L(x)` and `s` are orthogonal for every pair `(x,s)` + in the :meth:`discrete_complementarity_set` of the cone:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=8) + sage: dcs = K.discrete_complementarity_set() + sage: LL = K.lyapunov_like_basis() + sage: ips = [ (L*x).inner_product(s) for (x,s) in dcs + ....: for L in LL ] + sage: sum(map(abs, ips)) + 0 + + The Lyapunov-like transformations on a cone and its dual are + transposes of one another. However, there's no reason to expect + that one basis will consist of transposes of the other:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=8) + sage: LL1 = K.lyapunov_like_basis() + sage: LL2 = [L.transpose() for L in K.dual().lyapunov_like_basis()] + sage: V = VectorSpace(K.lattice().base_field(), K.lattice_dim()^2) + sage: LL1_vecs = [ V(m.list()) for m in LL1 ] + sage: LL2_vecs = [ V(m.list()) for m in LL2 ] + sage: V.span(LL1_vecs) == V.span(LL2_vecs) + True + + The space of all Lyapunov-like transformations is a Lie algebra + and should therefore be closed under the lie bracket:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=4) + sage: LL = K.lyapunov_like_basis() + sage: W = VectorSpace(K.lattice().base_field(), K.lattice_dim()**2) + sage: LL_W = W.span([ W(m.list()) for m in LL ]) + sage: brackets = [ W((L1*L2 - L2*L1).list()) for L1 in LL + ....: for L2 in LL ] + sage: all([ b in LL_W for b in brackets ]) + True + """ + # Matrices are not vectors in Sage, so we have to convert them + # to vectors explicitly before we can find a basis. We need these + # two values to construct the appropriate "long vector" space. + F = self.lattice().base_field() + n = self.lattice_dim() + + # These tensor products contain a basis for the orthogonal + # complement of the Lyapunov-like transformations on this cone. + tensor_products = [ s.tensor_product(x) + for (x,s) in self.discrete_complementarity_set() ] + + # Convert those tensor products to long vectors. + W = VectorSpace(F, n**2) + perp_vectors = [ W(tp.list()) for tp in tensor_products ] + + # Now find the Lyapunov-like transformations (as long vectors). + LL_vectors = W.span(perp_vectors).complement() + + # And finally convert the long vectors back to matrices. + M = MatrixSpace(F, n, n) + return [ M(v.list()) for v in LL_vectors.basis() ] def random_cone(lattice=None, min_ambient_dim=0, max_ambient_dim=None, diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 73fd2c6414e..f7bb0626e28 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -31,6 +31,7 @@ .. TODO:: Implement a parent for all geodesics of the hyperbolic plane? + Or implement geodesics as a parent in the subobjects category? """ @@ -1168,11 +1169,11 @@ def _crossratio_matrix(p0, p1, p2): # UHP r""" Given three points (the list `p`) in `\mathbb{CP}^{1}` in affine coordinates, return the linear fractional transformation taking - the elements of `p` to `0`,`1`, and `\infty'. + the elements of `p` to `0`, `1`, and `\infty`. INPUT: - - a list of three distinct elements of three distinct elements + - a list of three distinct elements of `\mathbb{CP}^1` in affine coordinates; that is, each element must be a complex number, `\infty`, or symbolic. diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index d8d3c411467..d2a4008207f 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -97,7 +97,7 @@ def __init__(self): sage: H = HyperbolicPlane() sage: TestSuite(H).run() """ - Parent.__init__(self, category=Sets().WithRealizations()) + Parent.__init__(self, category=Sets().Metric().WithRealizations()) self.a_realization() # We create a realization so at least one is known def _repr_(self): @@ -181,9 +181,10 @@ def super_categories(self): sage: H = HyperbolicPlane() sage: models = HyperbolicModels(H) sage: models.super_categories() - [Category of sets, Category of realizations of Hyperbolic plane] + [Category of metric spaces, + Category of realizations of Hyperbolic plane] """ - return [Sets(), Realizations(self.base())] + return [Sets().Metric(), Realizations(self.base())] class ParentMethods: def _an_element_(self): @@ -204,31 +205,3 @@ def _an_element_(self): """ return self(self.realization_of().PD().get_point(0)) - # TODO: Move to a category of metric spaces once created - @abstract_method - def dist(self, a, b): - """ - Return the distance between ``a`` and ``b``. - - EXAMPLES:: - - sage: PD = HyperbolicPlane().PD() - sage: PD.dist(PD.get_point(0), PD.get_point(I/2)) - arccosh(5/3) - """ - - class ElementMethods: - # TODO: Move to a category of metric spaces once created - def dist(self, other): - """ - Return the distance between ``self`` and ``other``. - - EXAMPLES:: - - sage: UHP = HyperbolicPlane().UHP() - sage: p1 = UHP.get_point(5 + 7*I) - sage: p2 = UHP.get_point(1 + I) - sage: p1.dist(p2) - arccosh(33/7) - """ - return self.parent().dist(self, other) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 73cbf2eee26..6717d0206fe 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -65,10 +65,6 @@ False sage: U.boundary_point_in_model(2) True - -.. TODO:: - - Implement a category for metric spaces. """ #*********************************************************************** diff --git a/src/sage/geometry/hyperplane_arrangement/arrangement.py b/src/sage/geometry/hyperplane_arrangement/arrangement.py index 2e7b4c8391d..bb58ffd52a0 100644 --- a/src/sage/geometry/hyperplane_arrangement/arrangement.py +++ b/src/sage/geometry/hyperplane_arrangement/arrangement.py @@ -23,7 +23,9 @@ sage: -2*h Hyperplane -6*x - 4*y + 10*z + 14 sage: x, y, z - (Hyperplane x + 0*y + 0*z + 0, Hyperplane 0*x + y + 0*z + 0, Hyperplane 0*x + 0*y + z + 0) + (Hyperplane x + 0*y + 0*z + 0, + Hyperplane 0*x + y + 0*z + 0, + Hyperplane 0*x + 0*y + z + 0) See :mod:`sage.geometry.hyperplane_arrangement.hyperplane` for more functionality of the individual hyperplanes. @@ -340,9 +342,11 @@ from sage.misc.cachefunc import cached_method from sage.misc.misc import uniq from sage.matrix.constructor import matrix, vector +from sage.modules.free_module import VectorSpace from sage.geometry.hyperplane_arrangement.hyperplane import AmbientVectorSpace, Hyperplane +from copy import copy class HyperplaneArrangementElement(Element): @@ -695,7 +699,6 @@ def intersection_poset(self): """ K = self.base_ring() from sage.geometry.hyperplane_arrangement.affine_subspace import AffineSubspace - from sage.modules.all import VectorSpace whole_space = AffineSubspace(0, VectorSpace(K, self.dimension())) L = [[whole_space]] active = True @@ -1359,16 +1362,17 @@ def vertices(self, exclude_sandwiched=False): sage: H. = HyperplaneArrangements(QQ) sage: chessboard = [] sage: N = 8 - sage: for x0, y0 in CartesianProduct(range(N+1), range(N+1)): - ....: chessboard.extend([x-x0, y-y0]) + sage: for x0 in range(N+1): + ....: for y0 in range(N+1): + ....: chessboard.extend([x-x0, y-y0]) sage: chessboard = H(chessboard) sage: len(chessboard.vertices()) 81 sage: chessboard.vertices(exclude_sandwiched=True) ((0, 0), (0, 8), (8, 0), (8, 8)) """ + import itertools from sage.matroids.all import Matroid - from sage.combinat.cartesian_product import CartesianProduct R = self.parent().base_ring() parallels = self._parallel_hyperplanes() A_list = [parallel[0][1] for parallel in parallels] @@ -1390,7 +1394,7 @@ def skip(b_list): for row, i in enumerate(indices): lhs[row] = A_list[i] b_list = [b_list_list[i] for i in indices] - for b in CartesianProduct(*b_list): + for b in itertools.product(*b_list): for i in range(d): rhs[i] = b[i] vertex = lhs.solve_right(rhs) @@ -1418,7 +1422,7 @@ def _make_region(self, hyperplanes): sage: h._make_region([x, 1-x, y, 1-y]) A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices """ - ieqs = [h.coefficients() for h in hyperplanes] + ieqs = [h.dense_coefficient_list() for h in hyperplanes] from sage.geometry.polyhedron.constructor import Polyhedron return Polyhedron(ieqs=ieqs, ambient_dim=self.dimension(), base_ring=self.parent().base_ring()) @@ -1454,8 +1458,9 @@ def regions(self): sage: chessboard = [] sage: N = 8 - sage: for x0, y0 in CartesianProduct(range(N+1), range(N+1)): - ....: chessboard.extend([x-x0, y-y0]) + sage: for x0 in range(N+1): + ....: for y0 in range(N+1): + ....: chessboard.extend([x-x0, y-y0]) sage: chessboard = H(chessboard) sage: len(chessboard.bounded_regions()) # long time, 359 ms on a Core i7 64 @@ -1468,7 +1473,7 @@ def regions(self): universe = Polyhedron(eqns=[[0] + [0] * dim], base_ring=R) regions = [universe] for hyperplane in self: - ieq = vector(R, hyperplane.coefficients()) + ieq = vector(R, hyperplane.dense_coefficient_list()) pos_half = Polyhedron(ieqs=[ ieq], base_ring=R) neg_half = Polyhedron(ieqs=[-ieq], base_ring=R) subdivided = [] @@ -1928,7 +1933,112 @@ def varchenko_matrix(self, names='h'): v.set_immutable() return v + @cached_method + def matroid(self): + r""" + Return the matroid associated to ``self``. + + Let `A` denote a central hyperplane arrangement and `n_H` the + normal vector of some hyperplane `H \in A`. We define a matroid + `M_A` as the linear matroid spanned by `\{ n_H | H \in A \}`. + The matroid `M_A` is such that the lattice of flats of `M` is + isomorphic to the intersection lattice of `A` + (Proposition 3.6 in [RS]_). + + EXAMPLES:: + + sage: P. = HyperplaneArrangements(QQ) + sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z) + sage: M = A.matroid(); M + Linear matroid of rank 3 on 7 elements represented over the Rational Field + + We check the lattice of flats is isomorphic to the + intersection lattice:: + + sage: f = sum([list(M.flats(i)) for i in range(M.rank()+1)], []) + sage: PF = Poset([f, lambda x,y: x < y]) + sage: PF.is_isomorphic(A.intersection_poset()) + True + """ + if not self.is_central(): + raise ValueError("the hyperplane arrangement must be central") + norms = [p.normal() for p in self] + from sage.matroids.constructor import Matroid + return Matroid(matrix=matrix(norms).transpose()) + + @cached_method + def minimal_generated_number(self): + r""" + Return the minimum `k` such that ``self`` is `k`-generated. + + Let `A` be a central hyperplane arrangement. Let `W_k` denote + the solution space of the linear system corresponding to the + linear dependencies among the hyperplanes of `A` of length at + most `k`. We say `A` is `k`-*generated* if + `\dim W_k = \operatorname{rank} A`. + Equivalently this says all dependencies forming the Orlik-Terao + ideal are generated by at most `k` hyperplanes. + + EXAMPLES: + + We construct Example 2.2 from [Vuz93]_:: + + sage: P. = HyperplaneArrangements(QQ) + sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, 3*x+5*z, 3*x+4*y+5*z) + sage: B = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, x+3*z, x+2*y+3*z) + sage: A.minimal_generated_number() + 3 + sage: B.minimal_generated_number() + 4 + + REFERENCES: + + .. [Vuz93] Sergey Yuzvinksy, + *The first two obstructions to the freeness of arrangements*, + Transactions of the American Mathematical Society, + Vol. 335, **1** (1993) pp. 231--244. + """ + V = VectorSpace(self.base_ring(), self.dimension()) + W = VectorSpace(self.base_ring(), self.n_hyperplanes()) + r = self.rank() + M = self.matroid() + norms = M.representation().columns() + circuits = M.circuits() + for i in range(2, self.n_hyperplanes()): + sol = [] + for d in circuits: + if len(d) > i: + continue + d = list(d) + dep = V.linear_dependence([norms[j] for j in d]) + w = W.zero().list() + for j,k in enumerate(d): + w[k] = dep[0][j] + sol.append(w) + mat = matrix(sol) + if mat.right_kernel().dimension() == r: + return i + return self.n_hyperplanes() + + def is_formal(self): + """ + Return if ``self`` is formal. + + A hyperplane arrangement is *formal* if it is 3-generated [Vuz93]_, + where `k`-generated is defined in :meth:`minimal_generated_number`. + + EXAMPLES:: + + sage: P. = HyperplaneArrangements(QQ) + sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, 3*x+5*z, 3*x+4*y+5*z) + sage: B = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, x+3*z, x+2*y+3*z) + sage: A.is_formal() + True + sage: B.is_formal() + False + """ + return self.minimal_generated_number() <= 3 class HyperplaneArrangements(Parent, UniqueRepresentation): """ diff --git a/src/sage/geometry/integral_points.pyx b/src/sage/geometry/integral_points.pyx index bde90c18d6d..3881ba5fce7 100644 --- a/src/sage/geometry/integral_points.pyx +++ b/src/sage/geometry/integral_points.pyx @@ -10,13 +10,14 @@ Cython helper methods to compute integral points in polyhedra. # http://www.gnu.org/licenses/ #***************************************************************************** +import copy +import itertools + from sage.matrix.constructor import matrix, column_matrix, vector, diagonal_matrix from sage.rings.all import QQ, RR, ZZ, gcd, lcm from sage.rings.integer cimport Integer from sage.combinat.permutation import Permutation -from sage.combinat.cartesian_product import CartesianProduct from sage.misc.all import prod, uniq -import copy ############################################################################## # The basic idea to enumerate the lattice points in the parallelotope @@ -209,7 +210,7 @@ cpdef loop_over_parallelotope_points(e, d, VDinv, R, lattice, A=None, b=None): s = ZZ.zero() # summation variable gen = lattice(0) q_times_d = vector(ZZ, dim) - for base in CartesianProduct(*[ range(0,i) for i in e ]): + for base in itertools.product(*[ range(0,i) for i in e ]): for i in range(0, dim): s = ZZ.zero() for j in range(0, dim): diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index 8f0eca94c9b..d0cc12f77b6 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -839,19 +839,19 @@ def _copy_faces(self, other, reverse=False): reflexive polytopes, faces of this polytope and its polar are in inclusion reversing bijection. - .. note:: + .. NOTE:: This function does not perform any checks that this operation makes sense. INPUT: - - ``other`` - another LatticePolytope, whose facial structure will be + - ``other`` -- another LatticePolytope, whose facial structure will be copied - - ``reverse`` - (default: False) if True, the facial structure of the - other polytope will be reversed, i.e. vertices will correspond to - facets etc. + - ``reverse`` -- (default: ``False``) if ``True``, the facial + structure of the other polytope will be reversed, + i.e. vertices will correspond to facets etc. TESTS:: @@ -1101,8 +1101,8 @@ def _pullback(self, data): INPUT: - - ``data`` - rational point or matrix of points (as columns) in the - ambient space + - ``data`` -- rational point or matrix of points (as columns) in the + ambient space OUTPUT: The same point(s) in the coordinates of the affine subspace space spanned by this polytope. @@ -5752,7 +5752,7 @@ def _palp_convert_permutation(permutation): PALP specifies a permutation group element by its domain. Furthermore, it only supports permutations of up to 62 objects and labels these by - `0 \dots 9', 'a \dots z', and 'A \dots Z'. + `0 \dots 9`, `a \dots z`, and `A \dots Z`. INPUT: @@ -5760,8 +5760,7 @@ def _palp_convert_permutation(permutation): OUTPUT: - A :class:`permutation group element - `. + A :class:`permutation group element `. EXAMPLES:: diff --git a/src/sage/geometry/linear_expression.py b/src/sage/geometry/linear_expression.py index 93e7d05cf4f..d52f0ddb04e 100644 --- a/src/sage/geometry/linear_expression.py +++ b/src/sage/geometry/linear_expression.py @@ -2,8 +2,8 @@ Linear Expressions A linear expression is just a linear polynomial in some (fixed) -variables. This class only implements linear expressions for others to -use. +variables (allowing a nonzero constant term). This class only implements +linear expressions for others to use. EXAMPLES:: @@ -12,6 +12,8 @@ Module of linear expressions in variables x, y, z over Rational Field sage: x + 2*y + 3*z + 4 x + 2*y + 3*z + 4 + sage: L(4) + 0*x + 0*y + 0*z + 4 You can also pass coefficients and a constant term to construct linear expressions:: @@ -23,7 +25,7 @@ sage: L([4, 1, 2, 3]) # note: constant is first in single-tuple notation x + 2*y + 3*z + 4 -The linear expressions are a module under the base ring, so you can +The linear expressions are a module over the base ring, so you can add them and multiply them with scalars:: sage: m = x + 2*y + 3*z + 4 @@ -91,9 +93,9 @@ def __init__(self, parent, coefficients, constant, check=True): self._const = constant if check: if self._coeffs.parent() is not self.parent().ambient_module(): - raise ValueError("cofficients are not in the ambient module") + raise ValueError("coefficients are not in the ambient module") if not self._coeffs.is_immutable(): - raise ValueError("cofficients are not immutable") + raise ValueError("coefficients are not immutable") if self._const.parent() is not self.parent().base_ring(): raise ValueError("the constant is not in the base ring") @@ -161,6 +163,32 @@ def coefficients(self): """ return [self._const] + list(self._coeffs) + dense_coefficient_list = coefficients + + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements in + the support of ``self`` and whose values are the corresponding + coefficients. + + INPUT: + + - ``copy`` -- ignored + + EXAMPLES:: + + sage: from sage.geometry.linear_expression import LinearExpressionModule + sage: L. = LinearExpressionModule(QQ) + sage: linear = L([1, 2, 3], 4) + sage: sorted(linear.monomial_coefficients().items()) + [(0, 1), (1, 2), (2, 3), ('b', 4)] + """ + zero = self.parent().base_ring().zero() + d = {i: v for i,v in enumerate(self._coeffs) if v != zero} + if self._const != zero: + d['b'] = self._const + return d + def _repr_vector(self, variable='x'): """ Return a string representation. @@ -351,6 +379,18 @@ def change_ring(self, base_ring): return self return P.change_ring(base_ring)(self) + def __hash__(self): + r""" + TESTS:: + + sage: from sage.geometry.linear_expression import LinearExpressionModule + sage: L. = LinearExpressionModule(QQ) + sage: hash(L([0,1])) + 3430019387558 # 64-bit + -1659481946 # 32-bit + """ + return hash(self._coeffs) ^ hash(self._const) + def __cmp__(self, other): """ Compare two linear expressions. @@ -379,10 +419,7 @@ def __cmp__(self, other): False """ assert type(self) is type(other) and self.parent() is other.parent() # guaranteed by framework - c = cmp(self._coeffs, other._coeffs) - if c != 0: return c - c = cmp(self._const, other._const) - return c + return cmp(self._coeffs, other._coeffs) or cmp(self._const, other._const) def evaluate(self, point): """ @@ -458,9 +495,31 @@ def __init__(self, base_ring, names=tuple()): sage: TestSuite(L).run() """ from sage.categories.modules import Modules - super(LinearExpressionModule, self).__init__(base_ring, category=Modules(base_ring)) + super(LinearExpressionModule, self).__init__(base_ring, category=Modules(base_ring).WithBasis().FiniteDimensional()) self._names = names - + + @cached_method + def basis(self): + """ + Return a basis of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.linear_expression import LinearExpressionModule + sage: L = LinearExpressionModule(QQ, ('x', 'y', 'z')) + sage: list(L.basis()) + [x + 0*y + 0*z + 0, + 0*x + y + 0*z + 0, + 0*x + 0*y + z + 0, + 0*x + 0*y + 0*z + 1] + """ + from sage.sets.family import Family + gens = self.gens() + d = {i: g for i,g in enumerate(gens)} + d['b'] = self.element_class(self, self.ambient_module().zero(), + self.base_ring().one()) + return Family(range(len(gens)) + ['b'], lambda i: d[i]) + @cached_method def ngens(self): """ @@ -482,7 +541,7 @@ def ngens(self): @cached_method def gens(self): """ - Return the generators. + Return the generators of ``self``. OUTPUT: @@ -576,7 +635,7 @@ def _element_constructor_(self, arg0, arg1=None): else: # Construct from list/tuple/iterable:: try: - arg0 = arg0.coefficients() + arg0 = arg0.dense_coefficient_list() except AttributeError: arg0 = list(arg0) const = arg0[0] diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 0d3fe99bad4..c19090f27b5 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -12,6 +12,7 @@ # http://www.gnu.org/licenses/ ######################################################################## +import itertools import six from sage.structure.element import Element, coerce_binop, is_Vector @@ -26,8 +27,6 @@ from sage.graphs.graph import Graph -from sage.combinat.cartesian_product import CartesianProduct - from constructor import Polyhedron @@ -120,6 +119,32 @@ def __init__(self, parent, Vrep, Hrep, **kwds): else: self._init_empty_polyhedron() + def __hash__(self): + r""" + TESTS:: + + sage: K. = QuadraticField(2) + sage: p = Polyhedron(vertices=[(0,1,a),(3,a,5)], + ....: rays=[(a,2,3), (0,0,1)], + ....: base_ring=K) + sage: q = Polyhedron(vertices=[(3,a,5),(0,1,a)], + ....: rays=[(0,0,1), (a,2,3)], + ....: base_ring=K) + sage: hash(p) == hash(q) + True + """ + # TODO: find something better *but* fast + return hash((self.dim(), + self.ambient_dim(), + self.n_Hrepresentation(), + self.n_Vrepresentation(), + self.n_equations(), + self.n_facets(), + self.n_inequalities(), + self.n_lines(), + self.n_rays(), + self.n_vertices())) + def _sage_input_(self, sib, coerced): """ Return Sage command to reconstruct ``self``. @@ -433,8 +458,8 @@ def _is_subpolyhedron(self, other): True """ return all( other_H.contains(self_V) - for other_H, self_V in - CartesianProduct(other.Hrepresentation(), self.Vrepresentation()) ) + for other_H in other.Hrepresentation() \ + for self_V in self.Vrepresentation()) def plot(self, point=None, line=None, polygon=None, # None means unspecified by the user @@ -819,6 +844,14 @@ def cdd_Hrepresentation(self): 1 -1 0 1 0 -1 end + + sage: triangle = Polyhedron(vertices = [[1,0],[0,1],[1,1]],base_ring=AA) + sage: triangle.base_ring() + Algebraic Real Field + sage: triangle.cdd_Hrepresentation() + Traceback (most recent call last): + ... + TypeError: The base ring must be ZZ, QQ, or RDF """ from cdd_file_format import cdd_Hrepresentation try: @@ -1891,8 +1924,17 @@ def base_ring(self): OUTPUT: - Either ``QQ`` (exact arithmetic using gmp, default) or ``RDF`` - (double precision floating-point arithmetic) + The ring over which the polyhedron is defined. Must be a + sub-ring of the reals to define a polyhedron, in particular + comparison must be defined. Popular choices are + + * ``ZZ`` (the ring of integers, lattice polytope), + + * ``QQ`` (exact arithmetic using gmp), + + * ``RDF`` (double precision floating-point arithmetic), or + + * ``AA`` (real algebraic field). EXAMPLES:: @@ -1909,7 +1951,7 @@ def center(self): """ Return the average of the vertices. - See also :meth:`interior_point`. + See also :meth:`representative_point`. OUTPUT: @@ -2381,7 +2423,7 @@ def Minkowski_difference(self, other): return P.element_class(P, None, [new_ieqs, new_eqns]) def __sub__(self, other): - """ + r""" Implement minus binary operation Polyhedra are not a ring with respect to dilatation and @@ -3867,7 +3909,7 @@ def lattice_polytope(self, envelope=False): vertices = [] for v in self.vertex_generator(): vbox = [ set([floor(x),ceil(x)]) for x in v ] - vertices.extend( CartesianProduct(*vbox) ) + vertices.extend( itertools.product(*vbox) ) # construct the (enveloping) lattice polytope from sage.geometry.lattice_polytope import LatticePolytope diff --git a/src/sage/geometry/polyhedron/base_ZZ.py b/src/sage/geometry/polyhedron/base_ZZ.py index 0e72f05b166..8de9c6e5581 100644 --- a/src/sage/geometry/polyhedron/base_ZZ.py +++ b/src/sage/geometry/polyhedron/base_ZZ.py @@ -547,8 +547,8 @@ def _subpoly_parallel_facets(self): edge_vectors.append([ v_prim*i for i in range(d+1) ]) origin = self.ambient_space().zero() parent = self.parent() - from sage.combinat.cartesian_product import CartesianProduct - for edges in CartesianProduct(*edge_vectors): + from itertools import product + for edges in product(*edge_vectors): v = [] point = origin for e in edges: diff --git a/src/sage/geometry/polyhedron/double_description.py b/src/sage/geometry/polyhedron/double_description.py index 3bff500b362..451e4638b90 100644 --- a/src/sage/geometry/polyhedron/double_description.py +++ b/src/sage/geometry/polyhedron/double_description.py @@ -628,7 +628,7 @@ def dim(self): return self._A.ncols() def __repr__(self): - """ + r""" Return a string representation. OUTPUT: diff --git a/src/sage/geometry/polyhedron/double_description_inhomogeneous.py b/src/sage/geometry/polyhedron/double_description_inhomogeneous.py index 9d4c6bd3d7d..2425070aca6 100644 --- a/src/sage/geometry/polyhedron/double_description_inhomogeneous.py +++ b/src/sage/geometry/polyhedron/double_description_inhomogeneous.py @@ -232,7 +232,7 @@ def _init_Vrep(self, inequalities, equations): return self._pivot_inequalities(A) def _split_linear_subspace(self): - """ + r""" Split the linear subspace in a generator with `x_0\not=0` and the remaining generators with `x_0=0`. @@ -504,7 +504,7 @@ def is_trivial(ray): self.equations = self._linear_subspace.matrix().rows() def _repr_(self): - """ + r""" Return a string representation. OUTPUT: diff --git a/src/sage/geometry/polyhedron/lattice_euclidean_group_element.py b/src/sage/geometry/polyhedron/lattice_euclidean_group_element.py index 96b2bc42eeb..f3fc6f54125 100644 --- a/src/sage/geometry/polyhedron/lattice_euclidean_group_element.py +++ b/src/sage/geometry/polyhedron/lattice_euclidean_group_element.py @@ -116,7 +116,7 @@ def __call__(self, x): return v def _repr_(self): - """ + r""" Return a string representation EXAMPLES:: diff --git a/src/sage/geometry/polyhedron/palp_database.py b/src/sage/geometry/polyhedron/palp_database.py index 98a85712530..4fb51d9312a 100644 --- a/src/sage/geometry/polyhedron/palp_database.py +++ b/src/sage/geometry/polyhedron/palp_database.py @@ -134,7 +134,7 @@ def _palp_Popen(self): return Popen(["class.x", "-b2a", "-di", self._data_basename], stdout=PIPE) def _read_vertices(self, stdout, rows, cols): - """ + r""" Read vertex data from the PALP output pipe. OUTPUT: @@ -158,7 +158,7 @@ def _read_vertices(self, stdout, rows, cols): return m def _read_vertices_transposed(self, stdout, rows, cols): - """ + r""" Read vertex data from the PALP output pipe. OUTPUT: @@ -428,9 +428,9 @@ def __init__(self, h11, h21, data_basename=None, **kwds): TESTS:: - sage: from sage.geometry.polyhedron.palp_database import Reflexive4dHodge - sage: Reflexive4dHodge(1,101) # optional - polytopes_db_4d - + sage: from sage.geometry.polyhedron.palp_database import Reflexive4dHodge + sage: Reflexive4dHodge(1,101) # optional - polytopes_db_4d + """ dim = 4 if data_basename is None: diff --git a/src/sage/geometry/polyhedron/ppl_lattice_polytope.py b/src/sage/geometry/polyhedron/ppl_lattice_polytope.py index 8ffaddec672..6b43c095f42 100644 --- a/src/sage/geometry/polyhedron/ppl_lattice_polytope.py +++ b/src/sage/geometry/polyhedron/ppl_lattice_polytope.py @@ -900,18 +900,19 @@ def base_rays(self, fiber, points): ((1),) """ quo = self.base_projection(fiber) - vertices = [] + vertices = set() for p in points: - v = vector(ZZ,quo(p)) + v = quo(p).vector() if v.is_zero(): continue - d = GCD_list(v.list()) - if d>1: - for i in range(0,v.degree()): + d = GCD_list(v.list()) + if d > 1: + v = v.__copy__() + for i in range(v.degree()): v[i] /= d - v.set_immutable() - vertices.append(v) - return tuple(uniq(vertices)) + v.set_immutable() + vertices.add(v) + return tuple(sorted(vertices)) @cached_method def has_IP_property(self): diff --git a/src/sage/geometry/polyhedron/representation.py b/src/sage/geometry/polyhedron/representation.py index b61b0caa2d5..3249ceb5184 100644 --- a/src/sage/geometry/polyhedron/representation.py +++ b/src/sage/geometry/polyhedron/representation.py @@ -135,9 +135,7 @@ def __cmp__(self, other): """ if not isinstance(other, PolyhedronRepresentation): return -1 - type_cmp = cmp(type(self), type(other)) - if (type_cmp != 0): return type_cmp - return cmp(self._vector, other._vector) + return cmp(type(self), type(other)) or cmp(self._vector, other._vector) def vector(self, base_ring=None): """ diff --git a/src/sage/graphs/asteroidal_triples.pyx b/src/sage/graphs/asteroidal_triples.pyx index 56bfe7d2ce6..fbe72d233ca 100644 --- a/src/sage/graphs/asteroidal_triples.pyx +++ b/src/sage/graphs/asteroidal_triples.pyx @@ -240,7 +240,7 @@ cdef list is_asteroidal_triple_free_C(int n, # We now search for an unseen vertex v = bitset_first_in_complement(seen) - while v!=-1: + while v != -1: # and add it to the queue waiting_list[0] = v waiting_beginning = 0 diff --git a/src/sage/graphs/base/dense_graph.pyx b/src/sage/graphs/base/dense_graph.pyx index f1abc3d4128..29bf399164d 100644 --- a/src/sage/graphs/base/dense_graph.pyx +++ b/src/sage/graphs/base/dense_graph.pyx @@ -102,18 +102,16 @@ from ``CGraph`` (for explanation, refer to the documentation there):: It also contains the following variables:: - cdef int radix_div_shift - cdef int radix_mod_mask cdef int num_longs cdef unsigned long *edges The array ``edges`` is a series of bits which are turned on or off, and due to this, dense graphs only support graphs without edge labels and with no multiple -edges. The ints ``radix_div_shift`` and ``radix_mod_mask`` are simply for doing -efficient division by powers of two, and ``num_longs`` stores the length of the -``edges`` array. Recall that this length reflects the number of available -vertices, not the number of "actual" vertices. For more details about this, -refer to the documentation for ``CGraph``. +edges. ``num_longs`` stores the length of the ``edges`` array. Recall that this +length reflects the number of available vertices, not the number of "actual" +vertices. For more details about this, refer to the documentation for +``CGraph``. + """ #******************************************************************************* @@ -125,6 +123,11 @@ refer to the documentation for ``CGraph``. include 'sage/data_structures/bitset.pxi' +from libc.string cimport memcpy + +cdef int radix = sizeof(unsigned long) * 8 # number of bits per 'unsigned long' +cdef int radix_mod_mask = radix - 1 # (assumes that radis is a power of 2) + cdef class DenseGraph(CGraph): """ Compiled dense graphs. @@ -151,7 +154,6 @@ cdef class DenseGraph(CGraph): for use in pickling. """ - def __cinit__(self, int nverts, int extra_vertices = 10, verts = None, arcs = None): """ Allocation and initialization happen in one place. @@ -167,35 +169,24 @@ cdef class DenseGraph(CGraph): """ if nverts == 0 and extra_vertices == 0: raise RuntimeError('Dense graphs must allocate space for vertices!') - cdef int radix = sizeof(unsigned long) << 3 - self.radix_mod_mask = radix - 1 - cdef int i = 0 - while ((1)<> self.radix_div_shift - if total_verts & self.radix_mod_mask: - i += 1 - self.num_longs = i + # self.num_longs = "ceil(total_verts/radix)" + self.num_longs = total_verts / radix + (0 != (total_verts & radix_mod_mask)) - self.edges = sage_malloc(total_verts * self.num_longs * sizeof(unsigned long)) - self.in_degrees = sage_malloc(total_verts * sizeof(int)) - self.out_degrees = sage_malloc(total_verts * sizeof(int)) + self.edges = sage_calloc(total_verts * self.num_longs, sizeof(unsigned long)) + self.in_degrees = sage_calloc(total_verts, sizeof(int)) + self.out_degrees = sage_calloc(total_verts, sizeof(int)) if not self.edges or not self.in_degrees or not self.out_degrees: - if self.edges: sage_free(self.edges) - if self.in_degrees: sage_free(self.in_degrees) - if self.out_degrees: sage_free(self.out_degrees) + sage_free(self.edges) + sage_free(self.in_degrees) + sage_free(self.out_degrees) raise MemoryError - for i from 0 <= i < self.num_longs * total_verts: - self.edges[i] = 0 - for i from 0 <= i < total_verts: - self.in_degrees[i] = 0 - self.out_degrees[i] = 0 + bitset_init(self.active_vertices, total_verts) bitset_set_first_n(self.active_vertices, self.num_verts) @@ -261,6 +252,7 @@ cdef class DenseGraph(CGraph): cdef int i, j if total_verts == 0: raise RuntimeError('Dense graphs must allocate space for vertices!') + cdef bitset_t bits cdef int min_verts, min_longs, old_longs = self.num_longs if total_verts < self.active_vertices.size: @@ -276,42 +268,27 @@ cdef class DenseGraph(CGraph): min_verts = self.active_vertices.size min_longs = self.num_longs - i = total_verts >> self.radix_div_shift - if total_verts & self.radix_mod_mask: - i += 1 - self.num_longs = i - if min_longs == -1: min_longs = self.num_longs + # self.num_longs = "ceil(total_verts/radix)" + self.num_longs = total_verts / radix + (0 != (total_verts & radix_mod_mask)) - cdef unsigned long *new_edges = sage_malloc(total_verts * self.num_longs * sizeof(unsigned long)) + if min_longs == -1: + min_longs = self.num_longs + # Resize of self.edges + cdef unsigned long *new_edges = sage_calloc(total_verts * self.num_longs, sizeof(unsigned long)) for i from 0 <= i < min_verts: - for j from 0 <= j < min_longs: - new_edges[i*self.num_longs + j] = self.edges[i*old_longs + j] - for j from min_longs <= j < self.num_longs: - new_edges[i*self.num_longs + j] = 0 - for i from min_verts <= i < total_verts: - for j from 0 <= j < self.num_longs: - new_edges[i*self.num_longs + j] = 0 + memcpy(new_edges+i*self.num_longs, self.edges+i*old_longs, min_longs*sizeof(unsigned long)) + sage_free(self.edges) self.edges = new_edges - self.in_degrees = sage_realloc(self.in_degrees, total_verts * sizeof(int)) + self.in_degrees = sage_realloc(self.in_degrees , total_verts * sizeof(int)) self.out_degrees = sage_realloc(self.out_degrees, total_verts * sizeof(int)) - cdef int first_limb - cdef unsigned long zero_gate - if total_verts > self.active_vertices.size: - first_limb = (self.active_vertices.size >> self.radix_div_shift) - zero_gate = (1) << (self.active_vertices.size & self.radix_mod_mask) - zero_gate -= 1 - for i from 0 <= i < total_verts: - self.edges[first_limb] &= zero_gate - for j from first_limb < j < self.num_longs: - self.edges[j] = 0 - - for i from self.active_vertices.size <= i < total_verts: - self.in_degrees[i] = 0 - self.out_degrees[i] = 0 + for i in range(self.active_vertices.size, total_verts): + self.in_degrees[i] = 0 + self.out_degrees[i] = 0 + bitset_realloc(self.active_vertices, total_verts) ################################### @@ -326,8 +303,8 @@ cdef class DenseGraph(CGraph): u, v -- non-negative integers """ - cdef int place = (u * self.num_longs) + (v >> self.radix_div_shift) - cdef unsigned long word = (1) << (v & self.radix_mod_mask) + cdef int place = (u * self.num_longs) + (v / radix) + cdef unsigned long word = (1) << (v & radix_mod_mask) if not self.edges[place] & word: self.in_degrees[v] += 1 self.out_degrees[u] += 1 @@ -373,9 +350,9 @@ cdef class DenseGraph(CGraph): 1 -- True """ - cdef int place = (u * self.num_longs) + (v >> self.radix_div_shift) - cdef unsigned long word = (1) << (v & self.radix_mod_mask) - return (self.edges[place] & word) >> (v & self.radix_mod_mask) + cdef int place = (u * self.num_longs) + (v / radix) + cdef unsigned long word = (1) << (v & radix_mod_mask) + return (self.edges[place] & word) >> (v & radix_mod_mask) cpdef bint has_arc(self, int u, int v) except -1: """ @@ -409,8 +386,8 @@ cdef class DenseGraph(CGraph): u, v -- non-negative integers, must be in self """ - cdef int place = (u * self.num_longs) + (v >> self.radix_div_shift) - cdef unsigned long word = (1) << (v & self.radix_mod_mask) + cdef int place = (u * self.num_longs) + (v / radix) + cdef unsigned long word = (1) << (v & radix_mod_mask) if self.edges[place] & word: self.in_degrees[v] -= 1 self.out_degrees[u] -= 1 @@ -444,6 +421,43 @@ cdef class DenseGraph(CGraph): self.check_vertex(v) self.del_arc_unsafe(u,v) + def complement(self): + r""" + Replaces the graph with its complement + + .. NOTE:: + + Assumes that the graph has no loop. + + EXAMPLE:: + + sage: from sage.graphs.base.dense_graph import DenseGraph + sage: G = DenseGraph(5) + sage: G.add_arc(0,1) + sage: G.has_arc(0,1) + True + sage: G.complement() + sage: G.has_arc(0,1) + False + """ + cdef int num_arcs_old = self.num_arcs + + # The following cast assumes that mp_limb_t is an unsigned long. + # (this assumption is already made in bitset.pxi) + cdef unsigned long * active_vertices_bitset + active_vertices_bitset = self.active_vertices.bits + + cdef int i,j + for i in range(self.active_vertices.size): + if bitset_in(self.active_vertices,i): + self.add_arc_unsafe(i,i) + for j in range(self.num_longs): # the actual job + self.edges[i*self.num_longs+j] ^= active_vertices_bitset[j] + self.in_degrees[i] = self.num_verts-self.in_degrees[i] + self.out_degrees[i] = self.num_verts-self.out_degrees[i] + + self.num_arcs = self.num_verts*(self.num_verts-1) - num_arcs_old + ################################### # Neighbor functions ################################### @@ -527,8 +541,8 @@ cdef class DenseGraph(CGraph): there were more """ - cdef int place = v >> self.radix_div_shift - cdef unsigned long word = (1) << (v & self.radix_mod_mask) + cdef int place = v / radix + cdef unsigned long word = (1) << (v & radix_mod_mask) cdef int i, num_nbrs = 0 for i from 0 <= i < self.active_vertices.size: if self.edges[place + i*self.num_longs] & word: diff --git a/src/sage/graphs/bliss.pyx b/src/sage/graphs/bliss.pyx index 6c64a57cc7c..a0b69588b5e 100644 --- a/src/sage/graphs/bliss.pyx +++ b/src/sage/graphs/bliss.pyx @@ -311,3 +311,4 @@ def canonical_form(G, partition=None, return_graph=False, certify=False): return sorted(edges),relabel return sorted(edges) + diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index 3abc113977f..e20173a7f4e 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -119,94 +119,86 @@ class DiGraph(GenericGraph): - """Directed graph. + r""" + Directed graph. A digraph or directed graph is a set of vertices connected by oriented - edges. For more information, see the - `Wikipedia article on digraphs - `_. + edges. See also the :wikipedia:`Directed_graph`. For a collection of + pre-defined digraphs, see the :mod:`~sage.graphs.digraph_generators` module. - One can very easily create a directed graph in Sage by typing:: + A :class:`DiGraph` object has many methods whose list can be obtained by + typing ``g.`` (i.e. hit the 'tab' key) or by reading the documentation + of :mod:`~sage.graphs.digraph`, :mod:`~sage.graphs.generic_graph`, and + :mod:`~sage.graphs.graph`. - sage: g = DiGraph() - - By typing the name of the digraph, one can get some basic information - about it:: - - sage: g - Digraph on 0 vertices - - This digraph is not very interesting as it is by default the empty - graph. But Sage contains several pre-defined digraph classes that can - be listed this way: - - * Within a Sage sessions, type ``digraphs.`` - (Do not press "Enter", and do not forget the final period "." ) - * Hit "tab". - - You will see a list of methods which will construct named digraphs. For - example:: - - sage: g = digraphs.ButterflyGraph(3) - sage: g.plot() - Graphics object consisting of 81 graphics primitives + INPUT: - You can also use the collection of pre-defined graphs, then create a - digraph from them. :: + By default, a :class:`DiGraph` object is simple (i.e. no *loops* nor + *multiple edges*) and unweighted. This can be easily tuned with the + appropriate flags (see below). - sage: g = DiGraph(graphs.PetersenGraph()) - sage: g.plot() - Graphics object consisting of 50 graphics primitives + - ``data`` -- can be any of the following (see the ``format`` argument): - Calling ``Digraph`` on a graph returns the original graph in which every - edge is replaced by two different edges going toward opposite directions. + #. ``DiGraph()`` -- build a digraph on 0 vertices. - In order to obtain more information about these digraph constructors, - access the documentation by typing ``digraphs.RandomDirectedGNP?``. + #. ``DiGraph(5)`` -- return an edgeless digraph on the 5 vertices 0,...,4. - Once you have defined the digraph you want, you can begin to work on it - by using the almost 200 functions on graphs and digraphs in the Sage - library! If your digraph is named ``g``, you can list these functions as - previously this way + #. ``DiGraph([list_of_vertices,list_of_edges])`` -- returns a digraph with + given vertices/edges. - * Within a Sage session, type ``g.`` - (Do not press "Enter", and do not forget the final period "." ) - * Hit "tab". + To bypass auto-detection, prefer the more explicit + ``DiGraph([V,E],format='vertices_and_edges')``. - As usual, you can get some information about what these functions do by - typing (e.g. if you want to know about the ``diameter()`` method) - ``g.diameter?``. + #. ``DiGraph(list_of_edges)`` -- return a digraph with a given list of + edges (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.add_edges`). - If you have defined a digraph ``g`` having several connected components - ( i.e. ``g.is_connected()`` returns False ), you can print each one of its - connected components with only two lines:: + To bypass auto-detection, prefer the more explicit ``DiGraph(L, + format='list_of_edges')``. - sage: for component in g.connected_components(): - ....: g.subgraph(component).plot() - Graphics object consisting of 50 graphics primitives + #. ``DiGraph({1:[2,3,4],3:[4]})`` -- return a digraph by associating to + each vertex the list of its out-neighbors. - The same methods works for strongly connected components :: + To bypass auto-detection, prefer the more explicit ``DiGraph(D, + format='dict_of_lists')``. - sage: for component in g.strongly_connected_components(): - ....: g.subgraph(component).plot() - Graphics object consisting of 50 graphics primitives + #. ``DiGraph({1: {2: 'a', 3:'b'} ,3:{2:'c'}})`` -- return a digraph by + associating a list of out-neighbors to each vertex and providing its + edge label. + To bypass auto-detection, prefer the more explicit ``DiGraph(D, + format='dict_of_dicts')``. - INPUT: + For digraphs with multiple edges, you can provide a list of labels + instead, e.g.: ``DiGraph({1: {2: ['a1', 'a2'], 3:['b']} + ,3:{2:['c']}})``. - - ``data`` - can be any of the following (see the ``format`` keyword): + #. ``DiGraph(a_matrix)`` -- return a digraph with given (weighted) adjacency + matrix (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.adjacency_matrix`). - #. A dictionary of dictionaries + To bypass auto-detection, prefer the more explicit ``DiGraph(M, + format='adjacency_matrix')``. To take weights into account, use + ``format='weighted_adjacency_matrix'`` instead. - #. A dictionary of lists + #. ``DiGraph(a_nonsquare_matrix)`` -- return a digraph with given + incidence matrix (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.incidence_matrix`). - #. A Sage adjacency matrix or incidence matrix + To bypass auto-detection, prefer the more explicit ``DiGraph(M, + format='incidence_matrix')``. - #. A pygraphviz graph + #. ``DiGraph([V, f])`` -- return a digraph with a vertex set ``V`` and an + edge `u,v` whenever ``f(u,v)`` is ``True``. Example: ``DiGraph([ + [1..10], lambda x,y: abs(x-y).is_square()])`` - #. A NetworkX digraph + #. ``DiGraph('FOC@?OC@_?')`` -- return a digraph from a dig6 string (see + documentation of :meth:`dig6_string`). - #. An igraph Graph (see http://igraph.org/python/) + #. ``DiGraph(another_digraph)`` -- return a digraph from a Sage (di)graph, + `pygraphviz `__ digraph, `NetworkX + `__ digraph, or `igraph + `__ digraph. - ``pos`` - a positioning dictionary: for example, the spring layout from NetworkX for the 5-cycle is:: @@ -229,32 +221,13 @@ class DiGraph(GenericGraph): - ``weighted`` - whether digraph thinks of itself as weighted or not. See self.weighted() - - ``format`` - if None, DiGraph tries to guess- can be - several values, including: - - - ``'adjacency_matrix'`` - a square Sage matrix M, - with M[i,j] equal to the number of edges {i,j} - - - ``'incidence_matrix'`` - a Sage matrix, with one - column C for each edge, where if C represents {i, j}, C[i] is -1 - and C[j] is 1 - - - ``'weighted_adjacency_matrix'`` - a square Sage - matrix M, with M[i,j] equal to the weight of the single edge {i,j}. - Given this format, weighted is ignored (assumed True). - - - ``NX`` - data must be a NetworkX DiGraph. - - .. NOTE:: - - As Sage's default edge labels is ``None`` while NetworkX uses - ``{}``, the ``{}`` labels of a NetworkX digraph are automatically - set to ``None`` when it is converted to a Sage graph. This - behaviour can be overruled by setting the keyword - ``convert_empty_dict_labels_to_None`` to ``False`` (it is - ``True`` by default). - - - ``igraph`` - data must be an igraph directed Graph. + - ``format`` - if set to ``None`` (default), :class:`DiGraph` tries to guess + input's format. To avoid this possibly time-consuming step, one of the + following values can be specified (see description above): ``"int"``, + ``"dig6"``, ``"rule"``, ``"list_of_edges"``, ``"dict_of_lists"``, + ``"dict_of_dicts"``, ``"adjacency_matrix"``, + ``"weighted_adjacency_matrix"``, ``"incidence_matrix"``, ``"NX"``, + ``"igraph"``. - ``sparse`` (boolean) -- ``sparse=True`` is an alias for ``data_structure="sparse"``, and ``sparse=False`` is an alias for @@ -279,7 +252,7 @@ class DiGraph(GenericGraph): ``data_structure='static_sparse'``. - ``vertex_labels`` - Whether to allow any object as a vertex (slower), or - only the integers 0, ..., n-1, where n is the number of vertices. + only the integers `0,...,n-1`, where `n` is the number of vertices. - ``convert_empty_dict_labels_to_None`` - this arguments sets the default edge labels used by NetworkX (empty dictionaries) @@ -349,7 +322,7 @@ class DiGraph(GenericGraph): [ 0 1 -1] [ -1 0 -1/2] [ 1 1/2 0] - sage: G = DiGraph(M,sparse=True); G + sage: G = DiGraph(M,sparse=True,weighted=True); G Digraph on 3 vertices sage: G.weighted() True @@ -462,6 +435,13 @@ class DiGraph(GenericGraph): True sage: type(J_imm._backend) == type(G_imm._backend) True + + From a a list of vertices and a list of edges:: + + sage: G = DiGraph([[1,2,3],[(1,2)]]); G + Digraph on 3 vertices + sage: G.edges() + [(1, 2, None)] """ _directed = True @@ -625,6 +605,15 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if format is None and isinstance(data,list) and \ len(data)>=2 and callable(data[1]): format = 'rule' + + if (format is None and + isinstance(data,list) and + len(data) == 2 and + isinstance(data[0],list) and # a list of two lists, the second of + isinstance(data[1],list) and # which contains iterables (the edges) + (not data[1] or callable(getattr(data[1][0],"__iter__",None)))): + format = "vertices_and_edges" + if format is None and isinstance(data,dict): keys = data.keys() if len(keys) == 0: format = 'dict_of_dicts' @@ -676,104 +665,20 @@ def __init__(self, data=None, pos=None, loops=None, format=None, # At this point, format has been set. We build the graph if format == 'dig6': - if weighted is None: weighted = False - if not isinstance(data, str): - raise ValueError('If input format is dig6, then data must be a string.') - n = data.find('\n') - if n == -1: - n = len(data) - ss = data[:n] - n, s = generic_graph_pyx.length_and_string_from_graph6(ss) - m = generic_graph_pyx.binary_string_from_dig6(s, n) - expected = n**2 - if len(m) > expected: - raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n)) - elif len(m) < expected: - raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n)) + if weighted is None: self._weighted = False self.allow_loops(True if loops else False,check=False) self.allow_multiple_edges(True if multiedges else False,check=False) - self.add_vertices(range(n)) - k = 0 - for i in xrange(n): - for j in xrange(n): - if m[k] == '1': - self._backend.add_edge(i, j, None, True) - k += 1 - elif format == 'adjacency_matrix': - assert is_Matrix(data) - # note: the adjacency matrix might be weighted and hence not - # necessarily consists of integers - if not weighted and data.base_ring() != ZZ: - try: - data = data.change_ring(ZZ) - except TypeError: - if weighted is False: - raise ValueError("Non-weighted graph's"+ - " adjacency matrix must have only nonnegative"+ - " integer entries") - weighted = True + from graph_input import from_dig6 + from_dig6(self, data) - if data.is_sparse(): - entries = set(data[i,j] for i,j in data.nonzero_positions()) - else: - entries = set(data.list()) - - if not weighted and any(e < 0 for e in entries): - if weighted is False: - raise ValueError("Non-weighted digraph's"+ - " adjacency matrix must have only nonnegative"+ - " integer entries") - weighted = True - if multiedges is None: multiedges = False - if weighted is None: - weighted = False - - if multiedges is None: - multiedges = ((not weighted) and any(e != 0 and e != 1 for e in entries)) + elif format == 'adjacency_matrix': + from graph_input import from_adjacency_matrix + from_adjacency_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted) - if not loops and any(data[i,i] for i in xrange(data.nrows())): - if loops is False: - raise ValueError("Non-looped digraph's adjacency"+ - " matrix must have zeroes on the diagonal.") - loops = True - self.allow_multiple_edges(multiedges,check=False) - self.allow_loops(True if loops else False,check=False) - self.add_vertices(range(data.nrows())) - e = [] - if weighted: - for i,j in data.nonzero_positions(): - e.append((i,j,data[i][j])) - elif multiedges: - for i,j in data.nonzero_positions(): - e += [(i,j)]*int(data[i][j]) - else: - for i,j in data.nonzero_positions(): - e.append((i,j)) - self.add_edges(e) elif format == 'incidence_matrix': - assert is_Matrix(data) - positions = [] - for c in data.columns(): - NZ = c.nonzero_positions() - if len(NZ) != 2: - msg += "There must be two nonzero entries (-1 & 1) per column." - raise ValueError(msg) - L = sorted(set(c.list())) - if L != [-1,0,1]: - msg += "Each column represents an edge: -1 goes to 1." - raise ValueError(msg) - if c[NZ[0]] == -1: - positions.append(tuple(NZ)) - else: - positions.append((NZ[1],NZ[0])) - if weighted is None: weighted = False - if multiedges is None: - total = len(positions) - multiedges = ( len(set(positions)) < total ) - self.allow_loops(True if loops else False,check=False) - self.allow_multiple_edges(multiedges,check=False) - self.add_vertices(range(data.nrows())) - self.add_edges(positions) + from graph_input import from_oriented_incidence_matrix + from_oriented_incidence_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted) + elif format == 'DiGraph': if loops is None: loops = data.allows_loops() elif not loops and data.has_loops(): @@ -800,67 +705,22 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self.allow_loops(loops,check=False) self.add_vertices(data[0]) self.add_edges((u,v) for u in data[0] for v in data[0] if f(u,v)) + + elif format == "vertices_and_edges": + self.allow_multiple_edges(bool(multiedges), check=False) + self.allow_loops(bool(loops), check=False) + self.add_vertices(data[0]) + self.add_edges(data[1]) + elif format == 'dict_of_dicts': - if not all(isinstance(data[u], dict) for u in data): - raise ValueError("Input dict must be a consistent format.") - - verts = set(data.keys()) - if loops is None or loops is False: - for u in data: - if u in data[u]: - if loops is None: - loops = True - elif loops is False: - u = next(u for u,neighb in data.iteritems() if u in neighb) - raise ValueError("The digraph was built with loops=False but input data has a loop at {}.".format(u)) - break - if loops is None: loops = False - if weighted is None: weighted = False - for u in data: - for v in data[u]: - if v not in verts: verts.add(v) - if multiedges is not False and not isinstance(data[u][v], list): - if multiedges is None: - multiedges = False - if multiedges: - raise ValueError("Dict of dicts for multidigraph must be in the format {v : {u : list}}") - if multiedges is None and len(data) > 0: - multiedges = True - self.allow_multiple_edges(multiedges,check=False) - self.allow_loops(loops,check=False) - self.add_vertices(verts) + from graph_input import from_dict_of_dicts + from_dict_of_dicts(self, data, loops=loops, multiedges=multiedges, weighted=weighted, + convert_empty_dict_labels_to_None = False if convert_empty_dict_labels_to_None is None else convert_empty_dict_labels_to_None) - if multiedges: - self.add_edges((u,v,l) for u,Nu in data.iteritems() for v,labels in Nu.iteritems() for l in labels) - else: - self.add_edges((u,v,l) for u,Nu in data.iteritems() for v,l in Nu.iteritems()) elif format == 'dict_of_lists': - # convert to a dict of lists if not already one - if not all(isinstance(data[u], list) for u in data): - data = {u: list(v) for u,v in data.iteritems()} - - if not loops and any(u in neighb for u,neighb in data.iteritems()): - if loops is False: - u = next(u for u,neighb in data.iteritems() if u in neighb) - raise ValueError("The digraph was built with loops=False but input data has a loop at {}.".format(u)) - loops = True - if loops is None: - loops = False - - if weighted is None: weighted = False + from graph_input import from_dict_of_lists + from_dict_of_lists(self, data, loops=loops, multiedges=multiedges, weighted=weighted) - if not multiedges and any(len(set(neighb)) != len(neighb) for neighb in data.itervalues()): - if multiedges is False: - uv = next((u,v) for u,neighb in data.iteritems() for v in neighb if neighb.count(v) > 1) - raise ValueError("Non-multidigraph got several edges (%s,%s)"%(u,v)) - multiedges = True - if multiedges is None: - multiedges = False - self.allow_multiple_edges(multiedges,check=False) - self.allow_loops(loops,check=False) - verts = set().union(data.keys(),*data.values()) - self.add_vertices(verts) - self.add_edges((u,v) for u,Nu in data.iteritems() for v in Nu) elif format == 'NX': # adjust for empty dicts instead of None in NetworkX default edge labels if convert_empty_dict_labels_to_None is None: @@ -943,7 +803,9 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if data_structure == "static_sparse": from sage.graphs.base.static_sparse_backend import StaticSparseBackend - ib = StaticSparseBackend(self, loops = loops, multiedges = multiedges) + ib = StaticSparseBackend(self, + loops = self.allows_loops(), + multiedges = self.allows_multiple_edges()) self._backend = ib self._immutable = True diff --git a/src/sage/graphs/generators/classical_geometries.py b/src/sage/graphs/generators/classical_geometries.py index cae8a1e9e30..33ebf67da74 100644 --- a/src/sage/graphs/generators/classical_geometries.py +++ b/src/sage/graphs/generators/classical_geometries.py @@ -21,6 +21,8 @@ from math import sin, cos, pi from sage.graphs.graph import Graph from sage.graphs import graph +from sage.rings.arith import is_prime_power +from sage.rings.finite_rings.constructor import FiniteField def SymplecticPolarGraph(d, q, algorithm=None): r""" @@ -87,7 +89,6 @@ def SymplecticPolarGraph(d, q, algorithm=None): G = _polar_graph(d, q, libgap.SymplecticGroup(d, q)) elif algorithm == None: # faster for small (q<4) fields - from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module import VectorSpace from sage.schemes.projective.projective_space import ProjectiveSpace from sage.matrix.constructor import identity_matrix, block_matrix, zero_matrix @@ -187,7 +188,6 @@ def AffineOrthogonalPolarGraph(d,q,sign="+"): s = 0 from sage.interfaces.gap import gap - from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module import VectorSpace from sage.matrix.constructor import Matrix from sage.libs.gap.libgap import libgap @@ -287,7 +287,6 @@ def _orthogonal_polar_graph(m, q, sign="+", point_type=[0]): """ from sage.schemes.projective.projective_space import ProjectiveSpace - from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module_element import free_module_element as vector from sage.matrix.constructor import Matrix from sage.libs.gap.libgap import libgap @@ -491,7 +490,6 @@ def NonisotropicOrthogonalPolarGraph(m, q, sign="+", perp=None): """ from sage.graphs.generators.classical_geometries import _orthogonal_polar_graph - from sage.rings.arith import is_prime_power p, k = is_prime_power(q,get_data=True) if k==0: raise ValueError('q must be a prime power') @@ -635,14 +633,12 @@ def UnitaryPolarGraph(m, q, algorithm="gap"): elif algorithm == None: # slow on large examples from sage.schemes.projective.projective_space import ProjectiveSpace - from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module_element import free_module_element as vector - from __builtin__ import sum as psum Fq = FiniteField(q**2, 'a') PG = map(vector, ProjectiveSpace(m - 1, Fq)) map(lambda x: x.set_immutable(), PG) def P(x,y): - return psum(map(lambda j: x[j]*y[m-1-j]**q, xrange(m)))==0 + return sum(map(lambda j: x[j]*y[m-1-j]**q, xrange(m)))==0 V = filter(lambda x: P(x,x), PG) G = Graph([V,lambda x,y: # bottleneck is here, of course: @@ -697,7 +693,6 @@ def NonisotropicUnitaryPolarGraph(m, q): Disc. Math. 13(1975), pp 357--381. http://dx.doi.org/10.1016/0012-365X(75)90057-6 """ - from sage.rings.arith import is_prime_power p, k = is_prime_power(q,get_data=True) if k==0: raise ValueError('q must be a prime power') @@ -743,7 +738,7 @@ def UnitaryDualPolarGraph(m, q): EXAMPLES: - The point graph of a generalized quadrangle of order (8,4):: + The point graph of a generalized quadrangle (see [GQwiki]_, [PT09]_) of order (8,4):: sage: G = graphs.UnitaryDualPolarGraph(5,2); G # long time Unitary Dual Polar Graph DU(5, 2); GQ(8, 4): Graph on 297 vertices @@ -881,12 +876,10 @@ def TaylorTwographDescendantSRG(q, clique_partition=None): ... ValueError: q must be an odd prime power """ - from sage.rings.arith import is_prime_power p, k = is_prime_power(q,get_data=True) if k==0 or p==2: raise ValueError('q must be an odd prime power') from sage.schemes.projective.projective_space import ProjectiveSpace - from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module_element import free_module_element as vector from sage.rings.finite_rings.integer_mod import mod from __builtin__ import sum @@ -941,3 +934,171 @@ def TaylorTwographSRG(q): G.seidel_switching(sum(l[:(q**2+1)/2],[])) G.name("Taylor two-graph SRG") return G + +def AhrensSzekeresGeneralizedQuadrangleGraph(q, dual=False): + r""" + Return the collinearity graph of the generalized quadrangle `AS(q)`, or of its dual + + Let `q` be an odd prime power. `AS(q)` is a generalized quadrangle [GQwiki]_ of + order `(q-1,q+1)`, see 3.1.5 in [PT09]_. Its points are elements + of `F_q^3`, and lines are sets of size `q` of the form + + * `\{ (\sigma, a, b) \mid \sigma\in F_q \}` + * `\{ (a, \sigma, b) \mid \sigma\in F_q \}` + * `\{ (c \sigma^2 - b \sigma + a, -2 c \sigma + b, \sigma) \mid \sigma\in F_q \}`, + + where `a`, `b`, `c` are arbitrary elements of `F_q`. + + INPUT: + + - ``q`` -- a power of an odd prime number + + - ``dual`` -- if ``False`` (default), return the collinearity graph of `AS(q)`. + Otherwise return the collinearity graph of the dual `AS(q)`. + + EXAMPLES:: + + sage: g=graphs.AhrensSzekeresGeneralizedQuadrangleGraph(5); g + AS(5); GQ(4, 6): Graph on 125 vertices + sage: g.is_strongly_regular(parameters=True) + (125, 28, 3, 7) + sage: g=graphs.AhrensSzekeresGeneralizedQuadrangleGraph(5,dual=True); g + AS(5)*; GQ(6, 4): Graph on 175 vertices + sage: g.is_strongly_regular(parameters=True) + (175, 30, 5, 5) + + REFERENCE: + + .. [GQwiki] `Generalized quadrangle + `__ + + .. [PT09] S. Payne, J. A. Thas. + Finite generalized quadrangles. + European Mathematical Society, + 2nd edition, 2009. + """ + from sage.combinat.designs.incidence_structures import IncidenceStructure + p, k = is_prime_power(q,get_data=True) + if k==0 or p==2: + raise ValueError('q must be an odd prime power') + F = FiniteField(q, 'a') + L = [] + for a in F: + for b in F: + L.append(tuple(map(lambda s: (s, a, b), F))) + L.append(tuple(map(lambda s: (a, s, b), F))) + for c in F: + L.append(tuple(map(lambda s: (c*s**2 - b*s + a, -2*c*s + b, s), F))) + if dual: + G = IncidenceStructure(L).intersection_graph() + G.name('AS('+str(q)+')*; GQ'+str((q+1,q-1))) + else: + G = IncidenceStructure(L).dual().intersection_graph() + G.name('AS('+str(q)+'); GQ'+str((q-1,q+1))) + return G + +def T2starGeneralizedQuadrangleGraph(q, dual=False, hyperoval=None, field=None, check_hyperoval=True): + r""" + Return the collinearity graph of the generalized quadrangle `T_2^*(q)`, or of its dual + + Let `q=2^k` and `\Theta=PG(3,q)`. `T_2^*(q)` is a generalized quadrangle [GQwiki]_ + of order `(q-1,q+1)`, see 3.1.3 in [PT09]_. Fix a plane `\Pi \subset \Theta` and a + `hyperoval `__ + `O \subset \Pi`. The points of `T_2^*(q):=T_2^*(O)` are the points of `\Theta` + outside `\Pi`, and the lines are the lines of `\Theta` outside `\Pi` + that meet `\Pi` in a point of `O`. + + INPUT: + + - ``q`` -- a power of two + + - ``dual`` -- if ``False`` (default), return the graph of `T_2^*(O)`. + Otherwise return the graph of the dual `T_2^*(O)`. + + - ``hyperoval`` -- a hyperoval (i.e. a complete 2-arc; a set of points in the plane + meeting every line in 0 or 2 points) in the plane of points with 0th coordinate + 0 in `PG(3,q)` over the field ``field``. Each point of ``hyperoval`` must be a length 4 + vector over ``field`` with 1st non-0 coordinate equal to 1. By default, ``hyperoval`` and + ``field`` are not specified, and constructed on the fly. In particular, ``hyperoval`` + we build is the classical one, i.e. a conic with the point of intersection of its + tangent lines. + + - ``field`` -- an instance of a finite field of order `q`, must be provided + if ``hyperoval`` is provided. + + - ``check_hyperoval`` -- (default: ``True``) if ``True``, + check ``hyperoval`` for correctness. + + + EXAMPLES: + + using the built-in construction:: + + sage: g=graphs.T2starGeneralizedQuadrangleGraph(4); g + T2*(O,4); GQ(3, 5): Graph on 64 vertices + sage: g.is_strongly_regular(parameters=True) + (64, 18, 2, 6) + sage: g=graphs.T2starGeneralizedQuadrangleGraph(4,dual=True); g + T2*(O,4)*; GQ(5, 3): Graph on 96 vertices + sage: g.is_strongly_regular(parameters=True) + (96, 20, 4, 4) + + supplying your own hyperoval:: + + sage: F=GF(4,'b') + sage: O=[vector(F,(0,0,0,1)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) + sage: g=graphs.T2starGeneralizedQuadrangleGraph(4, hyperoval=O, field=F); g + T2*(O,4); GQ(3, 5): Graph on 64 vertices + sage: g.is_strongly_regular(parameters=True) + (64, 18, 2, 6) + + TESTS:: + + sage: F=GF(4,'b') # repeating a point... + sage: O=[vector(F,(0,1,0,0)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) + sage: graphs.T2starGeneralizedQuadrangleGraph(4, hyperoval=O, field=F) + Traceback (most recent call last): + ... + RuntimeError: incorrect hyperoval size + sage: O=[vector(F,(0,1,1,0)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) + sage: graphs.T2starGeneralizedQuadrangleGraph(4, hyperoval=O, field=F) + Traceback (most recent call last): + ... + RuntimeError: incorrect hyperoval + """ + from sage.combinat.designs.incidence_structures import IncidenceStructure + from sage.combinat.designs.block_design import ProjectiveGeometryDesign as PG + from sage.modules.free_module_element import free_module_element as vector + + p, k = is_prime_power(q,get_data=True) + if k==0 or p!=2: + raise ValueError('q must be a power of 2') + if field is None: + F = FiniteField(q, 'a') + else: + F = field + + Theta = PG(3, 1, F, point_coordinates=1) + Pi = set(filter(lambda x: x[0]==F.zero(), Theta.ground_set())) + if hyperoval is None: + O = filter(lambda x: x[1]+x[2]*x[3]==0 or (x[1]==1 and x[2]==0 and x[3]==0), Pi) + O = set(O) + else: + map(lambda x: x.set_immutable(), hyperoval) + O = set(hyperoval) + if check_hyperoval: + if len(O) != q+2: + raise RuntimeError("incorrect hyperoval size") + for L in Theta.blocks(): + if set(L).issubset(Pi): + if not len(O.intersection(L)) in [0,2]: + raise RuntimeError("incorrect hyperoval") + L = map(lambda z: filter(lambda y: not y in O, z), + filter(lambda x: len(O.intersection(x)) == 1, Theta.blocks())) + if dual: + G = IncidenceStructure(L).intersection_graph() + G.name('T2*(O,'+str(q)+')*; GQ'+str((q+1,q-1))) + else: + G = IncidenceStructure(L).dual().intersection_graph() + G.name('T2*(O,'+str(q)+'); GQ'+str((q-1,q+1))) + return G diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py index 05efc1dc10b..c48916e73b9 100644 --- a/src/sage/graphs/generators/families.py +++ b/src/sage/graphs/generators/families.py @@ -46,7 +46,7 @@ def JohnsonGraph(n, k): sage: g.is_vertex_transitive() True - The complement of the Johnson graph `J(n,2)` is isomorphic to the Knesser + The complement of the Johnson graph `J(n,2)` is isomorphic to the Kneser Graph `K(n,2)`. In paritcular the complement of `J(5,2)` is isomorphic to the Petersen graph. :: @@ -689,6 +689,69 @@ def CubeGraph(n): return r +def GoethalsSeidelGraph(k,r): + r""" + Returns the graph `\text{Goethals-Seidel}(k,r)`. + + The graph `\text{Goethals-Seidel}(k,r)` comes from a construction presented + in Theorem 2.4 of [GS70]_. It relies on a :func:`(v,k)-BIBD + ` with `r` + blocks and a + :func:`~sage.combinat.matrices.hadamard_matrix.hadamard_matrix>` of order + `r+1`. The result is a + :func:`sage.graphs.strongly_regular_db.strongly_regular_graph` on `v(r+1)` + vertices with degree `k=(n+r-1)/2`. + + It appears under this name in Andries Brouwer's `database of strongly + regular graphs `__. + + INPUT: + + - ``k,r`` -- integers + + EXAMPLE:: + + sage: graphs.GoethalsSeidelGraph(3,3) + Graph on 28 vertices + sage: graphs.GoethalsSeidelGraph(3,3).is_strongly_regular(parameters=True) + (28, 15, 6, 10) + + """ + from sage.combinat.designs.bibd import balanced_incomplete_block_design + from sage.combinat.matrices.hadamard_matrix import hadamard_matrix + from sage.matrix.constructor import Matrix + from sage.matrix.constructor import block_matrix + from sage.matrix.constructor import identity_matrix + + v = (k-1)*r+1 + n = v*(r+1) + + # N is the (v times b) incidence matrix of a bibd + N = balanced_incomplete_block_design(v,k).incidence_matrix() + + # L is a (r+1 times r) matrix, where r is the row sum of N + L = hadamard_matrix(r+1).submatrix(0,1) + L = [Matrix(C).transpose() for C in L.columns()] + zero = Matrix(r+1,1,[0]*(r+1)) + + # For every row of N, we replace the 0s with a column of zeros, and we + # replace the ith 1 with the ith column of L. The result is P. + P = [] + for row in N: + Ltmp = L[:] + P.append([Ltmp.pop(0) if i else zero + for i in row]) + + P = block_matrix(P) + + # The final graph + PP = P*P.transpose() + for i in range(n): + PP[i,i] = 0 + + G = Graph(PP, format="seidel_adjacency_matrix") + return G + def DorogovtsevGoltsevMendesGraph(n): """ Construct the n-th generation of the Dorogovtsev-Goltsev-Mendes diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 84938f5c7eb..b78f3a1da5d 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -14566,10 +14566,10 @@ def shortest_path(self, u, v, by_weight=False, algorithm=None, [4, 17, 16, 12, 13, 9] sage: D.shortest_path(4, 9, algorithm='BFS') [4, 3, 2, 1, 8, 9] - sage: D.shortest_path(4, 9, algorithm='Dijkstra_NetworkX') - [4, 3, 2, 1, 8, 9] - sage: D.shortest_path(4, 9, algorithm='Dijkstra_Bid_NetworkX') - [4, 3, 2, 1, 8, 9] + sage: D.shortest_path(4, 8, algorithm='Dijkstra_NetworkX') + [4, 3, 2, 1, 8] + sage: D.shortest_path(4, 8, algorithm='Dijkstra_Bid_NetworkX') + [4, 3, 2, 1, 8] sage: D.shortest_path(4, 9, algorithm='Dijkstra_Bid') [4, 3, 19, 0, 10, 9] sage: D.shortest_path(5, 5) @@ -15941,6 +15941,10 @@ def breadth_first_search(self, start, ignore_direction=False, sage: list(D.breadth_first_search(0, ignore_direction=True)) [0, 1, 2] """ + from sage.rings.semirings.non_negative_integer_semiring import NN + if (distance is not None and distance not in NN): + raise ValueError("distance must be a non-negative integer, not {0}".format(distance)) + # Preferably use the Cython implementation if neighbors is None and not isinstance(start, list) and distance is None and hasattr(self._backend,"breadth_first_search") and not report_distance: for v in self._backend.breadth_first_search(start, ignore_direction=ignore_direction): @@ -15957,6 +15961,12 @@ def breadth_first_search(self, start, ignore_direction=False, else: queue = [(start, 0)] + # Non-existing start vertex is detected later if distance > 0. + if distance == 0: + for v in queue: + if not v[0] in self: + raise LookupError("start vertex ({0}) is not a vertex of the graph".format(v[0])) + for v, d in queue: if report_distance: yield v, d @@ -15983,7 +15993,6 @@ def depth_first_search(self, start, ignore_direction=False, INPUT: - - ``start`` - vertex or list of vertices from which to start the traversal @@ -16291,8 +16300,7 @@ def add_path(self, vertices): vert1 = v def complement(self): - """ - Returns the complement of the (di)graph. + """Returns the complement of the (di)graph. The complement of a graph has the same vertices, but exactly those edges that are not in the original graph. This is not well defined @@ -16320,7 +16328,10 @@ def complement(self): sage: G.complement() Traceback (most recent call last): ... - TypeError: complement not well defined for (di)graphs with multiple edges + ValueError: This method is not known to work on graphs with + multiedges. Perhaps this method can be updated to handle them, but + in the meantime if you want to use it please disallow multiedges + using allow_multiple_edges(). TESTS: @@ -16338,17 +16349,14 @@ def complement(self): Graph on 10 vertices """ - if self.has_multiple_edges(): - raise TypeError('complement not well defined for (di)graphs with multiple edges') self._scream_if_not_simple() - G = copy(self) - G.delete_edges(G.edges()) + + G = self.copy(data_structure='dense') + G._backend.c_graph()[0].complement() + if self.name(): G.name("complement({})".format(self.name())) - for u in self: - for v in self: - if not self.has_edge(u,v): - G.add_edge(u,v) + if getattr(self, '_immutable', False): return G.copy(immutable=True) return G diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 56282c0c86e..e4d1c773fe2 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -1,146 +1,11 @@ +# -*- coding: utf-8 -*- r""" Undirected graphs This module implements functions and operations involving undirected graphs. -**Graph basic operations:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.write_to_eps` | Writes a plot of the graph to ``filename`` in ``eps`` format. - :meth:`~Graph.to_undirected` | Since the graph is already undirected, simply returns a copy of itself. - :meth:`~Graph.to_directed` | Returns a directed version of the graph. - :meth:`~Graph.sparse6_string` | Returns the sparse6 representation of the graph as an ASCII string. - :meth:`~Graph.graph6_string` | Returns the graph6 representation of the graph as an ASCII string. - :meth:`~Graph.bipartite_sets` | Returns `(X,Y)` where X and Y are the nodes in each bipartite set of graph. - :meth:`~Graph.bipartite_color` | Returns a dictionary with vertices as the keys and the color class as the values. - :meth:`~Graph.is_directed` | If ``self`` is undirected, returns False. - :meth:`~Graph.join` | Returns the join of ``self`` and ``other``. - - -**Distances:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.centrality_degree` | Returns the degree centrality - - -**Graph properties:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.is_asteroidal_triple_free` | Tests whether the current graph is asteroidal triple free. - :meth:`~Graph.is_prime` | Tests whether the current graph is prime. - :meth:`~Graph.is_split` | Returns ``True`` if the graph is a Split graph, ``False`` otherwise. - :meth:`~Graph.is_triangle_free` | Returns whether ``self`` is triangle-free. - :meth:`~Graph.is_bipartite` | Returns True if graph G is bipartite, False if not. - :meth:`~Graph.is_line_graph` | Tests wether the graph is a line graph. - :meth:`~Graph.is_odd_hole_free` | Tests whether ``self`` contains an induced odd hole. - :meth:`~Graph.is_even_hole_free` | Tests whether ``self`` contains an induced even hole. - :meth:`~Graph.is_cartesian_product` | Tests whether ``self`` is a cartesian product of graphs. - :meth:`~Graph.is_long_hole_free` | Tests whether ``self`` contains an induced cycle of length at least 5. - :meth:`~Graph.is_long_antihole_free` | Tests whether ``self`` contains an induced anticycle of length at least 5. - :meth:`~Graph.is_weakly_chordal` | Tests whether ``self`` is weakly chordal. - :meth:`~Graph.is_strongly_regular` | Tests whether ``self`` is strongly regular. - :meth:`~Graph.is_distance_regular` | Tests whether ``self`` is distance-regular. - :meth:`~Graph.is_tree` | Return True if the graph is a tree. - :meth:`~Graph.is_forest` | Return True if the graph is a forest, i.e. a disjoint union of trees. - :meth:`~Graph.is_overfull` | Tests whether the current graph is overfull. - :meth:`~Graph.odd_girth` | Returns the odd girth of ``self``. - :meth:`~Graph.is_edge_transitive` | Returns true if ``self`` is edge-transitive. - :meth:`~Graph.is_arc_transitive` | Returns true if ``self`` is arc-transitive. - :meth:`~Graph.is_half_transitive` | Returns true if ``self`` is a half-transitive graph. - :meth:`~Graph.is_semi_symmetric` | Returns true if ``self`` is a semi-symmetric graph. - -**Connectivity, orientations, trees:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.gomory_hu_tree` | Returns a Gomory-Hu tree of ``self``. - :meth:`~Graph.minimum_outdegree_orientation` | Returns an orientation of ``self`` with the smallest possible maximum outdegree - :meth:`~Graph.bounded_outdegree_orientation` | Computes an orientation of ``self`` such that every vertex `v` has out-degree less than `b(v)` - :meth:`~Graph.strong_orientation` | Returns a strongly connected orientation of the current graph. - :meth:`~Graph.degree_constrained_subgraph` | Returns a degree-constrained subgraph. - :meth:`~Graph.bridges` | Returns the list of all bridges. - :meth:`~Graph.spanning_trees` | Returns the list of all spanning trees. - :meth:`~Graph.random_spanning_tree` | Returns a random spanning tree. - -**Clique-related methods:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.clique_complex` | Returns the clique complex of ``self`` - :meth:`~Graph.cliques_containing_vertex` | Returns the cliques containing each vertex - :meth:`~Graph.cliques_vertex_clique_number` | Returns a dictionary of sizes of the largest maximal cliques containing each vertex - :meth:`~Graph.cliques_get_clique_bipartite` | Returns a bipartite graph constructed such that maximal cliques are the right vertices and the left vertices are retained from the given graph - :meth:`~Graph.cliques_get_max_clique_graph` | Returns a graph constructed with maximal cliques as vertices, and edges between maximal cliques sharing vertices. - :meth:`~Graph.cliques_number_of` | Returns a dictionary of the number of maximal cliques containing each vertex, keyed by vertex. - :meth:`~Graph.clique_number` | Returns the order of the largest clique of the graph. - :meth:`~Graph.clique_maximum` | Returns the vertex set of a maximal order complete subgraph. - :meth:`~Graph.cliques_maximum` | Returns the list of all maximum cliques - :meth:`~Graph.cliques_maximal` | Returns the list of all maximal cliques - :meth:`~Graph.clique_polynomial` | Returns the clique polynomial - -**Algorithmically hard stuff:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.vertex_cover` | Returns a minimum vertex cover. - :meth:`~Graph.independent_set` | Returns a maximum independent set. - :meth:`~Graph.topological_minor` | Returns a topological `H`-minor of ``self`` if one exists. - :meth:`~Graph.convexity_properties` | Returns a ``ConvexityProperties`` object corresponding to ``self``. - :meth:`~Graph.matching_polynomial` | Computes the matching polynomial. - :meth:`~Graph.rank_decomposition` | Returns an rank-decomposition of ``self`` achieving optiml rank-width. - :meth:`~Graph.minor` | Returns the vertices of a minor isomorphic to `H`. - :meth:`~Graph.independent_set_of_representatives` | Returns an independent set of representatives. - :meth:`~Graph.coloring` | Returns the first (optimal) proper vertex-coloring found. - :meth:`~Graph.has_homomorphism_to` | Checks whether there is a morphism between two graphs. - :meth:`~Graph.chromatic_number` | Returns the minimal number of colors needed to color the vertices. - :meth:`~Graph.chromatic_polynomial` | Returns the chromatic polynomial. - :meth:`~Graph.chromatic_symmetric_function` | Return the chromatic symmetric function. - :meth:`~Graph.tutte_polynomial` | Returns the Tutte polynomial. - :meth:`~Graph.is_perfect` | Tests whether the graph is perfect. - :meth:`~Graph.treewidth` | Computes the tree-width and provides a decomposition. - - -**Leftovers:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.cores` | Returns the core number for each vertex in an ordered list. - :meth:`~Graph.matching` | Returns a maximum weighted matching of the graph - :meth:`~Graph.fractional_chromatic_index` | Computes the fractional chromatic index. - :meth:`~Graph.lovasz_theta` | Returns the Lovasz number (a.k.a theta). - :meth:`~Graph.kirchhoff_symanzik_polynomial` | Returns the Kirchhoff-Symanzik polynomial. - :meth:`~Graph.modular_decomposition` | Returns the modular decomposition. - :meth:`~Graph.maximum_average_degree` | Returns the Maximum Average Degree (MAD). - :meth:`~Graph.two_factor_petersen` | Returns a decomposition of the graph into 2-factors. - :meth:`~Graph.ihara_zeta_function_inverse` | Returns the inverse of the zeta function. - :meth:`~Graph.seidel_switching` | Returns Seidel switching w.r.t. a subset of vertices. - :meth:`~Graph.seidel_adjacency_matrix` | Returns the Seidel adjacency matrix of ``self``. - :meth:`~Graph.twograph` | Returns :class:`two-graph ` of ``self``. +{INDEX_OF_METHODS} AUTHORS: @@ -552,101 +417,104 @@ from sage.graphs.digraph import DiGraph from sage.graphs.independent_sets import IndependentSets from sage.combinat.combinatorial_map import combinatorial_map - +from sage.misc.rest_index_of_methods import doc_index, gen_thematic_rest_table_index class Graph(GenericGraph): r""" Undirected graph. A graph is a set of vertices connected by edges. See also the - :wikipedia:`Wikipedia article on graphs `. - - One can very easily create a graph in Sage by typing:: - - sage: g = Graph() - - By typing the name of the graph, one can get some basic information - about it:: - - sage: g - Graph on 0 vertices + :wikipedia:`Wikipedia article on graphs `. For a + collection of pre-defined graphs, see the + :mod:`~sage.graphs.graph_generators` module. - This graph is not very interesting as it is by default the empty graph. - But Sage contains a large collection of pre-defined graph classes that - can be listed this way: + A :class:`Graph` object has many methods whose list can be obtained by + typing ``g.`` (i.e. hit the 'tab' key) or by reading the documentation + of :mod:`~sage.graphs.graph`, :mod:`~sage.graphs.generic_graph`, and + :mod:`~sage.graphs.digraph`. - * Within a Sage session, type ``graphs.`` - (Do not press "Enter", and do not forget the final period ".") - - * Hit "tab". - - You will see a list of methods which will construct named graphs. For - example:: + INPUT: - sage: g = graphs.PetersenGraph() - sage: g.plot() - Graphics object consisting of 26 graphics primitives + By default, a :class:`Graph` object is simple (i.e. no *loops* nor *multiple + edges*) and unweighted. This can be easily tuned with the appropriate flags + (see below). - or:: + - ``data`` -- can be any of the following (see the ``format`` argument): - sage: g = graphs.ChvatalGraph() - sage: g.plot() - Graphics object consisting of 37 graphics primitives + #. ``Graph()`` -- build a graph on 0 vertices. - In order to obtain more information about these graph constructors, access - the documentation using the command ``graphs.RandomGNP?``. + #. ``Graph(5)`` -- return an edgeless graph on the 5 vertices 0,...,4. - Once you have defined the graph you want, you can begin to work on it - by using the almost 200 functions on graphs in the Sage library! - If your graph is named ``g``, you can list these functions as previously - this way + #. ``Graph([list_of_vertices,list_of_edges])`` -- returns a graph with + given vertices/edges. - * Within a Sage session, type ``g.`` - (Do not press "Enter", and do not forget the final period "." ) + To bypass auto-detection, prefer the more explicit + ``Graph([V,E],format='vertices_and_edges')``. - * Hit "tab". + #. ``Graph(list_of_edges)`` -- return a graph with a given list of edges + (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.add_edges`). - As usual, you can get some information about what these functions do by - typing (e.g. if you want to know about the ``diameter()`` method) - ``g.diameter?``. + To bypass auto-detection, prefer the more explicit ``Graph(L, + format='list_of_edges')``. - If you have defined a graph ``g`` having several connected components - (i.e. ``g.is_connected()`` returns False), you can print each one of its - connected components with only two lines:: + #. ``Graph({1:[2,3,4],3:[4]})`` -- return a graph by associating to each + vertex the list of its neighbors. - sage: for component in g.connected_components(): - ....: g.subgraph(component).plot() - Graphics object consisting of 37 graphics primitives + To bypass auto-detection, prefer the more explicit ``Graph(D, + format='dict_of_lists')``. + #. ``Graph({1: {2: 'a', 3:'b'} ,3:{2:'c'}})`` -- return a graph by + associating a list of neighbors to each vertex and providing its edge + label. - INPUT: + To bypass auto-detection, prefer the more explicit ``Graph(D, + format='dict_of_dicts')``. - - ``data`` -- can be any of the following (see the ``format`` argument): + For graphs with multiple edges, you can provide a list of labels + instead, e.g.: ``Graph({1: {2: ['a1', 'a2'], 3:['b']} ,3:{2:['c']}})``. - #. An integer specifying the number of vertices + #. ``Graph(a_symmetric_matrix)`` -- return a graph with given (weighted) + adjacency matrix (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.adjacency_matrix`). - #. A dictionary of dictionaries + To bypass auto-detection, prefer the more explicit ``Graph(M, + format='adjacency_matrix')``. To take weights into account, use + ``format='weighted_adjacency_matrix'`` instead. - #. A dictionary of lists + #. ``Graph(a_nonsymmetric_matrix)`` -- return a graph with given incidence + matrix (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.incidence_matrix`). - #. A Sage adjacency matrix or incidence matrix + To bypass auto-detection, prefer the more explicit ``Graph(M, + format='incidence_matrix')``. - #. A Sage :meth:`Seidel adjacency matrix ` + #. ``Graph([V, f])`` -- return a graph from a vertex set ``V`` and a + *symmetric* function ``f``. The graph contains an edge `u,v` whenever + ``f(u,v)`` is ``True``.. Example: ``Graph([ [1..10], lambda x,y: + abs(x-y).is_square()])`` - #. A pygraphviz graph + #. ``Graph(':I`ES@obGkqegW~')`` -- return a graph from a graph6 or sparse6 + string (see documentation of :meth:`graph6_string` or + :meth:`sparse6_string`). - #. A NetworkX graph + #. ``Graph(a_seidel_matrix, format='seidel_adjacency_matrix')`` -- return + a graph with a given seidel adjacency matrix (see documentation of + :meth:`seidel_adjacency_matrix`). - #. An igraph graph (see http://igraph.org/python/) + #. ``Graph(another_graph)`` -- return a graph from a Sage (di)graph, + `pygraphviz `__ graph, `NetworkX + `__ graph, or `igraph + `__ graph. - - ``pos`` - a positioning dictionary: for example, the - spring layout from NetworkX for the 5-cycle is:: + - ``pos`` - a positioning dictionary (cf. documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.layout`). For example, to + draw 4 vertices on a square:: - {0: [-0.91679746, 0.88169588], - 1: [ 0.47294849, 1.125 ], - 2: [ 1.125 ,-0.12867615], - 3: [ 0.12743933,-1.125 ], - 4: [-1.125 ,-0.50118505]} + {0: [-1,-1], + 1: [ 1,-1], + 2: [ 1, 1], + 3: [-1, 1]} - ``name`` - (must be an explicitly named parameter, i.e., ``name="complete")`` gives the graph a name @@ -655,57 +523,19 @@ class Graph(GenericGraph): if data is an instance of the ``Graph`` class) - ``multiedges`` - boolean, whether to allow multiple - edges (ignored if data is an instance of the ``Graph`` class) - - - ``weighted`` - whether graph thinks of itself as - weighted or not. See ``self.weighted()`` - - - ``format`` - if None, Graph tries to guess; can take - a number of values, namely: - - - ``'int'`` - an integer specifying the number of vertices in an - edge-free graph with vertices labelled from 0 to n-1 - - - ``'graph6'`` - Brendan McKay's graph6 format, in a - string (if the string has multiple graphs, the first graph is - taken) - - - ``'sparse6'`` - Brendan McKay's sparse6 format, in a - string (if the string has multiple graphs, the first graph is - taken) - - - ``'adjacency_matrix'`` - a square Sage matrix M, - with M[i,j] equal to the number of edges {i,j} - - - ``'weighted_adjacency_matrix'`` - a square Sage - matrix M, with M[i,j] equal to the weight of the single edge {i,j}. - Given this format, weighted is ignored (assumed True). - - - ``'seidel_adjacency_matrix'`` - a symmetric Sage matrix M - with 0s on the diagonal, and the other entries -1 or 1, - `M[i,j]=-1` indicating that {i,j} is an edge, otherwise `M[i,j]=1`. - - - ``'incidence_matrix'`` - a Sage matrix, with one - column C for each edge, where if C represents {i, j}, C[i] is -1 - and C[j] is 1 + edges (ignored if data is an instance of the ``Graph`` class). - - ``'elliptic_curve_congruence'`` - data must be an - iterable container of elliptic curves, and the graph produced has - each curve as a vertex (it's Cremona label) and an edge E-F - labelled p if and only if E is congruent to F mod p + - ``weighted`` - whether graph thinks of itself as weighted or not. See + :meth:`~sage.graphs.generic_graph.GenericGraph.weighted`. - - ``NX`` - data must be a NetworkX Graph. - - .. NOTE:: - - As Sage's default edge labels is ``None`` while NetworkX uses - ``{}``, the ``{}`` labels of a NetworkX graph are automatically - set to ``None`` when it is converted to a Sage graph. This - behaviour can be overruled by setting the keyword - ``convert_empty_dict_labels_to_None`` to ``False`` (it is - ``True`` by default). - - - ``igraph`` - data must be an `igraph `__ graph. + - ``format`` - if set to ``None`` (default), :class:`Graph` tries to guess + input's format. To avoid this possibly time-consuming step, one of the + following values can be specified (see description above): ``"int"``, + ``"graph6"``, ``"sparse6"``, ``"rule"``, ``"list_of_edges"``, + ``"dict_of_lists"``, ``"dict_of_dicts"``, ``"adjacency_matrix"``, + ``"weighted_adjacency_matrix"``, ``"seidel_adjacency_matrix"``, + ``"incidence_matrix"``, ``"elliptic_curve_congruence"``, ``"NX"``, + ``"igraph"``. - ``sparse`` (boolean) -- ``sparse=True`` is an alias for ``data_structure="sparse"``, and ``sparse=False`` is an alias for @@ -730,7 +560,7 @@ class Graph(GenericGraph): ``data_structure='static_sparse'``. Set to ``False`` by default. - ``vertex_labels`` - Whether to allow any object as a vertex (slower), or - only the integers 0, ..., n-1, where n is the number of vertices. + only the integers `0,...,n-1`, where `n` is the number of vertices. - ``convert_empty_dict_labels_to_None`` - this arguments sets the default edge labels used by NetworkX (empty dictionaries) @@ -898,9 +728,7 @@ class Graph(GenericGraph): sage: Graph(Matrix([[1],[1],[1]])) Traceback (most recent call last): ... - ValueError: Non-symmetric or non-square matrix assumed to be an - incidence matrix: There must be one or two nonzero entries per - column. Got entries [1, 1, 1] in column 0 + ValueError: There must be one or two nonzero entries per column in an incidence matrix. Got entries [1, 1, 1] in column 0 sage: Graph(Matrix([[1],[1],[0]])) Graph on 3 vertices @@ -918,9 +746,7 @@ class Graph(GenericGraph): sage: Graph(M) Traceback (most recent call last): ... - ValueError: Non-symmetric or non-square matrix assumed to be an - incidence matrix: There must be one or two nonzero entries per - column. Got entries [1, 1] in column 2 + ValueError: There must be one or two nonzero entries per column in an incidence matrix. Got entries [1, 1] in column 2 Check that :trac:`9714` is fixed:: @@ -999,6 +825,21 @@ class Graph(GenericGraph): sage: Graph(g).edges() # optional - python_igraph [(0, 1, {'name': 'a', 'weight': 1}), (0, 2, {'name': 'b', 'weight': 3})] + + When defining an undirected graph from a function ``f``, it is *very* + important that ``f`` be symmetric. If it is not, anything can happen:: + + sage: f_sym = lambda x,y : abs(x-y) == 1 + sage: f_nonsym = lambda x,y : (x-y) == 1 + sage: G_sym = Graph([[4,6,1,5,3,7,2,0], f_sym]) + sage: G_sym.is_isomorphic(graphs.PathGraph(8)) + True + sage: G_nonsym = Graph([[4,6,1,5,3,7,2,0], f_nonsym]) + sage: G_nonsym.size() + 4 + sage: G_nonsym.is_isomorphic(G_sym) + False + By default, graphs are mutable and can thus not be used as a dictionary key:: @@ -1046,6 +887,12 @@ class Graph(GenericGraph): ... ValueError: Graph's Seidel adjacency matrix must have 0s on the main diagonal + From a a list of vertices and a list of edges:: + + sage: G = Graph([[1,2,3],[(1,2)]]); G + Graph on 3 vertices + sage: G.edges() + [(1, 2, None)] """ _directed = False @@ -1140,9 +987,7 @@ def __init__(self, data=None, pos=None, loops=None, format=None, sage: Graph(matrix([[1,1],[1,1],[1,0]])) Traceback (most recent call last): ... - ValueError: Non-symmetric or non-square matrix assumed to be an - incidence matrix: There must be one or two nonzero entries per - column. Got entries [1, 1, 1] in column 0 + ValueError: There must be one or two nonzero entries per column in an incidence matrix. Got entries [1, 1, 1] in column 0 sage: Graph(matrix([[3,1,1],[0,1,1]])) Traceback (most recent call last): ... @@ -1150,7 +995,7 @@ def __init__(self, data=None, pos=None, loops=None, format=None, to 2, but column 0 does not """ GenericGraph.__init__(self) - msg = '' + from sage.structure.element import is_Matrix if sparse is False: @@ -1201,7 +1046,6 @@ def __init__(self, data=None, pos=None, loops=None, format=None, format = 'adjacency_matrix' else: format = 'incidence_matrix' - msg += "Non-symmetric or non-square matrix assumed to be an incidence matrix: " if format is None and isinstance(data, Graph): format = 'Graph' from sage.graphs.all import DiGraph @@ -1213,6 +1057,15 @@ def __init__(self, data=None, pos=None, loops=None, format=None, len(data)>=2 and callable(data[1])): format = 'rule' + + if (format is None and + isinstance(data,list) and + len(data) == 2 and + isinstance(data[0],list) and # a list of two lists, the second of + isinstance(data[1],list) and # which contains iterables (the edges) + (not data[1] or callable(getattr(data[1][0],"__iter__",None)))): + format = "vertices_and_edges" + if format is None and isinstance(data,dict): keys = data.keys() if len(keys) == 0: format = 'dict_of_dicts' @@ -1267,189 +1120,32 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if weighted is None: weighted = False self.allow_loops(loops if loops else False, check=False) self.allow_multiple_edges(multiedges if multiedges else False, check=False) - if not isinstance(data, str): - raise ValueError('If input format is graph6, then data must be a string.') - n = data.find('\n') - if n == -1: - n = len(data) - ss = data[:n] - n, s = generic_graph_pyx.length_and_string_from_graph6(ss) - m = generic_graph_pyx.binary_string_from_graph6(s, n) - expected = n*(n-1)/2 + (6 - n*(n-1)/2)%6 - if len(m) > expected: - raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n)) - elif len(m) < expected: - raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n)) - self.add_vertices(range(n)) - k = 0 - for i in xrange(n): - for j in xrange(i): - if m[k] == '1': - self._backend.add_edge(i, j, None, False) - k += 1 + from graph_input import from_graph6 + from_graph6(self, data) elif format == 'sparse6': if weighted is None: weighted = False self.allow_loops(False if loops is False else True, check=False) self.allow_multiple_edges(False if multiedges is False else True, check=False) - from math import ceil, floor - from sage.misc.functional import log - n = data.find('\n') - if n == -1: - n = len(data) - s = data[:n] - n, s = generic_graph_pyx.length_and_string_from_graph6(s[1:]) - if n == 0: - edges = [] - else: - k = int(ceil(log(n,2))) - ords = [ord(i) for i in s] - if any(o > 126 or o < 63 for o in ords): - raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)])) - bits = ''.join([generic_graph_pyx.int_to_binary_string(o-63).zfill(6) for o in ords]) - b = [] - x = [] - for i in xrange(int(floor(len(bits)/(k+1)))): - b.append(int(bits[(k+1)*i:(k+1)*i+1],2)) - x.append(int(bits[(k+1)*i+1:(k+1)*i+k+1],2)) - v = 0 - edges = [] - for i in xrange(len(b)): - if b[i] == 1: - v += 1 - if x[i] > v: - v = x[i] - else: - if v < n: - edges.append((x[i],v)) - self.add_vertices(range(n)) - self.add_edges(edges) + from graph_input import from_sparse6 + from_sparse6(self, data) + elif format == 'adjacency_matrix': - assert is_Matrix(data) - # note: the adjacency matrix might be weighted and hence not - # necessarily consists of integers - if not weighted and data.base_ring() != ZZ: - try: - data = data.change_ring(ZZ) - except TypeError: - if weighted is False: - raise ValueError("Non-weighted graph's"+ - " adjacency matrix must have only nonnegative"+ - " integer entries") - weighted = True + from graph_input import from_adjacency_matrix + from_adjacency_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted) - if data.is_sparse(): - entries = set(data[i,j] for i,j in data.nonzero_positions()) - else: - entries = set(data.list()) - - if not weighted and any(e < 0 for e in entries): - if weighted is False: - raise ValueError("Non-weighted digraph's"+ - " adjacency matrix must have only nonnegative"+ - " integer entries") - weighted = True - if multiedges is None: multiedges = False - if weighted is None: - weighted = False - - if multiedges is None: - multiedges = ((not weighted) and any(e != 0 and e != 1 for e in entries)) - - if not loops and any(data[i,i] for i in xrange(data.nrows())): - if loops is False: - raise ValueError("Non-looped digraph's adjacency"+ - " matrix must have zeroes on the diagonal.") - loops = True - if loops is None: - loops = False - self.allow_loops(loops, check=False) - self.allow_multiple_edges(multiedges, check=False) - self.add_vertices(range(data.nrows())) - e = [] - if weighted: - for i,j in data.nonzero_positions(): - if i <= j: - e.append((i,j,data[i][j])) - elif multiedges: - for i,j in data.nonzero_positions(): - if i <= j: - e += [(i,j)]*int(data[i][j]) - else: - for i,j in data.nonzero_positions(): - if i <= j: - e.append((i,j)) - self.add_edges(e) elif format == 'incidence_matrix': - assert is_Matrix(data) - - oriented = any(data[pos] < 0 for pos in data.nonzero_positions(copy=False)) - - positions = [] - for i in range(data.ncols()): - NZ = data.nonzero_positions_in_column(i) - if len(NZ) == 1: - if oriented: - raise ValueError("Column {} of the (oriented) incidence " - "matrix contains only one nonzero value".format(i)) - elif data[NZ[0],i] != 2: - raise ValueError("Each column of a non-oriented incidence " - "matrix must sum to 2, but column {} does not".format(i)) - if loops is None: - loops = True - positions.append((NZ[0],NZ[0])) - elif len(NZ) != 2 or \ - (oriented and not ((data[NZ[0],i] == +1 and data[NZ[1],i] == -1) or \ - (data[NZ[0],i] == -1 and data[NZ[1],i] == +1))) or \ - (not oriented and (data[NZ[0],i] != 1 or data[NZ[1],i] != 1)): - msg += "There must be one or two nonzero entries per column. " - msg += "Got entries {} in column {}".format([data[j,i] for j in NZ], i) - raise ValueError(msg) - else: - positions.append(tuple(NZ)) + from graph_input import from_incidence_matrix + from_incidence_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted) - if weighted is None: weighted = False - if multiedges is None: - total = len(positions) - multiedges = (len(set(positions)) < total ) - self.allow_loops(False if loops is None else loops, check=False) - self.allow_multiple_edges(multiedges, check=False) - self.add_vertices(range(data.nrows())) - self.add_edges(positions) elif format == 'seidel_adjacency_matrix': - assert is_Matrix(data) - if data.base_ring() != ZZ: - try: - data = data.change_ring(ZZ) - except TypeError: - raise ValueError("Graph's Seidel adjacency matrix must"+ - " have only 0,1,-1 integer entries") - - if data.is_sparse(): - entries = set(data[i,j] for i,j in data.nonzero_positions()) - else: - entries = set(data.list()) - - if any(e < -1 or e > 1 for e in entries): - raise ValueError("Graph's Seidel adjacency matrix must"+ - " have only 0,1,-1 integer entries") - if any(i==j for i,j in data.nonzero_positions()): - raise ValueError("Graph's Seidel adjacency matrix must"+ - " have 0s on the main diagonal") - if not data.is_symmetric(): - raise ValueError("Graph's Seidel adjacency matrix must"+ - " be symmetric") multiedges = False weighted = False loops = False self.allow_loops(False) self.allow_multiple_edges(False) - self.add_vertices(range(data.nrows())) - e = [] - for i,j in data.nonzero_positions(): - if i <= j and data[i,j] < 0: - e.append((i,j)) - self.add_edges(e) + from graph_input import from_seidel_adjacency_matrix + from_seidel_adjacency_matrix(self, data) elif format == 'Graph': if loops is None: loops = data.allows_loops() if multiedges is None: multiedges = data.allows_multiple_edges() @@ -1507,91 +1203,22 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self.add_vertices(verts) self.add_edges(e for e in combinations(verts,2) if f(*e)) self.add_edges((v,v) for v in verts if f(v,v)) - elif format == 'dict_of_dicts': - # adjust for empty dicts instead of None in NetworkX default edge labels - if convert_empty_dict_labels_to_None is None: - convert_empty_dict_labels_to_None = (format == 'NX') - if not all(isinstance(data[u], dict) for u in data): - raise ValueError("Input dict must be a consistent format.") + elif format == "vertices_and_edges": + self.allow_multiple_edges(bool(multiedges), check=False) + self.allow_loops(bool(loops), check=False) + self.add_vertices(data[0]) + self.add_edges(data[1]) - if not loops and any(u in neighb for u,neighb in data.iteritems()): - if loops is False: - u = next(u for u,neighb in data.iteritems() if u in neighb) - raise ValueError("The graph was built with loops=False but input data has a loop at {}.".format(u)) - loops = True - if loops is None: - loops = False + elif format == 'dict_of_dicts': + from graph_input import from_dict_of_dicts + from_dict_of_dicts(self, data, loops=loops, multiedges=multiedges, weighted=weighted, + convert_empty_dict_labels_to_None = False if convert_empty_dict_labels_to_None is None else convert_empty_dict_labels_to_None) - if weighted is None: weighted = False - for u in data: - for v in data[u]: - if hash(u) > hash(v): - if v in data and u in data[v]: - if data[u][v] != data[v][u]: - raise ValueError("Dict does not agree on edge (%s,%s)"%(u,v)) - continue - if multiedges is not False and not isinstance(data[u][v], list): - if multiedges is None: multiedges = False - if multiedges: - raise ValueError("Dict of dicts for multigraph must be in the format {v : {u : list}}") - if multiedges is None and len(data) > 0: - multiedges = True - self.allow_loops(loops, check=False) - self.allow_multiple_edges(multiedges, check=False) - verts = set().union(data.keys(), *data.values()) - self.add_vertices(verts) - if convert_empty_dict_labels_to_None: - for u in data: - for v in data[u]: - if hash(u) <= hash(v) or v not in data or u not in data[v]: - if multiedges: - for l in data[u][v]: - self._backend.add_edge(u,v,l,False) - else: - self._backend.add_edge(u,v,data[u][v] if data[u][v] != {} else None,False) - else: - for u in data: - for v in data[u]: - if hash(u) <= hash(v) or v not in data or u not in data[v]: - if multiedges: - for l in data[u][v]: - self._backend.add_edge(u,v,l,False) - else: - self._backend.add_edge(u,v,data[u][v],False) elif format == 'dict_of_lists': - if not all(isinstance(data[u], list) for u in data): - raise ValueError("Input dict must be a consistent format.") - - verts = set().union(data.keys(),*data.values()) - if loops is None or loops is False: - for u in data: - if u in data[u]: - if loops is None: - loops = True - elif loops is False: - u = next(u for u,neighb in data.iteritems() if u in neighb) - raise ValueError("The graph was built with loops=False but input data has a loop at {}.".format(u)) - break - if loops is None: - loops = False - if weighted is None: weighted = False - for u in data: - if len(set(data[u])) != len(data[u]): - if multiedges is False: - v = next((v for v in data[u] if data[u].count(v) > 1)) - raise ValueError("Non-multigraph got several edges (%s,%s)"%(u,v)) - if multiedges is None: - multiedges = True - if multiedges is None: multiedges = False - self.allow_loops(loops, check=False) - self.allow_multiple_edges(multiedges, check=False) - self.add_vertices(verts) - for u in data: - for v in data[u]: - if (multiedges or hash(u) <= hash(v) or - v not in data or u not in data[v]): - self._backend.add_edge(u,v,None,False) + from graph_input import from_dict_of_lists + from_dict_of_lists(self, data, loops=loops, multiedges=multiedges, weighted=weighted) + elif format == 'int': self.allow_loops(loops if loops else False, check=False) self.allow_multiple_edges(multiedges if multiedges else False, check=False) @@ -1652,7 +1279,7 @@ def __init__(self, data=None, pos=None, loops=None, format=None, raise ValueError("Unknown input format '{}'".format(format)) if weighted is None: weighted = False - self._weighted = weighted + self._weighted = getattr(self,'_weighted',weighted) self._pos = pos @@ -1668,6 +1295,8 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self._immutable = True ### Formats + + @doc_index("Basic methods") def graph6_string(self): """ Returns the graph6 representation of the graph as an ASCII string. @@ -1695,6 +1324,7 @@ def graph6_string(self): else: return generic_graph_pyx.small_integer_to_graph6(n) + generic_graph_pyx.binary_string_to_graph6(self._bit_vector()) + @doc_index("Basic methods") def sparse6_string(self): r""" Returns the sparse6 representation of the graph as an ASCII string. @@ -1783,6 +1413,7 @@ def sparse6_string(self): ### Attributes @combinatorial_map(name="partition of connected components") + @doc_index("Deprecated") def to_partition(self): """ Return the partition of connected components of ``self``. @@ -1803,6 +1434,7 @@ def to_partition(self): from sage.combinat.partition import Partition return Partition(sorted([len(y) for y in self.connected_components()], reverse=True)) + @doc_index("Basic methods") def is_directed(self): """ Since graph is undirected, returns False. @@ -1814,6 +1446,7 @@ def is_directed(self): """ return False + @doc_index("Connectivity, orientations, trees") def bridges(self): r""" Returns a list of the bridges (or cut edges). @@ -1839,6 +1472,7 @@ def bridges(self): bridges.extend(gs.edge_boundary(scc)) return bridges + @doc_index("Connectivity, orientations, trees") def spanning_trees(self): """ Returns a list of all spanning trees. @@ -1868,6 +1502,19 @@ def spanning_trees(self): - :meth:`~sage.graphs.graph.Graph.random_spanning_tree` -- returns a random spanning tree. + TESTS: + + Works with looped graphs:: + + sage: g = Graph({i:[i,(i+1)%6] for i in range(6)}) + sage: g.spanning_trees() + [Graph on 6 vertices, + Graph on 6 vertices, + Graph on 6 vertices, + Graph on 6 vertices, + Graph on 6 vertices, + Graph on 6 vertices] + REFERENCES: .. [RT75] Read, R. C. and Tarjan, R. E. @@ -1886,7 +1533,7 @@ def _recursive_spanning_trees(G,forest): return [forest.copy()] else: # Pick an edge e from G-forest - for e in G.edges(): + for e in G.edge_iterator(labels=False): if not forest.has_edge(e): break @@ -1919,11 +1566,12 @@ def _recursive_spanning_trees(G,forest): forest = Graph([]) forest.add_vertices(self.vertices()) forest.add_edges(self.bridges()) - return _recursive_spanning_trees(self, forest) + return _recursive_spanning_trees(Graph(self,immutable=False,loops=False), forest) else: return [] ### Properties + @doc_index("Graph properties") def is_tree(self, certificate=False, output='vertex'): """ Tests if the graph is a tree @@ -2054,6 +1702,7 @@ def vertices_to_edges(x): else: return self.num_verts() == self.num_edges() + 1 + @doc_index("Graph properties") def is_forest(self, certificate=False, output='vertex'): """ Tests if the graph is a forest, i.e. a disjoint union of trees. @@ -2105,6 +1754,7 @@ def is_forest(self, certificate=False, output='vertex'): if not isit: return (False, cycle) + @doc_index("Graph properties") def is_overfull(self): r""" Tests whether the current graph is overfull. @@ -2204,6 +1854,7 @@ def is_overfull(self): return (self.order() % 2 == 1) and ( 2 * self.size() > max(self.degree()) * (self.order() - 1)) + @doc_index("Graph properties") def is_even_hole_free(self, certificate = False): r""" Tests whether ``self`` contains an induced even hole. @@ -2312,6 +1963,7 @@ def is_even_hole_free(self, certificate = False): return True + @doc_index("Graph properties") def is_odd_hole_free(self, certificate = False): r""" Tests whether ``self`` contains an induced odd hole. @@ -2391,6 +2043,7 @@ def is_odd_hole_free(self, certificate = False): return True + @doc_index("Graph properties") def is_bipartite(self, certificate = False): """ Returns ``True`` if graph `G` is bipartite, ``False`` if not. @@ -2471,6 +2124,7 @@ def is_bipartite(self, certificate = False): else: return True + @doc_index("Graph properties") def is_triangle_free(self, algorithm='bitset'): r""" Returns whether ``self`` is triangle-free @@ -2562,6 +2216,7 @@ def is_triangle_free(self, algorithm='bitset'): else: raise ValueError("Algorithm '%s' not yet implemented. Please contribute." %(algorithm)) + @doc_index("Graph properties") def is_split(self): r""" Returns ``True`` if the graph is a Split graph, ``False`` otherwise. @@ -2635,6 +2290,7 @@ def is_split(self): return left == right + @doc_index("Algorithmically hard stuff") def treewidth(self,k=None,certificate=False): r""" Computes the tree-width of `G` (and provides a decomposition) @@ -2736,6 +2392,15 @@ def treewidth(self,k=None,certificate=False): ....: g.delete_edges(list(combinations(bag,2))) sage: g.size() 0 + + :trac:`19358`:: + + sage: g = Graph() + sage: for i in range(3): + ....: for j in range(2): + ....: g.add_path([i,(i,j),(i+1)%3]) + sage: g.treewidth() + 2 """ from sage.misc.cachefunc import cached_function from sage.sets.set import Set @@ -2781,9 +2446,8 @@ def rec(cut,cc): if len(cc)+len(cut) <= k+1: return [(cut,cut.union(cc))] if certificate else True - # The list of potential vertices that could be added to the current cut - extensions = {v for u in cut for v in g.neighbors(u) if v in cc} - for v in extensions: + # We explore all possible extensions of the cut + for v in cc: # New cuts and connected components, with v respectively added and # removed @@ -2849,6 +2513,7 @@ def rec(cut,cc): return G + @doc_index("Algorithmically hard stuff") def is_perfect(self, certificate = False): r""" Tests whether the graph is perfect. @@ -2977,6 +2642,7 @@ def is_perfect(self, certificate = False): return self_complement.is_odd_hole_free(certificate = certificate) + @doc_index("Graph properties") def odd_girth(self): r""" Returns the odd girth of self. @@ -3044,6 +2710,7 @@ def odd_girth(self): return Infinity + @doc_index("Graph properties") def is_edge_transitive(self): """ Returns true if self is an edge transitive graph. @@ -3090,6 +2757,7 @@ def is_edge_transitive(self): return gap("OrbitLength("+str(A._gap_())+",Set(" + str(e) + "),OnSets);") == self.size() + @doc_index("Graph properties") def is_arc_transitive(self): """ Returns true if self is an arc-transitive graph @@ -3132,6 +2800,7 @@ def is_arc_transitive(self): return gap("OrbitLength("+str(A._gap_())+",Set(" + str(e) + "),OnTuples);") == 2*self.size() + @doc_index("Graph properties") def is_half_transitive(self): """ Returns true if self is a half-transitive graph. @@ -3171,6 +2840,7 @@ def is_half_transitive(self): self.is_vertex_transitive() and not self.is_arc_transitive()) + @doc_index("Graph properties") def is_semi_symmetric(self): """ Returns true if self is semi-symmetric. @@ -3215,6 +2885,7 @@ def is_semi_symmetric(self): self.is_edge_transitive() and not self.is_vertex_transitive()) + @doc_index("Connectivity, orientations, trees") def degree_constrained_subgraph(self, bounds=None, solver=None, verbose=0): r""" Returns a degree-constrained subgraph. @@ -3313,15 +2984,15 @@ def degree_constrained_subgraph(self, bounds=None, solver=None, verbose=0): ### Orientations + @doc_index("Connectivity, orientations, trees") def strong_orientation(self): r""" - Returns a strongly connected orientation of the current graph. See - also the :wikipedia:`Strongly_connected_component`. + Returns a strongly connected orientation of the current graph. - An orientation of an undirected graph is a digraph obtained by - giving an unique direction to each of its edges. An orientation - is said to be strong if there is a directed path between each - pair of vertices. + An orientation of an undirected graph is a digraph obtained by giving an + unique direction to each of its edges. An orientation is said to be + strong if there is a directed path between each pair of vertices. See + also the :wikipedia:`Strongly_connected_component`. If the graph is 2-edge-connected, a strongly connected orientation can be found in linear time. If the given graph is not 2-connected, @@ -3419,6 +3090,7 @@ def strong_orientation(self): return d + @doc_index("Connectivity, orientations, trees") def minimum_outdegree_orientation(self, use_edge_labels=False, solver=None, verbose=0): r""" Returns an orientation of ``self`` with the smallest possible maximum @@ -3527,6 +3199,7 @@ def minimum_outdegree_orientation(self, use_edge_labels=False, solver=None, verb return O + @doc_index("Connectivity, orientations, trees") def bounded_outdegree_orientation(self, bound): r""" Computes an orientation of ``self`` such that every vertex `v` @@ -3684,6 +3357,7 @@ def bounded_outdegree_orientation(self, bound): ### Coloring + @doc_index("Basic methods") def bipartite_color(self): """ Returns a dictionary with vertices as the keys and the color class @@ -3705,6 +3379,7 @@ def bipartite_color(self): else: raise RuntimeError("Graph is not bipartite.") + @doc_index("Basic methods") def bipartite_sets(self): """ Returns `(X,Y)` where `X` and `Y` are the nodes in each bipartite set of @@ -3731,6 +3406,7 @@ def bipartite_sets(self): return left, right + @doc_index("Algorithmically hard stuff") def chromatic_number(self, algorithm="DLX", verbose = 0): r""" Returns the minimal number of colors needed to color the vertices @@ -3830,6 +3506,7 @@ def chromatic_number(self, algorithm="DLX", verbose = 0): else: raise ValueError("The 'algorithm' keyword must be set to either 'DLX', 'MILP' or 'CP'.") + @doc_index("Algorithmically hard stuff") def coloring(self, algorithm="DLX", hex_colors=False, verbose = 0): r""" Returns the first (optimal) proper vertex-coloring found. @@ -3904,6 +3581,7 @@ def coloring(self, algorithm="DLX", hex_colors=False, verbose = 0): else: raise ValueError("The 'algorithm' keyword must be set to either 'DLX' or 'MILP'.") + @doc_index("Algorithmically hard stuff") def chromatic_symmetric_function(self, R=None): r""" Return the chromatic symmetric function of ``self``. @@ -3977,6 +3655,7 @@ def chromatic_symmetric_function(self, R=None): ret += (-1)**len(F) * p[la] return ret + @doc_index("Leftovers") def matching(self, value_only=False, algorithm="Edmonds", use_edge_labels=True, solver=None, verbose=0): r""" Returns a maximum weighted matching of the graph @@ -4117,6 +3796,7 @@ def matching(self, value_only=False, algorithm="Edmonds", use_edge_labels=True, else: raise ValueError('algorithm must be set to either "Edmonds" or "LP"') + @doc_index("Algorithmically hard stuff") def has_homomorphism_to(self, H, core = False, solver = None, verbose = 0): r""" Checks whether there is a homomorphism between two graphs. @@ -4224,6 +3904,7 @@ def has_homomorphism_to(self, H, core = False, solver = None, verbose = 0): except MIPSolverException: return False + @doc_index("Leftovers") def fractional_chromatic_index(self, solver = None, verbose_constraints = 0, verbose = 0): r""" Computes the fractional chromatic index of ``self`` @@ -4344,6 +4025,7 @@ def fractional_chromatic_index(self, solver = None, verbose_constraints = 0, ver # Accomplished ! return obj + @doc_index("Leftovers") def maximum_average_degree(self, value_only=True, solver = None, verbose = 0): r""" Returns the Maximum Average Degree (MAD) of the current graph. @@ -4449,6 +4131,7 @@ def maximum_average_degree(self, value_only=True, solver = None, verbose = 0): else: return g_mad + @doc_index("Algorithmically hard stuff") def independent_set_of_representatives(self, family, solver=None, verbose=0): r""" Returns an independent set of representatives. @@ -4577,6 +4260,7 @@ def independent_set_of_representatives(self, family, solver=None, verbose=0): return repr + @doc_index("Algorithmically hard stuff") def minor(self, H, solver=None, verbose=0): r""" Returns the vertices of a minor isomorphic to `H` in the current graph. @@ -4742,6 +4426,7 @@ def minor(self, H, solver=None, verbose=0): ### Convexity + @doc_index("Algorithmically hard stuff") def convexity_properties(self): r""" Returns a ``ConvexityProperties`` object corresponding to ``self``. @@ -4783,6 +4468,7 @@ def convexity_properties(self): return ConvexityProperties(self) # Centrality + @doc_index("Distances") def centrality_degree(self, v=None): r""" Returns the degree centrality of a vertex. @@ -4830,6 +4516,7 @@ def centrality_degree(self, v=None): ### Constructors + @doc_index("Basic methods") def to_directed(self, implementation='c_graph', data_structure=None, sparse=None): """ @@ -4900,6 +4587,7 @@ def to_directed(self, implementation='c_graph', data_structure=None, return D + @doc_index("Basic methods") def to_undirected(self): """ Since the graph is already undirected, simply returns a copy of @@ -4912,6 +4600,7 @@ def to_undirected(self): """ return self.copy() + @doc_index("Basic methods") def join(self, other, verbose_relabel=None, labels="pairs"): """ Returns the join of ``self`` and ``other``. @@ -4980,7 +4669,7 @@ def join(self, other, verbose_relabel=None, labels="pairs"): G.name('%s join %s'%(self.name(), other.name())) return G - + @doc_index("Leftovers") def seidel_adjacency_matrix(self, vertices=None): r""" Returns the Seidel adjacency matrix of ``self``. @@ -5012,6 +4701,7 @@ def seidel_adjacency_matrix(self, vertices=None): self.complement().adjacency_matrix(sparse=False, \ vertices=vertices) + @doc_index("Leftovers") def seidel_switching(self, s, inplace=True): r""" Returns the Seidel switching of ``self`` w.r.t. subset of vertices ``s``. @@ -5054,6 +4744,7 @@ def seidel_switching(self, s, inplace=True): if not inplace: return G + @doc_index("Leftovers") def twograph(self): r""" Returns the two-graph of ``self`` @@ -5112,6 +4803,7 @@ def twograph(self): ### Visualization + @doc_index("Basic methods") def write_to_eps(self, filename, **options): r""" Writes a plot of the graph to ``filename`` in ``eps`` format. @@ -5144,6 +4836,7 @@ def write_to_eps(self, filename, **options): f.write( print_graph_eps(self.vertices(), self.edge_iterator(), pos) ) f.close() + @doc_index("Algorithmically hard stuff") def topological_minor(self, H, vertices = False, paths = False, solver=None, verbose=0): r""" Returns a topological `H`-minor from ``self`` if one exists. @@ -5375,6 +5068,7 @@ def topological_minor(self, H, vertices = False, paths = False, solver=None, ver ### Cliques + @doc_index("Clique-related methods") def cliques_maximal(self, algorithm = "native"): """ Returns the list of all maximal cliques, with each clique represented @@ -5450,6 +5144,7 @@ def cliques_maximal(self, algorithm = "native"): else: raise ValueError("Algorithm must be equal to 'native' or to 'NetworkX'.") + @doc_index("Clique-related methods") def clique_maximum(self, algorithm="Cliquer"): """ Returns the vertex set of a maximal order complete subgraph. @@ -5520,6 +5215,7 @@ def clique_maximum(self, algorithm="Cliquer"): else: raise NotImplementedError("Only 'MILP', 'Cliquer' and 'mcqd' are supported.") + @doc_index("Clique-related methods") def clique_number(self, algorithm="Cliquer", cliques=None): r""" Returns the order of the largest clique of the graph (the clique @@ -5610,6 +5306,7 @@ def clique_number(self, algorithm="Cliquer", cliques=None): else: raise NotImplementedError("Only 'networkx' 'MILP' 'Cliquer' and 'mcqd' are supported.") + @doc_index("Clique-related methods") def cliques_number_of(self, vertices=None, cliques=None): """ Returns a dictionary of the number of maximal cliques containing each @@ -5660,6 +5357,7 @@ def cliques_number_of(self, vertices=None, cliques=None): import networkx return networkx.number_of_cliques(self.networkx_graph(copy=False), vertices, cliques) + @doc_index("Clique-related methods") def cliques_get_max_clique_graph(self, name=''): """ Returns a graph constructed with maximal cliques as vertices, and @@ -5691,6 +5389,7 @@ def cliques_get_max_clique_graph(self, name=''): import networkx return Graph(networkx.make_max_clique_graph(self.networkx_graph(copy=False), name=name, create_using=networkx.MultiGraph())) + @doc_index("Clique-related methods") def cliques_get_clique_bipartite(self, **kwds): """ Returns a bipartite graph constructed such that maximal cliques are the @@ -5718,6 +5417,7 @@ def cliques_get_clique_bipartite(self, **kwds): import networkx return BipartiteGraph(networkx.make_clique_bipartite(self.networkx_graph(copy=False), **kwds)) + @doc_index("Algorithmically hard stuff") def independent_set(self, algorithm = "Cliquer", value_only = False, reduction_rules = True, solver = None, verbosity = 0): r""" Returns a maximum independent set. @@ -5807,6 +5507,7 @@ def independent_set(self, algorithm = "Cliquer", value_only = False, reduction_r return [u for u in self.vertices() if not u in my_cover] + @doc_index("Algorithmically hard stuff") def vertex_cover(self, algorithm = "Cliquer", value_only = False, reduction_rules = True, solver = None, verbosity = 0): r""" @@ -6091,6 +5792,7 @@ def vertex_cover(self, algorithm = "Cliquer", value_only = False, cover_g.sort() return cover_g + @doc_index("Clique-related methods") def cliques_vertex_clique_number(self, algorithm="cliquer", vertices=None, cliques=None): """ @@ -6162,6 +5864,7 @@ def cliques_vertex_clique_number(self, algorithm="cliquer", vertices=None, else: raise NotImplementedError("Only 'networkx' and 'cliquer' are supported.") + @doc_index("Clique-related methods") def cliques_containing_vertex(self, vertices=None, cliques=None): """ Returns the cliques containing each vertex, represented as a dictionary @@ -6212,6 +5915,7 @@ def cliques_containing_vertex(self, vertices=None, cliques=None): import networkx return networkx.cliques_containing_node(self.networkx_graph(copy=False),vertices, cliques) + @doc_index("Clique-related methods") def clique_complex(self): """ Returns the clique complex of self. This is the largest simplicial complex on @@ -6243,6 +5947,7 @@ def clique_complex(self): C._graph = self return C + @doc_index("Clique-related methods") def clique_polynomial(self, t = None): """ Returns the clique polynomial of self. @@ -6274,6 +5979,7 @@ def clique_polynomial(self, t = None): ### Miscellaneous + @doc_index("Leftovers") def cores(self, k = None, with_labels=False): """ Returns the core number for each vertex in an ordered list. @@ -6431,6 +6137,7 @@ def cores(self, k = None, with_labels=False): else: return core.values() + @doc_index("Leftovers") def modular_decomposition(self): r""" Returns the modular decomposition of the current graph. @@ -6573,6 +6280,7 @@ def modular_decomposition(self): return relabel(D) + @doc_index("Graph properties") def is_prime(self): r""" Tests whether the current graph is prime. @@ -6700,6 +6408,7 @@ def _gomory_hu_tree(self, vertices, method="FF"): return g + @doc_index("Connectivity, orientations, trees") def gomory_hu_tree(self, method="FF"): r""" Returns a Gomory-Hu tree of self. @@ -6796,6 +6505,7 @@ def gomory_hu_tree(self, method="FF"): g.set_pos(dict(self.get_pos())) return g + @doc_index("Leftovers") def two_factor_petersen(self): r""" Returns a decomposition of the graph into 2-factors. @@ -6865,6 +6575,7 @@ def two_factor_petersen(self): return classes_b + @doc_index("Leftovers") def kirchhoff_symanzik_polynomial(self, name='t'): """ Return the Kirchhoff-Symanzik polynomial of a graph. @@ -6964,6 +6675,7 @@ def kirchhoff_symanzik_polynomial(self, name='t'): D = matrix.diagonal(PolynomialRing(ZZ, name, self.size()).gens()) return (circuit_mtrx.transpose() * D * circuit_mtrx).determinant() + @doc_index("Leftovers") def ihara_zeta_function_inverse(self): """ Compute the inverse of the Ihara zeta function of the graph. @@ -7041,43 +6753,63 @@ def ihara_zeta_function_inverse(self): import types import sage.graphs.weakly_chordal -Graph.is_long_hole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_hole_free, None, Graph) -Graph.is_long_antihole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_antihole_free, None, Graph) -Graph.is_weakly_chordal = types.MethodType(sage.graphs.weakly_chordal.is_weakly_chordal, None, Graph) +Graph.is_long_hole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_hole_free, None, Graph) +Graph.is_long_antihole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_antihole_free, None, Graph) +Graph.is_weakly_chordal = types.MethodType(sage.graphs.weakly_chordal.is_weakly_chordal, None, Graph) import sage.graphs.asteroidal_triples Graph.is_asteroidal_triple_free = types.MethodType(sage.graphs.asteroidal_triples.is_asteroidal_triple_free, None, Graph) import sage.graphs.chrompoly -Graph.chromatic_polynomial = types.MethodType(sage.graphs.chrompoly.chromatic_polynomial, None, Graph) +Graph.chromatic_polynomial = types.MethodType(sage.graphs.chrompoly.chromatic_polynomial, None, Graph) import sage.graphs.graph_decompositions.rankwidth -Graph.rank_decomposition = types.MethodType(sage.graphs.graph_decompositions.rankwidth.rank_decomposition, None, Graph) +Graph.rank_decomposition = types.MethodType(sage.graphs.graph_decompositions.rankwidth.rank_decomposition, None, Graph) import sage.graphs.matchpoly -Graph.matching_polynomial = types.MethodType(sage.graphs.matchpoly.matching_polynomial, None, Graph) +Graph.matching_polynomial = types.MethodType(sage.graphs.matchpoly.matching_polynomial, None, Graph) import sage.graphs.cliquer -Graph.cliques_maximum = types.MethodType(sage.graphs.cliquer.all_max_clique, None, Graph) +Graph.cliques_maximum = types.MethodType(sage.graphs.cliquer.all_max_clique, None, Graph) import sage.graphs.spanning_tree -Graph.random_spanning_tree = types.MethodType(sage.graphs.spanning_tree.random_spanning_tree, None, Graph) +Graph.random_spanning_tree = types.MethodType(sage.graphs.spanning_tree.random_spanning_tree, None, Graph) import sage.graphs.graph_decompositions.graph_products -Graph.is_cartesian_product = types.MethodType(sage.graphs.graph_decompositions.graph_products.is_cartesian_product, None, Graph) +Graph.is_cartesian_product = types.MethodType(sage.graphs.graph_decompositions.graph_products.is_cartesian_product, None, Graph) import sage.graphs.distances_all_pairs -Graph.is_distance_regular = types.MethodType(sage.graphs.distances_all_pairs.is_distance_regular, None, Graph) +Graph.is_distance_regular = types.MethodType(sage.graphs.distances_all_pairs.is_distance_regular, None, Graph) import sage.graphs.base.static_dense_graph -Graph.is_strongly_regular = types.MethodType(sage.graphs.base.static_dense_graph.is_strongly_regular, None, Graph) +Graph.is_strongly_regular = types.MethodType(sage.graphs.base.static_dense_graph.is_strongly_regular, None, Graph) # From Python modules import sage.graphs.line_graph -Graph.is_line_graph = sage.graphs.line_graph.is_line_graph +Graph.is_line_graph = sage.graphs.line_graph.is_line_graph from sage.graphs.tutte_polynomial import tutte_polynomial -Graph.tutte_polynomial = tutte_polynomial +Graph.tutte_polynomial = tutte_polynomial from sage.graphs.lovasz_theta import lovasz_theta -Graph.lovasz_theta = lovasz_theta +Graph.lovasz_theta = lovasz_theta + +_additional_categories = { + Graph.is_long_hole_free : "Graph properties", + Graph.is_long_antihole_free : "Graph properties", + Graph.is_weakly_chordal : "Graph properties", + Graph.is_asteroidal_triple_free : "Graph properties", + Graph.chromatic_polynomial : "Algorithmically hard stuff", + Graph.rank_decomposition : "Algorithmically hard stuff", + Graph.matching_polynomial : "Algorithmically hard stuff", + Graph.cliques_maximum : "Clique-related methods", + Graph.random_spanning_tree : "Connectivity, orientations, trees", + Graph.is_cartesian_product : "Graph properties", + Graph.is_distance_regular : "Graph properties", + Graph.is_strongly_regular : "Graph properties", + Graph.is_line_graph : "Graph properties", + Graph.tutte_polynomial : "Algorithmically hard stuff", + Graph.lovasz_theta : "Leftovers", + } + +__doc__ = __doc__.replace("{INDEX_OF_METHODS}",gen_thematic_rest_table_index(Graph,_additional_categories)) diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index a28d0c3aa75..477400f23b3 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -202,6 +202,7 @@ def __append_to_doc(methods): "fusenes", "FuzzyBallGraph", "GeneralizedPetersenGraph", + "GoethalsSeidelGraph", "HanoiTowerGraph", "HararyGraph", "HyperStarGraph", @@ -235,6 +236,7 @@ def __append_to_doc(methods): __append_to_doc( ["AffineOrthogonalPolarGraph", + "AhrensSzekeresGeneralizedQuadrangleGraph", "NonisotropicOrthogonalPolarGraph", "NonisotropicUnitaryPolarGraph", "OrthogonalPolarGraph", @@ -242,6 +244,7 @@ def __append_to_doc(methods): "SymplecticPolarGraph", "TaylorTwographDescendantSRG", "TaylorTwographSRG", + "T2starGeneralizedQuadrangleGraph", "UnitaryDualPolarGraph", "UnitaryPolarGraph"]) @@ -1972,6 +1975,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None FriendshipGraph = staticmethod(sage.graphs.generators.families.FriendshipGraph) FuzzyBallGraph = staticmethod(sage.graphs.generators.families.FuzzyBallGraph) GeneralizedPetersenGraph = staticmethod(sage.graphs.generators.families.GeneralizedPetersenGraph) + GoethalsSeidelGraph = staticmethod(sage.graphs.generators.families.GoethalsSeidelGraph) HanoiTowerGraph = staticmethod(sage.graphs.generators.families.HanoiTowerGraph) HararyGraph = staticmethod(sage.graphs.generators.families.HararyGraph) HyperStarGraph = staticmethod(sage.graphs.generators.families.HyperStarGraph) @@ -1997,6 +2001,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None ########################################################################### import sage.graphs.generators.classical_geometries AffineOrthogonalPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.AffineOrthogonalPolarGraph) + AhrensSzekeresGeneralizedQuadrangleGraph = staticmethod(sage.graphs.generators.classical_geometries.AhrensSzekeresGeneralizedQuadrangleGraph) NonisotropicOrthogonalPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.NonisotropicOrthogonalPolarGraph) NonisotropicUnitaryPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.NonisotropicUnitaryPolarGraph) OrthogonalPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.OrthogonalPolarGraph) @@ -2006,6 +2011,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None TaylorTwographDescendantSRG = \ staticmethod(sage.graphs.generators.classical_geometries.TaylorTwographDescendantSRG) TaylorTwographSRG = staticmethod(sage.graphs.generators.classical_geometries.TaylorTwographSRG) + T2starGeneralizedQuadrangleGraph = staticmethod(sage.graphs.generators.classical_geometries.T2starGeneralizedQuadrangleGraph) UnitaryDualPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.UnitaryDualPolarGraph) UnitaryPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.UnitaryPolarGraph) diff --git a/src/sage/graphs/graph_input.py b/src/sage/graphs/graph_input.py new file mode 100644 index 00000000000..3692458a517 --- /dev/null +++ b/src/sage/graphs/graph_input.py @@ -0,0 +1,527 @@ +r""" +Functions for reading/building graphs/digraphs. + +This module gathers functions needed to build a graph from any other data. + +.. NOTE:: + + This is an **internal** module of Sage. All features implemented here are + made available to end-users through the constructors of :class:`Graph` and + :class:`DiGraph`. + +Note that because they are called by the constructors of :class:`Graph` and +:class:`DiGraph`, most of these functions modify a graph inplace. + +{INDEX_OF_FUNCTIONS} + +Functions +--------- + +""" + +def from_graph6(G, g6_string): + r""" + Fill ``G`` with the data of a graph6 string. + + INPUT: + + - ``G`` -- a graph + + - ``g6_string`` -- a graph6 string + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_graph6 + sage: g = Graph() + sage: from_graph6(g, 'IheA@GUAo') + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from generic_graph_pyx import length_and_string_from_graph6, binary_string_from_graph6 + + if not isinstance(g6_string, str): + raise ValueError('If input format is graph6, then g6_string must be a string.') + n = g6_string.find('\n') + if n == -1: + n = len(g6_string) + ss = g6_string[:n] + n, s = length_and_string_from_graph6(ss) + m = binary_string_from_graph6(s, n) + expected = n*(n-1)/2 + (6 - n*(n-1)/2)%6 + if len(m) > expected: + raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n)) + elif len(m) < expected: + raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n)) + G.add_vertices(range(n)) + k = 0 + for i in xrange(n): + for j in xrange(i): + if m[k] == '1': + G._backend.add_edge(i, j, None, False) + k += 1 + +def from_sparse6(G, g6_string): + r""" + Fill ``G`` with the data of a sparse6 string. + + INPUT: + + - ``G`` -- a graph + + - ``g6_string`` -- a sparse6 string + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_sparse6 + sage: g = Graph() + sage: from_sparse6(g, ':I`ES@obGkqegW~') + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from generic_graph_pyx import length_and_string_from_graph6, int_to_binary_string + from math import ceil, floor + from sage.misc.functional import log + n = g6_string.find('\n') + if n == -1: + n = len(g6_string) + s = g6_string[:n] + n, s = length_and_string_from_graph6(s[1:]) + if n == 0: + edges = [] + else: + k = int(ceil(log(n,2))) + ords = [ord(i) for i in s] + if any(o > 126 or o < 63 for o in ords): + raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)])) + bits = ''.join([int_to_binary_string(o-63).zfill(6) for o in ords]) + b = [] + x = [] + for i in xrange(int(floor(len(bits)/(k+1)))): + b.append(int(bits[(k+1)*i:(k+1)*i+1],2)) + x.append(int(bits[(k+1)*i+1:(k+1)*i+k+1],2)) + v = 0 + edges = [] + for i in xrange(len(b)): + if b[i] == 1: + v += 1 + if x[i] > v: + v = x[i] + else: + if v < n: + edges.append((x[i],v)) + G.add_vertices(range(n)) + G.add_edges(edges) + +def from_dig6(G, dig6_string): + r""" + Fill ``G`` with the data of a dig6 string. + + INPUT: + + - ``G`` -- a graph + + - ``dig6_string`` -- a dig6 string + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_dig6 + sage: g = DiGraph() + sage: from_dig6(g, digraphs.Circuit(10).dig6_string()) + sage: g.is_isomorphic(digraphs.Circuit(10)) + True + """ + from generic_graph_pyx import length_and_string_from_graph6, binary_string_from_dig6 + if not isinstance(dig6_string, str): + raise ValueError('If input format is dig6, then dig6_string must be a string.') + n = dig6_string.find('\n') + if n == -1: + n = len(dig6_string) + ss = dig6_string[:n] + n, s = length_and_string_from_graph6(ss) + m = binary_string_from_dig6(s, n) + expected = n**2 + if len(m) > expected: + raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n)) + elif len(m) < expected: + raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n)) + G.add_vertices(range(n)) + k = 0 + for i in xrange(n): + for j in xrange(n): + if m[k] == '1': + G._backend.add_edge(i, j, None, True) + k += 1 + +def from_seidel_adjacency_matrix(G, M): + r""" + Fill ``G`` with the data of a Seidel adjacency matrix. + + INPUT: + + - ``G`` -- a graph + + - ``M`` -- a Seidel adjacency matrix + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_seidel_adjacency_matrix + sage: g = Graph() + sage: from_seidel_adjacency_matrix(g, graphs.PetersenGraph().seidel_adjacency_matrix()) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from sage.matrix.matrix import is_Matrix + from sage.rings.integer_ring import ZZ + assert is_Matrix(M) + + if M.base_ring() != ZZ: + try: + M = M.change_ring(ZZ) + except TypeError: + raise ValueError("Graph's Seidel adjacency matrix must"+ + " have only 0,1,-1 integer entries") + + if M.is_sparse(): + entries = set(M[i,j] for i,j in M.nonzero_positions()) + else: + entries = set(M.list()) + + if any(e < -1 or e > 1 for e in entries): + raise ValueError("Graph's Seidel adjacency matrix must"+ + " have only 0,1,-1 integer entries") + if any(i==j for i,j in M.nonzero_positions()): + raise ValueError("Graph's Seidel adjacency matrix must"+ + " have 0s on the main diagonal") + if not M.is_symmetric(): + raise ValueError("Graph's Seidel adjacency matrix must"+ + " be symmetric") + G.add_vertices(range(M.nrows())) + e = [] + for i,j in M.nonzero_positions(): + if i <= j and M[i,j] < 0: + e.append((i,j)) + G.add_edges(e) + +def from_adjacency_matrix(G, M, loops=False, multiedges=False, weighted=False): + r""" + Fill ``G`` with the data of an adjacency matrix. + + INPUT: + + - ``G`` -- a :class:`Graph` or :class:`DiGraph`. + + - ``M`` -- an adjacency matrix + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_adjacency_matrix + sage: g = Graph() + sage: from_adjacency_matrix(g, graphs.PetersenGraph().adjacency_matrix()) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from sage.matrix.matrix import is_Matrix + from sage.rings.integer_ring import ZZ + assert is_Matrix(M) + # note: the adjacency matrix might be weighted and hence not + # necessarily consists of integers + if not weighted and M.base_ring() != ZZ: + try: + M = M.change_ring(ZZ) + except TypeError: + if weighted is False: + raise ValueError("Non-weighted graph's"+ + " adjacency matrix must have only nonnegative"+ + " integer entries") + weighted = True + + if M.is_sparse(): + entries = set(M[i,j] for i,j in M.nonzero_positions()) + else: + entries = set(M.list()) + + if not weighted and any(e < 0 for e in entries): + if weighted is False: + raise ValueError("Non-weighted digraph's"+ + " adjacency matrix must have only nonnegative"+ + " integer entries") + weighted = True + if multiedges is None: multiedges = False + if weighted is None: + weighted = False + + if multiedges is None: + multiedges = ((not weighted) and any(e != 0 and e != 1 for e in entries)) + + if not loops and any(M[i,i] for i in xrange(M.nrows())): + if loops is False: + raise ValueError("Non-looped digraph's adjacency"+ + " matrix must have zeroes on the diagonal.") + loops = True + if loops is None: + loops = False + G.allow_loops(loops, check=False) + G.allow_multiple_edges(multiedges, check=False) + G.add_vertices(range(M.nrows())) + e = [] + if G.is_directed(): + pairs = M.nonzero_positions() + else: + pairs = ((i,j) for i,j in M.nonzero_positions() if i<=j) + if weighted: + for i,j in pairs: + e.append((i,j,M[i][j])) + elif multiedges: + for i,j in pairs: + e += [(i,j)]*int(M[i][j]) + else: + for i,j in pairs: + e.append((i,j)) + G.add_edges(e) + G._weighted = weighted + +def from_incidence_matrix(G, M, loops=False, multiedges=False, weighted=False): + r""" + Fill ``G`` with the data of an incidence matrix. + + INPUT: + + - ``G`` -- a graph + + - ``M`` -- an incidence matrix + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_incidence_matrix + sage: g = Graph() + sage: from_incidence_matrix(g, graphs.PetersenGraph().incidence_matrix()) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from sage.matrix.matrix import is_Matrix + assert is_Matrix(M) + + oriented = any(M[pos] < 0 for pos in M.nonzero_positions(copy=False)) + + positions = [] + for i in range(M.ncols()): + NZ = M.nonzero_positions_in_column(i) + if len(NZ) == 1: + if oriented: + raise ValueError("Column {} of the (oriented) incidence " + "matrix contains only one nonzero value".format(i)) + elif M[NZ[0],i] != 2: + raise ValueError("Each column of a non-oriented incidence " + "matrix must sum to 2, but column {} does not".format(i)) + if loops is None: + loops = True + positions.append((NZ[0],NZ[0])) + elif len(NZ) != 2 or \ + (oriented and not ((M[NZ[0],i] == +1 and M[NZ[1],i] == -1) or \ + (M[NZ[0],i] == -1 and M[NZ[1],i] == +1))) or \ + (not oriented and (M[NZ[0],i] != 1 or M[NZ[1],i] != 1)): + msg = "There must be one or two nonzero entries per column in an incidence matrix. " + msg += "Got entries {} in column {}".format([M[j,i] for j in NZ], i) + raise ValueError(msg) + else: + positions.append(tuple(NZ)) + + if weighted is None: G._weighted = False + if multiedges is None: + total = len(positions) + multiedges = (len(set(positions)) < total ) + G.allow_loops(False if loops is None else loops, check=False) + G.allow_multiple_edges(multiedges, check=False) + G.add_vertices(range(M.nrows())) + G.add_edges(positions) + +def from_oriented_incidence_matrix(G, M, loops=False, multiedges=False, weighted=False): + r""" + Fill ``G`` with the data of an *oriented* incidence matrix. + + An oriented incidence matrix is the incidence matrix of a directed graph, in + which each non-loop edge corresponds to a `+1` and a `-1`, indicating its + source and destination. + + INPUT: + + - ``G`` -- a :class:`DiGraph` + + - ``M`` -- an incidence matrix + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_oriented_incidence_matrix + sage: g = DiGraph() + sage: from_oriented_incidence_matrix(g, digraphs.Circuit(10).incidence_matrix()) + sage: g.is_isomorphic(digraphs.Circuit(10)) + True + """ + from sage.matrix.matrix import is_Matrix + assert is_Matrix(M) + + positions = [] + for c in M.columns(): + NZ = c.nonzero_positions() + if len(NZ) != 2: + raise ValueError("There must be two nonzero entries (-1 & 1) per column.") + L = sorted(set(c.list())) + if L != [-1,0,1]: + msg += "Each column represents an edge: -1 goes to 1." + raise ValueError(msg) + if c[NZ[0]] == -1: + positions.append(tuple(NZ)) + else: + positions.append((NZ[1],NZ[0])) + if weighted is None: weighted = False + if multiedges is None: + total = len(positions) + multiedges = ( len(set(positions)) < total ) + G.allow_loops(True if loops else False,check=False) + G.allow_multiple_edges(multiedges,check=False) + G.add_vertices(range(M.nrows())) + G.add_edges(positions) + +def from_dict_of_dicts(G, M, loops=False, multiedges=False, weighted=False, convert_empty_dict_labels_to_None=False): + r""" + Fill ``G`` with the data of a dictionary of dictionaries. + + INPUT: + + - ``G`` -- a graph + + - ``M`` -- a dictionary of dictionaries. + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + - ``convert_empty_dict_labels_to_None`` (boolean) -- whether to adjust for + empty dicts instead of None in NetworkX default edge labels. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_dict_of_dicts + sage: g = Graph() + sage: from_dict_of_dicts(g, graphs.PetersenGraph().to_dictionary(edge_labels=True)) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + if not all(isinstance(M[u], dict) for u in M): + raise ValueError("Input dict must be a consistent format.") + + if not loops and any(u in neighb for u,neighb in M.iteritems()): + if loops is False: + u = next(u for u,neighb in M.iteritems() if u in neighb) + raise ValueError("The graph was built with loops=False but input M has a loop at {}.".format(u)) + loops = True + if loops is None: + loops = False + + if weighted is None: G._weighted = False + for u in M: + for v in M[u]: + if multiedges is not False and not isinstance(M[u][v], list): + if multiedges is None: multiedges = False + if multiedges: + raise ValueError("Dict of dicts for multigraph must be in the format {v : {u : list}}") + if multiedges is None and len(M) > 0: + multiedges = True + + G.allow_loops(loops, check=False) + G.allow_multiple_edges(multiedges, check=False) + verts = set().union(M.keys(), *M.values()) + G.add_vertices(verts) + if convert_empty_dict_labels_to_None: + relabel = lambda x : x if x!={} else None + else: + relabel = lambda x : x + + is_directed = G.is_directed() + if not is_directed and multiedges: + v_to_id = {v:i for i,v in enumerate(verts)} + for u in M: + for v in M[u]: + if v_to_id[u] <= v_to_id[v] or v not in M or u not in M[v] or u == v: + for l in M[u][v]: + G._backend.add_edge(u,v,relabel(l),False) + elif multiedges: + for u in M: + for v in M[u]: + for l in M[u][v]: + G._backend.add_edge(u,v,relabel(l),is_directed) + else: + for u in M: + for v in M[u]: + G._backend.add_edge(u,v,relabel(M[u][v]),is_directed) + +def from_dict_of_lists(G, D, loops=False, multiedges=False, weighted=False): + r""" + Fill ``G`` with the data of a dictionary of lists. + + INPUT: + + - ``G`` -- a :class:`Graph` or :class:`DiGraph`. + + - ``D`` -- a dictionary of lists. + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_dict_of_lists + sage: g = Graph() + sage: from_dict_of_lists(g, graphs.PetersenGraph().to_dictionary()) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + verts = set().union(D.keys(),*D.values()) + if loops is None or loops is False: + for u in D: + if u in D[u]: + if loops is None: + loops = True + elif loops is False: + u = next(u for u,neighb in D.iteritems() if u in neighb) + raise ValueError("The graph was built with loops=False but input D has a loop at {}.".format(u)) + break + if loops is None: + loops = False + if weighted is None: G._weighted = False + for u in D: + if len(set(D[u])) != len(D[u]): + if multiedges is False: + v = next((v for v in D[u] if D[u].count(v) > 1)) + raise ValueError("Non-multigraph got several edges (%s,%s)"%(u,v)) + if multiedges is None: + multiedges = True + if multiedges is None: multiedges = False + G.allow_loops(loops, check=False) + G.allow_multiple_edges(multiedges, check=False) + G.add_vertices(verts) + + is_directed = G.is_directed() + if not is_directed and multiedges: + v_to_id = {v:i for i,v in enumerate(verts)} + for u in D: + for v in D[u]: + if (v_to_id[u] <= v_to_id[v] or + v not in D or u not in D[v] or u == v): + G._backend.add_edge(u,v,None,False) + else: + for u in D: + for v in D[u]: + G._backend.add_edge(u,v,None,is_directed) + +from sage.misc.rest_index_of_methods import gen_rest_table_index +import sys +__doc__ = __doc__.format(INDEX_OF_FUNCTIONS=gen_rest_table_index(sys.modules[__name__])) diff --git a/src/sage/graphs/hyperbolicity.pyx b/src/sage/graphs/hyperbolicity.pyx index bf8a8a2c8b4..07d3b4703f4 100644 --- a/src/sage/graphs/hyperbolicity.pyx +++ b/src/sage/graphs/hyperbolicity.pyx @@ -208,7 +208,7 @@ def _my_subgraph(G, vertices, relabel=False, return_map=False): etc.). If ``relabel`` is ``True``, the vertices of the new graph are relabeled - with integers in the range '0\cdots |vertices|-1'. The relabeling map is + with integers in the range '0\cdots \mid vertices \mid -1'. The relabeling map is returned if ``return_map`` is also ``True``. TESTS: diff --git a/src/sage/graphs/hypergraph_generators.py b/src/sage/graphs/hypergraph_generators.py index 0deb4cbafdd..7dfb9473d99 100644 --- a/src/sage/graphs/hypergraph_generators.py +++ b/src/sage/graphs/hypergraph_generators.py @@ -159,4 +159,23 @@ def nauty(self, number_of_sets, number_of_vertices, yield tuple( tuple( x for x in G.neighbors(v)) for v in range(number_of_vertices, total)) + def CompleteUniform(self, n, k): + r""" + Return the complete `k`-uniform hypergraph on `n` points. + + INPUT: + + - ``k,n`` -- nonnegative integers with `k\leq n` + + EXAMPLE:: + + sage: h = hypergraphs.CompleteUniform(5,2); h + Incidence structure with 5 points and 10 blocks + sage: len(h.packing()) + 2 + """ + from sage.combinat.designs.incidence_structures import IncidenceStructure + from itertools import combinations + return IncidenceStructure(list(combinations(range(n),k))) + hypergraphs = HypergraphGenerators() diff --git a/src/sage/graphs/independent_sets.pyx b/src/sage/graphs/independent_sets.pyx index cbb99409f55..e91e0609dba 100644 --- a/src/sage/graphs/independent_sets.pyx +++ b/src/sage/graphs/independent_sets.pyx @@ -338,7 +338,7 @@ cdef class IndependentSets: - ``S`` -- a set of vertices to be tested. - TESTS:: + TESTS: All independent sets of PetersenGraph are... independent sets:: diff --git a/src/sage/graphs/schnyder.py b/src/sage/graphs/schnyder.py index c4b4b8398fc..6af206e9581 100644 --- a/src/sage/graphs/schnyder.py +++ b/src/sage/graphs/schnyder.py @@ -6,12 +6,14 @@ Walter Schnyder's Algorithm. AUTHORS: - -- Jonathan Bober, Emily Kirkman (2008-02-09): initial version + +- Jonathan Bober, Emily Kirkman (2008-02-09) -- initial version REFERENCE: - [1] Schnyder, Walter. Embedding Planar Graphs on the Grid. - Proc. 1st Annual ACM-SIAM Symposium on Discrete Algorithms, - San Francisco (1994), pp. 138-147. + +.. [1] Schnyder, Walter. Embedding Planar Graphs on the Grid. + Proc. 1st Annual ACM-SIAM Symposium on Discrete Algorithms, + San Francisco (1994), pp. 138-147. """ #***************************************************************************** # Copyright (C) 2008 Jonathan Bober and Emily Kirkman @@ -42,11 +44,13 @@ def _triangulate(g, comb_emb): method will work on one of these attempts.) INPUT: - g -- the graph to triangulate - comb_emb -- a planar combinatorial embedding of g - RETURNS: - A list of edges that are added to the graph (in place) + - g -- the graph to triangulate + - ``comb_emb`` -- a planar combinatorial embedding of g + + OUTPUT: + + A list of edges that are added to the graph (in place) EXAMPLES:: @@ -125,27 +129,33 @@ def _triangulate(g, comb_emb): return edges_added def _normal_label(g, comb_emb, external_face): - """ - Helper function to schnyder method for computing coordinates in the plane to - plot a planar graph with no edge crossings. + r""" + Helper function to schnyder method for computing coordinates in + the plane to plot a planar graph with no edge crossings. - Constructs a normal labelling of a triangular graph g, given the planar - combinatorial embedding of g and a designated external face. Returns labels - dictionary. The normal label is constructed by first contracting the graph - down to its external face, then expanding the graph back to the original while - simultaneously adding angle labels. + Constructs a normal labelling of a triangular graph g, given the + planar combinatorial embedding of g and a designated external + face. Returns labels dictionary. The normal label is constructed + by first contracting the graph down to its external face, then + expanding the graph back to the original while simultaneously + adding angle labels. INPUT: - g -- the graph to find the normal labeling of (g must be triangulated) - comb_emb -- a planar combinatorial embedding of g - external_face -- the list of three edges in the external face of g - RETURNS: - x -- tuple with entries - x[0] = dict of dicts of normal labeling for each vertex of g and each - adjacent neighbors u,v (u < v) of vertex: - { vertex : { (u,v): angel_label } } - x[1] = (v1,v2,v3) tuple of the three vertices of the external face. + - g -- the graph to find the normal labeling of (g must be triangulated) + - ``comb_emb`` -- a planar combinatorial embedding of g + - ``external_face`` -- the list of three edges in the external face of g + + OUTPUT: + + x -- tuple with entries + + x[0] = dict of dicts of normal labeling for each vertex of g and each + adjacent neighbors u,v (u < v) of vertex: + + { vertex : { (u,v): angel_label } } + + x[1] = (v1,v2,v3) tuple of the three vertices of the external face. EXAMPLES:: @@ -160,7 +170,6 @@ def _normal_label(g, comb_emb, external_face): sage: _realizer(g, tn) ({0: []}, (0, 1, 2)) - """ contracted = [] contractible = [] @@ -329,20 +338,27 @@ def _realizer(g, x, example=False): give a path to each of the three external vertices. INPUT: - g -- the graph to compute the realizer of - x -- tuple with entries - x[0] = dict of dicts representing a normal labeling of g. For - each vertex of g and each adjacent neighbors u,v (u < v) of - vertex: { vertex : { (u,v): angle_label } } - x[1] = (v1, v2, v3) tuple of the three external vertices (also - the roots of each tree) - - RETURNS: - x -- tuple with entries - x[0] = dict of lists of TreeNodes: - { root_vertex : [ list of all TreeNodes under root_vertex ] } - x[0] = (v1,v2,v3) tuple of the three external vertices (also the - roots of each tree) + + - g -- the graph to compute the realizer of + - x -- tuple with entries + + x[0] = dict of dicts representing a normal labeling of g. For + each vertex of g and each adjacent neighbors u,v (u < v) of + vertex: { vertex : { (u,v): angle_label } } + + x[1] = (v1, v2, v3) tuple of the three external vertices (also + the roots of each tree) + + OUTPUT: + + - x -- tuple with entries + + x[0] = dict of lists of TreeNodes: + + { root_vertex : [ list of all TreeNodes under root_vertex ] } + + x[1] = (v1,v2,v3) tuple of the three external vertices (also the + roots of each tree) EXAMPLES:: @@ -409,22 +425,26 @@ def _realizer(g, x, example=False): return tree_nodes, (v1,v2,v3) def _compute_coordinates(g, x): - """ + r""" Given a triangulated graph g with a dict of trees given by the realizer and tuple of the external vertices, we compute the coordinates of a planar geometric embedding in the grid. - The coordinates will be set to the _pos attribute of g. + The coordinates will be set to the ``_pos`` attribute of g. INPUT: - g -- the graph to compute the coordinates of - x -- tuple with entries - x[0] = dict of tree nodes for the three trees with each external - vertex as root - { root_vertex : [ list of all TreeNodes under root_vertex ] } - - x[1] = (v1, v2, v3) tuple of the three external vertices (also - the roots of each tree) + + - g -- the graph to compute the coordinates of + - x -- tuple with entries + + x[0] = dict of tree nodes for the three trees with each external + vertex as root: + + { root_vertex : [ list of all TreeNodes under root_vertex ] } + + x[1] = (v1, v2, v3) tuple of the three external vertices (also + the roots of each tree) + EXAMPLES:: sage: from sage.graphs.schnyder import _triangulate, _normal_label, _realizer, _compute_coordinates @@ -505,16 +525,17 @@ def _compute_coordinates(g, x): class TreeNode(): """ - A class to represent each node in the trees used by _realizer() and - _compute_coordinates() when finding a planar geometric embedding in + A class to represent each node in the trees used by :func:`_realizer` and + :func:`_compute_coordinates` when finding a planar geometric embedding in the grid. Each tree node is doubly linked to its parent and children. INPUT: - parent -- the parent TreeNode of self - children -- a list of TreeNode children of self - label -- the associated realizer vertex label + + - ``parent`` -- the parent TreeNode of ``self`` + - ``children`` -- a list of TreeNode children of ``self`` + - ``label`` -- the associated realizer vertex label EXAMPLES:: @@ -532,14 +553,14 @@ class TreeNode(): sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 - """ def __init__(self, parent = None, children = None, label = None): """ INPUT: - parent -- the parent TreeNode of self - children -- a list of TreeNode children of self - label -- the associated realizer vertex label + + - ``parent`` -- the parent TreeNode of ``self`` + - ``children`` -- a list of TreeNode children of ``self`` + - ``label`` -- the associated realizer vertex label EXAMPLE:: @@ -557,7 +578,6 @@ def __init__(self, parent = None, children = None, label = None): sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 - """ if children is None: children = [] @@ -566,10 +586,10 @@ def __init__(self, parent = None, children = None, label = None): self.label = label self.number_of_descendants = 1 - def compute_number_of_descendants(self): """ Computes the number of descendants of self and all descendants. + For each TreeNode, sets result as attribute self.number_of_descendants EXAMPLES:: @@ -599,6 +619,7 @@ def compute_number_of_descendants(self): def compute_depth_of_self_and_children(self): """ Computes the depth of self and all descendants. + For each TreeNode, sets result as attribute self.depth EXAMPLES:: @@ -617,7 +638,6 @@ def compute_depth_of_self_and_children(self): sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 - """ if self.parent is None: self.depth = 1 @@ -646,7 +666,6 @@ def append_child(self, child): sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 - """ if child in self.children: return diff --git a/src/sage/graphs/strongly_regular_db.pyx b/src/sage/graphs/strongly_regular_db.pyx index cfefb513375..7ae6e8a21e2 100644 --- a/src/sage/graphs/strongly_regular_db.pyx +++ b/src/sage/graphs/strongly_regular_db.pyx @@ -46,7 +46,7 @@ from sage.graphs.generators.smallgraphs import HigmanSimsGraph from sage.graphs.generators.smallgraphs import LocalMcLaughlinGraph from sage.graphs.generators.smallgraphs import SuzukiGraph from sage.graphs.graph import Graph -from libc.math cimport sqrt +from libc.math cimport sqrt, floor from sage.matrix.constructor import Matrix from sage.rings.finite_rings.constructor import FiniteField as GF from sage.coding.linear_code import LinearCode @@ -340,13 +340,90 @@ def is_orthogonal_polar(int v,int k,int l,int mu): from sage.graphs.generators.classical_geometries import OrthogonalPolarGraph return (OrthogonalPolarGraph, 2*m, q, "-") +@cached_function +def is_goethals_seidel(int v,int k,int l,int mu): + r""" + Test whether some + :func:`~sage.graphs.graph_generators.GraphGenerators.GoethalsSeidelGraph` graph is + `(v,k,\lambda,\mu)`-strongly regular. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_goethals_seidel + sage: t = is_goethals_seidel(28, 15, 6, 10); t + [, 3, 3] + sage: g = t[0](*t[1:]); g + Graph on 28 vertices + sage: g.is_strongly_regular(parameters=True) + (28, 15, 6, 10) + + sage: t = is_goethals_seidel(256, 135, 70, 72); t + [, 2, 15] + sage: g = t[0](*t[1:]); g + Graph on 256 vertices + sage: g.is_strongly_regular(parameters=True) + (256, 135, 70, 72) + + sage: t = is_goethals_seidel(5,5,5,5); t + + TESTS:: + + sage: for p in [(16, 9, 4, 6), (28, 15, 6, 10), (64, 35, 18, 20), (120, 63, 30, 36), + ....: (144, 77, 40, 42), (256, 135, 70, 72), (400, 209, 108, 110), + ....: (496, 255, 126, 136), (540, 275, 130, 150), (576, 299, 154, 156), + ....: (780, 399, 198, 210), (784, 405, 208, 210), (976, 495, 238, 264)]: + ....: print is_goethals_seidel(*p) + [, 2, 3] + [, 3, 3] + [, 2, 7] + [, 3, 7] + [, 2, 11] + [, 2, 15] + [, 2, 19] + [, 3, 15] + [, 5, 11] + [, 2, 23] + [, 3, 19] + [, 2, 27] + [, 5, 15] + """ + from sage.combinat.designs.bibd import balanced_incomplete_block_design + from sage.combinat.matrices.hadamard_matrix import hadamard_matrix + + # here we guess the parameters v_bibd,k_bibd and r_bibd of the block design + # + # - the number of vertices v is equal to v_bibd*(r_bibd+1) + # - the degree k of the graph is equal to k=(v+r_bibd-1)/2 + + r_bibd = k - (v-1-k) + v_bibd = v//(r_bibd+1) + k_bibd = (v_bibd-1)/r_bibd + 1 if r_bibd>0 else -1 + + if (v == v_bibd*(r_bibd+1) and + 2*k == v+r_bibd-1 and + 4*l == -2*v + 6*k -v_bibd -k_bibd and + hadamard_matrix(r_bibd+1, existence=True) and + balanced_incomplete_block_design(v_bibd, k_bibd, existence = True)): + from sage.graphs.generators.families import GoethalsSeidelGraph + return [GoethalsSeidelGraph, k_bibd, r_bibd] + @cached_function def is_NOodd(int v,int k,int l,int mu): r""" Test whether some NO^e(2n+1,q) graph is `(v,k,\lambda,\mu)`-strongly regular. - Here `q>2`, for in the case `q=2` this graph is complete. For more information, see - :func:`sage.graphs.generators.classical_geometries.NonisotropicOrthogonalPolarGraph` + Here `q>2`, for in the case `q=2` this graph is complete. For more + information, see + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicOrthogonalPolarGraph` and Sect. 7.C of [BvL84]_. INPUT: @@ -415,7 +492,7 @@ def is_NOperp_F5(int v,int k,int l,int mu): Test whether some NO^e,perp(2n+1,5) graph is `(v,k,\lambda,\mu)`-strongly regular. For more information, see - :func:`sage.graphs.generators.classical_geometries.NonisotropicOrthogonalPolarGraph` + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicOrthogonalPolarGraph` and Sect. 7.D of [BvL84]_. INPUT: @@ -470,8 +547,7 @@ def is_NO_F2(int v,int k,int l,int mu): Test whether some NO^e,perp(2n,2) graph is `(v,k,\lambda,\mu)`-strongly regular. For more information, see - :func:`sage.graphs.generators.classical_geometries.NonisotropicOrthogonalPolarGraph` - and + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicOrthogonalPolarGraph`. INPUT: @@ -521,8 +597,7 @@ def is_NO_F3(int v,int k,int l,int mu): Test whether some NO^e,perp(2n,3) graph is `(v,k,\lambda,\mu)`-strongly regular. For more information, see - :func:`sage.graphs.generators.classical_geometries.NonisotropicOrthogonalPolarGraph` - and + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicOrthogonalPolarGraph`. INPUT: @@ -576,7 +651,7 @@ def is_NU(int v,int k,int l,int mu): Test whether some NU(n,q)-graph, is `(v,k,\lambda,\mu)`-strongly regular. Note that n>2; for n=2 there is no s.r.g. For more information, see - :func:`sage.graphs.generators.classical_geometries.NonisotropicUnitaryPolarGraph` + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicUnitaryPolarGraph` and series C14 in [Hu75]_. INPUT: @@ -637,6 +712,170 @@ def is_NU(int v,int k,int l,int mu): from sage.graphs.generators.classical_geometries import NonisotropicUnitaryPolarGraph return (NonisotropicUnitaryPolarGraph, n, q) +@cached_function +def is_polhill(int v,int k,int l,int mu): + r""" + Test whether some graph from [Polhill09]_ is `(1024,k,\lambda,\mu)`-strongly regular. + + .. NOTE:: + + This function does not actually explore *all* strongly regular graphs + produced in [Polhill09]_, but only those on 1024 vertices. + + John Polhill offered his help if we attempt to write a code to guess, + given `(v,k,\lambda,\mu)`, which of his construction must be applied to + find the graph. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if the + parameters match, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_polhill + sage: t = is_polhill(1024, 231, 38, 56); t + [. at ...>] + sage: g = t[0](*t[1:]); g # not tested (too long) + Graph on 1024 vertices + sage: g.is_strongly_regular(parameters=True) # not tested (too long) + (1024, 231, 38, 56) + sage: t = is_polhill(1024, 264, 56, 72); t + [. at ...>] + sage: t = is_polhill(1024, 297, 76, 90); t + [. at ...>] + sage: t = is_polhill(1024, 330, 98, 110); t + [. at ...>] + sage: t = is_polhill(1024, 462, 206, 210); t + [. at ...>] + + REFERENCE: + + .. [Polhill09] J. Polhill, + Negative Latin square type partial difference sets and + amorphic association schemes with Galois rings, + Journal of Combinatorial Designs 17, no. 3 (2009): 266-282. + http://onlinelibrary.wiley.com/doi/10.1002/jcd.20206/abstract + """ + if (v,k,l,mu) not in [(1024, 231, 38, 56), + (1024, 264, 56, 72), + (1024, 297, 76, 90), + (1024, 330, 98, 110), + (1024, 462, 206, 210)]: + return + + from itertools import product + from sage.categories.cartesian_product import cartesian_product + from sage.rings.finite_rings.integer_mod_ring import IntegerModRing + from copy import copy + + def additive_cayley(vertices): + g = Graph() + g.add_vertices(vertices[0].parent()) + edges = [(x,x+vv) + for vv in set(vertices) + for x in g] + g.add_edges(edges) + g.relabel() + return g + + # D is a Partial Difference Set of (Z4)^2, see section 2. + G = cartesian_product([IntegerModRing(4),IntegerModRing(4)]) + D = [ + [(2,0),(0,1),(0,3),(1,1),(3,3)], + [(1,0),(3,0),(0,2),(1,3),(3,1)], + [(1,2),(3,2),(2,1),(2,3),(2,2)] + ] + D = [map(G,x) for x in D] + + # The K_i are hyperplanes partitionning the nonzero elements of + # GF(2^s)^2. See section 6. + s = 3 + G1 = GF(2**s,'x') + Gp = cartesian_product([G1,G1]) + K = [Gp((x,1)) for x in G1]+[Gp((1,0))] + K = [[x for x in Gp if x[0]*uu+x[1]*vv == 0] for (uu,vv) in K] + + # We now define the P_{i,j}. see section 6. + + P = {} + P[0,1] = range((-1) + 1 , 2**(s-2)+1) + P[1,1] = range((-1) + 2**(s-2)+2 , 2**(s-1)+1) + P[2,1] = range((-1) + 2**(s-1)+2 , 2**(s-1)+2**(s-2)+1) + P[3,1] = range((-1) + 2**(s-1)+2**(s-2)+2, 2**(s)+1) + + P[0,2] = range((-1) + 2**(s-2)+2 , 2**(s-1)+2) + P[1,2] = range((-1) + 2**(s-1)+3 , 2**(s-1)+2**(s-2)+2) + P[2,2] = range((-1) + 2**(s-1)+2**(s-2)+3, 2**(s)+1) + [0] + P[3,2] = range((-1) + 2 , 2**(s-2)+1) + + P[0,3] = range((-1) + 2**(s-1)+3 , 2**(s-1)+2**(s-2)+3) + P[1,3] = range((-1) + 2**(s-1)+2**(s-2)+4, 2**(s)+1) + [0,1] + P[2,3] = range((-1) + 3 , 2**(s-2)+2) + P[3,3] = range((-1) + 2**(s-2)+3 , 2**(s-1)+2) + + P[0,4] = range((-1) + 2**(s-1)+2**(s-2)+4, 2**(s)+1) + P[1,4] = range((-1) + 3 , 2**(s-2)+1) + [2**(s-1)+1,2**(s-1)+2**(s-2)+2] + P[2,4] = range((-1) + 2**(s-2)+3 , 2**(s-1)+1) + [2**(s-1)+2**(s-2)+1,1] + P[3,4] = range((-1) + 2**(s-1)+3 , 2**(s-1)+2**(s-2)+1) + [2**(s-2)+1,0] + + R = {x:copy(P[x]) for x in P} + + for x in P: + P[x] = [K[i] for i in P[x]] + P[x] = set(sum(P[x],[])).difference([Gp((0,0))]) + + P[1,4].add(Gp((0,0))) + P[2,4].add(Gp((0,0))) + P[3,4].add(Gp((0,0))) + + # We now define the R_{i,j}. see *end* of section 6. + + R[0,3] = range((-1) + 2**(s-1)+3 , 2**(s-1)+2**(s-2)+2) + R[1,3] = range((-1) + 2**(s-1)+2**(s-2)+4, 2**(s)+1) + [0,1,2**(s-1)+2**(s-2)+2] + R[0,4] = range((-1) + 2**(s-1)+2**(s-2)+4, 2**(s)+1) + [2**(s-1)+2**(s-2)+2] + R[1,4] = range((-1) + 3 , 2**(s-2)+1) + [2**(s-1)+1] + + for x in R: + R[x] = [K[i] for i in R[x]] + R[x] = set(sum(R[x],[])).difference([Gp((0,0))]) + + R[1,3].add(Gp((0,0))) + R[2,4].add(Gp((0,0))) + R[3,4].add(Gp((0,0))) + + # Dabcd = Da, Db, Dc, Dd (cf. p273) + # D1234 = D1, D2, D3, D4 (cf. p276) + Dabcd = [] + D1234 = [] + + Gprod = cartesian_product([G,Gp]) + for DD,PQ in [(Dabcd,P), (D1234,R)]: + for i in range(1,5): + Dtmp = [product([G.zero()],PQ[0,i]), + product(D[0],PQ[1,i]), + product(D[1],PQ[2,i]), + product(D[2],PQ[3,i])] + Dtmp = map(set,Dtmp) + Dtmp = map(Gprod,sum(map(list,Dtmp),[])) + DD.append(Dtmp) + + # Now that we have the data, we can return the graphs. + if k == 231: + return [lambda :additive_cayley(Dabcd[0])] + if k == 264: + return [lambda :additive_cayley(D1234[2])] + if k == 297: + return [lambda :additive_cayley(D1234[0]+D1234[1]+D1234[2]).complement()] + if k == 330: + return [lambda :additive_cayley(Dabcd[0]+Dabcd[1]+Dabcd[2]).complement()] + if k == 462: + return [lambda :additive_cayley(Dabcd[0]+Dabcd[1])] + def is_RSHCD(int v,int k,int l,int mu): r""" Test whether some RSHCD graph is `(v,k,\lambda,\mu)`-strongly regular. @@ -858,6 +1097,83 @@ def is_unitary_dual_polar(int v,int k,int l,int mu): from sage.graphs.generators.classical_geometries import UnitaryDualPolarGraph return (UnitaryDualPolarGraph, 5, p**t) +@cached_function +def is_GQqmqp(int v,int k,int l,int mu): + r""" + Test whether some `GQ(q-1,q+1)` or `GQ(q+1,q-1)`-graph is `(v,k,\lambda,\mu)`-srg. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_GQqmqp + sage: t = is_GQqmqp(27,10,1,5); t + (, 3, False) + sage: g = t[0](*t[1:]); g + AS(3); GQ(2, 4): Graph on 27 vertices + sage: t = is_GQqmqp(45,12,3,3); t + (, 3, True) + sage: g = t[0](*t[1:]); g + AS(3)*; GQ(4, 2): Graph on 45 vertices + sage: g.is_strongly_regular(parameters=True) + (45, 12, 3, 3) + sage: t = is_GQqmqp(16,6,2,2); t + (, 2, True) + sage: g = t[0](*t[1:]); g + T2*(O,2)*; GQ(3, 1): Graph on 16 vertices + sage: g.is_strongly_regular(parameters=True) + (16, 6, 2, 2) + sage: t = is_GQqmqp(64,18,2,6); t + (, 4, False) + sage: g = t[0](*t[1:]); g + T2*(O,4); GQ(3, 5): Graph on 64 vertices + sage: g.is_strongly_regular(parameters=True) + (64, 18, 2, 6) + + TESTS:: + + sage: (S,T)=(127,129) + sage: t = is_GQqmqp((S+1)*(S*T+1), S*(T+1), S-1, T+1); t + (, 128, False) + sage: (S,T)=(129,127) + sage: t = is_GQqmqp((S+1)*(S*T+1), S*(T+1), S-1, T+1); t + (, 128, True) + sage: (S,T)=(124,126) + sage: t = is_GQqmqp((S+1)*(S*T+1), S*(T+1), S-1, T+1); t + (, 125, False) + sage: (S,T)=(126,124) + sage: t = is_GQqmqp((S+1)*(S*T+1), S*(T+1), S-1, T+1); t + (, 125, True) + sage: t = is_GQqmqp(5,5,5,5); t + """ + # do we have GQ(s,t)? we must have mu=t+1, s=l+1, + # v=(s+1)(st+1), k=s(t+1) + S=l+1 + T=mu-1 + q = (S+T)//2 + p, w = is_prime_power(q, get_data=True) + if (v == (S+1)*(S*T+1) and + k == S*(T+1) and + q == p**w and + (S+T)/2 == q): + if p % 2 == 0: + from sage.graphs.generators.classical_geometries\ + import T2starGeneralizedQuadrangleGraph as F + else: + from sage.graphs.generators.classical_geometries\ + import AhrensSzekeresGeneralizedQuadrangleGraph as F + if (S,T) == (q-1, q+1): + return (F, q, False) + elif (S,T) == (q+1, q-1): + return (F, q, True) + @cached_function def is_twograph_descendant_of_srg(int v, int k0, int l, int mu): r""" @@ -934,8 +1250,9 @@ def is_taylor_twograph_srg(int v,int k,int l,int mu): OUTPUT: A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph - :func:`TaylorTwographSRG ` - if the parameters match, and ``None`` otherwise. + :func:`TaylorTwographSRG + ` if the + parameters match, and ``None`` otherwise. EXAMPLES:: @@ -966,6 +1283,72 @@ def is_taylor_twograph_srg(int v,int k,int l,int mu): return (TaylorTwographSRG, q) return +def is_switch_OA_srg(int v, int k, int l, int mu): + r""" + Test whether some *switch* `OA(k,n)+*` is `(v,k,\lambda,\mu)`-strongly regular. + + The "switch* `OA(k,n)+*` graphs appear on `Andries Brouwer's database + `__ and are built by + adding an isolated vertex to a + :meth:`~sage.graphs.graph_generators.GraphGenerators.OrthogonalArrayBlockGraph`, + and a :meth:`Seidel switching ` a set of disjoint + `n`-cocliques. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if the + parameters match, and ``None`` otherwise. + + EXAMPLES:: + + sage: graphs.strongly_regular_graph(170, 78, 35, 36) # indirect doctest + Graph on 170 vertices + + TESTS:: + + sage: from sage.graphs.strongly_regular_db import is_switch_OA_srg + sage: t = is_switch_OA_srg(5,5,5,5); t + sage: t = is_switch_OA_srg(170, 78, 35, 36); + sage: t[0](*t[1:]).is_strongly_regular(parameters=True) + (170, 78, 35, 36) + sage: t = is_switch_OA_srg(290, 136, 63, 64); + sage: t[0](*t[1:]).is_strongly_regular(parameters=True) + (290, 136, 63, 64) + sage: is_switch_OA_srg(626, 300, 143, 144) + (.switch_OA_srg at ..., 12, 25) + sage: is_switch_OA_srg(842, 406, 195, 196) + (.switch_OA_srg at ..., 14, 29) + + """ + from sage.combinat.designs.orthogonal_arrays import orthogonal_array + + cdef int n_2_p_1 = v + cdef int n = floor(sqrt(n_2_p_1-1)) + + if n*n != n_2_p_1-1: # is it a square? + return None + + cdef int c = k/n + if (k % n or + l != c*c-1 or + k != 1+(c-1)*(c+1)+(n-c)*(n-c-1) or + not orthogonal_array(c+1,n,existence=True,resolvable=True)): + return None + + def switch_OA_srg(c,n): + from itertools import izip + OA = map(tuple,orthogonal_array(c+1,n,resolvable=True)) + g = Graph([OA,lambda x,y: any(xx==yy for xx,yy in izip(x,y))],loops=False) + g.add_vertex(0) + g.seidel_switching(OA[:c*n]) + return g + + return (switch_OA_srg,c,n) + cdef eigenvalues(int v,int k,int l,int mu): r""" Return the eigenvalues of a (v,k,l,mu)-strongly regular graph. @@ -1218,6 +1601,59 @@ def SRG_176_105_68_54(): H = IncidenceStructure([x for x in W if 22 not in x]) return H.intersection_graph(3) +def SRG_210_99_48_45(): + r""" + Return a strongly regular graph with parameters `(210, 99, 48, 45)` + + This graph is from Example 4.2 in [KPRWZ10]_. One considers the action of + the symmetric group `S_7` on the 210 digraphs isomorphic to the + disjoint union of `K_1` and the circulant 6-vertex digraph + ``digraphs.Circulant(6,[1,4])``. It has 16 orbitals; the package [COCO]_ + found a megring of them, explicitly described in [KPRWZ10]_, resulting in + this graph. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_210_99_48_45 + sage: g=SRG_210_99_48_45() + sage: g.is_strongly_regular(parameters=True) + (210, 99, 48, 45) + + REFERENCES: + + .. [KPRWZ10] M. H. Klin, C. Pech, S. Reichard, A. Woldar, M. Zvi-Av, + Examples of computer experimentation in algebraic combinatorics, + ARS MATHEMATICA CONTEMPORANEA 3 (2010) 237–258 + http://amc-journal.eu/index.php/amc/article/viewFile/119/118 + + .. [COCO] I. A. Faradjev and M. H. Klin, + Computer package for computations with coherent configurations, + Proc. ISSAC-91, ACM Press, Bonn, 1991, pages 219–223; + code, by I.A.Faradjev (with contributions by A.E.Brouwer, D.V.Pasechnik) + https://github.com/dimpase/coco + + """ + from sage.libs.gap.libgap import libgap + from sage.combinat.permutation import Permutation + def ekg(g0): # return arcs of the Cayley digraph of on {g,g^4} + g = Permutation(g0) + return libgap.Set(map(lambda x: (x,g(x)), range(1,8))\ + + map(lambda x: (x,g(g(g(g(x))))), range(1,8))) + + kd=map(ekg, + [(7, 1, 2, 3, 4, 5), (7, 1, 3, 4, 5, 6), + (7, 3, 4, 5, 6, 2), (7, 1, 4, 3, 5, 6), + (7, 3, 1, 4, 5, 6), (7, 2, 4, 3, 5, 6), + (7, 3, 2, 4, 5, 1), (7, 2, 4, 3, 5, 1)]) + s=libgap.SymmetricGroup(7) + O=s.Orbit(kd[0],libgap.OnSetsTuples) + sa=s.Action(O,libgap.OnSetsTuples) + G=Graph() + for g in kd[1:]: + G.add_edges(libgap.Orbit(sa,[libgap.Position(O,kd[0]),\ + libgap.Position(O,g)],libgap.OnSets)) + return G + def SRG_243_110_37_60(): r""" Return a `(243, 110, 37, 60)`-strongly regular graph. @@ -1367,7 +1803,7 @@ def SRG_276_140_58_84(): def SRG_280_135_70_60(): r""" - Return a strongly regular graph with parameters (280, 135, 70, 60). + Return a strongly regular graph with parameters `(280, 135, 70, 60)`. This graph is built from the action of `J_2` on a `3.PGL(2,9)` subgroup it contains. @@ -1393,6 +1829,49 @@ def SRG_280_135_70_60(): g.relabel() return g +def SRG_280_117_44_52(): + r""" + Return a strongly regular graph with parameters `(280, 117, 44, 52)`. + + This graph is built according to a very pretty construction of Mathon and + Rosa [MR85]_: + + The vertices of the graph `G` are all partitions of a set of 9 elements + into `\{\{a,b,c\},\{d,e,f\},\{g,h,i\}\}`. The cross-intersection of two + such partitions `P=\{P_1,P_2,P_3\}` and `P'=\{P'_1,P'_2,P'_3\}` being + defined as `\{P_i \cap P'_j: 1\leq i,j\leq 3\}`, two vertices of `G` are + set to be adjacent if the cross-intersection of their respective + partitions does not contain exactly 7 nonempty sets. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_280_117_44_52 + sage: g=SRG_280_117_44_52() + sage: g.is_strongly_regular(parameters=True) + (280, 117, 44, 52) + + REFERENCE: + + .. [MR85] R. Mathon and A. Rosa, + A new strongly regular graph, + Journal of Combinatorial Theory, Series A 38, no. 1 (1985): 84-86. + http://dx.doi.org/10.1016/0097-3165(85)90025-1 + """ + from sage.graphs.hypergraph_generators import hypergraphs + + # V is the set of partions {{a,b,c},{d,e,f},{g,h,i}} of {0,...,8} + H = hypergraphs.CompleteUniform(9,3) + g = H.intersection_graph() + V = g.complement().cliques_maximal() + V = map(frozenset,V) + + # G is the graph defined on V in which two vertices are adjacent when they + # corresponding partitions cross-intersect on 7 nonempty sets + G = Graph([V, lambda x,y: + sum(any(xxx in yy for xxx in xx) for xx in x for yy in y) != 7], + loops=False) + return G + def strongly_regular_from_two_weight_code(L): r""" Return a strongly regular graph from a two-weight code. @@ -1522,6 +2001,130 @@ def SRG_560_208_72_80(): h.relabel() return h +def strongly_regular_from_two_intersection_set(M): + r""" + Return a strongly regular graph from a 2-intersection set. + + A set of points in the projective geometry `PG(k,q)` is said to be a + 2-intersection set if it intersects every hyperplane in either `h_1` or + `h_2` points, where `h_1,h_2\in \\NN`. + + From a 2-intersection set `S` can be defined a strongly-regular graph in the + following way: + + - Place the points of `S` on a hyperplane `H` in `PG(k+1,q)` + + - Define the graph `G` on all points of `PG(k+1,q)\backslash H` + + - Make two points of `V(G)=PG(k+1,q)\backslash H` adjacent if the line going + through them intersects `S` + + For more information, see e.g. [CDB13]_ where this explanation has been + taken from. + + INPUT: + + - `M` -- a `|S| \times k` matrix with entries in `F_q` representing the points of + the 2-intersection set. We assume that the first non-zero entry of each row is + equal to `1`, that is, they give points in homogeneous coordinates. + + The implementation does not check that `S` is actually a 2-intersection set. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import strongly_regular_from_two_intersection_set + sage: S=Matrix([(0,0,1),(0,1,0)] + map(lambda x: (1,x^2,x), GF(4,'b'))) + sage: g=strongly_regular_from_two_intersection_set(S) + sage: g.is_strongly_regular(parameters=True) + (64, 18, 2, 6) + + REFERENCES: + + .. [CDB13] I. Cardinali and B. De Bruyn, + Spin-embeddings, two-intersection sets and two-weight codes, + Ars Comb. 109 (2013): 309-319. + https://biblio.ugent.be/publication/4241842/file/4241845.pdf + """ + from itertools import product, izip + K = M.base_ring() + k = M.ncols() + g = Graph() + + M = [list(p) for p in M] + + # For every point in F_q^{k+1} not on the hyperplane of M + for u in [tuple(x) for x in product(K,repeat=k)]: + # For every v point of M + for v in M: + # u is adjacent with all vertices on a uv line. + g.add_edges([[u,tuple([u[i]+qq*v[i] for i in range(k)])] \ + for qq in K if not qq==K.zero()]) + g.relabel() + return g + + +def SRG_729_336_153_156(): + r""" + Return a `(729, 336, 153, 156)`-strongly regular graph. + + This graph is built from a 2-intersection code shared by L. Disset in his + thesis [Disset00]_ and available at + http://www.mat.uc.cl/~ldissett/newgraphs/. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_729_336_153_156 + sage: G = SRG_729_336_153_156() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (729, 336, 153, 156) + + REFERENCES: + + .. [Disset00] L. Dissett, + Combinatorial and computational aspects of finite geometries, + 2000, + https://tspace.library.utoronto.ca/bitstream/1807/14575/1/NQ49844.pdf + """ + L = [ + "101212212122202012010102120101112012121001201012120220122112001121201201201201010020012201001201201201202120121122012021201221021110200212121011211002012220000122201201", + "011100122001200111220011220020011222001200022000220012220122011220011101122012012001222010122200012011120112220112000120120012002012201122001220012122000201212001211211", + "000011111000011111112000001112000000111122222000001111112222000001111122222000111222222001111122222000001111112222000001112222000111122222000001111222000011122000011122", + "000000000111111111111000000000111111111111111222222222222222000000000000000111111111111222222222222000000000000000111111111111222222222222000000000000111111111222222222", + "000000000000000000000111111111111111111111111111111111111111000000000000000000000000000000000000000111111111111111111111111111111111111111222222222222222222222222222222", + "000000000000000000000000000000000000000000000000000000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + ] + + L = Matrix(GF(3),map(list,L)).transpose() + return strongly_regular_from_two_intersection_set(L) + +def SRG_729_448_277_272(): + r""" + Return a `(729, 448, 277, 272)`-strongly regular graph. + + This graph is built from a ternary `[140, 6]` code with weights `90, 99`, + found by Axel Kohnert [Kohnert07]_ and shared by Alfred Wassermann. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_729_448_277_272 + sage: G = SRG_729_448_277_272() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (729, 448, 277, 272) + """ + x = ("10111011111111101110101110111100111011111011101111101001001111011011011111100100111101000111101111101100011011001111101110111110101111111001", + "01220121111211011101011101112101220022120121011222010110011010120110112112001101021010101111012211011000020110012221212101011101211122020011", + "22102021112110111120211021122012100012202220112110101200110101202102122120011110020201211110021210110000101200121222122010211022211210110101", + "11010221121101111102210221220221000111011101121102012101101012012022222000211200202012211100111201200001122001211011120102110212212102121001", + "20201121211111111012202022201210001220122121211010121011010020110121220201212002010222011001111012100011010212110021202021102112221012110011", + "02022222111111110112020112011200022102212222110102210110100101102211201211220020002120110011110221100110002121100222120211021112010112220101") + M = Matrix(GF(3),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + def SRG_729_532_391_380(): r""" Return a `(729, 532, 391, 380)`-strongly regular graph. @@ -1663,6 +2266,39 @@ def SRG_625_416_279_272(): M = Matrix(GF(5),[list(l) for l in x]) return strongly_regular_from_two_weight_code(LinearCode(M)) +def SRG_625_468_353_342(): + r""" + Return a `(625, 468, 353, 342)`-strongly regular graph. + + This graph is built from a two-weight code presented in [BJ03]_ (cf. Theorem + 4.1). + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_625_468_353_342 + sage: G = SRG_625_468_353_342() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (625, 468, 353, 342) + + REFERENCE: + + .. [BJ03] I. Bouyukliev and S. Juriaan, + Some new results on optimal codes over `F_5`, + Designs, Codes and Cryptography 30, no. 1 (2003): 97-111, + http://www.moi.math.bas.bg/moiuser/~iliya/pdf_site/gf5srev.pdf + """ + x = ("111111111111111111111111111111000000000", + "111111222222333333444444000000111111000", + "223300133440112240112240133440123400110", + "402340414201142301132013234230044330401") + M = Matrix(GF(5),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + def SRG_243_220_199_200(): r""" Return a `(243, 220, 199, 200)`-strongly regular graph. @@ -1871,6 +2507,44 @@ def SRG_512_219_106_84(): M = Matrix(GF(2),[list(l) for l in x]) return strongly_regular_from_two_weight_code(LinearCode(M)) +def SRG_512_315_202_180(): + r""" + Return a `(512, 315, 202, 180)`-strongly regular graph. + + This graph is built from a projective binary code with weights `32, 40`, + found by Axel Kohnert [Kohnert07]_ and shared by Alfred Wassermann. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly + regular graph from a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_512_315_202_180 + sage: G = SRG_512_315_202_180() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (512, 315, 202, 180) + + REFERENCE: + + .. [Kohnert07] A. Kohnert, + Constructing two-weight codes with prescribed groups of automorphisms, + Discrete applied mathematics 155, no. 11 (2007): 1451-1457. + http://linearcodes.uni-bayreuth.de/twoweight/ + """ + x=("0100011110111000001011010110110111100010001000001000001001010110101101", + "1000111101110000000110101111101111000100010000010000000010101111001011", + "0001111011100000011101011101011110011000100000100000000101011100010111", + "0011110101000000111010111010111100110001000011000000001010111000101101", + "0111101000000001110101110111111001100010000100000000010101110011001011", + "1111010000000011101011101101110011010100001000000000101011100100010111", + "1110100010000111000111011011100110111000010000000001000111001010101101", + "1101000110001110001110110101001101110000100010000010001110010101001011", + "1010001110011100001101101010011011110001000100000100001100101010010111") + M = Matrix(GF(2),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + def SRG_256_153_92_90(): r""" Return a `(256, 153, 92, 90)`-strongly regular graph. @@ -2033,6 +2707,45 @@ def SRG_81_50_31_30(): M = Matrix(GF(3),[list(l) for l in x]) return strongly_regular_from_two_weight_code(LinearCode(M)) +def SRG_1288_792_476_504(): + r""" + Return a `(1288, 792, 476, 504)`-strongly regular graph. + + This graph is built on the words of weight 12 in the + :func:`~sage.coding.code_constructions.BinaryGolayCode`. Two of them are + then made adjacent if their symmetric difference has weight 12 (cf + [BvE92]_). + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_1288_792_476_504 + sage: G = SRG_1288_792_476_504() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (1288, 792, 476, 504) + + REFERENCE: + + .. [BvE92] A. Brouwer and C. Van Eijl, + On the p-Rank of the Adjacency Matrices of Strongly Regular Graphs + Journal of Algebraic Combinatorics (1992), vol.1, n.4, pp329-346, + http://dx.doi.org/10.1023/A%3A1022438616684 + """ + from sage.coding.code_constructions import BinaryGolayCode + C = BinaryGolayCode() + C = [[i for i,v in enumerate(c) if v] + for c in C] + C = [s for s in C if len(s) == 12] + G = Graph([map(frozenset,C), + lambda x,y:len(x.symmetric_difference(y))==12]) + G.relabel() + return G + + cdef bint seems_feasible(int v, int k, int l, int mu): r""" Tests is the set of parameters seems feasible @@ -2182,17 +2895,6 @@ def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint (324,95,22,30)-strongly regular graph is known to exist. Comments: - A realizable set of parameters that Sage cannot realize (help us!):: - - sage: graphs.strongly_regular_graph(1288, 495, 206, existence=True) - True - sage: graphs.strongly_regular_graph(1288, 495, 206) - Traceback (most recent call last): - ... - RuntimeError: Andries Brouwer's database claims that such a (1288,495,206,180)-strongly - regular graph exists, but Sage does not know how to build it. - ... - A large unknown set of parameters (not in Andries Brouwer's database):: sage: graphs.strongly_regular_graph(1394,175,0,25,existence=True) @@ -2228,20 +2930,8 @@ def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint ( 27, 16, 10, 8): [SchlaefliGraph], ( 36, 14, 4, 6): [Graph,('c~rLDEOcKTPO`U`HOIj@MWFLQFAaRIT`HIWqPsQQJ'+ 'DXGLqYM@gRLAWLdkEW@RQYQIErcgesClhKefC_ygSGkZ`OyHETdK[?lWStCapVgKK')], - ( 40, 12, 2, 4): [Graph,('g}iS[A@_S@OA_BWQIGaPCQE@CcQGcQECXAgaOdS@a'+ - 'CWEEAOIBH_HW?scb?f@GMBGGhIPGaQoh?q_bD_pGPq_WI`T_DBU?R_dECsSARGgogBO'+ - '{_IPBKZ?DI@Wgt_E?MPo{_?')], - ( 45, 12, 3, 3): [Graph,('l~}CKMF_C?oB_FPCGaICQOaH@DQAHQ@Ch?aJHAQ@G'+ - 'P_CQAIGcAJGO`IcGOY`@IGaGHGaKSCDI?gGDgGcE_@OQAg@PCSO_hOa`GIDADAD@XCI'+ - 'ASDKB?oKOo@_SHCc?SGcGd@A`B?bOOHGQH?ROQOW`?XOPa@C_hcGo`CGJK')], ( 50, 7, 0, 1): [HoffmanSingletonGraph], ( 56, 10, 0, 2): [SimsGewirtzGraph], - ( 64, 18, 2, 6): [Graph,('~?@?~aK[A@_[?O@_B_?O?K?B_?A??K??YQQPHGcQQ'+ - 'CaPIOHAX?POhAPIC`GcgSAHDE?PCiC@BCcDADIG_QCocS@AST?OOceGG@QGcKcdCbCB'+ - 'gIEHAScIDDOy?DAWaEg@IQO?maHPOhAW_dBCX?s@HOpKD@@GpOpHO?bCbHGOaGgpWQQ'+ - '?PDDDw@A_CSRIS_P?GeGpg`@?EOcaJGccbDC_dLAc_pHOe@`ocEGgo@sRo?WRAbAcPc'+ - '?iCiHEKBO_hOiOWpOSGSTBQCUAW_DDIWOqHBO?gghw_?`kOAXH?\\Ds@@@CpIDKOpc@'+ - 'OCoeIS_YOgGATGaqAhKGA?cqDOwQKGc?')], ( 77, 16, 0, 4): [M22Graph], ( 81, 50, 31, 30): [SRG_81_50_31_30], (100, 22, 0, 6): [HigmanSimsGraph], @@ -2258,6 +2948,7 @@ def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint (176, 49, 12, 14): [SRG_176_49_12_14], (176, 105, 68, 54): [SRG_176_105_68_54], (196, 91, 42, 42): [SRG_196_91_42_42], + (210, 99, 48, 45): [SRG_210_99_48_45], (220, 84, 38, 28): [SRG_220_84_38_28], (231, 30, 9, 3): [CameronGraph], (243, 110, 37, 60): [SRG_243_110_37_60], @@ -2268,19 +2959,25 @@ def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint (256, 153, 92, 90): [SRG_256_153_92_90], (275, 112, 30, 56): [McLaughlinGraph], (276, 140, 58, 84): [SRG_276_140_58_84], + (280, 117, 44, 52): [SRG_280_117_44_52], (280, 135, 70, 60): [SRG_280_135_70_60], (416, 100, 36, 20): [SRG_416_100_36_20], (512, 219, 106, 84): [SRG_512_219_106_84], (512, 73, 12, 10): [SRG_512_73_12_10], + (512, 315, 202,180): [SRG_512_315_202_180], (560, 208, 72, 80): [SRG_560_208_72_80], - (625, 416, 279,272): [SRG_625_416_279_272], (625, 364, 213,210): [SRG_625_364_213_210], + (625, 416, 279,272): [SRG_625_416_279_272], + (625, 468, 353,342): [SRG_625_468_353_342], + (729, 336, 153,156): [SRG_729_336_153_156], (729, 616, 523,506): [SRG_729_616_523_506], (729, 420, 243,240): [SRG_729_420_243_240], + (729, 448, 277,272): [SRG_729_448_277_272], (729, 560, 433,420): [SRG_729_560_433_420], (729, 476, 313,306): [SRG_729_476_313_306], (729, 532, 391,380): [SRG_729_532_391_380], (1024,825, 668,650): [SRG_1024_825_668_650], + (1288,792, 476,504): [SRG_1288_792_476_504], (1782,416, 100, 96): [SuzukiGraph], } @@ -2294,13 +2991,15 @@ def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint test_functions = [is_paley, is_johnson, is_orthogonal_array_block_graph, is_steiner, is_affine_polar, + is_goethals_seidel, is_orthogonal_polar, is_NOodd, is_NOperp_F5, is_NO_F2, is_NO_F3, is_NU, - is_unitary_polar, - is_unitary_dual_polar, + is_unitary_polar, is_unitary_dual_polar, is_GQqmqp, is_RSHCD, is_twograph_descendant_of_srg, - is_taylor_twograph_srg] + is_taylor_twograph_srg, + is_switch_OA_srg, + is_polhill] # Going through all test functions, for the set of parameters and its # complement. diff --git a/src/sage/groups/abelian_gps/abelian_group_element.py b/src/sage/groups/abelian_gps/abelian_group_element.py index c49325d699b..d1d61c93cc0 100644 --- a/src/sage/groups/abelian_gps/abelian_group_element.py +++ b/src/sage/groups/abelian_gps/abelian_group_element.py @@ -93,7 +93,6 @@ class AbelianGroupElement(AbelianGroupElementBase): sage: a*b in F True """ - def as_permutation(self): r""" Return the element of the permutation group G (isomorphic to the diff --git a/src/sage/groups/abelian_gps/element_base.py b/src/sage/groups/abelian_gps/element_base.py index ab2cff59587..1cf9c4a5a62 100644 --- a/src/sage/groups/abelian_gps/element_base.py +++ b/src/sage/groups/abelian_gps/element_base.py @@ -73,6 +73,16 @@ def __init__(self, parent, exponents): if len(self._exponents) != n: raise IndexError('argument length (= %s) must be %s.'%(len(exponents), n)) + def __hash__(self): + r""" + TESTS:: + + sage: F = AbelianGroup(3,[7,8,9]) + sage: hash(F.an_element()) # random + 1024 + """ + return hash(self.parent()) ^ hash(self._exponents) + def exponents(self): """ The exponents of the generators defining the group element. diff --git a/src/sage/groups/additive_abelian/additive_abelian_group.py b/src/sage/groups/additive_abelian/additive_abelian_group.py index 51fa899443a..3bf8aef61e6 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_group.py +++ b/src/sage/groups/additive_abelian/additive_abelian_group.py @@ -401,7 +401,7 @@ def __init__(self, cover, rels, gens): Additive abelian group isomorphic to Z/3 """ AdditiveAbelianGroup_class.__init__(self, cover, rels) - self._orig_gens = [self(x) for x in gens] + self._orig_gens = tuple(self(x) for x in gens) def gens(self): r""" @@ -416,7 +416,7 @@ def gens(self): sage: G.smith_form_gens() ((1, 2),) """ - return tuple(self._orig_gens) + return self._orig_gens def identity(self): r""" diff --git a/src/sage/groups/conjugacy_classes.py b/src/sage/groups/conjugacy_classes.py index 3553af8e8a0..d528090a295 100644 --- a/src/sage/groups/conjugacy_classes.py +++ b/src/sage/groups/conjugacy_classes.py @@ -180,7 +180,7 @@ def __iter__(self): sage: a = G(a) sage: C = ConjugacyClass(G, a) sage: it = iter(C) - sage: [next(it) for _ in range(5)] + sage: [next(it) for _ in range(5)] # random (nothing guarantees enumeration order) [ [1 1] [ 2 1] [ 0 1] [ 3 1] [ 3 4] [0 1], [-1 0], [-1 2], [-4 -1], [-1 -1] diff --git a/src/sage/groups/finitely_presented_named.py b/src/sage/groups/finitely_presented_named.py index 31214fc9751..88fd6df7420 100644 --- a/src/sage/groups/finitely_presented_named.py +++ b/src/sage/groups/finitely_presented_named.py @@ -130,14 +130,14 @@ def FinitelyGeneratedAbelianPresentation(int_list): sage: groups.presentation.FGAbelian([0,0]) Finitely presented group < a, b | a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([0,0,0]) - Finitely presented group < a, b, c | a^-1*c^-1*a*c, a^-1*b^-1*a*b, c^-1*b^-1*c*b > + Finitely presented group < a, b, c | a^-1*b^-1*a*b, a^-1*c^-1*a*c, b^-1*c^-1*b*c > And various infinite abelian groups:: sage: groups.presentation.FGAbelian([0,2]) Finitely presented group < a, b | a^2, a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([0,2,2]) - Finitely presented group < a, b, c | a^2, b^2, a^-1*c^-1*a*c, a^-1*b^-1*a*b, c^-1*b^-1*c*b > + Finitely presented group < a, b, c | a^2, b^2, a^-1*b^-1*a*b, a^-1*c^-1*a*c, b^-1*c^-1*b*c > Outputs are reduced to minimal generators and relations:: @@ -193,7 +193,7 @@ def FinitelyGeneratedAbelianPresentation(int_list): ret_rls = [F([i+1])**invariants[i] for i in range(len(invariants)) if invariants[i]!=0] # Build commutator relations - gen_pairs = list(Set(F.gens()).subsets(2)) + gen_pairs = [[F.gen(i),F.gen(j)] for i in range(F.ngens()-1) for j in range(i+1,F.ngens())] ret_rls = ret_rls + [x[0]**(-1)*x[1]**(-1)*x[0]*x[1] for x in gen_pairs] return FinitelyPresentedGroup(F, tuple(ret_rls)) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 0bdef462d65..45ef7a1c10c 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -227,6 +227,17 @@ def __init__(self, parent, x): x = AbstractWordTietzeWord(l, parent._gap_gens()) ElementLibGAP.__init__(self, parent, x) + def __hash__(self): + r""" + TESTS:: + + sage: G. = FreeGroup() + sage: hash(a*b*b*~a) + -485698212495963022 # 64-bit + -1876767630 # 32-bit + """ + return hash(self.Tietze()) + def _latex_(self): """ Return a LaTeX representation diff --git a/src/sage/groups/matrix_gps/coxeter_group.py b/src/sage/groups/matrix_gps/coxeter_group.py index bf1c04ec892..d4a46d4933b 100644 --- a/src/sage/groups/matrix_gps/coxeter_group.py +++ b/src/sage/groups/matrix_gps/coxeter_group.py @@ -23,6 +23,7 @@ from sage.categories.coxeter_groups import CoxeterGroups from sage.combinat.root_system.cartan_type import CartanType, CartanType_abstract +from sage.combinat.root_system.coxeter_matrix import CoxeterMatrix from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_generic from sage.groups.matrix_gps.group_element import MatrixGroupElement_generic from sage.graphs.graph import Graph @@ -31,7 +32,7 @@ from sage.rings.all import ZZ from sage.rings.infinity import infinity from sage.rings.universal_cyclotomic_field import UniversalCyclotomicField - +from sage.misc.superseded import deprecated_function_alias class CoxeterMatrixGroup(FinitelyGeneratedMatrixGroup_generic, UniqueRepresentation): r""" @@ -151,7 +152,7 @@ class CoxeterMatrixGroup(FinitelyGeneratedMatrixGroup_generic, UniqueRepresentat [ 3 1 2 15] [ 2 2 1 7] [ 3 15 7 1] - sage: G2 = W.coxeter_graph() + sage: G2 = W.coxeter_diagram() sage: CoxeterGroup(G2) is W True @@ -202,67 +203,13 @@ def __classcall_private__(cls, data, base_ring=None, index_set=None): sage: W4 = CoxeterGroup(G2) sage: W1 is W4 True - - Check with `\infty` because of the hack of using `-1` to represent - `\infty` in the Coxeter matrix:: - - sage: G = Graph([(0, 1, 3), (1, 2, oo)]) - sage: W1 = CoxeterGroup(matrix([[1, 3, 2], [3,1,-1], [2,-1,1]])) - sage: W2 = CoxeterGroup(G) - sage: W1 is W2 - True - sage: CoxeterGroup(W1.coxeter_graph()) is W1 - True """ - if isinstance(data, CartanType_abstract): - if index_set is None: - index_set = data.index_set() - data = data.coxeter_matrix() - elif isinstance(data, Graph): - G = data - n = G.num_verts() - - # Setup the basis matrix as all 2 except 1 on the diagonal - data = matrix(ZZ, [[2]*n]*n) - for i in range(n): - data[i, i] = ZZ.one() - - verts = G.vertices() - for e in G.edges(): - m = e[2] - if m is None: - m = 3 - elif m == infinity or m == -1: # FIXME: Hack because there is no ZZ\cup\{\infty\} - m = -1 - elif m <= 1: - raise ValueError("invalid Coxeter graph label") - i = verts.index(e[0]) - j = verts.index(e[1]) - data[j, i] = data[i, j] = m - - if index_set is None: - index_set = G.vertices() - else: - try: - data = matrix(data) - except (ValueError, TypeError): - data = CartanType(data).coxeter_matrix() - if not data.is_symmetric(): - raise ValueError("the Coxeter matrix is not symmetric") - if any(d != 1 for d in data.diagonal()): - raise ValueError("the Coxeter matrix diagonal is not all 1") - if any(val <= 1 and val != -1 for i, row in enumerate(data.rows()) - for val in row[i+1:]): - raise ValueError("invalid Coxeter label") - - if index_set is None: - index_set = range(data.nrows()) + data = CoxeterMatrix(data, index_set=index_set) if base_ring is None: base_ring = UniversalCyclotomicField() - data.set_immutable() return super(CoxeterMatrixGroup, cls).__classcall__(cls, - data, base_ring, tuple(index_set)) + data, base_ring, data.index_set()) def __init__(self, coxeter_matrix, base_ring, index_set): """ @@ -301,8 +248,7 @@ def __init__(self, coxeter_matrix, base_ring, index_set): True """ self._matrix = coxeter_matrix - self._index_set = index_set - n = ZZ(coxeter_matrix.nrows()) + n = coxeter_matrix.rank() # Compute the matrix with entries `2 \cos( \pi / m_{ij} )`. MS = MatrixSpace(base_ring, n, sparse=True) MC = MS._get_matrix_class() @@ -313,31 +259,19 @@ def __init__(self, coxeter_matrix, base_ring, index_set): from sage.functions.trig import cos from sage.symbolic.constants import pi val = lambda x: base_ring(2*cos(pi / x)) if x != -1 else base_ring(2) - gens = [MS.one() + MC(MS, entries={(i, j): val(coxeter_matrix[i, j]) + gens = [MS.one() + MC(MS, entries={(i, j): val(coxeter_matrix[index_set[i], index_set[j]]) for j in range(n)}, coerce=True, copy=True) for i in range(n)] - # Compute the matrix with entries `- \cos( \pi / m_{ij} )`. - # This describes the bilinear form corresponding to this - # Coxeter system, and might lead us out of our base ring. - base_field = base_ring.fraction_field() - MS2 = MatrixSpace(base_field, n, sparse=True) - MC2 = MS2._get_matrix_class() - self._bilinear = MC2(MS2, entries={(i, j): val(coxeter_matrix[i, j]) / base_field(-2) - for i in range(n) for j in range(n) - if coxeter_matrix[i, j] != 2}, - coerce=True, copy=True) - self._bilinear.set_immutable() category = CoxeterGroups() # Now we shall see if the group is finite, and, if so, refine # the category to ``category.Finite()``. Otherwise the group is # infinite and we refine the category to ``category.Infinite()``. - is_finite = self._finite_recognition() - if is_finite: + if self._matrix.is_finite(): category = category.Finite() else: category = category.Infinite() - FinitelyGeneratedMatrixGroup_generic.__init__(self, n, base_ring, + FinitelyGeneratedMatrixGroup_generic.__init__(self, ZZ(n), base_ring, gens, category=category) def _finite_recognition(self): @@ -512,7 +446,7 @@ def index_set(self): sage: W = CoxeterGroup([[1,3],[3,1]]) sage: W.index_set() - (0, 1) + (1, 2) sage: W = CoxeterGroup([[1,3],[3,1]], index_set=['x', 'y']) sage: W.index_set() ('x', 'y') @@ -520,7 +454,7 @@ def index_set(self): sage: W.index_set() (1, 2, 3) """ - return self._index_set + return self._matrix.index_set() def coxeter_matrix(self): """ @@ -540,37 +474,29 @@ def coxeter_matrix(self): """ return self._matrix - def coxeter_graph(self): + def coxeter_diagram(self): """ - Return the Coxeter graph of ``self``. + Return the Coxeter diagram of ``self``. EXAMPLES:: sage: W = CoxeterGroup(['H',3], implementation="reflection") - sage: G = W.coxeter_graph(); G + sage: G = W.coxeter_diagram(); G Graph on 3 vertices sage: G.edges() - [(1, 2, None), (2, 3, 5)] + [(1, 2, 3), (2, 3, 5)] sage: CoxeterGroup(G) is W True sage: G = Graph([(0, 1, 3), (1, 2, oo)]) sage: W = CoxeterGroup(G) - sage: W.coxeter_graph() == G + sage: W.coxeter_diagram() == G True - sage: CoxeterGroup(W.coxeter_graph()) is W + sage: CoxeterGroup(W.coxeter_diagram()) is W True """ - G = Graph() - G.add_vertices(self.index_set()) - for i, row in enumerate(self._matrix.rows()): - for j, val in enumerate(row[i+1:]): - if val == 3: - G.add_edge(self._index_set[i], self._index_set[i+1+j]) - elif val > 3: - G.add_edge(self._index_set[i], self._index_set[i+1+j], val) - elif val == -1: # FIXME: Hack because there is no ZZ\cup\{\infty\} - G.add_edge(self._index_set[i], self._index_set[i+1+j], infinity) - return G + return self._matrix.coxeter_graph() + + coxeter_graph = deprecated_function_alias(17798, coxeter_diagram) def bilinear_form(self): r""" @@ -596,7 +522,7 @@ def bilinear_form(self): [ 0 -1/2 1 0] [ 0 -1/2 0 1] """ - return self._bilinear + return self._matrix.bilinear_form() def is_finite(self): """ @@ -689,9 +615,9 @@ def simple_reflection(self, i): [ 0 1 0] [ 0 1 -1] """ - if not i in self._index_set: + if not i in self.index_set(): raise ValueError("%s is not in the index set %s" % (i, self.index_set())) - return self.gen(self._index_set.index(i)) + return self.gen(self.index_set().index(i)) class Element(MatrixGroupElement_generic): """ @@ -723,7 +649,7 @@ def has_right_descent(self, i): sage: map(lambda i: elt.has_right_descent(i), [1, 2, 3]) [True, False, True] """ - i = self.parent()._index_set.index(i) + i = self.parent().index_set().index(i) col = self.matrix().column(i) return all(x <= 0 for x in col) @@ -743,3 +669,4 @@ def canonical_matrix(self): [ 0 1 -1] """ return self.matrix() + diff --git a/src/sage/groups/matrix_gps/group_element.py b/src/sage/groups/matrix_gps/group_element.py index 5c0ae88326b..434c7ce8027 100644 --- a/src/sage/groups/matrix_gps/group_element.py +++ b/src/sage/groups/matrix_gps/group_element.py @@ -117,12 +117,23 @@ class MatrixGroupElement_base(MultiplicativeGroupElement): EXAMPLES:: sage: F = GF(3); MS = MatrixSpace(F,2,2) - sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] + sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] sage: G = MatrixGroup(gens) sage: g = G.random_element() sage: type(g) """ + def __hash__(self): + r""" + TESTS:: + + sage: MS = MatrixSpace(GF(3), 2) + sage: G = MatrixGroup([MS([1,1,0,1]), MS([1,0,1,1])]) + sage: g = G.an_element() + sage: hash(g) + 0 + """ + return hash(self.matrix()) def _repr_(self): """ diff --git a/src/sage/groups/matrix_gps/morphism.py b/src/sage/groups/matrix_gps/morphism.py index 775fb239b90..2d4a0e67d47 100644 --- a/src/sage/groups/matrix_gps/morphism.py +++ b/src/sage/groups/matrix_gps/morphism.py @@ -109,6 +109,19 @@ def __init__(self, homset, imgsH, check=True): sage: G = MatrixGroup([MS([3,0,0,1])]) sage: a = G.gens()[0]^2 sage: phi = G.hom([a]) + + TESTS: + + Check that :trac:`19406` is fixed:: + + sage: G = GL(2, GF(3)) + sage: H = GL(3, GF(2)) + sage: mat1 = H([[-1,0,0],[0,0,-1],[0,-1,0]]) + sage: mat2 = H([[1,1,1],[0,0,-1],[-1,0,0]]) + sage: phi = G.hom([mat1, mat2]) + Traceback (most recent call last): + ... + TypeError: images do not define a group homomorphism """ MatrixGroupMorphism.__init__(self, homset) # sets the parent from sage.libs.gap.libgap import libgap @@ -118,7 +131,7 @@ def __init__(self, homset, imgsH, check=True): imgs = [to_libgap(x) for x in imgsH] self._phi = phi = libgap.GroupHomomorphismByImages(G.gap(), H.gap(), gens, imgs) if not phi.IsGroupHomomorphism(): - raise ValueError('The map '+str(gensG)+'-->'+str(imgsH)+' is not a homomorphism') + raise ValueError('the map {}-->{} is not a homomorphism'.format(G.gens(), imgsH)) def gap(self): """ diff --git a/src/sage/homology/algebraic_topological_model.py b/src/sage/homology/algebraic_topological_model.py new file mode 100644 index 00000000000..dd3cc3c304d --- /dev/null +++ b/src/sage/homology/algebraic_topological_model.py @@ -0,0 +1,592 @@ +# -*- coding: utf-8 -*- +r""" +Algebraic topological model for a cell complex + +This file contains two functions, :func:`algebraic_topological_model` +and :func:`algebraic_topological_model_delta_complex`. The second +works more generally: for all simplicial, cubical, and +`\Delta`-complexes. The first only works for simplicial and cubical +complexes, but it is faster in those case. + +AUTHORS: + +- John H. Palmieri (2015-09) +""" + +######################################################################## +# Copyright (C) 2015 John H. Palmieri +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# http://www.gnu.org/licenses/ +######################################################################## + +# TODO: cythonize this. + +from sage.modules.free_module_element import vector +from sage.modules.free_module import VectorSpace +from sage.matrix.constructor import matrix, zero_matrix +from sage.matrix.matrix_space import MatrixSpace +from chain_complex import ChainComplex +from chain_complex_morphism import ChainComplexMorphism +from chain_homotopy import ChainContraction +from sage.rings.rational_field import QQ + +def algebraic_topological_model(K, base_ring=None): + r""" + Algebraic topological model for cell complex ``K`` + with coefficients in the field ``base_ring``. + + INPUT: + + - ``K`` -- either a simplicial complex or a cubical complex + - ``base_ring`` -- coefficient ring; must be a field + + OUTPUT: a pair ``(phi, M)`` consisting of + + - chain contraction ``phi`` + - chain complex `M` + + This construction appears in a paper by Pilarczyk and Réal [PR]_. + Given a cell complex `K` and a field `F`, there is a chain complex + `C` associated to `K` with coefficients in `F`. The *algebraic + topological model* for `K` is a chain complex `M` with trivial + differential, along with chain maps `\pi: C \to M` and `\iota: M + \to C` such that + + - `\pi \iota = 1_M`, and + - there is a chain homotopy `\phi` between `1_C` and `\iota \pi`. + + In particular, `\pi` and `\iota` induce isomorphisms on homology, + and since `M` has trivial differential, it is its own homology, + and thus also the homology of `C`. Thus `\iota` lifts homology + classes to their cycle representatives. + + The chain homotopy `\phi` satisfies some additional properties, + making it a *chain contraction*: + + - `\phi \phi = 0`, + - `\pi \phi = 0`, + - `\phi \iota = 0`. + + Given an algebraic topological model for `K`, it is then easy to + compute cup products and cohomology operations on the cohomology + of `K`, as described in [G-DR03]_ and [PR]_. + + Implementation details: the cell complex `K` must have an + :meth:`~sage.homology.cell_complex.GenericCellComplex.n_cells` + method from which we can extract a list of cells in each + dimension. Combining the lists in increasing order of dimension + then defines a filtration of the complex: a list of cells in which + the boundary of each cell consists of cells earlier in the + list. This is required by Pilarczyk and Réal's algorithm. There + must also be a + :meth:`~sage.homology.cell_complex.GenericCellComplex.chain_complex` + method, to construct the chain complex `C` associated to this + chain complex. + + In particular, this works for simplicial complexes and cubical + complexes. It doesn't work for `\Delta`-complexes, though: the list + of their `n`-cells has the wrong format. + + Note that from the chain contraction ``phi``, one can recover the + chain maps `\pi` and `\iota` via ``phi.pi()`` and + ``phi.iota()``. Then one can recover `C` and `M` from, for + example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, + respectively. + + EXAMPLES:: + + sage: from sage.homology.algebraic_topological_model import algebraic_topological_model + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: phi, M = algebraic_topological_model(RP2, GF(2)) + sage: M.homology() + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: T = cubical_complexes.Torus() + sage: phi, M = algebraic_topological_model(T, QQ) + sage: M.homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + + If you want to work with cohomology rather than homology, just + dualize the outputs of this function:: + + sage: M.dual().homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + sage: M.dual().degree_of_differential() + 1 + sage: phi.dual() + Chain homotopy between: + Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field + and Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + + In degree 0, the inclusion of the homology `M` into the chain + complex `C` sends the homology generator to a single vertex:: + + sage: K = simplicial_complexes.Simplex(2) + sage: phi, M = algebraic_topological_model(K, QQ) + sage: phi.iota().in_degree(0) + [0] + [0] + [1] + + In cohomology, though, one needs the dual of every degree 0 cell + to detect the degree 0 cohomology generator:: + + sage: phi.dual().iota().in_degree(0) + [1] + [1] + [1] + + TESTS:: + + sage: T = cubical_complexes.Torus() + sage: C = T.chain_complex() + sage: H, M = T.algebraic_topological_model() + sage: C.differential(1) * H.iota().in_degree(1).column(0) == 0 + True + sage: C.differential(1) * H.iota().in_degree(1).column(1) == 0 + True + sage: coC = T.chain_complex(cochain=True) + sage: coC.differential(1) * H.dual().iota().in_degree(1).column(0) == 0 + True + sage: coC.differential(1) * H.dual().iota().in_degree(1).column(1) == 0 + True + """ + if not base_ring.is_field(): + raise ValueError('the coefficient ring must be a field') + + # The following are all dictionaries indexed by dimension. + # For each n, gens[n] is an ordered list of the n-cells generating the complex M. + gens = {} + # For each n, phi_dict[n] is a dictionary of the form {idx: + # vector}, where idx is the index of an n-cell in the list of + # n-cells in K, and vector is the image of that n-cell, as an + # element in the free module of (n+1)-chains for K. + phi_dict = {} + # For each n, pi_dict[n] is a dictionary of the same form, except + # that the target vectors should be elements of the chain complex M. + pi_dict = {} + # For each n, iota_dict[n] is a dictionary of the form {cell: + # vector}, where cell is one of the generators for M and vector is + # its image in C, as an element in the free module of n-chains. + iota_dict = {} + + for n in range(K.dimension()+1): + gens[n] = [] + phi_dict[n] = {} + pi_dict[n] = {} + iota_dict[n] = {} + + C = K.chain_complex(base_ring=base_ring) + # old_cells: cells one dimension lower. + old_cells = [] + + for dim in range(K.dimension()+1): + n_cells = K.n_cells(dim) + diff = C.differential(dim) + # diff is sparse and low density. Dense matrices are faster + # over finite fields, but for low density matrices, sparse + # matrices are faster over the rationals. + if base_ring != QQ: + diff = diff.dense_matrix() + + rank = len(n_cells) + old_rank = len(old_cells) + V_old = VectorSpace(base_ring, old_rank) + zero = V_old.zero_vector() + + for c_idx, c in enumerate(zip(n_cells, VectorSpace(base_ring, rank).gens())): + # c is the pair (cell, the corresponding standard basis + # vector in the free module of chains). Separate its + # components, calling them c and c_vec: + c_vec = c[1] + c = c[0] + # No need to set zero values for any of the maps: we will + # assume any unset values are zero. + # From the paper: phi_dict[c] = 0. + + # c_bar = c - phi(bdry(c)) + c_bar = c_vec + bdry_c = diff * c_vec + # Apply phi to bdry_c and subtract from c_bar. + for (idx, coord) in bdry_c.iteritems(): + try: + c_bar -= coord * phi_dict[dim-1][idx] + except KeyError: + pass + + bdry_c_bar = diff * c_bar + + # Evaluate pi(bdry(c_bar)). + pi_bdry_c_bar = zero + + for (idx, coeff) in bdry_c_bar.iteritems(): + try: + pi_bdry_c_bar += coeff * pi_dict[dim-1][idx] + except KeyError: + pass + + # One small typo in the published algorithm: it says + # "if bdry(c_bar) == 0", but should say + # "if pi(bdry(c_bar)) == 0". + if not pi_bdry_c_bar: + # Append c to list of gens. + gens[dim].append(c) + # iota(c) = c_bar + iota_dict[dim][c] = c_bar + # pi(c) = c + pi_dict[dim][c_idx] = c_vec + else: + # Take any u in gens so that lambda_i = != 0. + # u_idx will be the index of the corresponding cell. + for (u_idx, lambda_i) in pi_bdry_c_bar.iteritems(): + # Now find the actual cell. + u = old_cells[u_idx] + if u in gens[dim-1]: + break + + # pi(c) = 0: no need to do anything about this. + for c_j_idx in range(old_rank): + # eta_ij = . + try: + eta_ij = pi_dict[dim-1][c_j_idx][u_idx] + except (KeyError, IndexError): + eta_ij = 0 + if eta_ij: + # Adjust phi(c_j). + try: + phi_dict[dim-1][c_j_idx] += eta_ij * lambda_i**(-1) * c_bar + except KeyError: + phi_dict[dim-1][c_j_idx] = eta_ij * lambda_i**(-1) * c_bar + # Adjust pi(c_j). + try: + pi_dict[dim-1][c_j_idx] += -eta_ij * lambda_i**(-1) * pi_bdry_c_bar + except KeyError: + pi_dict[dim-1][c_j_idx] = -eta_ij * lambda_i**(-1) * pi_bdry_c_bar + + gens[dim-1].remove(u) + del iota_dict[dim-1][u] + old_cells = n_cells + + # Now we have constructed the raw data for M, pi, iota, phi, so we + # have to convert that to data which can be used to construct chain + # complexes, chain maps, and chain contractions. + + # M_data will contain (trivial) matrices defining the differential + # on M. Keep track of the sizes using "M_rows" and "M_cols", which are + # just the ranks of consecutive graded pieces of M. + M_data = {} + M_rows = 0 + # pi_data: the matrices defining pi. Similar for iota_data and phi_data. + pi_data = {} + iota_data = {} + phi_data = {} + for n in range(K.dimension()+1): + n_cells = K.n_cells(n) + # Remove zero entries from pi_dict and phi_dict. + pi_dict[n] = {i: pi_dict[n][i] for i in pi_dict[n] if pi_dict[n][i]} + phi_dict[n] = {i: phi_dict[n][i] for i in phi_dict[n] if phi_dict[n][i]} + # Convert gens to data defining the chain complex M with + # trivial differential. + M_cols = len(gens[n]) + M_data[n] = zero_matrix(base_ring, M_rows, M_cols) + M_rows = M_cols + # Convert the dictionaries for pi, iota, phi to matrices which + # will define chain maps and chain homotopies. + pi_cols = [] + phi_cols = [] + for (idx, c) in enumerate(n_cells): + # First pi: + if idx in pi_dict[n]: + column = vector(base_ring, M_rows) + for (entry, coeff) in pi_dict[n][idx].iteritems(): + # Translate from cells in n_cells to cells in gens[n]. + column[gens[n].index(n_cells[entry])] = coeff + else: + column = vector(base_ring, M_rows) + pi_cols.append(column) + + # Now phi: + try: + column = phi_dict[n][idx] + except KeyError: + column = vector(base_ring, len(K.n_cells(n+1))) + phi_cols.append(column) + # Now iota: + iota_cols = [iota_dict[n][c] for c in gens[n]] + + pi_data[n] = matrix(base_ring, pi_cols).transpose() + iota_data[n] = matrix(base_ring, len(gens[n]), len(n_cells), iota_cols).transpose() + phi_data[n] = matrix(base_ring, phi_cols).transpose() + + M = ChainComplex(M_data, base_ring=base_ring, degree=-1) + pi = ChainComplexMorphism(pi_data, C, M) + iota = ChainComplexMorphism(iota_data, M, C) + phi = ChainContraction(phi_data, pi, iota) + return phi, M + +def algebraic_topological_model_delta_complex(K, base_ring=None): + r""" + Algebraic topological model for cell complex ``K`` + with coefficients in the field ``base_ring``. + + This has the same basic functionality as + :func:`algebraic_topological_model`, but it also works for + `\Delta`-complexes. For simplicial and cubical complexes it is + somewhat slower, though. + + INPUT: + + - ``K`` -- a simplicial complex, a cubical complex, or a + `\Delta`-complex + - ``base_ring`` -- coefficient ring; must be a field + + OUTPUT: a pair ``(phi, M)`` consisting of + + - chain contraction ``phi`` + - chain complex `M` + + See :func:`algebraic_topological_model` for the main + documentation. The difference in implementation between the two: + this uses matrix and vector algebra. The other function does more + of the computations "by hand" and uses cells (given as simplices + or cubes) to index various dictionaries. Since the cells in + `\Delta`-complexes are not as nice, the other function does not + work for them, while this function relies almost entirely on the + structure of the associated chain complex. + + EXAMPLES:: + + sage: from sage.homology.algebraic_topological_model import algebraic_topological_model_delta_complex as AT_model + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: phi, M = AT_model(RP2, GF(2)) + sage: M.homology() + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: T = delta_complexes.Torus() + sage: phi, M = AT_model(T, QQ) + sage: M.homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + + If you want to work with cohomology rather than homology, just + dualize the outputs of this function:: + + sage: M.dual().homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + sage: M.dual().degree_of_differential() + 1 + sage: phi.dual() + Chain homotopy between: + Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field + and Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + + In degree 0, the inclusion of the homology `M` into the chain + complex `C` sends the homology generator to a single vertex:: + + sage: K = delta_complexes.Simplex(2) + sage: phi, M = AT_model(K, QQ) + sage: phi.iota().in_degree(0) + [0] + [0] + [1] + + In cohomology, though, one needs the dual of every degree 0 cell + to detect the degree 0 cohomology generator:: + + sage: phi.dual().iota().in_degree(0) + [1] + [1] + [1] + + TESTS:: + + sage: T = cubical_complexes.Torus() + sage: C = T.chain_complex() + sage: H, M = AT_model(T, QQ) + sage: C.differential(1) * H.iota().in_degree(1).column(0) == 0 + True + sage: C.differential(1) * H.iota().in_degree(1).column(1) == 0 + True + sage: coC = T.chain_complex(cochain=True) + sage: coC.differential(1) * H.dual().iota().in_degree(1).column(0) == 0 + True + sage: coC.differential(1) * H.dual().iota().in_degree(1).column(1) == 0 + True + """ + def conditionally_sparse(m): + """ + Return a sparse matrix if the characteristic is zero. + + Multiplication of matrices with low density seems to be quicker + if the matrices are sparse, when over the rationals. Over + finite fields, dense matrices are faster regardless of + density. + """ + if base_ring == QQ: + return m.sparse_matrix() + else: + return m + + if not base_ring.is_field(): + raise ValueError('the coefficient ring must be a field') + + # The following are all dictionaries indexed by dimension. + # For each n, gens[n] is an ordered list of the n-cells generating the complex M. + gens = {} + pi_data = {} + phi_data = {} + iota_data = {} + + for n in range(-1, K.dimension()+1): + gens[n] = [] + + C = K.chain_complex(base_ring=base_ring) + n_cells = [] + pi_cols = [] + iota_cols = {} + + for dim in range(K.dimension()+1): + # old_cells: cells one dimension lower. + old_cells = n_cells + # n_cells: the standard basis for the vector space C.free_module(dim). + n_cells = C.free_module(dim).gens() + diff = C.differential(dim) + # diff is sparse and low density. Dense matrices are faster + # over finite fields, but for low density matrices, sparse + # matrices are faster over the rationals. + if base_ring != QQ: + diff = diff.dense_matrix() + + rank = len(n_cells) + old_rank = len(old_cells) + + # Create some matrix spaces to try to speed up matrix creation. + MS_pi_t = MatrixSpace(base_ring, old_rank, len(gens[dim-1])) + + pi_old = MS_pi_t.matrix(pi_cols).transpose() + iota_cols_old = iota_cols + iota_cols = {} + pi_cols_old = pi_cols + pi_cols = [] + phi_old = MatrixSpace(base_ring, rank, old_rank, sparse=(base_ring==QQ)).zero() + phi_old_cols = phi_old.columns() + phi_old = conditionally_sparse(phi_old) + to_be_deleted = [] + + zero_vector = vector(base_ring, rank) + pi_nrows = pi_old.nrows() + + for c_idx, c in enumerate(n_cells): + # c_bar = c - phi(bdry(c)): + # Avoid a bug in matrix-vector multiplication (trac 19378): + if not diff: + c_bar = c + pi_bdry_c_bar = False + else: + if base_ring == QQ: + c_bar = c - phi_old * (diff * c) + pi_bdry_c_bar = conditionally_sparse(pi_old) * (diff * c_bar) + else: + c_bar = c - phi_old * diff * c + pi_bdry_c_bar = conditionally_sparse(pi_old) * diff * c_bar + + # One small typo in the published algorithm: it says + # "if bdry(c_bar) == 0", but should say + # "if pi(bdry(c_bar)) == 0". + if not pi_bdry_c_bar: + # Append c to list of gens. + gens[dim].append(c_idx) + # iota(c) = c_bar + iota_cols[c_idx] = c_bar + # pi(c) = c + pi_cols.append(c) + else: + # Take any u in gens so that lambda_i = != 0. + # u_idx will be the index of the corresponding cell. + (u_idx, lambda_i) = pi_bdry_c_bar.leading_item() + for (u_idx, lambda_i) in pi_bdry_c_bar.iteritems(): + if u_idx not in to_be_deleted: + break + # This element/column needs to be deleted from gens and + # iota_old. Do that later. + to_be_deleted.append(u_idx) + # pi(c) = 0. + pi_cols.append(zero_vector) + for c_j_idx, c_j in enumerate(old_cells): + # eta_ij = . + # That is, eta_ij is the u_idx entry in the vector pi_old * c_j: + eta_ij = c_j.dot_product(pi_old.row(u_idx)) + if eta_ij: + # Adjust phi(c_j). + phi_old_cols[c_j_idx] += eta_ij * lambda_i**(-1) * c_bar + # Adjust pi(c_j). + pi_cols_old[c_j_idx] -= eta_ij * lambda_i**(-1) * pi_bdry_c_bar + + # The matrices involved have many zero entries. For + # such matrices, using sparse matrices is faster over + # the rationals, slower over finite fields. + phi_old = matrix(base_ring, phi_old_cols, sparse=(base_ring==QQ)).transpose() + keep = vector(base_ring, pi_nrows, {i:1 for i in range(pi_nrows) + if i not in to_be_deleted}) + cols = [v.pairwise_product(keep) for v in pi_cols_old] + pi_old = MS_pi_t.matrix(cols).transpose() + + # Here cols is a temporary storage for the columns of iota. + cols = [iota_cols_old[i] for i in sorted(iota_cols_old.keys())] + for r in sorted(to_be_deleted, reverse=True): + del cols[r] + del gens[dim-1][r] + iota_data[dim-1] = matrix(base_ring, len(gens[dim-1]), old_rank, cols).transpose() + # keep: rows to keep in pi_cols_old. Start with all + # columns, then delete those in to_be_deleted. + keep = sorted(set(range(pi_nrows)).difference(to_be_deleted)) + # Now cols is a temporary storage for columns of pi. + cols = [v.list_from_positions(keep) for v in pi_cols_old] + pi_data[dim-1] = matrix(base_ring, old_rank, len(gens[dim-1]), cols).transpose() + phi_data[dim-1] = phi_old + + V_gens = VectorSpace(base_ring, len(gens[dim])) + if pi_cols: + cols = [] + for v in pi_cols: + cols.append(V_gens(v.list_from_positions(gens[dim]))) + pi_cols = cols + + pi_data[dim] = matrix(base_ring, rank, len(gens[dim]), pi_cols).transpose() + cols = [iota_cols[i] for i in sorted(iota_cols.keys())] + iota_data[dim] = matrix(base_ring, len(gens[dim]), rank, cols).transpose() + + # M_data will contain (trivial) matrices defining the differential + # on M. Keep track of the sizes using "M_rows" and "M_cols", which are + # just the ranks of consecutive graded pieces of M. + M_data = {} + M_rows = 0 + for n in range(K.dimension()+1): + M_cols = len(gens[n]) + M_data[n] = zero_matrix(base_ring, M_rows, M_cols) + M_rows = M_cols + + M = ChainComplex(M_data, base_ring=base_ring, degree=-1) + + pi = ChainComplexMorphism(pi_data, C, M) + iota = ChainComplexMorphism(iota_data, M, C) + phi = ChainContraction(phi_data, pi, iota) + return phi, M + diff --git a/src/sage/homology/cell_complex.py b/src/sage/homology/cell_complex.py index 8bbd56b3ad6..f0767a22201 100644 --- a/src/sage/homology/cell_complex.py +++ b/src/sage/homology/cell_complex.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Generic cell complexes @@ -37,6 +38,8 @@ from sage.structure.sage_object import SageObject from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ +from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModuleElement +from sage.misc.cachefunc import cached_method class GenericCellComplex(SageObject): r""" @@ -410,7 +413,7 @@ def homology(self, dim=None, **kwds): .. note:: The keyword arguments to this function get passed on to - :meth:``chain_complex`` and its homology. + :meth:`chain_complex` and its homology. ALGORITHM: @@ -422,14 +425,14 @@ def homology(self, dim=None, **kwds): CHomP computes homology, not cohomology, and only works over the integers or finite prime fields. Therefore if any of these conditions fails, or if CHomP is not present, or if - ``algorithm`` is set to 'no_chomp', go to plan B: if ``self`` + ``algorithm`` is set to 'no_chomp', go to plan B: if this complex has a ``_homology`` method -- each simplicial complex has this, for example -- then call that. Such a method implements specialized algorithms for the particular type of cell complex. Otherwise, move on to plan C: compute the chain complex of - ``self`` and compute its homology groups. To do this: over a + this complex and compute its homology groups. To do this: over a field, just compute ranks and nullities, thus obtaining dimensions of the homology groups as vector spaces. Over the integers, compute Smith normal form of the boundary matrices @@ -650,6 +653,266 @@ def betti(self, dim=None, subcomplex=None): except AttributeError: return H.dimension() + def n_chains(self, n, base_ring=None, cochains=False): + r""" + Return the free module of chains in degree ``n`` over ``base_ring``. + + INPUTS: + + - ``n`` -- integer + - ``base_ring`` -- ring (optional, default `\ZZ`) + - ``cochains`` -- boolean (optional, default ``False``); if + ``True``, return cochains instead + + The only difference between chains and cochains is + notation. In a simplicial complex, for example, a simplex + ``(0,1,2)`` is written as "(0,1,2)" in the group of chains but + as "\chi_(0,1,2)" in the group of cochains. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: S2.n_chains(1, QQ) + Free module generated by {(2, 3), (0, 2), (1, 3), (1, 2), (0, 3), (0, 1)} over Rational Field + sage: list(simplicial_complexes.Sphere(2).n_chains(1, QQ, cochains=False).basis()) + [(2, 3), (0, 2), (1, 3), (1, 2), (0, 3), (0, 1)] + sage: list(simplicial_complexes.Sphere(2).n_chains(1, QQ, cochains=True).basis()) + [\chi_(2, 3), \chi_(0, 2), \chi_(1, 3), \chi_(1, 2), \chi_(0, 3), \chi_(0, 1)] + """ + return Chains(tuple(self.n_cells(n)), base_ring, cochains) + + # This is cached for speed reasons: it can be very slow to run + # this function. + @cached_method + def algebraic_topological_model(self, base_ring=None): + r""" + Algebraic topological model for this cell complex with + coefficients in ``base_ring``. + + The term "algebraic topological model" is defined by Pilarczyk + and Réal [PR]_. + + This is implemented for simplicial, cubical, and + `\Delta`-complexes, not for arbitrary generic cell complexes. + + INPUT: + + - ``base_ring`` - coefficient ring (optional, default + ``QQ``). Must be a field. + + Denote by `C` the chain complex associated to this cell + complex. The algebraic topological model is a chain complex + `M` with zero differential, with the same homology as `C`, + along with chain maps `\pi: C \to M` and `\iota: M \to C` + satisfying `\iota \pi = 1_M` and `\pi \iota` chain homotopic + to `1_C`. The chain homotopy `\phi` must satisfy + + - `\phi \phi = 0`, + - `\pi \phi = 0`, + - `\phi \iota = 0`. + + Such a chain homotopy is called a *chain contraction*. + + OUTPUT: a pair consisting of + + - chain contraction ``phi`` associated to `C`, `M`, `\pi`, and + `\iota` + - the chain complex `M` + + Note that from the chain contraction ``phi``, one can recover the + chain maps `\pi` and `\iota` via ``phi.pi()`` and + ``phi.iota()``. Then one can recover `C` and `M` from, for + example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, + respectively. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: phi, M = RP2.algebraic_topological_model(GF(2)) + sage: M.homology() + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: T = simplicial_complexes.Torus() + sage: phi, M = T.algebraic_topological_model(QQ) + sage: M.homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + """ + from algebraic_topological_model import algebraic_topological_model, algebraic_topological_model_delta_complex + from cubical_complex import CubicalComplex + from simplicial_complex import SimplicialComplex + from delta_complex import DeltaComplex + if base_ring is None: + base_ring = QQ + if not isinstance(self, (CubicalComplex, SimplicialComplex, DeltaComplex)): + raise NotImplementedError('only implemented for simplicial, cubical, and Delta complexes') + if isinstance(self, DeltaComplex): + return algebraic_topological_model_delta_complex(self, base_ring) + return algebraic_topological_model(self, base_ring) + + def homology_with_basis(self, base_ring=None, cohomology=False): + r""" + Return the unreduced homology of this complex with + coefficients in ``base_ring`` with a chosen basis. + + This is implemented for simplicial, cubical, and + `\Delta`-complexes, not for arbitrary generic cell complexes. + + INPUTS: + + - ``base_ring`` -- coefficient ring (optional, default + ``QQ``); must be a field + - ``cohomology`` -- boolean (optional, default ``False``); if + ``True``, return cohomology instead of homology + + Homology basis elements are named 'h_{dim,i}' where i ranges + between 0 and `r-1`, if `r` is the rank of the homology + group. Cohomology basis elements are denoted `h^{dim,i}` + instead. + + .. SEEALSO:: + + If ``cohomology`` is ``True``, this returns the cohomology + as a graded module. For the ring structure, use + :meth:`cohomology_ring`. + + EXAMPLES:: + + sage: K = simplicial_complexes.KleinBottle() + sage: H = K.homology_with_basis(QQ); H + Homology module of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets over Rational Field + sage: sorted(H.basis(), key=str) + [h_{0,0}, h_{1,0}] + sage: H = K.homology_with_basis(GF(2)); H + Homology module of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets over Finite Field of size 2 + sage: sorted(H.basis(), key=str) + [h_{0,0}, h_{1,0}, h_{1,1}, h_{2,0}] + + The homology is constructed as a graded object, so for + example, you can ask for the basis in a single degree:: + + sage: H.basis(1) + Finite family {(1, 0): h_{1,0}, (1, 1): h_{1,1}} + sage: S3 = delta_complexes.Sphere(3) + sage: H = S3.homology_with_basis(QQ, cohomology=True) + sage: list(H.basis(3)) + [h^{3,0}] + """ + from homology_vector_space_with_basis import HomologyVectorSpaceWithBasis + if base_ring is None: + base_ring = QQ + return HomologyVectorSpaceWithBasis(base_ring, self, cohomology) + + def cohomology_ring(self, base_ring=None): + r""" + Return the unreduced cohomology with coefficients in + ``base_ring`` with a chosen basis. + + This is implemented for simplicial, cubical, and + `\Delta`-complexes, not for arbitrary generic cell complexes. + The resulting elements are suitable for computing cup + products. For simplicial complexes, they should be suitable + for computing cohomology operations; so far, only mod 2 + cohomology operations have been implemented. + + INPUTS: + + - ``base_ring`` -- coefficient ring (optional, default + ``QQ``); must be a field + + The basis elements in dimension ``dim`` are named 'h^{dim,i}' + where `i` ranges between 0 and `r-1`, if `r` is the rank of + the cohomology group. + + .. NOTE:: + + For all but the smallest complexes, this is likely to be + slower than :meth:`cohomology` (with field coefficients), + possibly by several orders of magnitute. This and its + companion :meth:`homology_with_basis` carry extra + information which allows computation of cup products, for + example, but because of speed issues, you may only wish to + use these if you need that extra information. + + EXAMPLES:: + + sage: K = simplicial_complexes.KleinBottle() + sage: H = K.cohomology_ring(QQ); H + Cohomology ring of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets over Rational Field + sage: sorted(H.basis(), key=str) + [h^{0,0}, h^{1,0}] + sage: H = K.cohomology_ring(GF(2)); H + Cohomology ring of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets over Finite Field of size 2 + sage: sorted(H.basis(), key=str) + [h^{0,0}, h^{1,0}, h^{1,1}, h^{2,0}] + + sage: X = delta_complexes.SurfaceOfGenus(2) + sage: H = X.cohomology_ring(QQ); H + Cohomology ring of Delta complex with 3 vertices and 29 simplices + over Rational Field + sage: sorted(H.basis(1), key=str) + [h^{1,0}, h^{1,1}, h^{1,2}, h^{1,3}] + + sage: H = simplicial_complexes.Torus().cohomology_ring(QQ); H + Cohomology ring of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6) and 14 facets over Rational Field + sage: x = H.basis()[1,0]; x + h^{1,0} + sage: y = H.basis()[1,1]; y + h^{1,1} + + You can compute cup products of cohomology classes:: + + sage: x.cup_product(y) + h^{2,0} + sage: x * y # alternate notation + h^{2,0} + sage: y.cup_product(x) + -h^{2,0} + sage: x.cup_product(x) + 0 + + Cohomology operations:: + + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: K = RP2.suspension() + sage: K.set_immutable() + sage: y = K.cohomology_ring(GF(2)).basis()[2,0]; y + h^{2,0} + sage: y.Sq(1) + h^{3,0} + + To compute the cohomology ring, the complex must be + "immutable". This is only relevant for simplicial complexes, + and most simplicial complexes are immutable, but certain + constructions make them mutable. The suspension is one + example, and this is the reason for calling + ``K.set_immutable()`` above. Another example:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: T = S1.product(S1) + sage: T.is_immutable() + False + sage: T.cohomology_ring() + Traceback (most recent call last): + ... + ValueError: This simplicial complex must be immutable. Call set_immutable(). + sage: T.set_immutable() + sage: T.cohomology_ring() + Cohomology ring of Simplicial complex with 9 vertices and + 18 facets over Rational Field + """ + from homology_vector_space_with_basis import CohomologyRing + if base_ring is None: + base_ring = QQ + return CohomologyRing(base_ring, self) + ############################################################ # end of chain complexes, homology ############################################################ @@ -784,3 +1047,133 @@ def _repr_(self): else: cells_string = " and 1 %s" % cell_name return Name + " complex " + vertex_string + cells_string + + +class Chains(CombinatorialFreeModule): + r""" + Class for the free module of chains and/or cochains in a given + degree. + + INPUT: + + - ``n_cells`` -- tuple of `n`-cells, which thus forms a basis for + this module + - ``base_ring`` -- optional (default `\ZZ`) + - ``cochains`` -- boolean (optional, default ``False``); if + ``True``, return cochains instead + + One difference between chains and cochains is notation. In a + simplicial complex, for example, a simplex ``(0,1,2)`` is written + as "(0,1,2)" in the group of chains but as "\chi_(0,1,2)" in the + group of cochains. + + Also, since the free modules of chains and cochains are dual, + there is a pairing `\langle c, z \rangle`, sending a cochain `c` + and a chain `z` to a scalar. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: C_2 = S2.n_chains(1) + sage: C_2_co = S2.n_chains(1, cochains=True) + sage: x = C_2.basis()[Simplex((0,2))] + sage: y = C_2.basis()[Simplex((1,3))] + sage: z = x+2*y + sage: a = C_2_co.basis()[Simplex((1,3))] + sage: b = C_2_co.basis()[Simplex((0,3))] + sage: c = 3*a-2*b + sage: z + (0, 2) + 2*(1, 3) + sage: c + -2*\chi_(0, 3) + 3*\chi_(1, 3) + sage: c.eval(z) + 6 + """ + def __init__(self, n_cells, base_ring=None, cochains=False): + """ + EXAMPLES:: + + sage: T = cubical_complexes.Torus() + sage: T.n_chains(2, QQ) + Free module generated by {[1,1] x [0,1] x [1,1] x [0,1], + [0,0] x [0,1] x [0,1] x [1,1], [0,0] x [0,1] x [1,1] x [0,1], + [0,0] x [0,1] x [0,0] x [0,1], [0,1] x [1,1] x [0,1] x [0,0], + [0,1] x [0,0] x [0,0] x [0,1], [1,1] x [0,1] x [0,1] x [0,0], + [0,1] x [1,1] x [0,0] x [0,1], [0,0] x [0,1] x [0,1] x [0,0], + [0,1] x [0,0] x [0,1] x [0,0], [0,1] x [0,0] x [1,1] x [0,1], + [0,1] x [1,1] x [1,1] x [0,1], [0,1] x [0,0] x [0,1] x [1,1], + [1,1] x [0,1] x [0,0] x [0,1], [1,1] x [0,1] x [0,1] x [1,1], + [0,1] x [1,1] x [0,1] x [1,1]} over Rational Field + sage: T.n_chains(2).dimension() + 16 + + TESTS:: + + sage: T.n_chains(2).base_ring() + Integer Ring + sage: T.n_chains(8).dimension() + 0 + sage: T.n_chains(-3).dimension() + 0 + """ + if base_ring is None: + base_ring=ZZ + self._cochains = cochains + if cochains: + CombinatorialFreeModule.__init__(self, base_ring, n_cells, + prefix='\\chi', bracket=['_', '']) + else: + CombinatorialFreeModule.__init__(self, base_ring, n_cells, + prefix='', bracket=False) + + class Element(CombinatorialFreeModuleElement): + + def eval(self, other): + """ + Evaluate this cochain on the chain ``other``. + + INPUT: + + - ``other`` -- a chain for the same cell complex in the + same dimension with the same base ring + + OUTPUT: scalar + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: C_2 = S2.n_chains(1) + sage: C_2_co = S2.n_chains(1, cochains=True) + sage: x = C_2.basis()[Simplex((0,2))] + sage: y = C_2.basis()[Simplex((1,3))] + sage: z = x+2*y + sage: a = C_2_co.basis()[Simplex((1,3))] + sage: b = C_2_co.basis()[Simplex((0,3))] + sage: c = 3*a-2*b + sage: z + (0, 2) + 2*(1, 3) + sage: c + -2*\chi_(0, 3) + 3*\chi_(1, 3) + sage: c.eval(z) + 6 + + TESTS:: + + sage: z.eval(c) # z is not a cochain + Traceback (most recent call last): + ... + ValueError: this element is not a cochain + sage: c.eval(c) # can't evaluate a cochain on a cochain + Traceback (most recent call last): + ... + ValueError: the elements are not compatible + """ + if not self.parent()._cochains: + raise ValueError('this element is not a cochain') + if not (other.parent().indices() == self.parent().indices() + and other.base_ring() == self.base_ring() + and not other.parent()._cochains): + raise ValueError('the elements are not compatible') + result = sum(coeff * other.coefficient(cell) for cell, coeff in self) + return result + diff --git a/src/sage/homology/chain_complex_homspace.py b/src/sage/homology/chain_complex_homspace.py index d63f9e690e7..4bfc7e2da34 100644 --- a/src/sage/homology/chain_complex_homspace.py +++ b/src/sage/homology/chain_complex_homspace.py @@ -23,7 +23,9 @@ sage: i = H.identity() sage: x = i.associated_chain_complex_morphism(augmented=True) sage: x - Chain complex morphism from Chain complex with at most 4 nonzero terms over Integer Ring to Chain complex with at most 4 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 4 nonzero terms over Integer Ring + To: Chain complex with at most 4 nonzero terms over Integer Ring sage: x._matrix_dictionary {-1: [1], 0: [1 0 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0 0] @@ -62,11 +64,15 @@ sage: i = A.identity() sage: x = i.associated_chain_complex_morphism() sage: x - Chain complex morphism from Chain complex with at most 3 nonzero terms over Integer Ring to Chain complex with at most 3 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring sage: y = x*4 sage: z = y*y sage: (y+z) - Chain complex morphism from Chain complex with at most 3 nonzero terms over Integer Ring to Chain complex with at most 3 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring sage: f = x._matrix_dictionary sage: C = S.chain_complex() sage: G = Hom(C,C) @@ -93,7 +99,7 @@ #***************************************************************************** import sage.categories.homset -import sage.homology.chain_complex_morphism as chain_complex_morphism +from sage.homology.chain_complex_morphism import ChainComplexMorphism def is_ChainComplexHomspace(x): """ @@ -142,4 +148,4 @@ def __call__(self, f): True """ - return chain_complex_morphism.ChainComplexMorphism(f, self.domain(), self.codomain()) + return ChainComplexMorphism(f, self.domain(), self.codomain()) diff --git a/src/sage/homology/chain_complex_morphism.py b/src/sage/homology/chain_complex_morphism.py index 4f3e6ffc9a2..5e79d25db3b 100644 --- a/src/sage/homology/chain_complex_morphism.py +++ b/src/sage/homology/chain_complex_morphism.py @@ -14,7 +14,6 @@ EXAMPLES:: - from sage.matrix.constructor import zero_matrix sage: S = simplicial_complexes.Sphere(1) sage: S Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} @@ -27,7 +26,7 @@ sage: G = Hom(C,C) sage: x = G(f) sage: x - Chain complex morphism from Chain complex with at most 2 nonzero terms over Integer Ring to Chain complex with at most 2 nonzero terms over Integer Ring + Chain complex endomorphism of Chain complex with at most 2 nonzero terms over Integer Ring sage: x._matrix_dictionary {0: [0 0 0] [0 0 0] @@ -52,9 +51,10 @@ # #***************************************************************************** -import sage.matrix.all as matrix -from sage.structure.sage_object import SageObject -from sage.rings.integer_ring import ZZ +from sage.matrix.constructor import block_diagonal_matrix, zero_matrix +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.categories.category_types import ChainComplexes def is_ChainComplexMorphism(x): """ @@ -71,14 +71,15 @@ def is_ChainComplexMorphism(x): sage: i = H.identity() sage: x = i.associated_chain_complex_morphism() sage: x # indirect doctest - Chain complex morphism from Chain complex with at most 7 nonzero terms over - Integer Ring to Chain complex with at most 7 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 7 nonzero terms over Integer Ring + To: Chain complex with at most 7 nonzero terms over Integer Ring sage: is_ChainComplexMorphism(x) True """ return isinstance(x,ChainComplexMorphism) -class ChainComplexMorphism(SageObject): +class ChainComplexMorphism(Morphism): """ An element of this class is a morphism of chain complexes. """ @@ -88,7 +89,6 @@ def __init__(self, matrices, C, D, check=True): EXAMPLES:: - from sage.matrix.constructor import zero_matrix sage: S = simplicial_complexes.Sphere(1) sage: S Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} @@ -101,9 +101,7 @@ def __init__(self, matrices, C, D, check=True): sage: G = Hom(C,C) sage: x = G(f) sage: x - Chain complex morphism from Chain complex with at most 2 nonzero terms - over Integer Ring to Chain complex with at most 2 nonzero terms over - Integer Ring + Chain complex endomorphism of Chain complex with at most 2 nonzero terms over Integer Ring sage: x._matrix_dictionary {0: [0 0 0] [0 0 0] @@ -117,9 +115,9 @@ def __init__(self, matrices, C, D, check=True): sage: Y = simplicial_complexes.Simplex(0) sage: g = Hom(X,Y)({0:0, 1:0}) sage: g.associated_chain_complex_morphism() - Chain complex morphism from Chain complex with at most 2 nonzero - terms over Integer Ring to Chain complex with at most 1 nonzero terms - over Integer Ring + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 1 nonzero terms over Integer Ring Check that an error is raised if the matrices are the wrong size:: @@ -130,7 +128,9 @@ def __init__(self, matrices, C, D, check=True): ... ValueError: matrix in degree 0 is not the right size sage: Hom(C,D)({0: matrix(2, 1, [1, 1])}) # 2x1 is right. - Chain complex morphism from Chain complex with at most 1 nonzero terms over Integer Ring to Chain complex with at most 1 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 1 nonzero terms over Integer Ring + To: Chain complex with at most 1 nonzero terms over Integer Ring """ if not C.base_ring() == D.base_ring(): raise NotImplementedError('morphisms between chain complexes of different' @@ -151,9 +151,9 @@ def __init__(self, matrices, C, D, check=True): try: matrices[i] = initial_matrices.pop(i) except KeyError: - matrices[i] = matrix.zero_matrix(C.base_ring(), - D.differential(i).ncols(), - C.differential(i).ncols(), sparse=True) + matrices[i] = zero_matrix(C.base_ring(), + D.differential(i).ncols(), + C.differential(i).ncols(), sparse=True) if check: # All remaining matrices given must be 0x0. if not all(m.ncols() == m.nrows() == 0 for m in initial_matrices.values()): @@ -177,9 +177,13 @@ def __init__(self, matrices, C, D, check=True): mC = matrices[i+d] * C.differential(i) if mC != Dm: raise ValueError('matrices must define a chain complex morphism') - self._matrix_dictionary = matrices - self._domain = C - self._codomain = D + self._matrix_dictionary = {} + for i in matrices: + m = matrices[i] + # Use immutable matrices because they're hashable. + m.set_immutable() + self._matrix_dictionary[i] = m + Morphism.__init__(self, Hom(C,D, ChainComplexes(C.base_ring()))) def in_degree(self, n): """ @@ -212,10 +216,78 @@ def in_degree(self, n): try: return self._matrix_dictionary[n] except KeyError: - from sage.matrix.constructor import zero_matrix - rows = self._codomain.free_module_rank(n) - cols = self._domain.free_module_rank(n) - return zero_matrix(self._domain.base_ring(), rows, cols) + rows = self.codomain().free_module_rank(n) + cols = self.domain().free_module_rank(n) + return zero_matrix(self.domain().base_ring(), rows, cols) + + def to_matrix(self, deg=None): + """ + The matrix representing this chain map. + + If the degree ``deg`` is specified, return the matrix in that + degree; otherwise, return the (block) matrix for the whole + chain map. + + INPUTS: + + - ``deg`` -- (optional, default ``None``) the degree + + EXAMPLES:: + + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: f.to_matrix(0) + [1] + sage: f.to_matrix() + [1|0|] + [-+-+] + [0|0|] + [-+-+] + [0|0|] + """ + if deg is not None: + return self.in_degree(deg) + row = 0 + col = 0 + blocks = [self._matrix_dictionary[n] + for n in sorted(self._matrix_dictionary.keys())] + return block_diagonal_matrix(blocks) + + def dual(self): + """ + The dual chain map to this one. + + That is, the map from the dual of the codomain of this one to + the dual of its domain, represented in each degree by the + transpose of the corresponding matrix. + + EXAMPLES:: + + sage: X = simplicial_complexes.Simplex(1) + sage: Y = simplicial_complexes.Simplex(0) + sage: g = Hom(X,Y)({0:0, 1:0}) + sage: f = g.associated_chain_complex_morphism() + sage: f.in_degree(0) + [1 1] + sage: f.dual() + Chain complex morphism: + From: Chain complex with at most 1 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring + sage: f.dual().in_degree(0) + [1] + [1] + sage: ascii_art(f.domain()) + [-1] + [ 1] + 0 <-- C_0 <----- C_1 <-- 0 + sage: ascii_art(f.dual().codomain()) + [-1 1] + 0 <-- C_1 <-------- C_0 <-- 0 + """ + matrix_dict = self._matrix_dictionary + matrices = {i: matrix_dict[i].transpose() for i in matrix_dict} + return ChainComplexMorphism(matrices, self.codomain().dual(), self.domain().dual()) def __neg__(self): """ @@ -248,7 +320,7 @@ def __neg__(self): f = dict() for i in self._matrix_dictionary.keys(): f[i] = -self._matrix_dictionary[i] - return ChainComplexMorphism(f, self._domain, self._codomain) + return ChainComplexMorphism(f, self.domain(), self.codomain()) def __add__(self,x): """ @@ -278,12 +350,12 @@ def __add__(self,x): [0 0 0 2]} """ - if not isinstance(x,ChainComplexMorphism) or self._codomain != x._codomain or self._domain != x._domain or self._matrix_dictionary.keys() != x._matrix_dictionary.keys(): + if not isinstance(x,ChainComplexMorphism) or self.codomain() != x.codomain() or self.domain() != x.domain() or self._matrix_dictionary.keys() != x._matrix_dictionary.keys(): raise TypeError("Unsupported operation.") f = dict() for i in self._matrix_dictionary.keys(): f[i] = self._matrix_dictionary[i] + x._matrix_dictionary[i] - return ChainComplexMorphism(f, self._domain, self._codomain) + return ChainComplexMorphism(f, self.domain(), self.codomain()) def __mul__(self,x): """ @@ -350,25 +422,27 @@ def __mul__(self,x): sage: f = ChainComplexMorphism({}, C0, C1) sage: g = ChainComplexMorphism({}, C1, C2) sage: g * f - Chain complex morphism from Chain complex with at most 1 nonzero terms over Integer Ring to Chain complex with at most 1 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 1 nonzero terms over Integer Ring + To: Chain complex with at most 1 nonzero terms over Integer Ring sage: f._matrix_dictionary {0: [], 1: []} sage: g._matrix_dictionary {1: [], 2: []} """ - if not isinstance(x,ChainComplexMorphism) or self._domain != x._codomain: + if not isinstance(x,ChainComplexMorphism) or self.domain() != x.codomain(): try: - y = self._domain.base_ring()(x) + y = self.domain().base_ring()(x) except TypeError: raise TypeError("multiplication is not defined") f = dict() for i in self._matrix_dictionary: f[i] = self._matrix_dictionary[i] * y - return ChainComplexMorphism(f,self._domain,self._codomain) + return ChainComplexMorphism(f,self.domain(),self.codomain()) f = dict() for i in self._matrix_dictionary: f[i] = self._matrix_dictionary[i]*x.in_degree(i) - return ChainComplexMorphism(f,x._domain,self._codomain) + return ChainComplexMorphism(f,x.domain(),self.codomain()) def __rmul__(self,x): """ @@ -386,13 +460,13 @@ def __rmul__(self,x): False """ try: - y = self._domain.base_ring()(x) + y = self.domain().base_ring()(x) except TypeError: raise TypeError("multiplication is not defined") f = dict() for i in self._matrix_dictionary.keys(): f[i] = y * self._matrix_dictionary[i] - return ChainComplexMorphism(f,self._domain,self._codomain) + return ChainComplexMorphism(f,self.domain(),self.codomain()) def __sub__(self,x): """ @@ -420,7 +494,6 @@ def __sub__(self,x): [0 0 0 0] [0 0 0 0] [0 0 0 0]} - """ return self + (-x) @@ -435,8 +508,9 @@ def __eq__(self,x): sage: i = H.identity() sage: x = i.associated_chain_complex_morphism() sage: x - Chain complex morphism from Trivial chain complex over Integer Ring - to Trivial chain complex over Integer Ring + Chain complex morphism: + From: Trivial chain complex over Integer Ring + To: Trivial chain complex over Integer Ring sage: f = x._matrix_dictionary sage: C = S.chain_complex() sage: G = Hom(C,C) @@ -445,13 +519,13 @@ def __eq__(self,x): True """ return isinstance(x,ChainComplexMorphism) \ - and self._codomain == x._codomain \ - and self._domain == x._domain \ + and self.codomain() == x.codomain() \ + and self.domain() == x.domain() \ and self._matrix_dictionary == x._matrix_dictionary - def _repr_(self): + def is_identity(self): """ - Return the string representation of ``self``. + True if this is the identity map. EXAMPLES:: @@ -459,12 +533,73 @@ def _repr_(self): sage: H = Hom(S,S) sage: i = H.identity() sage: x = i.associated_chain_complex_morphism() - sage: x - Chain complex morphism from Trivial chain complex over Integer Ring - to Trivial chain complex over Integer Ring - sage: x._repr_() - 'Chain complex morphism from Trivial chain complex over Integer Ring - to Trivial chain complex over Integer Ring' + sage: x.is_identity() + True + """ + return self.to_matrix().is_one() + + def is_surjective(self): """ - return "Chain complex morphism from {} to {}".format(self._domain, self._codomain) + True if this map is surjective. + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: H = Hom(S1, S1) + sage: flip = H({0:0, 1:2, 2:1}) + sage: flip.associated_chain_complex_morphism().is_surjective() + True + + sage: pt = simplicial_complexes.Simplex(0) + sage: inclusion = Hom(pt, S1)({0:2}) + sage: inclusion.associated_chain_complex_morphism().is_surjective() + False + sage: inclusion.associated_chain_complex_morphism(cochain=True).is_surjective() + True + """ + m = self.to_matrix() + return m.rank() == m.nrows() + + def is_injective(self): + """ + True if this map is injective. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: H = Hom(S1, S1) + sage: flip = H({0:0, 1:2, 2:1}) + sage: flip.associated_chain_complex_morphism().is_injective() + True + + sage: pt = simplicial_complexes.Simplex(0) + sage: inclusion = Hom(pt, S1)({0:2}) + sage: inclusion.associated_chain_complex_morphism().is_injective() + True + sage: inclusion.associated_chain_complex_morphism(cochain=True).is_injective() + False + """ + return self.to_matrix().right_nullity() == 0 + + def __hash__(self): + """ + TESTS:: + + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1)}) + sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: hash(f) # random + 17 + """ + return hash(self.domain()) ^ hash(self.codomain()) ^ hash(tuple(self._matrix_dictionary.items())) + + def _repr_type(self): + """ + EXAMPLES:: + + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1)}) + sage: Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)})._repr_type() + 'Chain complex' + """ + return "Chain complex" diff --git a/src/sage/homology/chain_homotopy.py b/src/sage/homology/chain_homotopy.py new file mode 100644 index 00000000000..11350ec6c43 --- /dev/null +++ b/src/sage/homology/chain_homotopy.py @@ -0,0 +1,600 @@ +# -*- coding: utf-8 -*- +r""" +Chain homotopies and chain contractions + +Chain homotopies are standard constructions in homological algebra: +given chain complexes `C` and `D` and chain maps `f, g: C \to D`, say +with differential of degree `-1`, a *chain homotopy* `H` between `f` and +`g` is a collection of maps `H_n: C_n \to D_{n+1}` satisfying + +.. MATH:: + + \partial_D H + H \partial_C = f - g. + +The presence of a chain homotopy defines an equivalence relation +(*chain homotopic*) on chain maps. If `f` and `g` are chain homotopic, +then one can show that `f` and `g` induce the same map on homology. + +Chain contractions are not as well known. The papers [M-AR]_, [RM-A]_, +and [PR]_ provide some references. Given two chain complexes `C` and +`D`, a *chain contraction* is a chain homotopy `H: C \to C` for which +there are chain maps `\pi: C \to D` ("projection") and `\iota: D \to +C` ("inclusion") such that + +- `H` is a chain homotopy between `1_C` and `\iota \pi`, +- `\pi \iota = 1_D`, +- `\pi H = 0`, +- `H \iota = 0`, +- `H H = 0`. + +Such a chain homotopy provides a strong relation between the chain +complexes `C` and `D`; for example, their homology groups are +isomorphic. + +REFERENCES: + +.. [M-AR] H. Molina-Abril and P. Réal, *Homology computation using spanning + trees* in Progress in Pattern Recognition, Image Analysis, + Computer Vision, and Applications, Lecture Notes in Computer + Science, volume 5856, pp 272-278, Springer, Berlin (2009). + +.. [PR] P. Pilarczyk and P. Réal, *Computation of cubical homology, + cohomology, and (co)homological operations via chain contraction*, + Adv. Comput. Math. 41 (2015), pp 253--275. + +.. [RM-A] P. Réal and H. Molina-Abril, *Cell AT-models for digital + volumes* in Torsello, Escolano, Brun (eds.), Graph-Based + Representations in Pattern Recognition, Lecture Notes in + Computer Science, volume 5534, pp. 314-3232, Springer, Berlin (2009). +""" + +######################################################################## +# Copyright (C) 2015 John H. Palmieri +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# http://www.gnu.org/licenses/ +######################################################################## + +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.homology.chain_complex_morphism import ChainComplexMorphism + +# In a perfect world, this would inherit from something like +# "TwoMorphism" rather than "Morphism"... +class ChainHomotopy(Morphism): + r""" + A chain homotopy. + + A chain homotopy `H` between chain maps `f, g: C \to D` is a sequence + of maps `H_n: C_n \to D_{n+1}` (if the chain complexes are graded + homologically) satisfying + + .. MATH:: + + \partial_D H + H \partial_C = f - g. + + INPUT: + + - ``matrices`` -- dictionary of matrices, keyed by dimension + - ``f`` -- chain map `C \to D` + - ``g`` (optional) -- chain map `C \to D` + + The dictionary ``matrices`` defines ``H`` by specifying the matrix + defining it in each degree: the entry `m` corresponding to key `i` + gives the linear transformation `C_i \to D_{i+1}`. + + If `f` is specified but not `g`, then `g` can be recovered from + the defining formula. That is, if `g` is not specified, then it + is defined to be `f - \partial_D H - H \partial_C`. + + Note that the degree of the differential on the chain complex `C` + must agree with that for `D`, and those degrees determine the + "degree" of the chain homotopy map: if the degree of the + differential is `d`, then the chain homotopy consists of a + sequence of maps `C_n \to C_{n-d}`. The keys in the dictionary + ``matrices`` specify the starting degrees. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1)}) + sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: g = Hom(C,D)({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1)}, f, g) + + Note that the maps `f` and `g` are stored in the attributes ``H._f`` + and ``H._g``:: + + sage: H._f + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring + sage: H._f.in_degree(0) + [1] + sage: H._g.in_degree(0) + [0] + + A non-example:: + + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1)}, f, g) + Traceback (most recent call last): + ... + ValueError: the data do not define a valid chain homotopy + """ + def __init__(self, matrices, f, g=None): + r""" + Create a chain homotopy between the given chain maps + from a dictionary of matrices. + + EXAMPLES: + + If ``g`` is not specified, it is set equal to + `f - (H \partial + \partial H)`. :: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({1: matrix(ZZ, 1, 2, (1,0)), 2: matrix(ZZ, 2, 1, (0, 2))}, degree_of_differential=-1) + sage: D = ChainComplex({2: matrix(ZZ, 1, 1, (6,))}, degree_of_differential=-1) + sage: f_d = {1: matrix(ZZ, 1, 2, (0,3)), 2: identity_matrix(ZZ, 1)} + sage: f = Hom(C,D)(f_d) + sage: H_d = {0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 2, (2,2))} + sage: H = ChainHomotopy(H_d, f) + sage: H._g.in_degree(0) + [] + sage: H._g.in_degree(1) + [-13 -9] + sage: H._g.in_degree(2) + [-3] + + TESTS: + + Try to construct a chain homotopy in which the maps do not + have matching domains and codomains:: + + sage: g = Hom(C,C)({}) # the zero chain map + sage: H = ChainHomotopy(H_d, f, g) + Traceback (most recent call last): + ... + ValueError: the chain maps are not compatible + """ + domain = f.domain() + codomain = f.codomain() + deg = domain.degree_of_differential() + # Check that the chain complexes are compatible. This should + # never arise, because first there should be errors in + # constructing the chain maps. But just in case... + if domain.degree_of_differential() != codomain.degree_of_differential(): + raise ValueError('the chain complexes are not compatible') + if g is not None: + # Check that the chain maps are compatible. + if not (domain == g.domain() and codomain == + g.codomain()): + raise ValueError('the chain maps are not compatible') + # Check that the data define a chain homotopy. + for i in domain.differential(): + if i in matrices and i+deg in matrices: + if not (codomain.differential(i-deg) * matrices[i] + matrices[i+deg] * domain.differential(i) == f.in_degree(i) - g.in_degree(i)): + raise ValueError('the data do not define a valid chain homotopy') + elif i in matrices: + if not (codomain.differential(i-deg) * matrices[i] == f.in_degree(i) - g.in_degree(i)): + raise ValueError('the data do not define a valid chain homotopy') + elif i+deg in matrices: + if not (matrices[i+deg] * domain.differential(i) == f.in_degree(i) - g.in_degree(i)): + raise ValueError('the data do not define a valid chain homotopy') + else: + # Define g. + g_data = {} + for i in domain.differential(): + if i in matrices and i+deg in matrices: + g_data[i] = f.in_degree(i) - matrices[i+deg] * domain.differential(i) - codomain.differential(i-deg) * matrices[i] + elif i in matrices: + g_data[i] = f.in_degree(i) - codomain.differential(i-deg) * matrices[i] + elif i+deg in matrices: + g_data[i] = f.in_degree(i) - matrices[i+deg] * domain.differential(i) + g = ChainComplexMorphism(g_data, domain, codomain) + self._matrix_dictionary = {} + for i in matrices: + m = matrices[i] + # Use immutable matrices because they're hashable. + m.set_immutable() + self._matrix_dictionary[i] = m + self._f = f + self._g = g + Morphism.__init__(self, Hom(domain, codomain)) + + def is_algebraic_gradient_vector_field(self): + r""" + An algebraic gradient vector field is a linear map + `H: C \to C` such that `H H = 0`. + + (Some authors also require that `H \partial H = H`, whereas + some make this part of the definition of "homology gradient + vector field. We have made the second choice.) See + Molina-Abril and Réal [M-AR]_ and Réal and Molina-Abril + [RM-A]_ for this and related terminology. + + See also :meth:`is_homology_gradient_vector_field`. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)}) + + The chain complex `C` is chain homotopy equivalent to a copy of + `\ZZ` in degree 0. Two chain maps `C \to C` will be chain + homotopic as long as they agree in degree 0. :: + + sage: f = Hom(C,C)({0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 1, [3]), 2: matrix(ZZ, 1, 1, [3])}) + sage: g = Hom(C,C)({0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 1, [2]), 2: matrix(ZZ, 1, 1, [2])}) + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, f, g) + sage: H.is_algebraic_gradient_vector_field() + True + + A chain homotopy which is not an algebraic gradient vector field:: + + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, f, g) + sage: H.is_algebraic_gradient_vector_field() + False + """ + if self.domain() != self.codomain(): + return False + deg = self.domain().degree_of_differential() + matrices = self._matrix_dictionary + for i in matrices: + if i-deg in matrices: + if matrices[i-deg] * matrices[i] != 0: + return False + return True + + def is_homology_gradient_vector_field(self): + r""" + A homology gradient vector field is an algebraic gradient vector + field `H: C \to C` (i.e., a chain homotopy satisfying `H + H = 0`) such that `\partial H \partial = \partial` and `H + \partial H = H`. + + See Molina-Abril and Réal [M-AR]_ and Réal and Molina-Abril + [RM-A]_ for this and related terminology. + + See also :meth:`is_algebraic_gradient_vector_field`. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)}) + + sage: f = Hom(C,C)({0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 1, [3]), 2: matrix(ZZ, 1, 1, [3])}) + sage: g = Hom(C,C)({0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 1, [2]), 2: matrix(ZZ, 1, 1, [2])}) + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, f, g) + sage: H.is_homology_gradient_vector_field() + True + """ + if not self.is_algebraic_gradient_vector_field(): + return False + deg = self.domain().degree_of_differential() + matrices = self._matrix_dictionary + for i in matrices: + if i+deg in matrices: + diff_i = self.domain().differential(i) + if diff_i * matrices[i+deg] * diff_i != diff_i: + return False + if matrices[i] * self.domain().differential(i-deg) * matrices[i] != matrices[i]: + return False + return True + + def in_degree(self, n): + """ + The matrix representing this chain homotopy in degree ``n``. + + INPUT: + + - ``n`` -- degree + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({1: matrix(ZZ, 0, 2)}) # one nonzero term in degree 1 + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) # one nonzero term in degree 0 + sage: f = Hom(C, D)({}) + sage: H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f) + sage: H.in_degree(1) + [3 1] + + This returns an appropriately sized zero matrix if the chain + homotopy is not defined in degree n:: + + sage: H.in_degree(-3) + [] + """ + try: + return self._matrix_dictionary[n] + except KeyError: + from sage.matrix.constructor import zero_matrix + deg = self.domain().degree_of_differential() + rows = self.codomain().free_module_rank(n-deg) + cols = self.domain().free_module_rank(n) + return zero_matrix(self.domain().base_ring(), rows, cols) + + def dual(self): + r""" + Dual chain homotopy to this one. + + That is, if this one is a chain homotopy between chain maps + `f, g: C \to D`, then its dual is a chain homotopy between the + dual of `f` and the dual of `g`, from `D^*` to `C^*`. It is + represented in each degree by the transpose of the + corresponding matrix. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({1: matrix(ZZ, 0, 2)}) # one nonzero term in degree 1 + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) # one nonzero term in degree 0 + sage: f = Hom(C, D)({}) + sage: H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f) + sage: H.in_degree(1) + [3 1] + sage: H.dual().in_degree(0) + [3] + [1] + """ + matrix_dict = self._matrix_dictionary + deg = self.domain().degree_of_differential() + matrices = {i-deg: matrix_dict[i].transpose() for i in matrix_dict} + return ChainHomotopy(matrices, self._f.dual(), self._g.dual()) + + def __hash__(self): + """ + TESTS:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({1: matrix(ZZ, 0, 2)}) # one nonzero term in degree 1 + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) # one nonzero term in degree 0 + sage: f = Hom(C, D)({}) + sage: H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f) + sage: hash(H) # random + 314159265358979 + """ + return hash(self._f) ^ hash(self._g) ^ hash(tuple(self._matrix_dictionary.items())) + + def _repr_(self): + """ + String representation + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1)}) + sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: g = Hom(C,D)({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1)}, f, g) + Chain homotopy between: + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring + and Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring + """ + s = 'Chain homotopy between:' + s += '\n {}'.format('\n '.join(self._f._repr_().split('\n'))) + s += '\n and {}'.format('\n '.join(self._g._repr_().split('\n'))) + return s + +class ChainContraction(ChainHomotopy): + r""" + A chain contraction. + + An algebraic gradient vector field `H: C \to C` (that is a chain + homotopy satisfying `H H = 0`) for which there are chain + maps `\pi: C \to D` ("projection") and `\iota: D \to C` + ("inclusion") such that + + - `H` is a chain homotopy between `1_C` and `\iota \pi`, + - `\pi \iota = 1_D`, + - `\pi H = 0`, + - `H \iota = 0`. + + ``H`` is defined by a dictionary ``matrices`` of matrices. + + INPUTS: + + - ``matrices`` -- dictionary of matrices, keyed by dimension + - ``pi`` -- a chain map `C \to D` + - ``iota`` -- a chain map `D \to C` + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainContraction + sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) + + The chain complex `C` is chain homotopy equivalent to `D`, which is just + a copy of `\ZZ` in degree 0, and we construct a chain contraction:: + + sage: pi = Hom(C,D)({0: identity_matrix(ZZ, 1)}) + sage: iota = Hom(D,C)({0: identity_matrix(ZZ, 1)}) + sage: H = ChainContraction({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, pi, iota) + """ + def __init__(self, matrices, pi, iota): + r""" + Create a chain contraction from the given data. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainContraction + sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) + + The chain complex `C` is chain homotopy equivalent to `D`, + which is just a copy of `\ZZ` in degree 0, and we try + construct a chain contraction, but get the map `\iota` wrong:: + + sage: pi = Hom(C,D)({0: identity_matrix(ZZ, 1)}) + sage: iota = Hom(D,C)({0: zero_matrix(ZZ, 1)}) + sage: H = ChainContraction({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, pi, iota) + Traceback (most recent call last): + ... + ValueError: the composite 'pi iota' is not the identity + + Another bad `\iota`:: + + sage: iota = pi # wrong domain, codomain + sage: H = ChainContraction({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, pi, iota) + Traceback (most recent call last): + ... + ValueError: the chain maps are not composable + + `\iota` is okay, but wrong data defining `H`:: + + sage: iota = Hom(D,C)({0: identity_matrix(ZZ, 1)}) + sage: H = ChainContraction({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, pi, iota) + Traceback (most recent call last): + ... + ValueError: not an algebraic gradient vector field + """ + from sage.matrix.constructor import identity_matrix + from chain_complex_morphism import ChainComplexMorphism + + if not (pi.domain() == iota.codomain() + and pi.codomain() == iota.domain()): + raise ValueError('the chain maps are not composable') + C = pi.domain() + D = pi.codomain() + base_ring = C.base_ring() + + # Check that the composite 'pi iota' is 1. + for i in D.nonzero_degrees(): + if pi.in_degree(i) * iota.in_degree(i) != identity_matrix(base_ring, D.free_module_rank(i)): + raise ValueError("the composite 'pi iota' is not the identity") + + # Construct the chain map 'id_C'. + id_C_dict = {} + for i in C.nonzero_degrees(): + id_C_dict[i] = identity_matrix(base_ring, C.free_module_rank(i)) + id_C = ChainComplexMorphism(id_C_dict, C, C) + + # Now check that + # - `H` is a chain homotopy between `id_C` and `\iota \pi` + # - `HH = 0` + ChainHomotopy.__init__(self, matrices, id_C, iota * pi) + if not self.is_algebraic_gradient_vector_field(): + raise ValueError('not an algebraic gradient vector field') + # Check that `\pi H = 0`: + deg = C.degree_of_differential() + for i in matrices: + if pi.in_degree(i-deg) * matrices[i] != 0: + raise ValueError('the data do not define a valid chain contraction: pi H != 0') + # Check that `H \iota = 0`: + for i in iota._matrix_dictionary: + if i in matrices: + if matrices[i] * iota.in_degree(i) != 0: + raise ValueError('the data do not define a valid chain contraction: H iota != 0') + self._pi = pi + self._iota = iota + + def pi(self): + r""" + The chain map `\pi` associated to this chain contraction. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: phi, M = S2.algebraic_topological_model(QQ) + sage: phi.pi() + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + sage: phi.pi().in_degree(0) # Every vertex represents a homology class. + [1 1 1 1] + sage: phi.pi().in_degree(1) # No homology in degree 1. + [] + + The degree 2 homology generator is detected on a single simplex:: + + sage: phi.pi().in_degree(2) + [0 0 0 1] + """ + return self._pi + + def iota(self): + r""" + The chain map `\iota` associated to this chain contraction. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: phi, M = S2.algebraic_topological_model(QQ) + sage: phi.iota() + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + + Lifting the degree zero homology class gives a single vertex:: + + sage: phi.iota().in_degree(0) + [0] + [0] + [0] + [1] + + Lifting the degree two homology class gives the signed sum of + all of the 2-simplices:: + + sage: phi.iota().in_degree(2) + [-1] + [-1] + [ 1] + [ 1] + """ + return self._iota + + def dual(self): + """ + The chain contraction dual to this one. + + This is useful when switching from homology to cohomology. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: phi, M = S2.algebraic_topological_model(QQ) + sage: phi.iota() + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + + Lifting the degree zero homology class gives a single vertex, + but the degree zero cohomology class needs to be detected on + every vertex, and vice versa for degree 2:: + + sage: phi.iota().in_degree(0) + [0] + [0] + [0] + [1] + sage: phi.dual().iota().in_degree(0) + [1] + [1] + [1] + [1] + sage: phi.iota().in_degree(2) + [-1] + [-1] + [ 1] + [ 1] + sage: phi.dual().iota().in_degree(2) + [0] + [0] + [0] + [1] + """ + matrix_dict = self._matrix_dictionary + deg = self.domain().degree_of_differential() + matrices = {i-deg: matrix_dict[i].transpose() for i in matrix_dict} + return ChainContraction(matrices, self.iota().dual(), self.pi().dual()) + diff --git a/src/sage/homology/cubical_complex.py b/src/sage/homology/cubical_complex.py index 9214a7b4f6a..07e5e3a19e5 100644 --- a/src/sage/homology/cubical_complex.py +++ b/src/sage/homology/cubical_complex.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Finite cubical complexes @@ -547,6 +548,67 @@ def _triangulation_(self): simplices.append(S.join(Simplex((v,)), rename_vertices=False)) return simplices + def alexander_whitney(self, dim): + r""" + Subdivide this cube into pairs of cubes. + + This provides a cubical approximation for the diagonal map + `K \to K \times K`. + + INPUT: + + - ``dim`` -- integer between 0 and one more than the + dimension of this cube + + OUTPUT: + + - a list containing triples ``(coeff, left, right)`` + + This uses the algorithm described by Pilarczyk and Réal [PR]_ + on p. 267; the formula is originally due to Serre. Calling + this method ``alexander_whitney`` is an abuse of notation, + since the actual Alexander-Whitney map goes from `C(K \times + L) \to C(K) \otimes C(L)`, where `C(-)` denotes the associated + chain complex, but this subdivision of cubes is at the heart + of it. + + EXAMPLES:: + + sage: from sage.homology.cubical_complex import Cube + sage: C1 = Cube([[0,1], [3,4]]) + sage: C1.alexander_whitney(0) + [(1, [0,0] x [3,3], [0,1] x [3,4])] + sage: C1.alexander_whitney(1) + [(1, [0,1] x [3,3], [1,1] x [3,4]), (-1, [0,0] x [3,4], [0,1] x [4,4])] + sage: C1.alexander_whitney(2) + [(1, [0,1] x [3,4], [1,1] x [4,4])] + """ + from sage.sets.set import Set + N = Set(self.nondegenerate_intervals()) + result = [] + for J in N.subsets(dim): + Jprime = N.difference(J) + nu = 0 + for i in J: + for j in Jprime: + if j +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# http://www.gnu.org/licenses/ +######################################################################## + +# To do: implement morphisms of cubical complexes, with methods +# - domain +# - codomain +# - associated_chain_complex_morphism +# Once this is done, the code here ought to work without modification. + +from sage.categories.graded_algebras_with_basis import GradedAlgebrasWithBasis +from sage.categories.graded_modules_with_basis import GradedModulesWithBasis +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.rings.rational_field import QQ + +class InducedHomologyMorphism(Morphism): + r""" + An element of this class is a morphism of (co)homology groups + induced by a map of simplicial complexes. It requires working + with field coefficients. + + INPUTS: + + - ``map`` -- the map of simplicial complexes + - ``base_ring`` -- a field (optional, default ``QQ``) + - ``cohomology`` -- boolean (optional, default ``False``). If + ``True``, return the induced map in cohomology rather than + homology. + + .. note:: + + This is not intended to be used directly by the user, but instead + via the method + :meth:`~sage.homology.simplicial_complex_morphism.SimplicialComplexMorphism.induced_homology_morphism`. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: H = Hom(S1, S1) + sage: f = H({0:0, 1:2, 2:1}) # f switches two vertices + sage: f_star = f.induced_homology_morphism(QQ, cohomology=True) + sage: f_star + Graded algebra endomorphism of Cohomology ring of Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} over Rational Field + Defn: induced by: + Simplicial complex endomorphism of Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + Defn: 0 |--> 0 + 1 |--> 2 + 2 |--> 1 + sage: f_star.to_matrix(1) + [-1] + sage: f_star.to_matrix() + [ 1| 0] + [--+--] + [ 0|-1] + + sage: T = simplicial_complexes.Torus() + sage: y = T.homology_with_basis(QQ).basis()[(1,1)] + sage: y.to_cycle() + (0, 3) - (0, 6) + (3, 6) + + Since `(0,3) - (0,6) + (3,6)` is a cycle representing a homology + class in the torus, we can define a map `S^1 \to T` inducing an + inclusion on `H_1`:: + + sage: Hom(S1, T)({0:0, 1:3, 2:6}) + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 14 facets + Defn: 0 |--> 0 + 1 |--> 3 + 2 |--> 6 + sage: g = Hom(S1, T)({0:0, 1:3, 2: 6}) + sage: g_star = g.induced_homology_morphism(QQ) + sage: g_star.to_matrix(0) + [1] + sage: g_star.to_matrix(1) + [0] + [1] + sage: g_star.to_matrix() + [1|0] + [-+-] + [0|0] + [0|1] + [-+-] + [0|0] + + We can evaluate such a map on (co)homology classes:: + + sage: H = S1.homology_with_basis(QQ) + sage: a = H.basis()[(1,0)] + sage: g_star(a) + h_{1,1} + + sage: T = S1.product(S1, is_mutable=False) + sage: diag = Hom(S1,T).diagonal_morphism() + sage: b,c = list(T.cohomology_ring().basis(1)) + sage: diag_c = diag.induced_homology_morphism(cohomology=True) + sage: diag_c(b) + h^{1,0} + sage: diag_c(c) + h^{1,0} + """ + def __init__(self, map, base_ring=None, cohomology=False): + """ + INPUTS: + + - ``map`` -- the map of simplicial complexes + - ``base_ring`` -- a field (optional, default ``QQ``) + - ``cohomology`` -- boolean (optional, default ``False``). If + ``True``, return the induced map in cohomology rather than + homology. + + EXAMPLES:: + + sage: from sage.homology.homology_morphism import InducedHomologyMorphism + sage: K = simplicial_complexes.RandomComplex(8, 3) + sage: H = Hom(K,K) + sage: id = H.identity() + sage: f = InducedHomologyMorphism(id, QQ) + sage: f.to_matrix(0) == 1 and f.to_matrix(1) == 1 and f.to_matrix(2) == 1 + True + sage: f = InducedHomologyMorphism(id, ZZ) + Traceback (most recent call last): + ... + ValueError: the coefficient ring must be a field + sage: S1 = simplicial_complexes.Sphere(1).barycentric_subdivision() + sage: S1.is_mutable() + True + sage: g = Hom(S1, S1).identity() + sage: h = g.induced_homology_morphism(QQ) + Traceback (most recent call last): + ... + ValueError: the domain and codomain complexes must be immutable + sage: S1.set_immutable() + sage: g = Hom(S1, S1).identity() + sage: h = g.induced_homology_morphism(QQ) + """ + if map.domain().is_mutable() or map.codomain().is_mutable(): + raise ValueError('the domain and codomain complexes must be immutable') + if base_ring is None: + base_ring = QQ + if not base_ring.is_field(): + raise ValueError('the coefficient ring must be a field') + + self._cohomology = cohomology + self._map = map + self._base_ring = base_ring + if cohomology: + domain = map.domain().cohomology_ring(base_ring=base_ring) + codomain = map.codomain().cohomology_ring(base_ring=base_ring) + Morphism.__init__(self, Hom(domain, codomain, + category=GradedAlgebrasWithBasis(base_ring))) + else: + domain = map.domain().homology_with_basis(base_ring=base_ring, cohomology=cohomology) + codomain = map.codomain().homology_with_basis(base_ring=base_ring, cohomology=cohomology) + Morphism.__init__(self, Hom(domain, codomain, + category=GradedModulesWithBasis(base_ring))) + + def base_ring(self): + """ + The base ring for this map + + EXAMPLES:: + + sage: K = simplicial_complexes.Simplex(2) + sage: H = Hom(K,K) + sage: id = H.identity() + sage: id.induced_homology_morphism(QQ).base_ring() + Rational Field + sage: id.induced_homology_morphism(GF(13)).base_ring() + Finite Field of size 13 + """ + return self._base_ring + + def to_matrix(self, deg=None): + """ + The matrix for this map. + + If degree ``deg`` is specified, return the matrix just in that + degree; otherwise, return the block matrix representing the + entire map. + + INPUTS: + + - ``deg`` -- (optional, default ``None``) the degree + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: S1_b = S1.barycentric_subdivision() + sage: S1_b.set_immutable() + sage: d = {(0,): 0, (0,1): 1, (1,): 2, (1,2): 0, (2,): 1, (0,2): 2} + sage: f = Hom(S1_b, S1)(d) + sage: h = f.induced_homology_morphism(QQ) + sage: h.to_matrix(1) + [2] + sage: h.to_matrix() + [1|0] + [-+-] + [0|2] + """ + base_ring = self.base_ring() + if self._cohomology: + domain = self._map.codomain() + codomain = self._map.domain() + else: + domain = self._map.domain() + codomain = self._map.codomain() + phi_codomain, H_codomain = codomain.algebraic_topological_model(base_ring) + phi_domain, H_domain = domain.algebraic_topological_model(base_ring) + mat = phi_codomain.pi().to_matrix(deg) * self._map.associated_chain_complex_morphism(self.base_ring(), cochain=self._cohomology).to_matrix(deg) * phi_domain.iota().to_matrix(deg) + if deg is None: + import numpy as np + betti_domain = [H_domain.free_module_rank(n) + for n in range(domain.dimension()+1)] + betti_codomain = [H_codomain.free_module_rank(n) + for n in range(codomain.dimension()+1)] + # Compute cumulative sums of Betti numbers to get subdivisions: + row_subdivs = list(np.cumsum(betti_codomain[:-1])) + col_subdivs = list(np.cumsum(betti_domain[:-1])) + mat.subdivide(row_subdivs, col_subdivs) + return mat + + def __call__(self, elt): + """ + Evaluate this map on ``elt``, an element of (co)homology. + + INPUT: + + - ``elt`` -- informally, an element of the domain of this + map. More formally, an element of + :class:`homology_vector_space_with_basis.HomologyVectorSpaceWithBasis`. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: f = {0:0, 1:2, 2:1} + sage: H = Hom(S1,S1) + sage: g = H(f) + sage: h = g.induced_homology_morphism(QQ) + sage: x = S1.homology_with_basis().basis()[(1,0)] + sage: x + h_{1,0} + sage: h(x) # indirect doctest + -h_{1,0} + """ + base_ring = self.base_ring() + if self._cohomology: + codomain = self._map.domain().homology_with_basis(base_ring, cohomology=True) + if elt.parent().complex() != self._map.codomain(): + raise ValueError('element is not a cohomology class for the correct complex') + else: + codomain = self._map.codomain().homology_with_basis(base_ring) + if elt.parent().complex() != self._map.domain(): + raise ValueError('element is not a homology class for the correct complex') + + return codomain.from_vector(self.to_matrix() * elt.to_vector()) + + def __eq__(self, other): + """ + Return ``True`` if and only if this map agrees with ``other``. + + INPUTS: + + - ``other`` -- another induced homology morphism + + This automatically returns ``False`` if the morphisms have + different domains, codomains, base rings, or values for their + cohomology flags + + Otherwise, determine this by computing the matrices for this + map and ``other`` using the (same) basis for the homology + vector spaces. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: f = Hom(S1, K)({0: 0, 1:1, 2:2}) + sage: g = Hom(S1, K)({0: 0, 1:0, 2:0}) + sage: f.induced_homology_morphism(QQ) == g.induced_homology_morphism(QQ) + True + sage: f.induced_homology_morphism(QQ) == g.induced_homology_morphism(GF(2)) + False + sage: id = Hom(K, K).identity() # different domain + sage: f.induced_homology_morphism(QQ) == id.induced_homology_morphism(QQ) + False + """ + if (self._map.domain() != other._map.domain() + or self._map.codomain() != other._map.codomain() + or self.base_ring() != other.base_ring() + or self._cohomology != other._cohomology): + return False + dim = min(self._map.domain().dimension(), self._map.codomain().dimension()) + return all(self.to_matrix(d) == other.to_matrix(d) for d in range(dim+1)) + + def is_identity(self): + """ + True if this is the identity map on (co)homology. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: H = Hom(S1, S1) + sage: flip = H({0:0, 1:2, 2:1}) + sage: flip.induced_homology_morphism(QQ).is_identity() + False + sage: flip.induced_homology_morphism(GF(2)).is_identity() + True + sage: rotate = H({0:1, 1:2, 2:0}) + sage: rotate.induced_homology_morphism(QQ).is_identity() + True + """ + return self.to_matrix().is_one() + + def is_surjective(self): + """ + True if this map is surjective on (co)homology. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: H = Hom(S1, K) + sage: f = H({0:0, 1:1, 2:2}) + sage: f.induced_homology_morphism().is_surjective() + True + sage: f.induced_homology_morphism(cohomology=True).is_surjective() + False + """ + m = self.to_matrix() + return m.rank() == m.nrows() + + def is_injective(self): + """ + True if this map is injective on (co)homology. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: H = Hom(S1, K) + sage: f = H({0:0, 1:1, 2:2}) + sage: f.induced_homology_morphism().is_injective() + False + sage: f.induced_homology_morphism(cohomology=True).is_injective() + True + + sage: T = simplicial_complexes.Torus() + sage: g = Hom(S1, T)({0:0, 1:3, 2: 6}) + sage: g_star = g.induced_homology_morphism(QQ) + sage: g.is_injective() + True + """ + return self.to_matrix().right_nullity() == 0 + + def _repr_type(self): + """ + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: f = Hom(S1, K)({0: 0, 1:1, 2:2}) + sage: f.induced_homology_morphism()._repr_type() + 'Graded vector space' + sage: f.induced_homology_morphism(cohomology=True)._repr_type() + 'Graded algebra' + """ + return "Graded vector space" if not self._cohomology else "Graded algebra" + + def _repr_defn(self): + """ + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: f = Hom(S1, K)({0: 0, 1:1, 2:2}) + sage: print f.induced_homology_morphism()._repr_defn() + induced by: + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 + """ + s = "induced by:" + s += '\n {}'.format('\n '.join(self._map._repr_().split('\n'))) + return s diff --git a/src/sage/homology/homology_vector_space_with_basis.py b/src/sage/homology/homology_vector_space_with_basis.py new file mode 100644 index 00000000000..15c42394f4c --- /dev/null +++ b/src/sage/homology/homology_vector_space_with_basis.py @@ -0,0 +1,834 @@ +# -*- coding: utf-8 -*- +""" +Homology and cohomology with a basis + +This module provides homology and cohomology vector spaces suitable +for computing cup products and cohomology operations. + +REFERENCES: + +.. [G-DR03] R. González-Díaz and P. Réal, *Computation of cohomology + operations on finite simplicial complexes* in Homology, + Homotopy and Applications 5 (2003), 83-93. + +.. [G-DR99] R. González-Díaz and P. Réal, *A combinatorial method for + computing Steenrod squares* in J. Pure Appl. Algebra 139 (1999), 89-108. + +AUTHORS: + +- John H. Palmieri, Travis Scrimshaw (2015-09) +""" + +######################################################################## +# Copyright (C) 2015 John H. Palmieri +# Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# http://www.gnu.org/licenses/ +######################################################################## + +from sage.misc.cachefunc import cached_method +from sage.categories.algebras import Algebras +from sage.categories.modules import Modules +from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModuleElement +from sage.sets.family import Family +from simplicial_complex import SimplicialComplex + +class HomologyVectorSpaceWithBasis(CombinatorialFreeModule): + r""" + Homology (or cohomology) vector space. + + This provides enough structure to allow the computation of cup + products and cohomology operations. See the class + :class:`CohomologyRing` (which derives from this) for examples. + + It also requires field coefficients (hence the "VectorSpace" in + the name of the class). + + .. NOTE:: + + This is not intended to be created directly by the user, but + instead via the methods + :meth:`~sage.homology.cell_complex.GenericCellComplex.homology_with_basis` and + :meth:`~sage.homology.cell_complex.GenericCellComplex.cohomology_ring` + for the class of :class:`cell + complexes`. + + INPUT: + + - ``base_ring`` -- must be a field + - ``cell_complex`` -- the cell complex whose homology we are + computing + - ``cohomology`` -- (default: ``False``) if ``True``, return + the cohomology as a module + - ``category`` -- (optional) a subcategory of modules with basis + + EXAMPLES: + + Homology classes are denoted by ``h_{d,i}`` where ``d`` is the + degree of the homology class and ``i`` is their index in the list + of basis elements in that degree. Cohomology classes are denoted + ``h^{1,0}``:: + + sage: RP2 = cubical_complexes.RealProjectivePlane() + sage: RP2.homology_with_basis(GF(2)) + Homology module of Cubical complex with 21 vertices and 81 cubes + over Finite Field of size 2 + sage: RP2.cohomology_ring(GF(2)) + Cohomology ring of Cubical complex with 21 vertices and 81 cubes + over Finite Field of size 2 + sage: simplicial_complexes.Torus().homology_with_basis(QQ) + Homology module of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6) and 14 facets over Rational Field + + To access a basis element, use its degree and index (0 or 1 in the 1st + cohomology group of a torus):: + + sage: H = simplicial_complexes.Torus().cohomology_ring(QQ) + sage: H.basis(1) + Finite family {(1, 0): h^{1,0}, (1, 1): h^{1,1}} + sage: x = H.basis()[1,0]; x + h^{1,0} + sage: y = H.basis()[1,1]; y + h^{1,1} + sage: 2*x-3*y + 2*h^{1,0} - 3*h^{1,1} + + You can compute cup products of cohomology classes:: + + sage: x.cup_product(y) + h^{2,0} + sage: y.cup_product(x) + -h^{2,0} + sage: x.cup_product(x) + 0 + + This works with simplicial, cubical, and `\Delta`-complexes:: + + sage: Klein_c = cubical_complexes.KleinBottle() + sage: H = Klein_c.cohomology_ring(GF(2)) + sage: x,y = H.basis(1) + sage: x.cup_product(x) + h^{2,0} + sage: x.cup_product(y) + 0 + sage: y.cup_product(y) + h^{2,0} + + sage: Klein_d = delta_complexes.KleinBottle() + sage: H = Klein_d.cohomology_ring(GF(2)) + sage: u,v = H.basis(1) + sage: u.cup_product(u) + h^{2,0} + sage: u.cup_product(v) + 0 + sage: v.cup_product(v) + h^{2,0} + + The basis elements in the simplicial complex case have been chosen + differently; apply the change of basis `x \mapsto a + b`, `y \mapsto + b` to see the same product structure. :: + + sage: Klein_s = simplicial_complexes.KleinBottle() + sage: H = Klein_s.cohomology_ring(GF(2)) + sage: a,b = H.basis(1) + sage: a.cup_product(a) + 0 + sage: a.cup_product(b) + h^{2,0} + sage: (a+b).cup_product(a+b) + h^{2,0} + sage: b.cup_product(b) + h^{2,0} + """ + def __init__(self, base_ring, cell_complex, cohomology=False, cat=None): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.ProjectivePlane() + sage: H = RP2.homology_with_basis(QQ) + sage: TestSuite(H).run() + sage: H = RP2.homology_with_basis(GF(2)) + sage: TestSuite(H).run() + sage: H = RP2.cohomology_ring(GF(2)) + sage: TestSuite(H).run() + sage: H = RP2.cohomology_ring(GF(5)) + sage: TestSuite(H).run() + sage: H = simplicial_complexes.ComplexProjectivePlane().cohomology_ring() + sage: TestSuite(H).run() + """ + # phi is the associated chain contraction. + # M is the homology chain complex. + phi, M = cell_complex.algebraic_topological_model(base_ring) + if cohomology: + phi = phi.dual() + # We only need the rank of M in each degree, and since + # we're working over a field, we don't need to dualize M + # if working with cohomology. + cat = Modules(base_ring).WithBasis().Graded().or_subcategory(cat) + self._contraction = phi + self._complex = cell_complex + self._cohomology = cohomology + self._graded_indices = {deg: range(M.free_module_rank(deg)) + for deg in range(cell_complex.dimension()+1)} + indices = [(deg, i) for deg in self._graded_indices + for i in self._graded_indices[deg]] + CombinatorialFreeModule.__init__(self, base_ring, indices, category=cat) + + def basis(self, d=None): + """ + Return (the degree ``d`` homogeneous component of) the basis + of this graded vector space. + + INPUT: + + - ``d`` -- (optional) the degree + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.ProjectivePlane() + sage: H = RP2.homology_with_basis(QQ) + sage: H.basis() + Finite family {(0, 0): h_{0,0}} + sage: H.basis(0) + Finite family {(0, 0): h_{0,0}} + sage: H.basis(1) + Finite family {} + sage: H.basis(2) + Finite family {} + """ + if d is None: + return Family(self._indices, self.monomial) + else: + indices = [(d, i) for i in self._graded_indices.get(d, [])] + return Family(indices, self.monomial) + + def degree_on_basis(self, i): + r""" + Return the degree of the basis element indexed by ``i``. + + EXAMPLES:: + + sage: H = simplicial_complexes.Torus().homology_with_basis(GF(7)) + sage: H.degree_on_basis((2,0)) + 2 + """ + return i[0] + + def contraction(self): + r""" + The chain contraction associated to this homology computation. + + That is, to work with chain representatives of homology + classes, we need the chain complex `C` associated to the cell + complex, the chain complex `H` of its homology (with trivial + differential), chain maps `\pi: C \to H` and `\iota: H \to C`, + and a chain contraction `\phi` giving a chain homotopy between + `1_C` and `\iota \circ \pi`. + + OUTPUT: `\phi` + + See :class:`~sage.homology.chain_homotopy.ChainContraction` for information + about chain contractions, and see + :func:`~sage.homology.algebraic_topological_model.algebraic_topological_model` + for the construction of this particular chain contraction `\phi`. + + EXAMPLES:: + + sage: H = simplicial_complexes.Simplex(2).homology_with_basis(QQ) + sage: H.contraction() + Chain homotopy between: + Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field + and Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field + + From the chain contraction, one can also recover the maps `\pi` + and `\iota`:: + + sage: phi = H.contraction() + sage: phi.pi() + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 1 nonzero terms over Rational Field + sage: phi.iota() + Chain complex morphism: + From: Chain complex with at most 1 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + """ + return self._contraction + + def complex(self): + """ + The cell complex whose homology is being computed. + + EXAMPLES:: + + sage: H = simplicial_complexes.Simplex(2).homology_with_basis(QQ) + sage: H.complex() + Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} + """ + return self._complex + + def _repr_(self): + """ + EXAMPLES:: + + sage: simplicial_complexes.Torus().homology_with_basis(QQ) + Homology module of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6) and 14 facets over Rational Field + """ + if self._cohomology: + base = "Cohomology" + else: + base = "Homology" + return base + " module of {} over {}".format(self._complex, self.base_ring()) + + def _repr_term(self, i): + """ + Return ``'h_{i[0],i[1]}'`` for homology, ``'h^{i[0],i[1]}'`` for + cohomology, for the basis element indexed by ``i``. + + EXAMPLES:: + + sage: H = simplicial_complexes.Torus().homology_with_basis(QQ) + sage: H.basis()[1,0] # indirect doctest + h_{1,0} + sage: latex(H.basis()[1,1]) # indirect doctest + h_{1,1} + sage: co = simplicial_complexes.KleinBottle().cohomology_ring(GF(2)) + sage: co.basis()[1,0] # indirect doctest + h^{1,0} + + """ + sym = '^' if self._cohomology else '_' + return 'h{}{{{},{}}}'.format(sym, i[0], i[1]) + + _latex_term = _repr_term + + @cached_method + def _to_cycle_on_basis(self, i): + """ + Return the (co)cycle representative of the basis element + indexed by ``i``. + + .. SEEALSO:: + + :meth:`HomologyVectorSpaceWithBasis.Element.to_cocycle` + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: H = S2.homology_with_basis(QQ) + sage: H._to_cycle_on_basis((2,0)) + -(0, 1, 2) + (0, 1, 3) - (0, 2, 3) + (1, 2, 3) + + sage: S2.cohomology_ring(QQ)._to_cycle_on_basis((2,0)) + \chi_(0, 1, 3) + sage: S2.cohomology_ring(QQ)._to_cycle_on_basis((0,0)) + \chi_(0,) + \chi_(1,) + \chi_(2,) + \chi_(3,) + + sage: RP3 = simplicial_complexes.RealProjectiveSpace(3) + sage: H = RP3.cohomology_ring(GF(2)) + sage: H._to_cycle_on_basis((0,0)) + \chi_(1,) + \chi_(2,) + \chi_(3,) + \chi_(4,) + \chi_(5,) + \chi_(6,) + + \chi_(7,) + \chi_(8,) + \chi_(9,) + \chi_(10,) + \chi_(11,) + sage: H._to_cycle_on_basis((1,0)) + \chi_(1, 2) + \chi_(1, 3) + \chi_(1, 4) + \chi_(1, 7) + + \chi_(1, 10) + \chi_(2, 4) + \chi_(2, 6) + \chi_(2, 9) + + \chi_(2, 10) + \chi_(2, 11) + \chi_(3, 4) + \chi_(3, 5) + + \chi_(3, 11) + \chi_(4, 8) + \chi_(4, 9) + \chi_(5, 9) + + \chi_(5, 10) + \chi_(7, 9) + \chi_(8, 10) + sage: H._to_cycle_on_basis((2,0)) + \chi_(2, 3, 8) + \chi_(2, 7, 8) + \chi_(3, 4, 8) + \chi_(3, 5, 9) + + \chi_(3, 6, 7) + \chi_(3, 6, 8) + \chi_(3, 6, 10) + + \chi_(3, 8, 9) + \chi_(3, 9, 10) + \chi_(4, 5, 7) + + \chi_(4, 5, 9) + \chi_(5, 6, 7) + \chi_(5, 7, 8) + sage: H._to_cycle_on_basis((3,0)) + \chi_(3, 4, 5, 9) + """ + vec = self.contraction().iota().in_degree(i[0]).column(i[1]) + chains = self.complex().n_chains(i[0], self.base_ring(), + cochains=self._cohomology) + return chains.from_vector(vec) + + class Element(CombinatorialFreeModuleElement): + def to_cycle(self): + r""" + (Co)cycle representative of this homogeneous (co)homology class. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: H = S2.homology_with_basis(QQ) + sage: h20 = H.basis()[2,0]; h20 + h_{2,0} + sage: h20.to_cycle() + -(0, 1, 2) + (0, 1, 3) - (0, 2, 3) + (1, 2, 3) + + Chains are written as linear combinations of simplices + `\sigma`. Cochains are written as linear combinations of + characteristic functions `\chi_{\sigma}` for those + simplices:: + + sage: S2.cohomology_ring(QQ).basis()[2,0].to_cycle() + \chi_(0, 1, 3) + sage: S2.cohomology_ring(QQ).basis()[0,0].to_cycle() + \chi_(0,) + \chi_(1,) + \chi_(2,) + \chi_(3,) + """ + if not self.is_homogeneous(): + raise ValueError("only defined for homogeneous elements") + return sum(c * self.parent()._to_cycle_on_basis(i) for i,c in self) + +class CohomologyRing(HomologyVectorSpaceWithBasis): + """ + The cohomology ring. + + .. NOTE:: + + This is not intended to be created directly by the user, but + instead via the + :meth:`cohomology ring` + of a :class:`cell + complex`. + + INPUT: + + - ``base_ring`` -- must be a field + - ``cell_complex`` -- the cell complex whose homology we are + computing + + EXAMPLES:: + + sage: CP2 = simplicial_complexes.ComplexProjectivePlane() + sage: H = CP2.cohomology_ring(QQ) + sage: H.basis(2) + Finite family {(2, 0): h^{2,0}} + sage: x = H.basis(2)[2,0] + + The product structure is the cup product:: + + sage: x.cup_product(x) + h^{4,0} + sage: x * x + h^{4,0} + + There are mod 2 cohomology operations defined, also:: + + sage: Hmod2 = CP2.cohomology_ring(GF(2)) + sage: y = Hmod2.basis(2)[2,0] + sage: y.Sq(2) + h^{4,0} + """ + def __init__(self, base_ring, cell_complex): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.ProjectivePlane() + sage: H = RP2.cohomology_ring(GF(2)) + sage: TestSuite(H).run() + sage: H = RP2.cohomology_ring(GF(5)) + sage: TestSuite(H).run() + """ + cat = Algebras(base_ring).WithBasis().Graded() + HomologyVectorSpaceWithBasis.__init__(self, base_ring, cell_complex, True, cat) + + def _repr_(self): + """ + EXAMPLES:: + + sage: simplicial_complexes.Torus().cohomology_ring(QQ) + Cohomology ring of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6) and 14 facets over Rational Field + """ + return "Cohomology ring of {} over {}".format(self._complex, self.base_ring()) + + @cached_method + def one(self): + """ + The multiplicative identity element. + + EXAMPLES:: + + sage: H = simplicial_complexes.Torus().cohomology_ring(QQ) + sage: H.one() + h^{0,0} + sage: all(H.one() * x == x == x * H.one() for x in H.basis()) + True + """ + one = self.base_ring().one() + d = {(0,i): one for i in self._graded_indices[0]} + return self._from_dict(d, remove_zeros=False) + + @cached_method + def product_on_basis(self, li, ri): + r""" + The cup product of the basis elements indexed by ``li`` and ``ri`` + in this cohomology ring. + + INPUT: + + - ``li``, ``ri`` -- index of a cohomology class + + .. SEEALSO:: + + :meth:`CohomologyRing.Element.cup_product` -- the + documentation for this method describes the algorithm. + + EXAMPLES:: + + sage: RP3 = simplicial_complexes.RealProjectiveSpace(3) + sage: H = RP3.cohomology_ring(GF(2)) + sage: c = H.basis()[1,0] + sage: c.cup_product(c).cup_product(c) # indirect doctest + h^{3,0} + + sage: T = simplicial_complexes.Torus() + sage: x,y = T.cohomology_ring(QQ).basis(1) + sage: x.cup_product(y) + h^{2,0} + sage: x.cup_product(x) + 0 + + sage: one = T.cohomology_ring(QQ).basis()[0,0] + sage: x.cup_product(one) + h^{1,0} + sage: one.cup_product(y) == y + True + sage: one.cup_product(one) + h^{0,0} + sage: x.cup_product(y) + y.cup_product(x) + 0 + + This also works with cubical complexes:: + + sage: T = cubical_complexes.Torus() + sage: x,y = T.cohomology_ring(QQ).basis(1) + sage: x.cup_product(y) + -h^{2,0} + sage: x.cup_product(x) + 0 + + and `\Delta`-complexes:: + + sage: T_d = delta_complexes.Torus() + sage: a,b = T_d.cohomology_ring(QQ).basis(1) + sage: a.cup_product(b) + h^{2,0} + sage: b.cup_product(a) + -h^{2,0} + sage: RP2 = delta_complexes.RealProjectivePlane() + sage: w = RP2.cohomology_ring(GF(2)).basis()[1,0] + sage: w.cup_product(w) + h^{2,0} + + A non-connected example:: + + sage: K = cubical_complexes.Torus().disjoint_union(cubical_complexes.Torus()) + sage: a,b,c,d = K.cohomology_ring(QQ).basis(1) + sage: x,y = K.cohomology_ring(QQ).basis(0) + sage: a.cup_product(x) == a + True + sage: a.cup_product(y) + 0 + """ + B = self.basis() + scomplex = self.complex() + base_ring = self.base_ring() + deg_left = li[0] + deg_right = ri[0] + deg_tot = deg_left + deg_right + left_cycle = self._to_cycle_on_basis(li) + right_cycle = self._to_cycle_on_basis(ri) + n_chains_left = scomplex.n_chains(deg_left, base_ring) + n_chains_right = scomplex.n_chains(deg_right, base_ring) + + result = {} + H = scomplex.homology_with_basis(base_ring) + for gamma_index in H._graded_indices.get(deg_tot, []): + gamma_coeff = base_ring.zero() + for cell, coeff in H._to_cycle_on_basis((deg_tot, gamma_index)): + if hasattr(cell, 'alexander_whitney'): + # Simplicial and cubical case: each cell has a + # method 'alexander_whitney' which computes + # the appropriate faces. + for (c, left_cell, right_cell) in cell.alexander_whitney(deg_left): + left = n_chains_left(left_cell) + right = n_chains_right(right_cell) + gamma_coeff += c * coeff * left_cycle.eval(left) * right_cycle.eval(right) + else: + # Delta complex case: each "cell" in n_chains + # is just a pair (integer, tuple), where the + # integer is its index in the list, and the + # jth entry of the tuple is the index of its + # jth face in the list of (n-1)-chains. Use + # this data to compute the appropriate faces + # by hand. + left_cell = cell + for i in range(deg_tot, deg_left, -1): + idx = left_cell[1][i] + left_cell = (idx, scomplex.n_cells(i-1)[idx]) + right_cell = cell + for i in range(deg_tot, deg_right, -1): + idx = right_cell[1][0] + right_cell = (idx, scomplex.n_cells(i-1)[idx]) + left = n_chains_left(left_cell) + right = n_chains_right(right_cell) + gamma_coeff += coeff * left_cycle.eval(left) * right_cycle.eval(right) + if gamma_coeff != base_ring.zero(): + result[(deg_tot, gamma_index)] = gamma_coeff + return self._from_dict(result, remove_zeros=False) + + class Element(HomologyVectorSpaceWithBasis.Element): + def cup_product(self, other): + r""" + Return the cup product of this element and ``other``. + + Algorithm: see González-Díaz and Réal [G-DR03]_, p. 88. + Given two cohomology classes, lift them to cocycle + representatives via the chain contraction for this + complex, using + :meth:`~HomologyVectorSpaceWithBasis.Element.to_cycle`. In + the sum of their dimensions, look at all of the homology + classes `\gamma`: lift each of those to a cycle + representative, apply the Alexander-Whitney diagonal map + to each cell in the cycle, evaluate the two cocycles on + these factors, and multiply. The result is the value of + the cup product cocycle on this homology class. After this + has been done for all homology classes, since homology and + cohomology are dual, one can tell which cohomology class + corresponds to the cup product. + + .. SEEALSO:: + + :meth:`CohomologyRing.product_on_basis` + + EXAMPLES:: + + sage: RP3 = simplicial_complexes.RealProjectiveSpace(3) + sage: H = RP3.cohomology_ring(GF(2)) + sage: c = H.basis()[1,0] + sage: c.cup_product(c) + h^{2,0} + sage: c * c * c + h^{3,0} + + We can also take powers:: + + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: a = RP2.cohomology_ring(GF(2)).basis()[1,0] + sage: a**0 + h^{0,0} + sage: a**1 + h^{1,0} + sage: a**2 + h^{2,0} + sage: a**3 + 0 + + A non-connected example:: + + sage: K = cubical_complexes.Torus().disjoint_union(cubical_complexes.Sphere(2)) + sage: a,b = K.cohomology_ring(QQ).basis(2) + sage: a**0 + h^{0,0} + h^{0,1} + + """ + return self * other + + def Sq(self, i): + r""" + Return the result of applying `Sq^i` to this element. + + INPUT: + + - ``i`` -- nonnegative integer + + .. WARNING:: + + This is only implemented for simplicial complexes. + + This cohomology operation is only defined in + characteristic 2. + + Algorithm: see González-Díaz and Réal [G-DR99]_, + Corollary 3.2. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.RealProjectiveSpace(2) + sage: x = RP2.cohomology_ring(GF(2)).basis()[1,0] + sage: x.Sq(1) + h^{2,0} + + sage: K = RP2.suspension() + sage: K.set_immutable() + sage: y = K.cohomology_ring(GF(2)).basis()[2,0] + sage: y.Sq(1) + h^{3,0} + + sage: RP4 = simplicial_complexes.RealProjectiveSpace(4) + sage: H = RP4.cohomology_ring(GF(2)) + sage: x = H.basis()[1,0] + sage: y = H.basis()[2,0] + sage: z = H.basis()[3,0] + sage: x.Sq(1) == y + True + sage: z.Sq(1) # long time + h^{4,0} + + TESTS:: + + sage: T = cubical_complexes.Torus() + sage: x = T.cohomology_ring(GF(2)).basis()[1,0] + sage: x.Sq(1) + Traceback (most recent call last): + ... + NotImplementedError: Steenrod squares are only implemented for simplicial complexes + sage: S2 = simplicial_complexes.Sphere(2) + sage: x = S2.cohomology_ring(GF(7)).basis()[2,0] + sage: x.Sq(1) + Traceback (most recent call last): + ... + ValueError: Steenrod squares are only defined in characteristic 2 + """ + P = self.parent() + scomplex = P.complex() + if not isinstance(scomplex, SimplicialComplex): + raise NotImplementedError('Steenrod squares are only implemented for simplicial complexes') + base_ring = P.base_ring() + if base_ring.characteristic() != 2: + raise ValueError('Steenrod squares are only defined in characteristic 2') + # We keep the same notation as in [G-DR99]. + # The trivial cases: + if i == 0: + # Sq^0 is the identity. + return self + + # Construct each graded component of ``self`` + ret = P.zero() + H = scomplex.homology_with_basis(base_ring) + deg_comp = {} + for index,coeff in self: + d = deg_comp.get(index[0], {}) + d[index] = coeff + deg_comp[index[0]] = d + + # Do the square on each graded componenet of ``self``. + for j in deg_comp: + # Make it into an actual element + m = j + i + if not P._graded_indices.get(m, []) or i > j: + continue + elt = P._from_dict(deg_comp[j], remove_zeros=False) + if i == j: + ret += elt.cup_product(elt) + continue + + n = j - i + # Now assemble the indices over which the sums take place. + # S(n) is defined to be floor((m+1)/2) + floor(n/2). + S_n = (m+1) // 2 + n // 2 + if n == 0: + sums = [[S_n]] + else: + sums = [[i_n] + l for i_n in range(S_n, m+1) + for l in sum_indices(n-1, i_n, S_n)] + # At this point, 'sums' is a list of lists of the form + # [i_n, i_{n-1}, ..., i_0]. (It is reversed from the + # obvious order because this is closer to the order in + # which the face maps will be applied.) Now we sum over + # these, according to the formula in [G-DR99], Corollary 3.2. + result = {} + cycle = elt.to_cycle() + n_chains = scomplex.n_chains(j, base_ring) + for gamma_index in H._graded_indices.get(m, []): + gamma_coeff = base_ring.zero() + for cell, coeff in H._to_cycle_on_basis((m, gamma_index)): + for indices in sums: + indices = list(indices) + left = cell + right = cell + # Since we are working with a simplicial complex, 'cell' is a simplex. + if not m % 2: + left_endpoint = m + while indices: + right_endpoint = indices[0] - 1 + for k in range(left_endpoint, indices.pop(0), -1): + left = left.face(k) + try: + left_endpoint = indices[0] - 1 + for k in range(right_endpoint, indices.pop(0), -1): + right = right.face(k) + except IndexError: + pass + for k in range(right_endpoint, -1, -1): + right = right.face(k) + else: + right_endpoint = m + while indices: + left_endpoint = indices[0] - 1 + try: + for k in range(right_endpoint, indices.pop(0), -1): + right = right.face(k) + right_endpoint = indices[0] - 1 + except IndexError: + pass + for k in range(left_endpoint, indices.pop(0), -1): + left = left.face(k) + for k in range(right_endpoint, -1, -1): + right = right.face(k) + + left = n_chains(left) + right = n_chains(right) + gamma_coeff += coeff * cycle.eval(left) * cycle.eval(right) + if gamma_coeff != base_ring.zero(): + result[(m, gamma_index)] = gamma_coeff + ret += P._from_dict(result, remove_zeros=False) + return ret + +def sum_indices(k, i_k_plus_one, S_k_plus_one): + r""" + This is a recursive function for computing the indices for the + nested sums in González-Díaz and Réal [G-DR99]_, Corollary 3.2. + + In the paper, given indices `i_n`, `i_{n-1}`, ..., `i_{k+1}`, + given `k`, and given `S(k+1)`, the number `S(k)` is defined to be + + .. MATH:: + + S(k) = -S(k+1) + floor(k/2) + floor((k+1)/2) + i_{k+1}, + + and `i_k` ranges from `S(k)` to `i_{k+1}-1`. There are two special + cases: if `k=0`, then `i_0 = S(0)`. Also, the initial case of + `S(k)` is `S(n)`, which is set in the method :meth:`Sq` before + calling this function. For this function, given `k`, `i_{k+1}`, + and `S(k+1)`, return a list consisting of the allowable possible + indices `[i_k, i_{k-1}, ..., i_1, i_0]` given by the above + formula. + + INPUT: + + - ``k`` -- non-negative integer + - ``i_k_plus_one`` -- the positive integer `i_{k+1}` + - ``S_k_plus_one`` -- the integer `S(k+1)` + + EXAMPLES:: + + sage: from sage.homology.homology_vector_space_with_basis import sum_indices + sage: sum_indices(1, 3, 3) + [[1, 0], [2, 1]] + sage: sum_indices(0, 4, 2) + [[2]] + """ + S_k = -S_k_plus_one + k//2 + (k+1)//2 + i_k_plus_one + if k == 0: + return [[S_k]] + return [[i_k] + l for i_k in range(S_k, i_k_plus_one) + for l in sum_indices(k-1, i_k, S_k)] + diff --git a/src/sage/homology/simplicial_complex.py b/src/sage/homology/simplicial_complex.py index c8c188552f6..98c5ab48003 100644 --- a/src/sage/homology/simplicial_complex.py +++ b/src/sage/homology/simplicial_complex.py @@ -158,18 +158,19 @@ from sage.misc.lazy_import import lazy_import from sage.homology.cell_complex import GenericCellComplex from sage.structure.sage_object import SageObject -from sage.structure.category_object import CategoryObject +from sage.structure.parent import Parent from sage.rings.integer import Integer from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.sets.set import Set from sage.rings.integer_ring import ZZ from sage.structure.parent_gens import normalize_names from sage.misc.latex import latex +from sage.misc.misc import union from sage.matrix.constructor import matrix from sage.homology.chain_complex import ChainComplex from sage.graphs.graph import Graph from functools import reduce -lazy_import('sage.categories.category_types', 'SimplicialComplexes') +lazy_import('sage.categories.simplicial_complexes', 'SimplicialComplexes') def lattice_paths(t1, t2, length=None): """ @@ -625,6 +626,50 @@ def product(self, other, rename_vertices=True): answer.append(Simplex(new)) return answer + def alexander_whitney(self, dim): + r""" + Subdivide this simplex into a pair of simplices. + + If this simplex has vertices `v_0`, `v_1`, ..., `v_n`, then + subdivide it into simplices `(v_0, v_1, ..., v_{dim})` and + `(v_{dim}, v_{dim + 1}, ..., v_n)`. + + INPUTS: + + - ``dim`` -- integer between 0 and one more than the + dimension of this simplex + + OUTPUT: + + - a list containing just the triple ``(1, left, right)``, + where ``left`` and ``right`` are the two simplices described + above. + + This method allows one to construct a coproduct from the + `p+q`-chains to the tensor product of the `p`-chains and the + `q`-chains. The number 1 (a Sage integer) is the coefficient + of ``left tensor right`` in this coproduct. (The corresponding + formula is more complicated for the cubes that make up a + cubical complex, and the output format is intended to be + consistent for both cubes and simplices.) + + Calling this method ``alexander_whitney`` is an abuse of + notation, since the actual Alexander-Whitney map goes from + `C(X \times Y) \to C(X) \otimes C(Y)`, where `C(-)` denotes + the chain complex of singular chains, but this subdivision of + simplices is at the heart of it. + + EXAMPLES:: + + sage: s = Simplex((0,1,3,4)) + sage: s.alexander_whitney(0) + [(1, (0,), (0, 1, 3, 4))] + sage: s.alexander_whitney(2) + [(1, (0, 1, 3), (3, 4))] + """ + return [(ZZ.one(), Simplex(self.tuple()[:dim+1]), + Simplex(self.tuple()[dim:]))] + def __cmp__(self, other): """ Return ``True`` iff this simplex is the same as ``other``: that @@ -691,7 +736,7 @@ def _latex_(self): """ return latex(self.__tuple) -class SimplicialComplex(CategoryObject, GenericCellComplex): +class SimplicialComplex(Parent, GenericCellComplex): r""" Define a simplicial complex. @@ -775,7 +820,7 @@ class SimplicialComplex(CategoryObject, GenericCellComplex): sage: l=designs.ProjectiveGeometryDesign(2,1,GF(4,name='a')) sage: f = lambda S: not any(len(set(S).intersection(x))>2 for x in l) - sage: SimplicialComplex(from_characteristic_function=(f, range(21))) + sage: SimplicialComplex(from_characteristic_function=(f, l.ground_set())) Simplicial complex with 21 vertices and 168 facets TESTS: @@ -792,7 +837,15 @@ class SimplicialComplex(CategoryObject, GenericCellComplex): True sage: SimplicialComplex(S, is_immutable=False).is_mutable() True - """ + + .. WARNING:: + + Simplicial complexes are not proper parents as they do + not possess element classes. In particular, parents are assumed + to be hashable (and hence immutable) by the coercion framework. + However this is close enough to being a parent with elements + being the faces of ``self`` that we currently allow this abuse. + """ def __init__(self, maximal_faces=None, @@ -833,8 +886,7 @@ def __init__(self, if (maximal_faces is not None and from_characteristic_function is not None): raise ValueError("maximal_faces and from_characteristic_function cannot be both defined") - CategoryObject.__init__(self, category=SimplicialComplexes()) - from sage.misc.misc import union + Parent.__init__(self, category=SimplicialComplexes().Finite()) C = None vertex_set = [] @@ -987,7 +1039,7 @@ def __cmp__(self,right): sage: X == SimplicialComplex([[1,3]]) True """ - if set(self._facets) == set(right._facets): + if isinstance(right, SimplicialComplex) and set(self._facets) == set(right._facets): return 0 else: return -1 @@ -1033,6 +1085,57 @@ def vertices(self): """ return self._vertex_set + def _an_element_(self): + """ + The first facet of this complex. + + EXAMPLES:: + + sage: SimplicialComplex()._an_element_() + () + sage: simplicial_complexes.Sphere(3)._an_element_() + (1, 2, 3, 4) + """ + return self.facets()[0] + + def __contains__(self, x): + """ + True if ``x`` is a simplex which is contained in this complex. + + EXAMPLES:: + + sage: K = SimplicialComplex([(0,1,2), (0,2,3)]) + sage: Simplex((0,2)) in K + True + sage: Simplex((1,3)) in K + False + sage: 0 in K # not a simplex + False + """ + if not isinstance(x, Simplex): + return False + dim = x.dimension() + return x in self.n_faces(dim) + + def __call__(self, simplex): + """ + If ``simplex`` is a simplex in this complex, return it. + Otherwise, raise a ``ValueError``. + + EXAMPLE:: + + sage: K = SimplicialComplex([(0,1,2), (0,2,3)]) + sage: K(Simplex((1,2))) + (1, 2) + sage: K(Simplex((0,1,3))) + Traceback (most recent call last): + ... + ValueError: the simplex is not in this complex + """ + if simplex not in self: + raise ValueError('the simplex is not in this complex') + return simplex + def maximal_faces(self): """ The maximal faces (a.k.a. facets) of this simplicial complex. @@ -2081,8 +2184,6 @@ def add_face(self, face): self._facets = Facets # Update the vertex set - from sage.misc.misc import union - if self._sorted: self._vertex_set = Simplex(sorted(reduce(union, [self._vertex_set, new_face]))) else: @@ -3284,11 +3385,18 @@ def _Hom_(self, other, category=None): sage: T = simplicial_complexes.Sphere(2) sage: H = Hom(S,T) # indirect doctest sage: H - Set of Morphisms from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} in Category of simplicial complexes + Set of Morphisms from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + in Category of finite simplicial complexes sage: f = {0:0,1:1,2:3} sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 3} from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 3 sage: S._Hom_(T, Objects()) Traceback (most recent call last): diff --git a/src/sage/homology/simplicial_complex_homset.py b/src/sage/homology/simplicial_complex_homset.py index 46d3aed15d4..041288618b6 100644 --- a/src/sage/homology/simplicial_complex_homset.py +++ b/src/sage/homology/simplicial_complex_homset.py @@ -6,9 +6,7 @@ - Travis Scrimshaw (2012-08-18): Made all simplicial complexes immutable to work with the homset cache. -EXAMPLES: - -:: +EXAMPLES:: sage: S = simplicial_complexes.Sphere(1) sage: T = simplicial_complexes.Sphere(2) @@ -16,7 +14,12 @@ sage: f = {0:0,1:1,2:3} sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 3} from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 3 sage: x.is_injective() True sage: x.is_surjective() @@ -33,7 +36,7 @@ sage: S = simplicial_complexes.Sphere(1) sage: T = simplicial_complexes.Sphere(2) sage: H = Hom(S,T) - sage: loads(dumps(H))==H + sage: loads(dumps(H)) == H True """ @@ -55,7 +58,7 @@ #***************************************************************************** import sage.categories.homset -import sage.homology.simplicial_complex_morphism as simplicial_complex_morphism +from sage.homology.simplicial_complex_morphism import SimplicialComplexMorphism def is_SimplicialComplexHomset(x): """ @@ -67,7 +70,9 @@ def is_SimplicialComplexHomset(x): sage: T = SimplicialComplex(is_mutable=False) sage: H = Hom(S, T) sage: H - Set of Morphisms from Simplicial complex with vertex set () and facets {()} to Simplicial complex with vertex set () and facets {()} in Category of simplicial complexes + Set of Morphisms from Simplicial complex with vertex set () and facets {()} + to Simplicial complex with vertex set () and facets {()} + in Category of finite simplicial complexes sage: from sage.homology.simplicial_complex_homset import is_SimplicialComplexHomset sage: is_SimplicialComplexHomset(H) True @@ -80,7 +85,7 @@ def __call__(self, f): INPUT: - ``f`` -- a dictionary with keys exactly the vertices of the domain - and values vertices of the codomain + and values vertices of the codomain EXAMPLES:: @@ -90,13 +95,16 @@ def __call__(self, f): sage: H = Hom(S,T) sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 2, 4: 2} from Simplicial complex with vertex set (0, 1, 2, 3, 4) and 5 facets to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3, 4) and 5 facets + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: [0, 1, 2, 3, 4] --> [0, 1, 2, 2, 2] """ - return simplicial_complex_morphism.SimplicialComplexMorphism(f,self.domain(),self.codomain()) + return SimplicialComplexMorphism(f,self.domain(),self.codomain()) def diagonal_morphism(self,rename_vertices=True): r""" - Returns the diagonal morphism in `Hom(S, S \times S)`. + Return the diagonal morphism in `Hom(S, S \times S)`. EXAMPLES:: @@ -104,36 +112,40 @@ def diagonal_morphism(self,rename_vertices=True): sage: H = Hom(S,S.product(S, is_mutable=False)) sage: d = H.diagonal_morphism() sage: d - Simplicial complex morphism {0: 'L0R0', 1: 'L1R1', 2: 'L2R2', 3: 'L3R3'} from - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} - to Simplicial complex with 16 vertices and 96 facets + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + To: Simplicial complex with 16 vertices and 96 facets + Defn: 0 |--> L0R0 + 1 |--> L1R1 + 2 |--> L2R2 + 3 |--> L3R3 sage: T = SimplicialComplex([[0], [1]], is_mutable=False) sage: U = T.product(T,rename_vertices = False, is_mutable=False) sage: G = Hom(T,U) sage: e = G.diagonal_morphism(rename_vertices = False) sage: e - Simplicial complex morphism {0: (0, 0), 1: (1, 1)} from - Simplicial complex with vertex set (0, 1) and facets {(0,), (1,)} - to Simplicial complex with 4 vertices and facets {((1, 1),), ((1, 0),), ((0, 0),), ((0, 1),)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1) and facets {(0,), (1,)} + To: Simplicial complex with 4 vertices and facets {((1, 1),), ((1, 0),), ((0, 0),), ((0, 1),)} + Defn: 0 |--> (0, 0) + 1 |--> (1, 1) """ - - if self._codomain == self._domain.product(self._domain,rename_vertices=rename_vertices): - X = self._domain.product(self._domain,rename_vertices=rename_vertices) - f = dict() - if rename_vertices: - for i in self._domain.vertices().set(): - f[i] = "L"+str(i)+"R"+str(i) - else: - for i in self._domain.vertices().set(): - f[i] = (i,i) - return simplicial_complex_morphism.SimplicialComplexMorphism(f, self._domain,X) + # Preserve whether the codomain is mutable when renaming the vertices. + mutable = self._codomain.is_mutable() + X = self._domain.product(self._domain,rename_vertices=rename_vertices, is_mutable=mutable) + if self._codomain != X: + raise TypeError("diagonal morphism is only defined for Hom(X,XxX)") + f = {} + if rename_vertices: + f = {i: "L{0}R{0}".format(i) for i in self._domain.vertices().set()} else: - raise TypeError("Diagonal morphism is only defined for Hom(X,XxX).") + f = {i: (i,i) for i in self._domain.vertices().set()} + return SimplicialComplexMorphism(f, self._domain, X) def identity(self): """ - Returns the identity morphism of `Hom(S,S)`. + Return the identity morphism of `Hom(S,S)`. EXAMPLES:: @@ -146,21 +158,18 @@ def identity(self): sage: T = SimplicialComplex([[0,1]], is_mutable=False) sage: G = Hom(T,T) sage: G.identity() - Simplicial complex morphism {0: 0, 1: 1} from - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} to - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} + Simplicial complex endomorphism of Simplicial complex with vertex set (0, 1) and facets {(0, 1)} + Defn: 0 |--> 0 + 1 |--> 1 """ - if self.is_endomorphism_set(): - f = dict() - for i in self._domain.vertices().set(): - f[i]=i - return simplicial_complex_morphism.SimplicialComplexMorphism(f,self._domain,self._codomain) - else: - raise TypeError("Identity map is only defined for endomorphism sets.") + if not self.is_endomorphism_set(): + raise TypeError("identity map is only defined for endomorphism sets") + f = {i:i for i in self._domain.vertices().set()} + return SimplicialComplexMorphism(f, self._domain, self._codomain) def an_element(self): """ - Returns a (non-random) element of ``self``. + Return a (non-random) element of ``self``. EXAMPLES:: @@ -169,17 +178,19 @@ def an_element(self): sage: H = Hom(S,T) sage: x = H.an_element() sage: x - Simplicial complex morphism {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0} from Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets to Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 7 facets + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets + To: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 7 facets + Defn: [0, 1, 2, 3, 4, 5, 6, 7] --> [0, 0, 0, 0, 0, 0, 0, 0] """ X_vertices = self._domain.vertices().set() try: i = next(self._codomain.vertices().set().__iter__()) except StopIteration: - if len(X_vertices) == 0: - return dict() + if not X_vertices: + return {} else: - raise TypeError("There are no morphisms from a non-empty simplicial complex to an empty simplicial comples.") - f = dict() - for x in X_vertices: - f[x]=i - return simplicial_complex_morphism.SimplicialComplexMorphism(f,self._domain,self._codomain) + raise TypeError("there are no morphisms from a non-empty simplicial complex to an empty simplicial complex") + f = {x:i for x in X_vertices} + return SimplicialComplexMorphism(f, self._domain, self._codomain) + diff --git a/src/sage/homology/simplicial_complex_morphism.py b/src/sage/homology/simplicial_complex_morphism.py index 75b4d0172a2..4ad792a2778 100644 --- a/src/sage/homology/simplicial_complex_morphism.py +++ b/src/sage/homology/simplicial_complex_morphism.py @@ -20,7 +20,10 @@ sage: S = SimplicialComplex([[0,2],[1,5],[3,4]], is_mutable=False) sage: H = Hom(S,S.product(S, is_mutable=False)) sage: H.diagonal_morphism() - Simplicial complex morphism {0: 'L0R0', 1: 'L1R1', 2: 'L2R2', 3: 'L3R3', 4: 'L4R4', 5: 'L5R5'} from Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and facets {(3, 4), (1, 5), (0, 2)} to Simplicial complex with 36 vertices and 18 facets + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and facets {(3, 4), (1, 5), (0, 2)} + To: Simplicial complex with 36 vertices and 18 facets + Defn: [0, 1, 2, 3, 4, 5] --> ['L0R0', 'L1R1', 'L2R2', 'L3R3', 'L4R4', 'L5R5'] sage: S = SimplicialComplex([[0,2],[1,5],[3,4]], is_mutable=False) sage: T = SimplicialComplex([[0,2],[1,3]], is_mutable=False) @@ -53,8 +56,13 @@ sage: i = H.identity() sage: j = i.fiber_product(i) sage: j - Simplicial complex morphism {'L1R1': 1, 'L3R3': 3, 'L2R2': 2, 'L0R0': 0} from Simplicial complex with 4 vertices and 4 facets to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} - + Simplicial complex morphism: + From: Simplicial complex with 4 vertices and 4 facets + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: L1R1 |--> 1 + L3R3 |--> 3 + L2R2 |--> 2 + L0R0 |--> 0 sage: S = simplicial_complexes.Sphere(2) sage: T = S.product(SimplicialComplex([[0,1]]), rename_vertices = False, is_mutable=False) sage: H = Hom(T,S) @@ -72,7 +80,10 @@ sage: y = G(g) sage: z = y.fiber_product(x) sage: z # this is the mapping path space - Simplicial complex morphism {'L2R(2, 0)': 2, 'L2R(2, 1)': 2, 'L0R(0, 0)': 0, 'L0R(0, 1)': 0, 'L1R(1, 0)': 1, 'L1R(1, 1)': 1} from Simplicial complex with 6 vertices and 6 facets to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with 6 vertices and 6 facets + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: ['L2R(2, 0)', 'L2R(2, 1)', 'L0R(0, 0)', 'L0R(0, 1)', 'L1R(1, 0)', 'L1R(1, 1)'] --> [2, 2, 0, 0, 1, 1] """ #***************************************************************************** @@ -91,13 +102,15 @@ # #***************************************************************************** -import sage.homology.simplicial_complex as simplicial_complex -import sage.matrix.all as matrix -from sage.structure.sage_object import SageObject +from sage.homology.simplicial_complex import Simplex, SimplicialComplex +from sage.matrix.constructor import matrix, zero_matrix from sage.rings.integer_ring import ZZ from sage.homology.chain_complex_morphism import ChainComplexMorphism from sage.combinat.permutation import Permutation from sage.algebras.steenrod.steenrod_algebra_misc import convert_perm +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.categories.simplicial_complexes import SimplicialComplexes def is_SimplicialComplexMorphism(x): """ @@ -116,7 +129,7 @@ def is_SimplicialComplexMorphism(x): """ return isinstance(x,SimplicialComplexMorphism) -class SimplicialComplexMorphism(SageObject): +class SimplicialComplexMorphism(Morphism): """ An element of this class is a morphism of simplicial complexes. """ @@ -143,7 +156,7 @@ def __init__(self,f,X,Y): sage: x.image() == y.image() False """ - if not isinstance(X,simplicial_complex.SimplicialComplex) or not isinstance(Y,simplicial_complex.SimplicialComplex): + if not isinstance(X,SimplicialComplex) or not isinstance(Y,SimplicialComplex): raise ValueError("X and Y must be SimplicialComplexes.") if not set(f.keys()) == X._vertex_set.set(): raise ValueError("f must be a dictionary from the vertex set of X to single values in the vertex set of Y.") @@ -155,12 +168,11 @@ def __init__(self,f,X,Y): fi = [] for j in tup: fi.append(f[j]) - v = simplicial_complex.Simplex(set(fi)) + v = Simplex(set(fi)) if not v in Y_faces[v.dimension()]: raise ValueError("f must be a dictionary from the vertices of X to the vertices of Y.") self._vertex_dictionary = f - self._domain = X - self._codomain = Y + Morphism.__init__(self, Hom(X,Y,SimplicialComplexes())) def __eq__(self,x): """ @@ -172,7 +184,11 @@ def __eq__(self,x): sage: H = Hom(S,S) sage: i = H.identity() sage: i - Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 3} from Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex endomorphism of Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 + 3 |--> 3 sage: f = {0:0,1:1,2:2,3:2} sage: j = H(f) sage: i==j @@ -187,9 +203,8 @@ def __eq__(self,x): sage: l = G(g) sage: k == l True - """ - if not isinstance(x,SimplicialComplexMorphism) or self._codomain != x._codomain or self._domain != x._domain or self._vertex_dictionary != x._vertex_dictionary: + if not isinstance(x,SimplicialComplexMorphism) or self.codomain() != x.codomain() or self.domain() != x.domain() or self._vertex_dictionary != x._vertex_dictionary: return False else: return True @@ -227,8 +242,8 @@ def __call__(self,x,orientation=False): sage: g(Simplex([0,1]), orientation=True) ((0, 1), -1) """ - dim = self._domain.dimension() - if not isinstance(x,simplicial_complex.Simplex) or x.dimension() > dim or not x in self._domain.faces()[x.dimension()]: + dim = self.domain().dimension() + if not isinstance(x,Simplex) or x.dimension() > dim or not x in self.domain().faces()[x.dimension()]: raise ValueError("x must be a simplex of the source of f") tup=x.tuple() fx=[] @@ -239,25 +254,42 @@ def __call__(self,x,orientation=False): oriented = Permutation(convert_perm(fx)).signature() else: oriented = 1 - return (simplicial_complex.Simplex(set(fx)), oriented) + return (Simplex(set(fx)), oriented) else: - return simplicial_complex.Simplex(set(fx)) + return Simplex(set(fx)) - def _repr_(self): + def _repr_type(self): """ - Return a string representation of ``self``. + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(1) + sage: T = simplicial_complexes.Sphere(2) + sage: H = Hom(S,T) + sage: f = {0:0,1:1,2:2} + sage: H(f)._repr_type() + 'Simplicial complex' + """ + return "Simplicial complex" + + def _repr_defn(self): + """ + If there are fewer than 5 vertices, print the image of each vertex + on a separate line. Otherwise, print the map as a single line. EXAMPLES:: - sage: S = simplicial_complexes.Sphere(2) - sage: H = Hom(S,S) - sage: i = H.identity() - sage: i - Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 3} from Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} - sage: i._repr_() - 'Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 3} from Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)}' + sage: S = simplicial_complexes.Simplex(1) + sage: print Hom(S,S).identity()._repr_defn() + 0 |--> 0 + 1 |--> 1 + sage: T = simplicial_complexes.Torus() + sage: print Hom(T,T).identity()._repr_defn() + [0, 1, 2, 3, 4, 5, 6] --> [0, 1, 2, 3, 4, 5, 6] """ - return "Simplicial complex morphism " + str(self._vertex_dictionary) + " from " + self._domain._repr_() + " to " + self._codomain._repr_() + vd = self._vertex_dictionary + if len(vd) < 5: + return '\n'.join("{} |--> {}".format(v, vd[v]) for v in vd) + return "{} --> {}".format(vd.keys(), vd.values()) def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain=False): """ @@ -271,10 +303,17 @@ def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain= sage: f = {0:0,1:1,2:2} sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 2} from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 sage: a = x.associated_chain_complex_morphism() sage: a - Chain complex morphism from Chain complex with at most 2 nonzero terms over Integer Ring to Chain complex with at most 3 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring sage: a._matrix_dictionary {0: [0 0 0] [0 1 0] @@ -288,13 +327,21 @@ def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain= [0 0 1], 2: []} sage: x.associated_chain_complex_morphism(augmented=True) - Chain complex morphism from Chain complex with at most 3 nonzero terms over Integer Ring to Chain complex with at most 4 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 4 nonzero terms over Integer Ring sage: x.associated_chain_complex_morphism(cochain=True) - Chain complex morphism from Chain complex with at most 3 nonzero terms over Integer Ring to Chain complex with at most 2 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring sage: x.associated_chain_complex_morphism(augmented=True,cochain=True) - Chain complex morphism from Chain complex with at most 4 nonzero terms over Integer Ring to Chain complex with at most 3 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 4 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring sage: x.associated_chain_complex_morphism(base_ring=GF(11)) - Chain complex morphism from Chain complex with at most 2 nonzero terms over Finite Field of size 11 to Chain complex with at most 3 nonzero terms over Finite Field of size 11 + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Finite Field of size 11 + To: Chain complex with at most 3 nonzero terms over Finite Field of size 11 Some simplicial maps which reverse the orientation of a few simplices:: @@ -315,20 +362,18 @@ def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain= {0: [0 1] [1 0], 1: [-1]} """ - max_dim = max(self._domain.dimension(),self._codomain.dimension()) - min_dim = min(self._domain.dimension(),self._codomain.dimension()) + max_dim = max(self.domain().dimension(),self.codomain().dimension()) + min_dim = min(self.domain().dimension(),self.codomain().dimension()) matrices = {} if augmented is True: - m = matrix.Matrix(base_ring,1,1,1) + m = matrix(base_ring,1,1,1) if not cochain: matrices[-1] = m else: matrices[-1] = m.transpose() for dim in range(min_dim+1): -# X_faces = list(self._domain.faces()[dim]) -# Y_faces = list(self._codomain.faces()[dim]) - X_faces = list(self._domain.n_cells(dim)) - Y_faces = list(self._codomain.n_cells(dim)) + X_faces = list(self.domain().n_cells(dim)) + Y_faces = list(self.codomain().n_cells(dim)) num_faces_X = len(X_faces) num_faces_Y = len(Y_faces) mval = [0 for i in range(num_faces_X*num_faces_Y)] @@ -338,33 +383,33 @@ def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain= pass else: mval[X_faces.index(i)+(Y_faces.index(y)*num_faces_X)] = oriented - m = matrix.Matrix(base_ring,num_faces_Y,num_faces_X,mval,sparse=True) + m = matrix(base_ring,num_faces_Y,num_faces_X,mval,sparse=True) if not cochain: matrices[dim] = m else: matrices[dim] = m.transpose() for dim in range(min_dim+1,max_dim+1): try: - l1 = len(self._codomain.n_cells(dim)) + l1 = len(self.codomain().n_cells(dim)) except KeyError: l1 = 0 try: - l2 = len(self._domain.n_cells(dim)) + l2 = len(self.domain().n_cells(dim)) except KeyError: l2 = 0 - m = matrix.zero_matrix(base_ring,l1,l2,sparse=True) + m = zero_matrix(base_ring,l1,l2,sparse=True) if not cochain: matrices[dim] = m else: matrices[dim] = m.transpose() if not cochain: return ChainComplexMorphism(matrices,\ - self._domain.chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ - self._codomain.chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) + self.domain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ + self.codomain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) else: return ChainComplexMorphism(matrices,\ - self._codomain.chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ - self._domain.chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) + self.codomain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ + self.domain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) def image(self): """ @@ -408,41 +453,8 @@ def image(self): Simplicial complex with vertex set (0, 2) and facets {(0, 2)} """ - fa = [self(i) for i in self._domain.facets()] - return simplicial_complex.SimplicialComplex(fa, maximality_check=True) - - def domain(self): - """ - Returns the domain of the morphism. - - EXAMPLES:: - - sage: S = SimplicialComplex([[0,1],[2,3]], is_mutable=False) - sage: T = SimplicialComplex([[0,1]], is_mutable=False) - sage: f = {0:0,1:1,2:0,3:1} - sage: H = Hom(S,T) - sage: x = H(f) - sage: x.domain() - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(2, 3), (0, 1)} - """ - return self._domain - - def codomain(self): - """ - Returns the codomain of the morphism. - - EXAMPLES:: - - sage: S = SimplicialComplex([[0,1],[2,3]], is_mutable=False) - sage: T = SimplicialComplex([[0,1]], is_mutable=False) - sage: f = {0:0,1:1,2:0,3:1} - sage: H = Hom(S,T) - sage: x = H(f) - sage: x.codomain() - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} - - """ - return self._codomain + fa = [self(i) for i in self.domain().facets()] + return SimplicialComplex(fa, maximality_check=True) def is_surjective(self): """ @@ -469,7 +481,7 @@ def is_surjective(self): sage: x.is_surjective() True """ - return self._codomain == self.image() + return self.codomain() == self.image() def is_injective(self): """ @@ -492,7 +504,7 @@ def is_injective(self): True """ - v = [self._vertex_dictionary[i[0]] for i in self._domain.faces()[0]] + v = [self._vertex_dictionary[i[0]] for i in self.domain().faces()[0]] for i in v: if v.count(i) > 1: return False @@ -519,16 +531,21 @@ def is_identity(self): sage: f = {0:0,1:1,2:2,3:3} sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 3} from Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} to Simplicial complex with vertex set (0, 1, 2, 3, 4) and 5 facets + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + To: Simplicial complex with vertex set (0, 1, 2, 3, 4) and 5 facets + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 + 3 |--> 3 sage: x.is_identity() False - """ - if self._domain != self._codomain: + if self.domain() != self.codomain(): return False else: f = dict() - for i in self._domain._vertex_set.set(): + for i in self.domain()._vertex_set.set(): f[i] = i if self._vertex_dictionary != f: return False @@ -555,28 +572,31 @@ def fiber_product(self, other, rename_vertices = True): sage: y = G(g) sage: z = x.fiber_product(y) sage: z - Simplicial complex morphism {'L1R2': 1, 'L1R1': 1, 'L2R0': 0, 'L0R0': 0} - from Simplicial complex with 4 vertices and facets - {('L2R0',), ('L1R1',), ('L0R0', 'L1R2')} to Simplicial complex - with vertex set (0, 1, 2) and facets {(2,), (0, 1)} + Simplicial complex morphism: + From: Simplicial complex with 4 vertices and facets {('L2R0',), ('L1R1',), ('L0R0', 'L1R2')} + To: Simplicial complex with vertex set (0, 1, 2) and facets {(2,), (0, 1)} + Defn: L1R2 |--> 1 + L1R1 |--> 1 + L2R0 |--> 0 + L0R0 |--> 0 """ - if self._codomain != other._codomain: + if self.codomain() != other.codomain(): raise ValueError("self and other must have the same codomain.") - X = self._domain.product(other._domain,rename_vertices = rename_vertices) + X = self.domain().product(other.domain(),rename_vertices = rename_vertices) v = [] f = dict() - eff1 = self._domain._vertex_set - eff2 = other._domain._vertex_set + eff1 = self.domain()._vertex_set + eff2 = other.domain()._vertex_set for i in eff1: for j in eff2: - if self(simplicial_complex.Simplex([i])) == other(simplicial_complex.Simplex([j])): + if self(Simplex([i])) == other(Simplex([j])): if rename_vertices: v.append("L"+str(i)+"R"+str(j)) f["L"+str(i)+"R"+str(j)] = self._vertex_dictionary[i] else: v.append((i,j)) f[(i,j)] = self._vertex_dictionary[i] - return SimplicialComplexMorphism(f, X.generated_subcomplex(v), self._codomain) + return SimplicialComplexMorphism(f, X.generated_subcomplex(v), self.codomain()) def mapping_torus(self): r""" @@ -612,15 +632,93 @@ def mapping_torus(self): ... ValueError: self must have the same domain and codomain. """ - if self._domain != self._codomain: + if self.domain() != self.codomain(): raise ValueError("self must have the same domain and codomain.") map_dict = self._vertex_dictionary - interval = simplicial_complex.SimplicialComplex([["I0","I1"],["I1","I2"]]) - product = interval.product(self._domain,False) + interval = SimplicialComplex([["I0","I1"],["I1","I2"]]) + product = interval.product(self.domain(),False) facets = list(product.maximal_faces()) - for facet in self._domain._facets: + for facet in self.domain()._facets: left = [ ("I0",v) for v in facet ] right = [ ("I2",map_dict[v]) for v in facet ] for i in range(facet.dimension()+1): facets.append(tuple(left[:i+1]+right[i:])) - return simplicial_complex.SimplicialComplex(facets) + return SimplicialComplex(facets) + + def induced_homology_morphism(self, base_ring=None, cohomology=False): + """ + The map in (co)homology induced by this map + + INPUTS: + + - ``base_ring`` -- must be a field (optional, default ``QQ``) + + - ``cohomology`` -- boolean (optional, default ``False``). If + ``True``, the map induced in cohomology rather than homology. + + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(1) + sage: T = S.product(S, is_mutable=False) + sage: H = Hom(S,T) + sage: diag = H.diagonal_morphism() + sage: h = diag.induced_homology_morphism(QQ) + sage: h + Graded vector space morphism: + From: Homology module of Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} over Rational Field + To: Homology module of Simplicial complex with 9 vertices and 18 facets over Rational Field + Defn: induced by: + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with 9 vertices and 18 facets + Defn: 0 |--> L0R0 + 1 |--> L1R1 + 2 |--> L2R2 + + We can view the matrix form for the homomorphism:: + + sage: h.to_matrix(0) # in degree 0 + [1] + sage: h.to_matrix(1) # in degree 1 + [ 2] + [-1] + sage: h.to_matrix() # the entire homomorphism + [ 1| 0] + [--+--] + [ 0| 2] + [ 0|-1] + [--+--] + [ 0| 0] + + We can evaluate it on (co)homology classes:: + + sage: coh = diag.induced_homology_morphism(QQ, cohomology=True) + sage: coh.to_matrix(1) + [1 1] + sage: x,y = list(T.cohomology_ring(QQ).basis(1)) + sage: coh(x) + h^{1,0} + sage: coh(2*x+3*y) + 5*h^{1,0} + + Note that the complexes must be immutable for this to + work. Many, but not all, complexes are immutable when + constructed:: + + sage: S.is_immutable() + True + sage: S.barycentric_subdivision().is_immutable() + False + sage: S2 = S.suspension() + sage: S2.is_immutable() + False + sage: h = Hom(S,S2)({0: 0, 1:1, 2:2}).induced_homology_morphism() + Traceback (most recent call last): + ... + ValueError: the domain and codomain complexes must be immutable + sage: S2.set_immutable(); S2.is_immutable() + True + sage: h = Hom(S,S2)({0: 0, 1:1, 2:2}).induced_homology_morphism() + """ + from homology_morphism import InducedHomologyMorphism + return InducedHomologyMorphism(self, base_ring, cohomology) diff --git a/src/sage/interfaces/chomp.py b/src/sage/interfaces/chomp.py index 24fd9087940..d4d45b60d9b 100644 --- a/src/sage/interfaces/chomp.py +++ b/src/sage/interfaces/chomp.py @@ -126,10 +126,10 @@ def __call__(self, program, complex, subcomplex=None, **kwds): EXAMPLES:: - sage: from sage.interfaces.chomp import CHomP - sage: T = cubical_complexes.Torus() - sage: CHomP()('homcubes', T) # indirect doctest, optional - CHomP - {0: 0, 1: Z x Z, 2: Z} + sage: from sage.interfaces.chomp import CHomP + sage: T = cubical_complexes.Torus() + sage: CHomP()('homcubes', T) # indirect doctest, optional - CHomP + {0: 0, 1: Z x Z, 2: Z} """ from sage.misc.temporary_file import tmp_filename from sage.homology.all import CubicalComplex, cubical_complexes diff --git a/src/sage/interfaces/expect.py b/src/sage/interfaces/expect.py index 789da270163..67f267f9154 100644 --- a/src/sage/interfaces/expect.py +++ b/src/sage/interfaces/expect.py @@ -67,6 +67,8 @@ from sage.env import SAGE_EXTCODE, LOCAL_IDENTIFIER from sage.misc.object_multiplexer import Multiplex +from six import reraise as raise_ + BAD_SESSION = -2 # The subprocess is a shared resource. In a multi-threaded @@ -883,7 +885,7 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if except (TypeError, RuntimeError): pass return self._eval_line(line,allow_use_file=allow_use_file, wait_for_prompt=wait_for_prompt, restart_if_needed=False) - raise RuntimeError, "%s\nError evaluating %s in %s"%(msg, line, self), sys.exc_info()[2] + raise_(RuntimeError, "%s\nError evaluating %s in %s"%(msg, line, self), sys.exc_info()[2]) if len(line)>0: try: @@ -1329,7 +1331,7 @@ def __init__(self, parent, value, is_name=False, name=None): # coercion to work properly. except (RuntimeError, ValueError) as x: self._session_number = -1 - raise TypeError, x, sys.exc_info()[2] + raise_(TypeError, x, sys.exc_info()[2]) except BaseException: self._session_number = -1 raise diff --git a/src/sage/interfaces/octave.py b/src/sage/interfaces/octave.py index df08d363fe9..c599690cfc3 100644 --- a/src/sage/interfaces/octave.py +++ b/src/sage/interfaces/octave.py @@ -94,9 +94,8 @@ sage: a = octave.eval(t + ';') # optional - octave, < 1/100th of a second sage: a = octave(t) # optional - octave -Note that actually reading a back out takes forever. This *must* -be fixed ASAP - see -http://trac.sagemath.org/sage_trac/ticket/940/. +Note that actually reading ``a`` back out takes forever. This *must* +be fixed as soon as possible, see :trac:`940`. Tutorial -------- @@ -286,7 +285,7 @@ def quit(self, verbose=False): # to signals. if not self._expect is None: if verbose: - print "Exiting spawned %s process."%self + print "Exiting spawned %s process." % self return def _start(self): @@ -308,9 +307,38 @@ def _start(self): # set random seed self.set_seed(self._seed) + def _equality_symbol(self): + """ + EXAMPLES:: + + sage: octave('0 == 1') # optional - octave + 0 + sage: octave('1 == 1') # optional - octave + 1 + """ + return '==' + + def _true_symbol(self): + """ + EXAMPLES:: + + sage: octave('1 == 1') # optional - octave + 1 + """ + return '1' + + def _false_symbol(self): + """ + EXAMPLES:: + + sage: octave('0 == 1') # optional - octave + 0 + """ + return '0' + def set(self, var, value): """ - Set the variable var to the given value. + Set the variable ``var`` to the given ``value``. EXAMPLES:: @@ -325,7 +353,7 @@ def set(self, var, value): def get(self, var): """ - Get the value of the variable var. + Get the value of the variable ``var``. EXAMPLES:: @@ -344,9 +372,9 @@ def clear(self, var): EXAMPLES:: sage: octave.set('x', '2') # optional - octave - sage: octave.clear('x') # optional - octave - sage: octave.get('x') # optional - octave - "error: `x' undefined near line ... column 1" + sage: octave.clear('x') # optional - octave + sage: octave.get('x') # optional - octave + "error: 'x' undefined near line ... column 1" """ self.eval('clear %s'%var) @@ -387,18 +415,16 @@ def version(self): return octave_version() def solve_linear_system(self, A, b): - """ + r""" Use octave to compute a solution x to A\*x = b, as a list. INPUT: + - ``A`` -- mxn matrix A with entries in `\QQ` or `\RR` - - ``A`` - mxn matrix A with entries in QQ or RR + - ``b`` -- m-vector b entries in `\QQ` or `\RR` (resp) - - ``b`` - m-vector b entries in QQ or RR (resp) - - - OUTPUT: An list x (if it exists) which solves M\*x = b + OUTPUT: A list x (if it exists) which solves M\*x = b EXAMPLES:: @@ -507,33 +533,211 @@ def _object_class(self): return OctaveElement +octave_functions = set() + +def to_complex(octave_string, R): + r""" + Helper function to convert octave complex number + + TESTS:: + + sage: from sage.interfaces.octave import to_complex + sage: to_complex('(0,1)', CDF) + 1.0*I + sage: to_complex('(1.3231,-0.2)', CDF) + 1.3231 - 0.2*I + """ + real, imag = octave_string.strip('() ').split(',') + return R(float(real), float(imag)) + class OctaveElement(ExpectElement): - def _matrix_(self, R): + def _get_sage_ring(self): + r""" + TESTS:: + + sage: octave('1')._get_sage_ring() # optional - octave + Real Double Field + sage: octave('I')._get_sage_ring() # optional - octave + Complex Double Field + sage: octave('[]')._get_sage_ring() # optional - octave + Real Double Field + """ + if self.isinteger(): + import sage.rings.integer_ring + return sage.rings.integer_ring.ZZ + elif self.isreal(): + import sage.rings.real_double + return sage.rings.real_double.RDF + elif self.iscomplex(): + import sage.rings.complex_double + return sage.rings.complex_double.CDF + else: + raise TypeError("no Sage ring associated to this element.") + + def __nonzero__(self): + r""" + Test whether this element is nonzero. + + EXAMPLES:: + + sage: bool(octave('0')) # optional - octave + False + sage: bool(octave('[]')) # optional - octave + False + sage: bool(octave('[0,0]')) # optional - octave + False + sage: bool(octave('[0,0,0;0,0,0]')) # optional - octave + False + + sage: bool(octave('0.1')) # optional - octave + True + sage: bool(octave('[0,1,0]')) # optional - octave + True + sage: bool(octave('[0,0,-0.1;0,0,0]')) # optional - octave + True + """ + return str(self) != ' [](0x0)' and any(x != '0' for x in str(self).split()) + + def _matrix_(self, R=None): r""" Return Sage matrix from this octave element. EXAMPLES:: + sage: A = octave('[1,2;3,4.5]') # optional - octave + sage: matrix(A) # optional - octave + [1.0 2.0] + [3.0 4.5] + sage: _.base_ring() # optional - octave + Real Double Field + + sage: A = octave('[I,1;-1,0]') # optional - octave + sage: matrix(A) # optional - octave + [1.0*I 1.0] + [ -1.0 0.0] + sage: _.base_ring() # optional - octave + Complex Double Field + sage: A = octave('[1,2;3,4]') # optional - octave sage: matrix(ZZ, A) # optional - octave [1 2] [3 4] - sage: A = octave('[1,2;3,4.5]') # optional - octave - sage: matrix(RR, A) # optional - octave - [1.00000000000000 2.00000000000000] - [3.00000000000000 4.50000000000000] """ + oc = self.parent() + if not self.ismatrix(): + raise TypeError('not an octave matrix') + if R is None: + R = self._get_sage_ring() + + s = str(self).strip('\n ') + w = [u.strip().split(' ') for u in s.split('\n')] + nrows = len(w) + ncols = len(w[0]) + + if self.iscomplex(): + w = [[to_complex(x,R) for x in row] for row in w] + from sage.matrix.all import MatrixSpace - s = str(self).strip() - v = s.split('\n ') - nrows = len(v) - if nrows == 0: - return MatrixSpace(R,0,0)(0) - ncols = len(v[0].split()) - M = MatrixSpace(R, nrows, ncols) - v = sum([[x for x in w.split()] for w in v], []) - return M(v) + return MatrixSpace(R, nrows, ncols)(w) + + def _vector_(self, R=None): + r""" + Return Sage vector from this octave element. + + EXAMPLES:: + + sage: A = octave('[1,2,3,4]') # optional - octave + sage: vector(ZZ, A) # optional - octave + (1, 2, 3, 4) + sage: A = octave('[1,2.3,4.5]') # optional - octave + sage: vector(A) # optional - octave + (1.0, 2.3, 4.5) + sage: A = octave('[1,I]') # optional - octave + sage: vector(A) # optional - octave + (1.0, 1.0*I) + """ + oc = self.parent() + if not self.isvector(): + raise TypeError('not an octave vector') + if R is None: + R = self._get_sage_ring() + + s = str(self).strip('\n ') + w = s.strip().split(' ') + nrows = len(w) + + if self.iscomplex(): + w = [to_complex(x, R) for x in w] + + from sage.modules.free_module import FreeModule + return FreeModule(R, nrows)(w) + + def _scalar_(self): + """ + Return Sage scalar from this octave element. + + EXAMPLES:: + + sage: A = octave('2833') # optional - octave + sage: As = A.sage(); As # optional - octave + 2833.0 + sage: As.parent() # optional - octave + Real Double Field + + sage: B = sqrt(A) # optional - octave + sage: Bs = B.sage(); Bs # optional - octave + 53.2259 + sage: Bs.parent() # optional - octave + Real Double Field + + sage: C = sqrt(-A) # optional - octave + sage: Cs = C.sage(); Cs # optional - octave + 53.2259*I + sage: Cs.parent() # optional - octave + Complex Double Field + """ + if not self.isscalar(): + raise TypeError("not an octave scalar") + + R = self._get_sage_ring() + if self.iscomplex(): + return to_complex(str(self), R) + else: + return R(str(self)) + + def _sage_(self): + """ + Try to parse the octave object and return a sage object. + + EXAMPLES:: + sage: A = octave('2833') # optional - octave + sage: A.sage() # optional - octave + 2833.0 + sage: B = sqrt(A) # optional - octave + sage: B.sage() # optional - octave + 53.2259 + sage: C = sqrt(-A) # optional - octave + sage: C.sage() # optional - octave + 53.2259*I + sage: A = octave('[1,2,3,4]') # optional - octave + sage: A.sage() # optional - octave + (1.0, 2.0, 3.0, 4.0) + sage: A = octave('[1,2.3,4.5]') # optional - octave + sage: A.sage() # optional - octave + (1.0, 2.3, 4.5) + sage: A = octave('[1,2.3+I,4.5]') # optional - octave + sage: A.sage() # optional - octave + (1.0, 2.3 + 1.0*I, 4.5) + """ + if self.isscalar(): + return self._scalar_() + elif self.isvector(): + return self._vector_() + elif self.ismatrix(): + return self._matrix_() + else: + raise NotImplementedError('octave type is not recognized') # An instance octave = Octave() diff --git a/src/sage/interfaces/sage0.py b/src/sage/interfaces/sage0.py index 0795ca7eec7..1462030975e 100644 --- a/src/sage/interfaces/sage0.py +++ b/src/sage/interfaces/sage0.py @@ -402,6 +402,22 @@ def _rich_repr_(self, display_manager, **kwds): """ return None + def _repr_option(self, option): + """ + Disable repr option. + + This is necessary because otherwise our :meth:`__getattr__` + would be called. + + EXAMPLES:: + + sage: from sage.repl.rich_output import get_display_manager + sage: m = sage0(4) + sage: m._repr_option('ascii_art') + False + """ + return False + def __getattr__(self, attrname): """ EXAMPLES:: diff --git a/src/sage/interfaces/singular.py b/src/sage/interfaces/singular.py index 2e2190809f5..c9743de9501 100644 --- a/src/sage/interfaces/singular.py +++ b/src/sage/interfaces/singular.py @@ -334,6 +334,8 @@ from sage.misc.misc import get_verbose from sage.misc.superseded import deprecation +from six import reraise as raise_ + class SingularError(RuntimeError): """ Raised if Singular printed an error message @@ -1261,7 +1263,7 @@ def __init__(self, parent, type, value, is_name=False): # coercion to work properly. except SingularError as x: self._session_number = -1 - raise TypeError, x, sys.exc_info()[2] + raise_(TypeError, x, sys.exc_info()[2]) except BaseException: self._session_number = -1 raise diff --git a/src/sage/lfunctions/zero_sums.pyx b/src/sage/lfunctions/zero_sums.pyx index e35bf038bad..6c4535c3d9c 100644 --- a/src/sage/lfunctions/zero_sums.pyx +++ b/src/sage/lfunctions/zero_sums.pyx @@ -1552,7 +1552,8 @@ cdef class LFunctionZeroSum_EllipticCurve(LFunctionZeroSum_abstract): specified by max_Delta. This computation can be run on curves with very large conductor (so long as the conductor is known or quickly computable) when Delta is not too large (see below). - Uses Bober's rank bounding method as described in [Bob-13]. + + Uses Bober's rank bounding method as described in [Bob-13]_. INPUT: diff --git a/src/sage/libs/flint/fmpz.pxd b/src/sage/libs/flint/fmpz.pxd index dc497331ce0..99e748be1a4 100644 --- a/src/sage/libs/flint/fmpz.pxd +++ b/src/sage/libs/flint/fmpz.pxd @@ -1,3 +1,5 @@ +# distutils: libraries = flint + from libc.stdio cimport FILE from sage.libs.gmp.types cimport mpz_t from sage.libs.flint.types cimport * @@ -45,6 +47,8 @@ cdef extern from "flint/fmpz.h": int fmpz_fits_si(fmpz_t f) void fmpz_zero(fmpz_t f) void fmpz_one(fmpz_t f) + void fmpz_setbit(fmpz_t f, ulong i) + int fmpz_tstbit(fmpz_t f, ulong i) # Input and output int fmpz_read(fmpz_t) diff --git a/src/sage/libs/pari/paridecl.pxd b/src/sage/libs/pari/paridecl.pxd index fac8f363ccd..8e039a6084a 100644 --- a/src/sage/libs/pari/paridecl.pxd +++ b/src/sage/libs/pari/paridecl.pxd @@ -1435,6 +1435,7 @@ cdef extern from "sage/libs/pari/parisage.h": long fetch_var_higher() GEN fetch_var_value(long vx, GEN t) GEN gp_read_str(char *t) + GEN gp_read_str_multiline(char *t) entree* install(void *f, char *name, char *code) entree* is_entry(char *s) void kill0(char *e) @@ -2203,7 +2204,7 @@ cdef extern from "sage/libs/pari/parisage.h": # classpoly.c - GEN polclass(GEN D, long xvar) + GEN polclass(GEN D, long inv, long xvar) # compile.c @@ -2216,8 +2217,8 @@ cdef extern from "sage/libs/pari/parisage.h": # concat.c - GEN concat(GEN x, GEN y) - GEN concat1(GEN x) + GEN gconcat(GEN x, GEN y) + GEN gconcat1(GEN x) GEN matconcat(GEN v) GEN shallowconcat(GEN x, GEN y) GEN shallowconcat1(GEN x) @@ -3181,7 +3182,101 @@ cdef extern from "sage/libs/pari/parisage.h": GEN rnfkummer(GEN bnr, GEN subgroup, long all, long prec) + # lfun.c + + long is_linit(GEN data) + GEN ldata_get_an(GEN ldata) + long ldata_get_selfdual(GEN ldata) + long ldata_isreal(GEN ldata) + GEN ldata_get_gammavec(GEN ldata) + long ldata_get_degree(GEN ldata) + long ldata_get_k(GEN ldata) + GEN ldata_get_conductor(GEN ldata) + GEN ldata_get_rootno(GEN ldata) + GEN ldata_get_residue(GEN ldata) + GEN ldata_vecan(GEN ldata, long L, long prec) + long ldata_get_type(GEN ldata) + long linit_get_type(GEN linit) + GEN linit_get_ldata(GEN linit) + GEN linit_get_tech(GEN linit) + GEN lfun_get_domain(GEN tech) + GEN lfun_get_dom(GEN tech) + long lfun_get_bitprec(GEN tech) + GEN lfun_get_factgammavec(GEN tech) + GEN lfun_get_step(GEN tech) + GEN lfun_get_pol(GEN tech) + GEN lfun_get_Residue(GEN tech) + GEN lfun_get_k2(GEN tech) + GEN lfun_get_w2(GEN tech) + GEN lfun_get_expot(GEN tech) + long lfun_get_der(GEN tech) + long lfun_get_bitprec(GEN tech) + GEN lfun(GEN ldata, GEN s, long prec) + GEN lfun_bitprec(GEN ldata, GEN s, long bitprec) + GEN lfun0_bitprec(GEN ldata, GEN s, long der, long bitprec) + GEN lfun0(GEN ldata, GEN s, long der, long prec) + long lfuncheckfeq(GEN data, GEN t0, long prec) + long lfuncheckfeq_bitprec(GEN data, GEN t0, long bitprec) + GEN lfunconductor(GEN data, GEN maxcond, long flag, long prec) + GEN lfuncreate(GEN obj) + GEN lfunan(GEN ldata, long L, long prec) + GEN lfunhardy(GEN ldata, GEN t, long prec) + GEN lfunhardy_bitprec(GEN ldata, GEN t, long bitprec) + GEN lfuninit(GEN ldata, GEN dom, long der, long prec) + GEN lfuninit_bitprec(GEN ldata, GEN dom, long der, long bitprec) + GEN lfuninit_make(long t, GEN ldata, GEN molin, GEN domain) + long lfunisvgaell(GEN Vga, long flag) + GEN lfunlambda(GEN ldata, GEN s, long prec) + GEN lfunlambda_bitprec(GEN ldata, GEN s, long bitprec) + GEN lfunlambda0(GEN ldata, GEN s, long der, long prec) + GEN lfunlambda0_bitprec(GEN ldata, GEN s, long der, long bitprec) + GEN lfunmisc_to_ldata(GEN ldata) + GEN lfunmisc_to_ldata_shallow(GEN ldata) + long lfunorderzero(GEN ldata, long prec) + long lfunorderzero_bitprec(GEN ldata, long bitprec) + GEN lfunprod_get_fact(GEN tech) + GEN lfunrootno(GEN data, long prec) + GEN lfunrootno_bitprec(GEN data, long bitprec) + GEN lfunrootres(GEN data, long prec) + GEN lfunrootres_bitprec(GEN data, long bitprec) + GEN lfunrtopoles(GEN r) + GEN lfuntheta(GEN data, GEN t, long m, long prec) + GEN lfuntheta_bitprec(GEN data, GEN t, long m, long bitprec) + GEN lfunthetainit(GEN ldata, GEN tinf, long m, long prec) + GEN lfunthetainit_bitprec(GEN ldata, GEN tdom, long m, long bitprec) + GEN lfunthetacheckinit(GEN data, GEN tinf, long m, long *ptbitprec, long fl) + GEN lfunzeros(GEN ldata, GEN lim, long divz, long prec) + GEN lfunzeros_bitprec(GEN ldata, GEN lim, long divz, long bitprec) + int sdomain_isincl(GEN dom, GEN dom0) + GEN theta_get_an(GEN tdata) + GEN theta_get_K(GEN tdata) + GEN theta_get_R(GEN tdata) + long theta_get_bitprec(GEN tdata) + long theta_get_m(GEN tdata) + GEN theta_get_tdom(GEN tdata) + GEN theta_get_sqrtN(GEN tdata) + + # lfunutils.c + + GEN dirzetak(GEN nf, GEN b) + GEN ellmoddegree(GEN e, long prec) + GEN lfunabelianrelinit(GEN bnfabs, GEN bnf, GEN polrel, GEN dom, long der, long prec) + GEN lfunabelianrelinit_bitprec(GEN bnfabs, GEN bnf, GEN polrel, GEN dom, long der, long bitprec) + GEN lfunconvol(GEN a1, GEN a2) + GEN lfundiv(GEN ldata1, GEN ldata2, long prec) + GEN lfunzetakinit(GEN pol, GEN dom, long der, long flag, long prec) + GEN lfunzetakinit_bitprec(GEN pol, GEN dom, long der, long flag, long bitprec) + GEN lfunetaquo(GEN ldata) + GEN lfunmfspec(GEN ldata, long prec) + GEN lfunmfpeters(GEN ldata, long prec) + GEN lfunmfpeters_bitprec(GEN ldata, long bitprec) + GEN lfunmul(GEN ldata1, GEN ldata2, long prec) + GEN lfunqf(GEN ldata) + GEN lfunsymsq(GEN ldata, GEN known, long prec) + GEN lfunsymsqspec(GEN ldata, long prec) + # lll.c + GEN ZM_lll_norms(GEN x, double D, long flag, GEN *B) GEN kerint(GEN x) GEN lll(GEN x) diff --git a/src/sage/libs/singular/groebner_strategy.pyx b/src/sage/libs/singular/groebner_strategy.pyx index 231cce0ed21..b4c2be95922 100644 --- a/src/sage/libs/singular/groebner_strategy.pyx +++ b/src/sage/libs/singular/groebner_strategy.pyx @@ -312,7 +312,7 @@ cdef class NCGroebnerStrategy(SageObject): sage: A. = FreeAlgebra(QQ, 3) sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: I = H.ideal([y^2, x^2, z^2-H.one()]) - sage: NCGroebnerStrategy(I) + sage: NCGroebnerStrategy(I) #random Groebner Strategy for ideal generated by 3 elements over Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} TESTS:: @@ -412,7 +412,7 @@ cdef class NCGroebnerStrategy(SageObject): sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: I = H.ideal([y^2, x^2, z^2-H.one()]) sage: strat = NCGroebnerStrategy(I) - sage: strat # indirect doctest + sage: strat # indirect doctest #random Groebner Strategy for ideal generated by 3 elements over Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} """ return "Groebner Strategy for ideal generated by %d elements over %s"%(self._ideal.ngens(),self._parent) diff --git a/src/sage/matrix/action.pyx b/src/sage/matrix/action.pyx index 23ea977ba36..e9df2451601 100644 --- a/src/sage/matrix/action.pyx +++ b/src/sage/matrix/action.pyx @@ -53,8 +53,10 @@ AUTHOR: #***************************************************************************** # Copyright (C) 2007 Robert Bradshaw # -# Distributed under the terms of the GNU General Public License (GPL) -# +# 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/ #***************************************************************************** @@ -63,6 +65,7 @@ import operator from matrix_space import MatrixSpace, is_MatrixSpace from sage.modules.free_module import FreeModule, is_FreeModule +from sage.structure.element cimport coercion_model cdef class MatrixMulAction(Action): @@ -70,8 +73,7 @@ cdef class MatrixMulAction(Action): if not is_MatrixSpace(G): raise TypeError, "Not a matrix space: %s" % G if G.base_ring() is not S.base_ring(): - from sage.structure.element import get_coercion_model - base = get_coercion_model().common_parent(G.base_ring(), S.base_ring()) + base = coercion_model.common_parent(G.base_ring(), S.base_ring()) else: base = G.base_ring() Action.__init__(self, G, S, is_left, operator.mul) diff --git a/src/sage/matrix/constructor.py b/src/sage/matrix/constructor.py index 97348ba1468..3f108e1beed 100644 --- a/src/sage/matrix/constructor.py +++ b/src/sage/matrix/constructor.py @@ -4159,6 +4159,3 @@ def ith_to_zero_rotation_matrix(v, i, ring=None): entries[(k, k)] = 1 entries.update({(j,j):aa, (j,i):bb, (i,j):-bb, (i,i):aa}) return matrix(entries, nrows=dim, ring=ring) - - - diff --git a/src/sage/matrix/matrix1.pyx b/src/sage/matrix/matrix1.pyx index 1a05c581e21..d65eba6305d 100644 --- a/src/sage/matrix/matrix1.pyx +++ b/src/sage/matrix/matrix1.pyx @@ -9,18 +9,20 @@ TESTS:: sage: TestSuite(A).run() """ -################################################################################ +#***************************************************************************** # Copyright (C) 2005, 2006 William Stein # -# Distributed under the terms of the GNU General Public License (GPL). -# The full text of the GPL is available at: -# +# 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/ -################################################################################ +#***************************************************************************** include "sage/ext/python.pxi" import sage.modules.free_module +from sage.structure.element cimport coercion_model cdef class Matrix(matrix0.Matrix): @@ -1363,8 +1365,6 @@ cdef class Matrix(matrix0.Matrix): top_ring = self._base_ring bottom_ring = other._base_ring if top_ring is not bottom_ring: - from sage.structure.element import get_coercion_model - coercion_model = get_coercion_model() R = coercion_model.common_parent(top_ring, bottom_ring) if top_ring is not R: self = self.change_ring(R) diff --git a/src/sage/matrix/matrix_mod2_dense.pyx b/src/sage/matrix/matrix_mod2_dense.pyx index be72592dd1f..6e1b3014f6f 100644 --- a/src/sage/matrix/matrix_mod2_dense.pyx +++ b/src/sage/matrix/matrix_mod2_dense.pyx @@ -625,6 +625,15 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse sage: r1 = A*v1 sage: r0.column(0) == r1 True + + TESTS: + + Check that :trac:`19378` is fixed:: + + sage: m = matrix(GF(2), 11, 0) + sage: v = vector(GF(2), 0) + sage: m * v + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) """ cdef mzd_t *tmp if not isinstance(v, Vector_mod2_dense): @@ -634,6 +643,9 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse raise ArithmeticError("number of columns of matrix must equal degree of vector") VS = VectorSpace(self._base_ring, self._nrows) + # If the vector is 0-dimensional, the result will be the 0-vector + if not self.ncols(): + return VS.zero() cdef Vector_mod2_dense c = Vector_mod2_dense.__new__(Vector_mod2_dense) c._init(self._nrows, VS) c._entries = mzd_init(1, self._nrows) diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index 58569480464..6336b8e1c25 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -118,11 +118,13 @@ class MatrixSpace(UniqueRepresentation, parent_gens.ParentWithGens): sage: MatrixSpace(ZZ,10,5) Full MatrixSpace of 10 by 5 dense matrices over Integer Ring sage: MatrixSpace(ZZ,10,5).category() - Category of infinite modules over (euclidean domains and infinite enumerated sets) + Category of infinite modules over (euclidean domains + and infinite enumerated sets and metric spaces) sage: MatrixSpace(ZZ,10,10).category() - Category of infinite algebras over (euclidean domains and infinite enumerated sets) + Category of infinite algebras over (euclidean domains + and infinite enumerated sets and metric spaces) sage: MatrixSpace(QQ,10).category() - Category of infinite algebras over quotient fields + Category of infinite algebras over (quotient fields and metric spaces) TESTS:: diff --git a/src/sage/matrix/operation_table.py b/src/sage/matrix/operation_table.py index eca698fd93d..e76a66c0ab8 100644 --- a/src/sage/matrix/operation_table.py +++ b/src/sage/matrix/operation_table.py @@ -419,16 +419,28 @@ def __init__(self, S, operation, names='letters', elements=None): # If not, we'll discover that next in actual use. self._table = [] + + # the elements might not be hashable. But if they are it is much + # faster to lookup in a hash table rather than in a list! + try: + get_row = {e: i for i,e in enumerate(self._elts)}.__getitem__ + except TypeError: + get_row = self._elts.index + for g in self._elts: row = [] for h in self._elts: try: result = self._operation(g, h) - row.append(self._elts.index(result)) - except ValueError: # list/index condition - raise ValueError('%s%s%s=%s, and so the set is not closed' % (g, self._ascii_symbol, h, result)) except Exception: raise TypeError('elements %s and %s of %s are incompatible with operation: %s' % (g,h,S,self._operation)) + + try: + r = get_row(result) + except (KeyError,ValueError): + raise ValueError('%s%s%s=%s, and so the set is not closed' % (g, self._ascii_symbol, h, result)) + + row.append(r) self._table.append(row) def _name_maker(self, names): @@ -559,8 +571,8 @@ def __getitem__(self, pair): TESTS:: sage: from sage.matrix.operation_table import OperationTable - sage: G=DiCyclicGroup(3) - sage: T=OperationTable(G, operator.mul) + sage: G = DiCyclicGroup(3) + sage: T = OperationTable(G, operator.mul) sage: T[G('(1,2)(3,4)(5,6,7)')] Traceback (most recent call last): ... diff --git a/src/sage/matroids/lean_matrix.pyx b/src/sage/matroids/lean_matrix.pyx index 08f0db2ee3a..2c920b21519 100644 --- a/src/sage/matroids/lean_matrix.pyx +++ b/src/sage/matroids/lean_matrix.pyx @@ -741,7 +741,7 @@ cdef class GenericMatrix(LeanMatrix): def __repr__(self): """ - Print representation. + Return representation. EXAMPLES:: @@ -1039,8 +1039,8 @@ cdef class BinaryMatrix(LeanMatrix): bitset_free(self._temp) def __repr__(self): - """ - Print representation string + r""" + Return representation string EXAMPLES:: @@ -1666,8 +1666,8 @@ cdef class TernaryMatrix(LeanMatrix): bitset_free(self._u) def __repr__(self): - """ - Print representation string + r""" + Return representation string EXAMPLES:: @@ -2241,8 +2241,8 @@ cdef class QuaternaryMatrix(LeanMatrix): bitset_free(self._u) def __repr__(self): - """ - Print representation string + r""" + Return representation string EXAMPLES:: @@ -2829,7 +2829,7 @@ cdef class IntegerMatrix(LeanMatrix): def __repr__(self): """ - Print representation. + Return representation. EXAMPLES:: diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index bd705e8f433..5309ccee5d6 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -5575,9 +5575,10 @@ cdef class Matroid(SageObject): ``True`` if the matroid is 3-connected, ``False`` otherwise. INPUT: + - ``basis`` -- a basis of the matroid. - ``fund_cocircuits`` -- a iterable of some fundamental cocircuits with - respect to ``basis``. It must contain all separating fundamental cocircuits. + respect to ``basis``. It must contain all separating fundamental cocircuits. OUTPUT: @@ -5598,8 +5599,8 @@ cdef class Matroid(SageObject): .. NOTE:: - The function does not check its input at all. You may want to make - sure the matroid is both simple and cosimple. + The function does not check its input at all. You may want to make + sure the matroid is both simple and cosimple. """ # Step 1: base case if self.rank() <= 2: @@ -6731,6 +6732,8 @@ cdef class Matroid(SageObject): True sage: sum(map(len,P))==len(M.groundset()) True + sage: Matroid(matrix([])).partition() + [] ALGORITHM: @@ -6741,7 +6744,8 @@ cdef class Matroid(SageObject): from sage.matroids.union_matroid import MatroidSum, PartitionMatroid if self.loops(): raise ValueError("Cannot partition matroids with loops.") - + if self.size()==0: + return [] # doubling search for minimum independent sets that partitions the groundset n = self.size() r = self.rank() diff --git a/src/sage/misc/c3_controlled.pyx b/src/sage/misc/c3_controlled.pyx index cb1c4fe2e18..9b40f1641af 100644 --- a/src/sage/misc/c3_controlled.pyx +++ b/src/sage/misc/c3_controlled.pyx @@ -326,9 +326,9 @@ For a typical category, few bases, if any, need to be added to force sage: x.mro == x.mro_standard False sage: x.all_bases_len() - 82 + 92 sage: x.all_bases_controlled_len() - 89 + 100 The following can be used to search through the Sage named categories for any that requires the addition of some bases:: @@ -343,8 +343,7 @@ for any that requires the addition of some bases:: Category of finite dimensional algebras with basis over Rational Field, Category of finite dimensional hopf algebras with basis over Rational Field, Category of finite permutation groups, - Category of graded hopf algebras with basis over Rational Field, - Category of hopf algebras with basis over Rational Field] + Category of graded hopf algebras with basis over Rational Field] AUTHOR: diff --git a/src/sage/misc/converting_dict.py b/src/sage/misc/converting_dict.py new file mode 100644 index 00000000000..6273b7e5e12 --- /dev/null +++ b/src/sage/misc/converting_dict.py @@ -0,0 +1,291 @@ +r""" +Converting Dictionary + +At the moment, the only class contained in this model is a key +converting dictionary, which applies some function (e.g. type +conversion function) to all arguments used as keys. + +.. It is conceivable that a other dicts might be added later on. + +AUTHORS: + +- Martin von Gagern (2015-01-31): initial version + +EXAMPLES: + +A ``KeyConvertingDict`` will apply a conversion function to all method +arguments which are keys:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d["3"] = 42 + sage: d.items() + [(3, 42)] + +This is used e.g. in the result of a variety, to allow access to the +result no matter how a generator is identified:: + + sage: K. = QQ[] + sage: I = ideal([x^2+2*y-5,x+y+3]) + sage: v = I.variety(AA)[0]; v + {x: 4.464101615137755?, y: -7.464101615137755?} + sage: v.keys()[0].parent() + Multivariate Polynomial Ring in x, y over Algebraic Real Field + sage: v[x] + 4.464101615137755? + sage: v["y"] + -7.464101615137755? +""" + +#***************************************************************************** +# Copyright (C) 2015 Martin von Gagern +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import collections + +class KeyConvertingDict(dict): + r""" + A dictionary which automatically applys a conversions to its keys. + + The most common application is the case where the conversion + function is the object representing some category, so that key + conversion means a type conversion to adapt keys to that + category. This allows different representations for keys which in + turn makes accessing the correct element easier. + + INPUT: + + - ``key_conversion_function`` -- a function which will be + applied to all method arguments which represent keys. + - ``data`` -- optional dictionary or sequence of key-value pairs + to initialize this mapping. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d["3"] = 42 + sage: d.items() + [(3, 42)] + sage: d[5.0] = 64 + sage: d["05"] + 64 + + """ + + def __init__(self, key_conversion_function, data=None): + r""" + Construct a dictionary with a given conversion function. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d["3"] = 42 + sage: d.items() + [(3, 42)] + sage: KeyConvertingDict(int, {"5": 7}).items() + [(5, 7)] + sage: KeyConvertingDict(int, [("9", 99)]).items() + [(9, 99)] + """ + super(KeyConvertingDict, self).__init__() + self.key_conversion_function = key_conversion_function + if data: + self.update(data) + + def __getitem__(self, key): + r""" + Retrieve an element from the dictionary. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: d["3"] + 42 + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).__getitem__(key) + + def __setitem__(self, key, value): + r""" + Assign an element in the dictionary. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + - ``value`` -- The associated value, will be left unmodified. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d["3"] = 42 + sage: d.items() + [(3, 42)] + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).__setitem__(key, value) + + def __delitem__(self, key): + r""" + Remove a mapping from the dictionary. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: del d["3"] + sage: len(d) + 0 + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).__delitem__(key) + + def __contains__(self, key): + r""" + Test whether a given key is contained in the mapping. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: "3" in d + True + sage: 4 in d + False + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).__contains__(key) + + def has_key(self, key): + r""" + Deprecated; present just for the sake of compatibility. + Use ``key in self`` instead. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: d.has_key("3") + True + sage: d.has_key(4) + False + """ + return key in self + + def pop(self, key, *args): + r""" + Remove and retreive a given element from the dictionary + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + - ``default`` -- The value to return if the element is not mapped, optional. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: d.pop("3") + 42 + sage: d.pop("3", 33) + 33 + sage: d.pop("3") + Traceback (most recent call last): + ... + KeyError: ... + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).pop(key, *args) + + def setdefault(self, key, default=None): + r""" + Create a given mapping unless there already exists a mapping + for that key. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + - ``default`` -- The value to associate with the key. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d.setdefault("3") + sage: d.items() + [(3, None)] + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).setdefault(key, default) + + def update(self, *args, **kwds): + r""" + Update the dictionary with key-value pairs from another dictionary, + sequence of key-value pairs, or keyword arguments. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + - ``args`` -- A single dict or sequence of pairs. + - ``kwds`` -- Named elements require that the conversion + function accept strings. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d.update([("3",1),(4,2)]) + sage: d[3] + 1 + sage: d.update({"5": 7, "9": 12}) + sage: d[9] + 12 + sage: d = KeyConvertingDict(QQ['x']) + sage: d.update(x=42) + sage: d + {x: 42} + """ + f = self.key_conversion_function + u = super(KeyConvertingDict, self).update + if args: + if len(args) != 1: + raise TypeError("update expected at most 1 argument") + arg = args[0] + if isinstance(arg, collections.Mapping): + seq = ((f(k), arg[k]) for k in arg) + else: + seq = ((f(k), v) for k, v in arg) + u(seq) + if kwds: + seq = ((f(k), v) for k, v in kwds.iteritems()) + u(seq) diff --git a/src/sage/misc/cython.py b/src/sage/misc/cython.py index 01ee022b37d..8fd9e67f6ff 100644 --- a/src/sage/misc/cython.py +++ b/src/sage/misc/cython.py @@ -283,7 +283,7 @@ def pyx_preparse(s): def cython(filename, verbose=False, compile_message=False, use_cache=False, create_local_c_file=False, annotate=True, sage_namespace=True, create_local_so_file=False): - """ + r""" Compile a Cython file. This converts a Cython file to a C (or C++ file), and then compiles that. The .c file and the .so file are created in a temporary directory. @@ -341,6 +341,11 @@ def cython(filename, verbose=False, compile_message=False, sage: x x^2 + Check that compiling c++ code works:: + + sage: cython("#clang C++\n"+ + ....: "from libcpp.vector cimport vector\n" + ....: "cdef vector[int] * v = new vector[int](4)\n") """ if not filename.endswith('pyx'): print("Warning: file (={}) should have extension .pyx".format(filename), file=sys.stderr) @@ -454,7 +459,6 @@ def cython(filename, verbose=False, compile_message=False, include_dirs = %s) """%(extra_args, name, name, extension, additional_source_files, libs, language, includes) open('%s/setup.py'%build_dir,'w').write(setup) - cython_include = ' '.join(["-I '%s'"%x for x in includes if len(x.strip()) > 0 ]) options = ['-p'] @@ -463,7 +467,12 @@ def cython(filename, verbose=False, compile_message=False, if sage_namespace: options.append('--pre-import sage.all') - cmd = "cd '%s' && cython %s %s '%s.pyx' 1>log 2>err " % (build_dir, ' '.join(options), cython_include, name) + cmd = "cd '{DIR}' && cython {OPT} {INC} {LANG} '{NAME}.pyx' 1>log 2>err ".format( + DIR=build_dir, + OPT=' '.join(options), + INC=cython_include, + LANG='--cplus' if language=='c++' else '', + NAME=name) if create_local_c_file: target_c = '%s/_%s.c'%(os.path.abspath(os.curdir), base) @@ -481,9 +490,6 @@ def cython(filename, verbose=False, compile_message=False, err = subtract_from_line_numbers(open('%s/err'%build_dir).read(), offset) raise RuntimeError("Error converting {} to C:\n{}\n{}".format(filename, log, err)) - if language=='c++': - os.system("cd '%s' && mv '%s.c' '%s.cpp'"%(build_dir,name,name)) - cmd = 'cd %s && python setup.py build 1>log 2>err'%build_dir if verbose: print(cmd) diff --git a/src/sage/misc/functional.py b/src/sage/misc/functional.py index 607cca30874..5920e8e9c7b 100644 --- a/src/sage/misc/functional.py +++ b/src/sage/misc/functional.py @@ -124,7 +124,8 @@ def category(x): sage: V = VectorSpace(QQ,3) sage: category(V) - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis over + (quotient fields and metric spaces) """ try: return x.category() diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index e3a3e453223..8deb81cad68 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -1722,7 +1722,33 @@ def random_sublist(X, s): return [a for a in X if random.random() <= s] +def some_tuples(elements, repeat, bound): + r""" + Return an iterator over at most ``bound`` number of ``repeat``-tuples of + ``elements``. + + TESTS:: + + sage: from sage.misc.misc import some_tuples + sage: l = some_tuples([0,1,2,3], 2, 3) + sage: l + + sage: len(list(l)) + 3 + + sage: l = some_tuples(range(50), 3, 10) + sage: len(list(l)) + 10 + .. TODO:: + + Currently, this only return an iterator over the first element of the + cartesian product. It would be smarter to return something more + "random like" as it is used in tests. However, this should remain + deterministic. + """ + from itertools import islice, product + return islice(product(elements, repeat=repeat), bound) def powerset(X): r""" diff --git a/src/sage/misc/mrange.py b/src/sage/misc/mrange.py index eb03223dbf6..711059bb601 100644 --- a/src/sage/misc/mrange.py +++ b/src/sage/misc/mrange.py @@ -608,3 +608,110 @@ def cartesian_product_iterator(X): [()] """ return xmrange_iter(X, tuple) + +def cantor_product(*args, **kwds): + r""" + Return an iterator over the product of the inputs along the diagonals a la + :wikipedia:`Cantor pairing `. + + INPUT: + + - a certain number of iterables + + - ``repeat`` -- an optional integer. If it is provided, the input is + repeated ``repeat`` times. + + EXAMPLES:: + + sage: from sage.misc.mrange import cantor_product + sage: list(cantor_product([0, 1], repeat=3)) + [(0, 0, 0), + (1, 0, 0), + (0, 1, 0), + (0, 0, 1), + (1, 1, 0), + (1, 0, 1), + (0, 1, 1), + (1, 1, 1)] + sage: list(cantor_product([0, 1], [0, 1, 2, 3])) + [(0, 0), (1, 0), (0, 1), (1, 1), (0, 2), (1, 2), (0, 3), (1, 3)] + + Infinite iterators are valid input as well:: + + sage: from itertools import islice + sage: list(islice(cantor_product(ZZ, QQ), 14)) + [(0, 0), + (1, 0), + (0, 1), + (-1, 0), + (1, 1), + (0, -1), + (2, 0), + (-1, 1), + (1, -1), + (0, 1/2), + (-2, 0), + (2, 1), + (-1, -1), + (1, 1/2)] + + TESTS:: + + sage: C = cantor_product([0, 1], [0, 1, 2, 3], [0, 1, 2]) + sage: sum(1 for _ in C) == 2*4*3 + True + + sage: from itertools import count + sage: list(cantor_product([], count())) + [] + sage: list(cantor_product(count(), [], count())) + [] + + sage: list(cantor_product(count(), repeat=0)) + [()] + + sage: next(cantor_product(count(), repeat=-1)) + Traceback (most recent call last): + ... + ValueError: repeat argument cannot be negative + sage: next(cantor_product(count(), toto='hey')) + Traceback (most recent call last): + ... + TypeError: 'toto' is an invalid keyword argument for this function + """ + from itertools import count + from sage.combinat.integer_lists import IntegerListsLex + + m = len(args) # numer of factors + lengths = [None] * m # None or length of factors + data = [[] for _ in range(m)] # the initial slice of each factor + iterators = [iter(a) for a in args] # the iterators + repeat = int(kwds.pop('repeat', 1)) + if repeat == 0: + yield () + return + elif repeat < 0: + raise ValueError("repeat argument cannot be negative") + if kwds: + raise TypeError("'{}' is an invalid keyword argument for this function".format(next(kwds.iterkeys()))) + mm = m * repeat + + for n in count(0): + # try to add one more term to each bin + for i, a in enumerate(iterators): + if lengths[i] is None: + try: + data[i].append(next(a)) + except StopIteration: + assert len(data[i]) == n + if n == 0: + return + lengths[i] = n + + # iterate through what we have + ceiling = [n if lengths[i] is None else lengths[i]-1 for i in range(m)] * repeat + for v in IntegerListsLex(n, length=mm, ceiling=ceiling): + yield tuple(data[i%m][v[i]] for i in range(mm)) + + if all(l is not None for l in lengths) and repeat*sum(l-1 for l in lengths) == n: + return diff --git a/src/sage/misc/rest_index_of_methods.py b/src/sage/misc/rest_index_of_methods.py index 3800ff7f86a..c928f036c62 100644 --- a/src/sage/misc/rest_index_of_methods.py +++ b/src/sage/misc/rest_index_of_methods.py @@ -7,7 +7,9 @@ {INDEX_OF_FUNCTIONS} """ -def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): +from sage.misc.sageinspect import _extract_embedded_position + +def gen_rest_table_index(list_of_entries, names=None, sort=True, only_local_functions=True): r""" Return a ReST table describing a list of functions. @@ -24,6 +26,10 @@ def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): deprecated or those starting with an underscore. In the case of a class, note that inherited methods are not displayed. + - ``names`` -- a dictionary associating a name to a function. Takes + precedence over the automatically computed name for the functions. Only + used when ``list_of_entries`` is a list. + - ``sort`` (boolean; ``True``) -- whether to sort the list of methods lexicographically. @@ -57,7 +63,12 @@ def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): :widths: 30, 70 :delim: @ - :func:`~sage.misc.rest_index_of_methods.gen_rest_table_index` @ Return a ReST table describing a list of functions... + :func:`~sage.misc.rest_index_of_methods.doc_index` @ Attribute an index name to a function. + :func:`~sage.misc.rest_index_of_methods.gen_rest_table_index` @ Return a ReST table describing a list of functions. + :func:`~sage.misc.rest_index_of_methods.gen_thematic_rest_table_index` @ Return a ReST string of thematically sorted function (or methods) of a module (or class). + :func:`~sage.misc.rest_index_of_methods.list_of_subfunctions` @ Returns the functions (resp. methods) of a given module (resp. class) with their names. + + The table of a class:: @@ -104,33 +115,43 @@ def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): :widths: 30, 70 :delim: @ - :func:`~sage.misc.rest_index_of_methods.gen_rest_table_index` @ Return a ReST table describing a list of functions... + :func:`~sage.misc.rest_index_of_methods.doc_index` @ Attribute an index name to a function. + :func:`~sage.misc.rest_index_of_methods.gen_rest_table_index` @ Return a ReST table describing a list of functions. + :func:`~sage.misc.rest_index_of_methods.gen_thematic_rest_table_index` @ Return a ReST string of thematically sorted function (or methods) of a module (or class). + :func:`~sage.misc.rest_index_of_methods.list_of_subfunctions` @ Returns the functions (resp. methods) of a given module (resp. class) with their names. + + sage: print gen_rest_table_index(sage.misc.rest_index_of_methods, only_local_functions=False) .. csv-table:: :class: contentstable :widths: 30, 70 :delim: @ + + :func:`~sage.misc.rest_index_of_methods.doc_index` @ Attribute an index name to a function. + :func:`~sage.misc.rest_index_of_methods.gen_thematic_rest_table_index` @ Return a ReST string of thematically sorted function (or methods) of a module (or class). + :func:`~sage.misc.rest_index_of_methods.list_of_subfunctions` @ Returns the functions (resp. methods) of a given module (resp. class) with their names. + A function that is imported into a class under a different name is listed + under its 'new' name:: + + sage: 'cliques_maximum' in gen_rest_table_index(Graph) + True + sage: 'all_max_cliques`' in gen_rest_table_index(Graph) + False """ import inspect + if names is None: + names = {} # If input is a class/module, we list all its non-private and methods/functions if (inspect.isclass(list_of_entries) or inspect.ismodule(list_of_entries)): root = list_of_entries - def local_filter(f,name): - if only_local_functions: - return inspect.getmodule(root) == inspect.getmodule(f) - else: - return inspect.isclass(list_of_entries) or not (f is gen_rest_table_index) - list_of_entries = [getattr(root,name) for name,f in root.__dict__.items() if - (not name.startswith('_') and # private functions - not hasattr(f,'trac_number') and # deprecated functions - not inspect.isclass(f) and # classes - local_filter(f,name) # possibly filter imported functions - )] + list_of_entries,names = list_of_subfunctions(root, only_local_functions=only_local_functions) + + fname = lambda x:names.get(x,getattr(x,"__name__","")) assert isinstance(list_of_entries,list) @@ -140,20 +161,26 @@ def local_filter(f,name): " :delim: @\n\n") if sort: - list_of_entries.sort(key=lambda x:getattr(x,'__name__','')) + list_of_entries.sort(key=fname) for e in list_of_entries: if inspect.ismethod(e): - link = ":meth:`~"+str(e.im_class.__module__)+"."+str(e.im_class.__name__)+"."+e.__name__+"`" + link = ":meth:`~"+str(e.im_class.__module__)+"."+str(e.im_class.__name__)+"."+fname(e)+"`" elif inspect.isfunction(e): - link = ":func:`~"+str(e.__module__)+"."+str(e.__name__)+"`" + link = ":func:`~"+str(e.__module__)+"."+fname(e)+"`" else: continue + # Extract lines injected by cython + doc = e.__doc__ + doc_tmp = _extract_embedded_position(doc) + if doc_tmp: + doc = doc_tmp[0] + # Descriptions of the method/function - if e.__doc__: - desc = e.__doc__.split('\n\n')[0] # first paragraph + if doc: + desc = doc.split('\n\n')[0] # first paragraph desc = " ".join([x.strip() for x in desc.splitlines()]) # concatenate lines desc = desc.strip() # remove leading spaces else: @@ -163,4 +190,120 @@ def local_filter(f,name): return s+'\n' +def list_of_subfunctions(root, only_local_functions=True): + r""" + Returns the functions (resp. methods) of a given module (resp. class) with their names. + + INPUT: + + - ``root`` -- the module, or class, whose elements are to be listed. + + - ``only_local_functions`` (boolean; ``True``) -- if ``root`` is a module, + ``only_local_functions = True`` means that imported functions will be + filtered out. This can be useful to disable for making indexes of + e.g. catalog modules such as :mod:`sage.coding.codes_catalog`. + + OUTPUT: + + A pair ``(list,dict)`` where ``list`` is a list of function/methods and + ``dict`` associates to every function/method the name under which it appears + in ``root``. + + EXAMPLE:: + + sage: from sage.misc.rest_index_of_methods import list_of_subfunctions + sage: l = list_of_subfunctions(Graph)[0] + sage: Graph.bipartite_color in l + True + """ + import inspect + if inspect.ismodule(root): + ismodule = True + elif inspect.isclass(root): + ismodule = False + superclasses = inspect.getmro(root)[1:] + else: + raise ValueError("'root' must be a module or a class.") + + def local_filter(f,name): + if only_local_functions: + if ismodule: + return inspect.getmodule(root) == inspect.getmodule(f) + else: + return not any(hasattr(s,name) for s in superclasses) + else: + return inspect.isclass(root) or not (f is gen_rest_table_index) + + functions = {getattr(root,name):name for name,f in root.__dict__.items() if + (not name.startswith('_') and # private functions + not hasattr(f,'trac_number') and # deprecated functions + not inspect.isclass(f) and # classes + callable(f) and # e.g. GenericGraph.graphics_array_defaults + local_filter(f,name)) # possibly filter imported functions + } + return functions.keys(),functions + +def gen_thematic_rest_table_index(root,additional_categories=None,only_local_functions=True): + r""" + Return a ReST string of thematically sorted function (or methods) of a module (or class). + + INPUT: + + - ``root`` -- the module, or class, whose elements are to be listed. + + - ``additional_categories`` -- a dictionary associating a category (given as + a string) to a function's name. Can be used when the decorator + :func:`doc_index` does not work on a function. + + - ``only_local_functions`` (boolean; ``True``) -- if ``root`` is a module, + ``only_local_functions = True`` means that imported functions will be + filtered out. This can be useful to disable for making indexes of + e.g. catalog modules such as :mod:`sage.coding.codes_catalog`. + + EXAMPLE:: + + sage: from sage.misc.rest_index_of_methods import gen_thematic_rest_table_index, list_of_subfunctions + sage: l = list_of_subfunctions(Graph)[0] + sage: Graph.bipartite_color in l + True + """ + from collections import defaultdict + if additional_categories is None: + additional_categories = {} + + functions,names = list_of_subfunctions(root,only_local_functions=only_local_functions) + theme_to_function = defaultdict(list) + for f in functions: + theme_to_function[getattr(f,"doc_index",additional_categories.get(f,"Unsorted"))].append(f) + s = ["**"+theme+"**\n\n"+gen_rest_table_index(list_of_functions,names=names) + for theme, list_of_functions in sorted(theme_to_function.items())] + return "\n\n".join(s) + +def doc_index(name): + r""" + Attribute an index name to a function. + + This decorator can be applied to a function/method in order to specify in + which index it must appear, in the index generated by + :func:`gen_thematic_rest_table_index`. + + INPUT: + + - ``name`` -- a string, which will become the title of the index in which + this function/method will appear. + + EXAMPLE:: + + sage: from sage.misc.rest_index_of_methods import doc_index + sage: @doc_index("Wouhouuuuu") + ....: def a(): + ....: print "Hey" + sage: a.doc_index + 'Wouhouuuuu' + """ + def hey(f): + setattr(f,"doc_index",name) + return f + return hey + __doc__ = __doc__.format(INDEX_OF_FUNCTIONS=gen_rest_table_index([gen_rest_table_index])) diff --git a/src/sage/misc/sage_unittest.py b/src/sage/misc/sage_unittest.py index 7642066a69c..ba4327caffc 100644 --- a/src/sage/misc/sage_unittest.py +++ b/src/sage/misc/sage_unittest.py @@ -527,12 +527,12 @@ def some_elements(self, S=None): sage: list(tester.some_elements()) [0, 1, 2, 3, 4] - sage: C = CartesianProduct(Z, Z, Z, Z) + sage: C = cartesian_product([Z]*4) sage: len(C) 390625 sage: tester = InstanceTester(C, elements = C, max_runs=4) sage: list(tester.some_elements()) - [[0, 0, 0, 0], [0, 0, 0, 1], [0, 0, 0, 2], [0, 0, 0, 3]] + [(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)] """ if S is None: if self._elements is None: diff --git a/src/sage/modular/arithgroup/arithgroup_element.pyx b/src/sage/modular/arithgroup/arithgroup_element.pyx index 3402a75d254..46021d08082 100644 --- a/src/sage/modular/arithgroup/arithgroup_element.pyx +++ b/src/sage/modular/arithgroup/arithgroup_element.pyx @@ -137,19 +137,19 @@ cdef class ArithmeticSubgroupElement(MultiplicativeGroupElement): yield self.__x[1,1] def __repr__(self): - """ - Return the string representation of self. + r""" + Return the string representation of ``self``. EXAMPLES:: sage: Gamma1(5)([6,1,5,1]).__repr__() '[6 1]\n[5 1]' """ - return "%s"%self.__x + return "%s" % self.__x def _latex_(self): - """ - Return latex representation of self. + r""" + Return latex representation of ``self``. EXAMPLES:: diff --git a/src/sage/modular/arithgroup/congroup_gammaH.py b/src/sage/modular/arithgroup/congroup_gammaH.py index 09487983532..388e48eec28 100644 --- a/src/sage/modular/arithgroup/congroup_gammaH.py +++ b/src/sage/modular/arithgroup/congroup_gammaH.py @@ -109,9 +109,11 @@ def _normalize_H(H, level): Normalize representatives for a given subgroup H of the units modulo level. - NOTE: This function does *not* make any attempt to find a minimal - set of generators for H. It simply normalizes the inputs for use - in hashing. + .. NOTE:: + + This function does *not* make any attempt to find a minimal + set of generators for H. It simply normalizes the inputs for use + in hashing. EXAMPLES:: @@ -137,8 +139,8 @@ def _normalize_H(H, level): H.remove(1) return H -class GammaH_class(CongruenceSubgroup): +class GammaH_class(CongruenceSubgroup): r""" The congruence subgroup `\Gamma_H(N)` for some subgroup `H \trianglelefteq (\ZZ / N\ZZ)^\times`, which is the subgroup of `{\rm @@ -147,7 +149,7 @@ class GammaH_class(CongruenceSubgroup): TESTS: - We test calculation of various invariants of the group: :: + We test calculation of various invariants of the group:: sage: GammaH(33,[2]).projective_index() 96 @@ -164,7 +166,7 @@ class GammaH_class(CongruenceSubgroup): sage: Gamma1(23).genus() 12 - We calculate the dimensions of some modular forms spaces: :: + We calculate the dimensions of some modular forms spaces:: sage: GammaH(33,[2]).dimension_cusp_forms(2) 5 @@ -175,7 +177,7 @@ class GammaH_class(CongruenceSubgroup): sage: GammaH(32079, [21676]).dimension_cusp_forms(20) 180266112 - We can sometimes show that there are no weight 1 cusp forms: :: + We can sometimes show that there are no weight 1 cusp forms:: sage: GammaH(20, [9]).dimension_cusp_forms(1) 0 @@ -183,8 +185,9 @@ class GammaH_class(CongruenceSubgroup): def __init__(self, level, H, Hlist=None): r""" - The congruence subgroup `\Gamma_H(N)`. The subgroup H - must be input as a list. + The congruence subgroup `\Gamma_H(N)`. + + The subgroup `H` must be given as a list. EXAMPLES:: @@ -483,13 +486,16 @@ def _coset_reduction_data_first_coord(G): of the reduction step (the first coordinate). INPUT: - G -- a congruence subgroup Gamma_0(N), Gamma_1(N), or Gamma_H(N). + + G -- a congruence subgroup Gamma_0(N), Gamma_1(N), or Gamma_H(N). OUTPUT: - A list v such that - v[u] = (min(u*h: h in H), - gcd(u,N) , - an h such that h*u = min(u*h: h in H)). + + A list v such that + + v[u] = (min(u*h: h in H), + gcd(u,N) , + an h such that h*u = min(u*h: h in H)). EXAMPLES:: @@ -558,11 +564,13 @@ def _coset_reduction_data_second_coord(G): of the reduction step (the second coordinate). INPUT: - self + + self OUTPUT: - a dictionary v with keys the divisors of N such that v[d] - is the subgroup {h in H : h = 1 (mod N/d)}. + + a dictionary v with keys the divisors of N such that v[d] + is the subgroup {h in H : h = 1 (mod N/d)}. EXAMPLES:: @@ -624,19 +632,24 @@ def _reduce_coset(self, uu, vv): Compute a canonical form for a given Manin symbol. INPUT: + Two integers (uu,vv) that define an element of `(Z/NZ)^2`. - uu -- an integer - vv -- an integer + + - uu -- an integer + - vv -- an integer OUTPUT: - pair of integers that are equivalent to (uu,vv). - NOTE: We do *not* require that gcd(uu,vv,N) = 1. If the gcd is - not 1, we return (0,0). + pair of integers that are equivalent to (uu,vv). + + .. NOTE:: + + We do *not* require that gcd(uu,vv,N) = 1. If the gcd is + not 1, we return (0,0). EXAMPLES: - An example at level 9.:: + An example at level 9:: sage: G = GammaH(9,[7]); G Congruence Subgroup Gamma_H(9) with H generated by [7] @@ -735,6 +748,7 @@ def reduce_cusp(self, c): def _reduce_cusp(self, c): r""" Compute a minimal representative for the given cusp c. + Returns a pair (c', t), where c' is the minimal representative for the given cusp, and t is either 1 or -1, as explained below. Largely for internal use. @@ -745,9 +759,13 @@ def _reduce_cusp(self, c): Two cusps `u1/v1` and `u2/v2` are equivalent modulo `\Gamma_H(N)` if and only if - `v1 = h*v2 (mod N)` and `u1 = h^(-1)*u2 (mod gcd(v1,N))` + + - `v1 = h*v2 (mod N)` and `u1 = h^(-1)*u2 (mod gcd(v1,N))` + or - `v1 = -h*v2 (mod N)` and `u1 = -h^(-1)*u2 (mod gcd(v1,N))` + + - `v1 = -h*v2 (mod N)` and `u1 = -h^(-1)*u2 (mod gcd(v1,N))` + for some `h \in H`. Then t is 1 or -1 as c and c' fall into the first or second case, respectively. diff --git a/src/sage/modular/modform/element.py b/src/sage/modular/modform/element.py index b85a6573941..afd26301a54 100644 --- a/src/sage/modular/modform/element.py +++ b/src/sage/modular/modform/element.py @@ -466,17 +466,19 @@ def q_expansion(self, prec=None): O(q^1) sage: f.q_expansion(0) O(q^0) + sage: f.q_expansion(-1) + Traceback (most recent call last): + ... + ValueError: prec (= -1) must be non-negative """ if prec is None: prec = self.parent().prec() prec = rings.Integer(prec) - if prec < 0: - raise ValueError("prec (=%s) must be at least 0"%prec) try: current_prec, f = self.__q_expansion except AttributeError: current_prec = 0 - f = self.parent()._q_expansion_ring()(0, -1) + f = self.parent()._q_expansion_ring()(0, 0) if current_prec == prec: return f diff --git a/src/sage/modular/modform_hecketriangle/abstract_ring.py b/src/sage/modular/modform_hecketriangle/abstract_ring.py index 6577462c05f..f5ba198020a 100644 --- a/src/sage/modular/modform_hecketriangle/abstract_ring.py +++ b/src/sage/modular/modform_hecketriangle/abstract_ring.py @@ -18,13 +18,11 @@ from sage.rings.all import FractionField, PolynomialRing, PowerSeriesRing, ZZ, QQ, infinity from sage.algebras.free_algebra import FreeAlgebra -from sage.rings.arith import bernoulli, sigma from sage.structure.parent import Parent from sage.misc.cachefunc import cached_method -from hecke_triangle_groups import HeckeTriangleGroup -from constructor import FormsRing, FormsSpace, rational_type +from constructor import FormsRing, FormsSpace from series_constructor import MFSeriesConstructor @@ -132,9 +130,9 @@ def _latex_(self): from sage.misc.latex import latex return "\\mathcal{{ {} }}_{{n={}}}({})".format(self._analytic_type.latex_space_name(), self._group.n(), latex(self._base_ring)) - def _element_constructor_(self, x): + def _element_constructor_(self, el): r""" - Return ``x`` coerced/converted into this forms ring. + Return ``el`` coerced/converted into this forms ring. EXAMPLES:: @@ -152,14 +150,36 @@ def _element_constructor_(self, x): False sage: MR(el).parent() == MR True + + sage: el = MR.Delta().full_reduce() + sage: MRinf = ModularFormsRing(n=infinity) + sage: MRinf(el) + (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3)/4096 + sage: el.parent() + CuspForms(n=3, k=12, ep=1) over Integer Ring + sage: MRinf(el).parent() + ModularFormsRing(n=+Infinity) over Integer Ring """ from graded_ring_element import FormsRingElement - if isinstance(x, FormsRingElement): - x = self._rat_field(x._rat) + if isinstance(el, FormsRingElement): + if (self.hecke_n() == infinity and el.hecke_n() == ZZ(3)): + el_f = el._reduce_d()._rat + (x,y,z,d) = self.pol_ring().gens() + + num_sub = el_f.numerator().subs( x=(y**2 + 3*x)/ZZ(4), y=(9*x*y - y**3)/ZZ(8), z=(3*z - y)/ZZ(2)) + denom_sub = el_f.denominator().subs( x=(y**2 + 3*x)/ZZ(4), y=(9*x*y - y**3)/ZZ(8), z=(3*z - y)/ZZ(2)) + new_num = num_sub.numerator()*denom_sub.denominator() + new_denom = denom_sub.numerator()*num_sub.denominator() + + el = self._rat_field(new_num) / self._rat_field(new_denom) + elif self.group() == el.group(): + el = self._rat_field(el._rat) + else: + raise ValueError("{} has group {} != {}".format(el, el.group(), self.group())) else: - x = self._rat_field(x) - return self.element_class(self, x) + el = self._rat_field(el) + return self.element_class(self, el) def _coerce_map_from_(self, S): r""" @@ -171,6 +191,8 @@ def _coerce_map_from_(self, S): sage: MR1 = QuasiWeakModularFormsRing(base_ring=CC) sage: MR2 = ModularFormsRing() sage: MR3 = CuspFormsRing() + sage: MR4 = ModularFormsRing(n=infinity) + sage: MR5 = ModularFormsRing(n=4) sage: MR3.has_coerce_map_from(MR2) False sage: MR1.has_coerce_map_from(MR2) @@ -181,6 +203,10 @@ def _coerce_map_from_(self, S): False sage: MR1.has_coerce_map_from(ZZ) True + sage: MR4.has_coerce_map_from(MR2) + True + sage: MR4.has_coerce_map_from(MR5) + False sage: from sage.modular.modform_hecketriangle.space import ModularForms, CuspForms sage: MF2 = ModularForms(k=6, ep=-1) @@ -189,14 +215,19 @@ def _coerce_map_from_(self, S): True sage: MR2.has_coerce_map_from(MF3) True + sage: MR4.has_coerce_map_from(MF2) + True """ from space import FormsSpace_abstract + from functors import _common_subgroup if ( isinstance(S, FormsRing_abstract)\ - and self._group == S._group\ + and self._group == _common_subgroup(self._group, S._group)\ and self._analytic_type >= S._analytic_type\ and self.base_ring().has_coerce_map_from(S.base_ring()) ): return True + elif isinstance(S, FormsRing_abstract): + return False elif isinstance(S, FormsSpace_abstract): raise RuntimeError( "This case should not occur." ) # return self._coerce_map_from_(S.graded_ring()) @@ -747,11 +778,11 @@ def diff_alg(self): sage: from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing sage: ModularFormsRing().diff_alg() - Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dY*Y: Y*dY + 1, dZ*Z: Z*dZ + 1, dX*X: X*dX + 1} + Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dZ*Z: Z*dZ + 1, dY*Y: Y*dY + 1, dX*X: X*dX + 1} sage: from sage.modular.modform_hecketriangle.space import CuspForms sage: CuspForms(k=12, base_ring=AA).diff_alg() - Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dY*Y: Y*dY + 1, dZ*Z: Z*dZ + 1, dX*X: X*dX + 1} + Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dZ*Z: Z*dZ + 1, dY*Y: Y*dY + 1, dX*X: X*dX + 1} """ # We only use two operators for now which do not involve 'd', so for performance diff --git a/src/sage/modular/modform_hecketriangle/abstract_space.py b/src/sage/modular/modform_hecketriangle/abstract_space.py index 0cc399ad054..76f28b569b4 100644 --- a/src/sage/modular/modform_hecketriangle/abstract_space.py +++ b/src/sage/modular/modform_hecketriangle/abstract_space.py @@ -122,9 +122,9 @@ def _latex_(self): from sage.misc.latex import latex return "{}_{{ n={} }}({},\ {})({})".format(self._analytic_type.latex_space_name(), self._group.n(), self._weight, self._ep, latex(self._base_ring)) - def _element_constructor_(self, x): + def _element_constructor_(self, el): r""" - Return ``x`` coerced into this forms space. + Return ``el`` coerced into this forms space. EXAMPLES:: @@ -141,6 +141,14 @@ def _element_constructor_(self, x): sage: MF(Delta).parent() == MF True + sage: E2 = MF.E2() + sage: e2 = QuasiWeakModularForms(n=infinity, k=2, ep=-1)(E2) + sage: e2 + 1 - 24*q^2 - 72*q^4 + O(q^5) + sage: e2.parent() + QuasiWeakModularForms(n=+Infinity, k=2, ep=-1) over Integer Ring + sage: e2.as_ring_element() + (-f_i + 3*E2)/2 sage: MF(x^3) 1 + 720*q + 179280*q^2 + 16954560*q^3 + 396974160*q^4 + O(q^5) sage: MF(x^3).parent() == MF @@ -226,35 +234,49 @@ def _element_constructor_(self, x): """ from graded_ring_element import FormsRingElement - if isinstance(x, FormsRingElement): - return self.element_class(self, x._rat) + if isinstance(el, FormsRingElement): + if (self.hecke_n() == infinity and el.hecke_n() == ZZ(3)): + el_f = el._reduce_d()._rat + (x,y,z,d) = self.pol_ring().gens() + + num_sub = el_f.numerator().subs( x=(y**2 + 3*x)/ZZ(4), y=(9*x*y - y**3)/ZZ(8), z=(3*z - y)/ZZ(2)) + denom_sub = el_f.denominator().subs( x=(y**2 + 3*x)/ZZ(4), y=(9*x*y - y**3)/ZZ(8), z=(3*z - y)/ZZ(2)) + new_num = num_sub.numerator()*denom_sub.denominator() + new_denom = denom_sub.numerator()*num_sub.denominator() + + el = self._rat_field(new_num) / self._rat_field(new_denom) + elif self.group() == el.group(): + el = el._rat + else: + raise ValueError("{} has group {} != {}".format(el, el.group(), self.group())) + return self.element_class(self, el) # This assumes that the series corresponds to a _weakly # holomorphic_ (quasi) form. It also assumes that the form is # holomorphic at -1 for n=infinity (this assumption however # can be changed in construct_form # resp. construct_quasi_form)) - P = parent(x) + P = parent(el) if is_LaurentSeriesRing(P) or is_PowerSeriesRing(P): if (self.is_modular()): - return self.construct_form(x) + return self.construct_form(el) else: - return self.construct_quasi_form(x) - if is_FreeModuleElement(x) and (self.module() is P or self.ambient_module() is P): - return self.element_from_ambient_coordinates(x) - if (not self.is_ambient()) and (isinstance(x, list) or isinstance(x, tuple) or is_FreeModuleElement(x)) and len(x) == self.rank(): + return self.construct_quasi_form(el) + if is_FreeModuleElement(el) and (self.module() is P or self.ambient_module() is P): + return self.element_from_ambient_coordinates(el) + if (not self.is_ambient()) and (isinstance(el, list) or isinstance(el, tuple) or is_FreeModuleElement(el)) and len(el) == self.rank(): try: - return self.element_from_coordinates(x) + return self.element_from_coordinates(el) except (ArithmeticError, TypeError): pass if self.ambient_module() and self.ambient_module().has_coerce_map_from(P): - return self.element_from_ambient_coordinates(self.ambient_module()(x)) - if (isinstance(x,list) or isinstance(x, tuple)) and len(x) == self.degree(): + return self.element_from_ambient_coordinates(self.ambient_module()(el)) + if (isinstance(el,list) or isinstance(el, tuple)) and len(el) == self.degree(): try: - return self.element_from_ambient_coordinates(x) + return self.element_from_ambient_coordinates(el) except (ArithmeticError, TypeError): pass - return self.element_class(self, x) + return self.element_class(self, el) def _coerce_map_from_(self, S): r""" @@ -268,6 +290,8 @@ def _coerce_map_from_(self, S): sage: MF3 = ModularForms(n=4, k=24, ep=-1) sage: MF4 = CuspForms(n=4, k=0, ep=1) sage: MF5 = ZeroForm(n=4, k=10, ep=-1) + sage: MF6 = QuasiWeakModularForms(n=3, k=24, ep=1) + sage: MF7 = QuasiWeakModularForms(n=infinity, k=24, ep=1) sage: subspace1 = MF3.subspace([MF3.gen(0), MF3.gen(1)]) sage: subspace2 = MF3.subspace([MF3.gen(2)]) sage: subspace3 = MF3.subspace([MF3.gen(0), MF3.gen(0)+MF3.gen(2)]) @@ -282,6 +306,10 @@ def _coerce_map_from_(self, S): False sage: MF1.has_coerce_map_from(ZZ) True + sage: MF7.has_coerce_map_from(MF6) + True + sage: MF7.has_coerce_map_from(MF2) + False sage: MF3.has_coerce_map_from(subspace1) True sage: subspace1.has_coerce_map_from(MF3) diff --git a/src/sage/modular/modform_hecketriangle/functors.py b/src/sage/modular/modform_hecketriangle/functors.py index 0b81ed0295e..f4e7c3e0c2b 100644 --- a/src/sage/modular/modform_hecketriangle/functors.py +++ b/src/sage/modular/modform_hecketriangle/functors.py @@ -92,6 +92,35 @@ def _get_base_ring(ring, var_name="d"): return base_ring +def _common_subgroup(group1, group2): + r""" + Return a common (Hecke triangle) subgroup of both given groups + ``group1`` and ``group2`` if it exists. Otherwise return ``None``. + + EXAMPLES:: + + sage: from sage.modular.modform_hecketriangle.functors import _common_subgroup + sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup + sage: _common_subgroup(HeckeTriangleGroup(n=3), HeckeTriangleGroup(n=infinity)) + Hecke triangle group for n = +Infinity + sage: _common_subgroup(HeckeTriangleGroup(n=infinity), HeckeTriangleGroup(n=3)) + Hecke triangle group for n = +Infinity + sage: _common_subgroup(HeckeTriangleGroup(n=4), HeckeTriangleGroup(n=infinity)) is None + True + sage: _common_subgroup(HeckeTriangleGroup(n=4), HeckeTriangleGroup(n=4)) + Hecke triangle group for n = 4 + """ + + if group1 == group2: + return group1 + elif (group1.n() == 3) and (group2.n() == infinity): + return group2 + elif (group1.n() == infinity) and (group2.n() == 3): + return group1 + else: + return None + + def ConstantFormsSpaceFunctor(group): r""" Construction functor for the space of constant forms. @@ -458,19 +487,21 @@ def merge(self, other): other = other._ambient_space_functor if isinstance(other, FormsSpaceFunctor): - if not (self._group == other._group): + group = _common_subgroup(self._group, other._group) + if group == None: return None analytic_type = self._analytic_type + other._analytic_type if (self._k == other._k) and (self._ep == other._ep): - return FormsSpaceFunctor(analytic_type, self._group, self._k, self._ep) + return FormsSpaceFunctor(analytic_type, group, self._k, self._ep) else: - return FormsRingFunctor(analytic_type, self._group, True) + return FormsRingFunctor(analytic_type, group, True) elif isinstance(other, FormsRingFunctor): - if not (self._group == other._group): + group = _common_subgroup(self._group, other._group) + if group == None: return None red_hom = other._red_hom analytic_type = self._analytic_type + other._analytic_type - return FormsRingFunctor(analytic_type, self._group, red_hom) + return FormsRingFunctor(analytic_type, group, red_hom) def __eq__(self, other): r""" @@ -647,17 +678,19 @@ def merge(self, other): other = other._ambient_space_functor if isinstance(other, FormsSpaceFunctor): - if not (self._group == other._group): + group = _common_subgroup(self._group, other._group) + if group == None: return None red_hom = self._red_hom analytic_type = self._analytic_type + other._analytic_type - return FormsRingFunctor(analytic_type, self._group, red_hom) + return FormsRingFunctor(analytic_type, group, red_hom) elif isinstance(other, FormsRingFunctor): - if not (self._group == other._group): + group = _common_subgroup(self._group, other._group) + if group == None: return None red_hom = self._red_hom & other._red_hom analytic_type = self._analytic_type + other._analytic_type - return FormsRingFunctor(analytic_type, self._group, red_hom) + return FormsRingFunctor(analytic_type, group, red_hom) def __eq__(self, other): r""" diff --git a/src/sage/modular/modform_hecketriangle/graded_ring_element.py b/src/sage/modular/modform_hecketriangle/graded_ring_element.py index d743dec533e..f6e669d6a63 100644 --- a/src/sage/modular/modform_hecketriangle/graded_ring_element.py +++ b/src/sage/modular/modform_hecketriangle/graded_ring_element.py @@ -713,6 +713,12 @@ def _add_(self,other): 2 - 32*q + 736*q^2 - 896*q^3 + 6368*q^4 + O(q^5) sage: (MF.E4() + MF.f_i()^2).parent() ModularForms(n=+Infinity, k=4, ep=1) over Integer Ring + + sage: el = ModularForms(n=3).Delta() + MF.E4()*MF.E6() + sage: el + (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3 + 4096*E4^2*f_i)/4096 + sage: el.parent() + ModularFormsRing(n=+Infinity) over Integer Ring """ return self.parent()(self._rat+other._rat) @@ -763,6 +769,12 @@ def _sub_(self,other): 64*q - 512*q^2 + 1792*q^3 - 4096*q^4 + O(q^5) sage: (MF.E4() - MF.f_i()^2).parent() ModularForms(n=+Infinity, k=4, ep=1) over Integer Ring + + sage: el = ModularForms(n=3).Delta() - MF.E4() + sage: el + (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3 - 4096*E4)/4096 + sage: el.parent() + ModularFormsRing(n=+Infinity) over Integer Ring """ #reduce at the end? See example "sage: ((E4+E6)-E6).parent()" @@ -838,6 +850,12 @@ def _mul_(self,other): q + 8*q^2 + 12*q^3 - 64*q^4 + O(q^5) sage: (MF.E4()*MF.f_inf()).parent() ModularForms(n=+Infinity, k=8, ep=1) over Integer Ring + + sage: el = ModularForms(n=3).E2()*MF.E6() + sage: el + 1 - 8*q - 272*q^2 - 1760*q^3 - 2560*q^4 + O(q^5) + sage: el.parent() + QuasiModularForms(n=+Infinity, k=8, ep=1) over Integer Ring """ res = self.parent().rat_field()(self._rat*other._rat) @@ -918,6 +936,12 @@ def _div_(self,other): 1/2 - 4*q - 236*q^2 - 2128*q^3 + 49428*q^4 + O(q^5) sage: (MF.f_i()/(MF.E4() + MF.f_i()^2)).parent() MeromorphicModularForms(n=+Infinity, k=-2, ep=-1) over Integer Ring + + sage: el = ModularForms(n=3).E2()/MF.E2() + sage: el + 1 + 8*q + 48*q^2 + 480*q^3 + 4448*q^4 + O(q^5) + sage: el.parent() + QuasiMeromorphicModularForms(n=+Infinity, k=0, ep=1) over Integer Ring """ res = self.parent().rat_field()(self._rat/other._rat) diff --git a/src/sage/modular/modform_hecketriangle/readme.py b/src/sage/modular/modform_hecketriangle/readme.py index 3f39d16b300..418c2a175db 100644 --- a/src/sage/modular/modform_hecketriangle/readme.py +++ b/src/sage/modular/modform_hecketriangle/readme.py @@ -2,8 +2,8 @@ Overview of Hecke triangle groups and modular forms for Hecke triangle groups AUTHORS: -- Jonas Jermann (2013): initial version +- Jonas Jermann (2013): initial version Hecke triangle groups and elements: @@ -460,7 +460,7 @@ - **Block decomposition of elements:** - For each group element a very specfic conjugacy representative + For each group element a very specific conjugacy representative can be obtained. For hyperbolic and parabolic elements the representative is a product ``V(j)``-matrices. They all have non-negative trace and the number of factors is called @@ -1093,7 +1093,7 @@ - **Theta subgroup:** The Hecke triangle group corresponding to ``n=infinity`` is also completely supported. In particular the (special) behavior around - the cusp ``-1`` is considered and can be specfied. + the cusp ``-1`` is considered and can be specified. EXAMPLES:: @@ -1146,6 +1146,15 @@ sage: MF.q_basis(m=-1, order_1=-1, min_exp=-1) q^-1 - 203528/7*q^5 + O(q^6) + Elements with respect to the full group are automatically coerced + to elements of the Theta subgroup if necessary:: + + sage: el = QuasiMeromorphicModularFormsRing(n=3).Delta().full_reduce() + E2 + sage: el + (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3 + 4096*E2)/4096 + sage: el.parent() + QuasiModularFormsRing(n=+Infinity) over Integer Ring + - **Determine exact coefficients from numerical ones:** There is some experimental support for replacing numerical coefficients with diff --git a/src/sage/modular/modsym/ambient.py b/src/sage/modular/modsym/ambient.py index 5ef072af57a..d59a2e6475e 100644 --- a/src/sage/modular/modsym/ambient.py +++ b/src/sage/modular/modsym/ambient.py @@ -531,7 +531,7 @@ def _action_on_modular_symbols(self, g): INPUT: - ``g`` (list) -- `g=[a,b,c,d]` where `a,b,c,d` are integers + `g` (list) -- `g=[a,b,c,d]` where `a,b,c,d` are integers defining a `2\times2` integer matrix. OUTPUT: @@ -539,10 +539,10 @@ def _action_on_modular_symbols(self, g): (matrix) The matrix of the action of `g` on this Modular Symbol space, with respect to the standard basis. - .. note:: + .. NOTE:: - Use _matrix_of_operator_on_modular_symbols for more general - operators. + Use ``_matrix_of_operator_on_modular_symbols`` for more general + operators. EXAMPLES:: @@ -810,7 +810,7 @@ def modular_symbol_sum(self, x, check=True): The sum `\sum_{i=0}^{k-2} a_i [ i, \alpha, \beta ]` as an element of this modular symbol space. - EXAMPLES: + EXAMPLES:: sage: M = ModularSymbols(11,4) sage: R.=QQ[] @@ -2787,7 +2787,7 @@ def _cuspidal_new_submodule_dimension_formula(self): r""" Return the dimension of the new cuspidal subspace, via the formula. - EXAMPLES: + EXAMPLES:: sage: M = ModularSymbols(100,2) sage: M._cuspidal_new_submodule_dimension_formula() @@ -2898,19 +2898,15 @@ def boundary_space(self): def _hecke_image_of_ith_basis_vector(self, n, i): """ Return `T_n(e_i)`, where `e_i` is the - `i`th basis vector of this ambient space. + `i` th basis vector of this ambient space. INPUT: - - - ``n`` - an integer which should be prime. - + - ``n`` -- an integer which should be prime. OUTPUT: - - - ``modular symbol`` - element of this ambient space - + - ``modular symbol`` -- element of this ambient space EXAMPLES:: @@ -3100,7 +3096,7 @@ def _cuspidal_new_submodule_dimension_formula(self): r""" Return the dimension of the new cuspidal subspace, via the formula. - EXAMPLES: + EXAMPLES:: sage: M = ModularSymbols(Gamma1(22),2) sage: M._cuspidal_new_submodule_dimension_formula() @@ -3807,16 +3803,13 @@ def _hecke_images(self, i, v): INPUT: + - ``i`` -- nonnegative integer - - ``i`` - nonnegative integer - - - ``v`` - a list of positive integer - + - ``v`` -- a list of positive integer OUTPUT: - - - ``matrix`` - whose rows are the Hecke images + - ``matrix`` -- whose rows are the Hecke images EXAMPLES:: @@ -3829,8 +3822,6 @@ def _hecke_images(self, i, v): [ 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0] [ 0 1 0 2 0 -1 1 1 0 0 0 0 0 0 0] [ 0 1 1 -1 -1 0 -1 1 1 0 1 2 0 -2 2] - - """ if self.weight() != 2: raise NotImplementedError("hecke images only implemented when the weight is 2") diff --git a/src/sage/modular/modsym/manin_symbol.pyx b/src/sage/modular/modsym/manin_symbol.pyx index 6e4e6351c8b..d4da00430ad 100644 --- a/src/sage/modular/modsym/manin_symbol.pyx +++ b/src/sage/modular/modsym/manin_symbol.pyx @@ -436,13 +436,13 @@ def _print_polypart(i, j): EXAMPLES:: - sage: from sage.modular.modsym.manin_symbol import _print_polypart - sage: _print_polypart(2,3) - 'X^2*Y^3' - sage: _print_polypart(2,0) - 'X^2' - sage: _print_polypart(0,1) - 'Y' + sage: from sage.modular.modsym.manin_symbol import _print_polypart + sage: _print_polypart(2,3) + 'X^2*Y^3' + sage: _print_polypart(2,0) + 'X^2' + sage: _print_polypart(0,1) + 'Y' """ if i > 1: xpart = "X^%s"%i diff --git a/src/sage/modular/modsym/modular_symbols.py b/src/sage/modular/modsym/modular_symbols.py index b13170bf0b4..96aec36e734 100644 --- a/src/sage/modular/modsym/modular_symbols.py +++ b/src/sage/modular/modsym/modular_symbols.py @@ -117,7 +117,7 @@ def __getitem__(self, j): return [self.__alpha, self.__beta][j] def _latex_(self): - """ + r""" Return Latex representation of this modular symbol. EXAMPLES:: diff --git a/src/sage/modular/modsym/p1list.pyx b/src/sage/modular/modsym/p1list.pyx index 1ab36b2a6b5..20acee773f0 100644 --- a/src/sage/modular/modsym/p1list.pyx +++ b/src/sage/modular/modsym/p1list.pyx @@ -764,8 +764,8 @@ cdef class P1List: return sage.modular.modsym.p1list._make_p1list, (self.__N, ) def __getitem__(self, n): - """ - Standard indexing/slicing function for the class P1List. + r""" + Standard indexing/slicing function for the class ``P1List``. EXAMPLES:: diff --git a/src/sage/modular/modsym/space.py b/src/sage/modular/modsym/space.py index 7bf69c3fa9d..b1a844a79e4 100644 --- a/src/sage/modular/modsym/space.py +++ b/src/sage/modular/modsym/space.py @@ -2318,15 +2318,16 @@ def _matrix_of_galois_action(self, t, P): INPUT: - - `t` -- integer - - `P` -- list of cusps + - `t` -- integer - EXAMPLES:: + - `P` -- list of cusps + + EXAMPLES: We compute the matrix of the element of the Galois group associated to 5 and 31 for level 32. In the first case the Galois action is trivial, and in the second it is - nontrivial.:: + nontrivial. :: sage: M = ModularSymbols(32) sage: P = [c for c in Gamma0(32).cusps() if not c.is_infinity()] diff --git a/src/sage/modular/overconvergent/weightspace.py b/src/sage/modular/overconvergent/weightspace.py index 3e6e55445f4..a1f65f55d25 100644 --- a/src/sage/modular/overconvergent/weightspace.py +++ b/src/sage/modular/overconvergent/weightspace.py @@ -566,6 +566,20 @@ def chi(self): """ return self._chi + def __hash__(self): + r""" + TESTS:: + + sage: w = pAdicWeightSpace(23)(12, DirichletGroup(23, QQ).0) + sage: hash(w) + -2363716619315244394 # 64-bit + 470225558 # 32-bit + """ + if self._chi.is_trivial(): + return hash(self._k) + else: + return hash( (self._k,self._chi.modulus(),self._chi) ) + def _repr_(self): r""" String representation of self. diff --git a/src/sage/modules/fg_pid/fgp_element.py b/src/sage/modules/fg_pid/fgp_element.py index 2b4f43bb14f..749f67a3991 100644 --- a/src/sage/modules/fg_pid/fgp_element.py +++ b/src/sage/modules/fg_pid/fgp_element.py @@ -343,8 +343,28 @@ def vector(self): try: return self.__vector except AttributeError: self.__vector = self.parent().coordinate_vector(self, reduce=True) + self.__vector.set_immutable() return self.__vector + def __hash__(self): + r""" + TESTS:: + + sage: V = span([[1/2,0,0],[3/2,2,1],[0,0,1]],ZZ) + sage: W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]) + sage: Q = V/W + sage: x = Q.0 + 3*Q.1 + sage: hash(x) + 3713081631933328131 # 64-bit + 1298787075 # 32-bit + + sage: A = AdditiveAbelianGroup([3]) + sage: hash(A.an_element()) + 3430019387558 # 64-bit + -1659481946 # 32-bit + """ + return hash(self.vector()) + def _vector_(self, base_ring=None): """ Support for conversion to vectors. @@ -367,10 +387,21 @@ def _vector_(self, base_ring=None): (1, 3) sage: vector(CDF, x) (1.0, 3.0) + + TESTS:: + + sage: V = span([[1/2,0,0],[3/2,2,1],[0,0,1]],ZZ) + sage: W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]) + sage: Q = V/W + sage: x = Q.0 + 3*Q.1 + sage: vector(x).is_mutable() + True + sage: vector(CDF,x).is_mutable() + True """ v = self.vector() if base_ring is None or v.base_ring() is base_ring: - return v + return v.__copy__() else: return v.change_ring(base_ring) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 85db5e993d1..c688167722d 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -654,7 +654,8 @@ class FreeModule_generic(Module): def __init__(self, base_ring, rank, degree, sparse=False, coordinate_ring=None, category=None): """ - Create the free module of given rank over the given base_ring. + Create the free module of given rank ``rank`` over the given base + ring ``base_ring``. INPUT: @@ -672,10 +673,11 @@ def __init__(self, base_ring, rank, degree, sparse=False, - ``category`` -- category (default: None) If ``base_ring`` is a field, then the default category is the - category of vector spaces over that field; otherwise it is the - category of free modules over that ring. In addition, the - category is intersected with the category of finite enumerated - sets if the ring is finite or the rank is 0. + category of finite-dimensional vector spaces over that field; + otherwise it is the category of finite-dimensional free modules + over that ring. In addition, the category is intersected with the + category of finite enumerated sets if the ring is finite or the + rank is 0. EXAMPLES:: @@ -683,17 +685,22 @@ def __init__(self, base_ring, rank, degree, sparse=False, Ambient free module of rank 3 over the integral domain Multivariate Polynomial Ring in x0, x1, x2 over Rational Field sage: FreeModule(GF(7),3).category() - Category of vector spaces with basis over (finite fields - and subquotients of monoids and quotients of semigroups) + Category of finite dimensional vector spaces with basis over + (finite fields and subquotients of monoids and quotients of semigroups) sage: V = QQ^4; V.category() - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis over + (quotient fields and metric spaces) sage: V = GF(5)**20; V.category() - Category of vector spaces with basis over (finite fields - and subquotients of monoids and quotients of semigroups) + Category of finite dimensional vector spaces with basis over + (finite fields and subquotients of monoids + and quotients of semigroups) sage: FreeModule(ZZ,3).category() - Category of modules with basis over (euclidean domains and infinite enumerated sets) + Category of finite dimensional modules with basis over + (euclidean domains and infinite enumerated sets + and metric spaces) sage: (QQ^0).category() - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis + over (quotient fields and metric spaces) TESTS:: @@ -734,7 +741,7 @@ def __init__(self, base_ring, rank, degree, sparse=False, if category is None: from sage.categories.all import FreeModules - category = FreeModules(base_ring.category()) + category = FreeModules(base_ring.category()).FiniteDimensional() super(FreeModule_generic, self).__init__(base_ring, category=category) self.__coordinate_ring = coordinate_ring @@ -903,7 +910,7 @@ def _an_element_(self): def _element_constructor_(self, x, coerce=True, copy=True, check=True): r""" - Create an element of this free module from x. + Create an element of this free module from ``x``. The ``coerce`` and ``copy`` arguments are passed on to the underlying element constructor. If @@ -968,7 +975,7 @@ def _element_constructor_(self, x, coerce=True, copy=True, check=True): def is_submodule(self, other): """ - Return True if self is a submodule of other. + Return ``True`` if ``self`` is a submodule of ``other``. EXAMPLES:: @@ -1002,11 +1009,9 @@ def is_submodule(self, other): sage: M.is_submodule(N) True - Since basis() is not implemented in general, submodule testing does - not work for all PID's. However, trivial cases are already used - (and useful) for coercion, e.g. - - :: + Since :meth:`basis` is not implemented in general, submodule + testing does not work for all PID's. However, trivial cases are + already used (and useful) for coercion, e.g. :: sage: QQ(1/2) * vector(ZZ['x']['y'],[1,2,3,4]) (1/2, 1, 3/2, 2) @@ -3067,10 +3072,10 @@ def _Hom_(self, Y, category): sage: type(H) sage: H - Set of Morphisms from Vector space of dimension 2 over - Rational Field to Ambient free module of rank 3 over the - principal ideal domain Integer Ring in Category of vector - spaces with basis over quotient fields + Set of Morphisms from Vector space of dimension 2 over Rational Field + to Ambient free module of rank 3 over the principal ideal domain Integer Ring + in Category of finite dimensional vector spaces with basis over + (quotient fields and metric spaces) """ if Y.base_ring().is_field(): import vector_space_homspace diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index 5d0b8159274..6a63c1f3427 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -2077,23 +2077,33 @@ cdef class FreeModuleElement(Vector): # abstract base class def dict(self, copy=True): """ - Return dictionary of nonzero entries of self. + Return dictionary of nonzero entries of ``self``. + + More precisely, this returns a dictionary whose keys are indices + of basis elements in the support of ``self`` and whose values are + the corresponding coefficients. INPUT: - - ``copy`` -- bool (default: True) + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` OUTPUT: - - Python dictionary + - Python dictionary EXAMPLES:: sage: v = vector([0,0,0,0,1/2,0,3/14]) sage: v.dict() {4: 1/2, 6: 3/14} + sage: sorted(v.support()) + [4, 6] - In some cases when copy=False, we get back a dangerous reference:: + In some cases, when ``copy=False``, we get back a dangerous + reference:: sage: v = vector({0:5, 2:3/7}, sparse=True) sage: v.dict(copy=False) @@ -2110,6 +2120,8 @@ cdef class FreeModuleElement(Vector): # abstract base class e[i] = c return e + monomial_coefficients = dict + ############################# # Plotting ############################# @@ -4580,8 +4592,21 @@ cdef class FreeModuleElement_generic_sparse(FreeModuleElement): sage: w = vector(R, [], sparse=True) sage: parent(v._dot_product_coerce_(w)) Univariate Polynomial Ring in x over Real Double Field + + TESTS: + + Check that :trac:`19377` is fixed:: + + sage: w = vector(ZZ, (1,2,3), sparse=False) + sage: v = vector(ZZ, (1,2,3), sparse=True) + sage: v._dot_product_coerce_(w) + 14 """ - cdef dict e = (right)._entries + cdef dict e + try: + e = (right)._entries + except TypeError: + e = right.dict() z = left.base_ring().zero() if left.base_ring() is not right.base_ring(): z *= right.base_ring().zero() @@ -4821,30 +4846,41 @@ cdef class FreeModuleElement_generic_sparse(FreeModuleElement): def dict(self, copy=True): """ - Return dictionary of nonzero entries of self. + Return dictionary of nonzero entries of ``self``. + + More precisely, this returns a dictionary whose keys are indices + of basis elements in the support of ``self`` and whose values are + the corresponding coefficients. INPUT: - - ``copy`` -- bool (default: True) + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` OUTPUT: - - Python dictionary + - Python dictionary EXAMPLES:: sage: v = vector([0,0,0,0,1/2,0,3/14], sparse=True) sage: v.dict() {4: 1/2, 6: 3/14} + sage: sorted(v.support()) + [4, 6] """ if copy: return dict(self._entries) else: return self._entries + monomial_coefficients = dict + def list(self, copy=True): """ - Return list of elements of self. + Return list of elements of ``self``. INPUT: diff --git a/src/sage/modules/free_module_homspace.py b/src/sage/modules/free_module_homspace.py index 5d674f304e9..b455db03d87 100644 --- a/src/sage/modules/free_module_homspace.py +++ b/src/sage/modules/free_module_homspace.py @@ -27,11 +27,12 @@ sage: V2 = FreeModule(IntegerRing(),2) sage: H = Hom(V3,V2) sage: H - Set of Morphisms from Ambient free module of rank 3 over the - principal ideal domain Integer Ring to Ambient free module - of rank 2 over the principal ideal domain Integer Ring in - Category of modules with basis over (euclidean domains and - infinite enumerated sets) + Set of Morphisms from Ambient free module of rank 3 over + the principal ideal domain Integer Ring + to Ambient free module of rank 2 + over the principal ideal domain Integer Ring + in Category of finite dimensional modules with basis over + (euclidean domains and infinite enumerated sets and metric spaces) sage: B = H.basis() sage: len(B) 6 diff --git a/src/sage/modules/with_basis/morphism.py b/src/sage/modules/with_basis/morphism.py index 4fb91391cc0..17f1d211f2f 100644 --- a/src/sage/modules/with_basis/morphism.py +++ b/src/sage/modules/with_basis/morphism.py @@ -380,10 +380,11 @@ def __call__(self, *args): x = args[self._position] assert(x.parent() is self.domain()) + mc = x.monomial_coefficients(copy=False) if self._is_module_with_basis_over_same_base_ring: - return self.codomain().linear_combination( (self._on_basis(*(before+(index,)+after)), coeff ) for (index, coeff) in args[self._position] ) + return self.codomain().linear_combination( (self._on_basis(*(before+(index,)+after)), coeff ) for (index, coeff) in mc.iteritems() ) else: - return sum(( coeff * self._on_basis(*(before+(index,)+after)) for (index, coeff) in args[self._position]), self._zero) + return sum(( coeff * self._on_basis(*(before+(index,)+after)) for (index, coeff) in mc.iteritems() ), self._zero) # As per the specs of Map, we should in fact implement _call_. # However we currently need to abuse Map.__call__ (which strict diff --git a/src/sage/monoids/free_monoid_element.py b/src/sage/monoids/free_monoid_element.py index 7fef15de61c..9872b16c232 100644 --- a/src/sage/monoids/free_monoid_element.py +++ b/src/sage/monoids/free_monoid_element.py @@ -80,6 +80,23 @@ def __init__(self, F, x, check=True): # TODO: should have some other checks here... raise TypeError("Argument x (= %s) is of the wrong type."%x) + def __hash__(self): + r""" + TESTS:: + + sage: R. = FreeMonoid(2) + sage: hash(x) + 1914282862589934403 # 64-bit + 139098947 # 32-bit + sage: hash(y) + 2996819001369607946 # 64-bit + 13025034 # 32-bit + sage: hash(x*y) + 7114093379175463612 # 64-bit + 2092317372 # 32-bit + """ + return hash(tuple(self._element_list)) + def __iter__(self): """ Returns an iterator which yields tuples of variable and exponent. diff --git a/src/sage/monoids/indexed_free_monoid.py b/src/sage/monoids/indexed_free_monoid.py index 734d24087bd..e4772f34910 100644 --- a/src/sage/monoids/indexed_free_monoid.py +++ b/src/sage/monoids/indexed_free_monoid.py @@ -371,6 +371,19 @@ def __init__(self, F, x): """ IndexedMonoidElement.__init__(self, F, tuple(map(tuple, x))) + def __hash__(self): + r""" + TESTS:: + + sage: F = FreeMonoid(index_set=tuple('abcde')) + sage: hash(F ([(1,2),(0,1)]) ) + 2401565693828035651 # 64-bit + 1164080195 # 32-bit + sage: hash(F ([(0,2),(1,1)]) ) + -3359280905493236379 # 64-bit + -1890405019 # 32-bit + """ + return hash(self._monomial) def _sorted_items(self): """ @@ -486,6 +499,20 @@ def _sorted_items(self): pass return v + def __hash__(self): + r""" + TESTS:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: hash( F([(0,1), (2,2)]) ) + 8087055352805725849 # 64-bit + 250091161 # 32-bit + sage: hash( F([(2,1)]) ) + 5118585357534560720 # 64-bit + 1683816912 # 32-bit + """ + return hash(frozenset(self._monomial.items())) + def _mul_(self, other): """ Multiply ``self`` by ``other``. diff --git a/src/sage/plot/arc.py b/src/sage/plot/arc.py index 04fac945f42..61502267446 100644 --- a/src/sage/plot/arc.py +++ b/src/sage/plot/arc.py @@ -22,6 +22,7 @@ from math import fmod, sin, cos, pi, atan + class Arc(GraphicPrimitive): """ Primitive class for the Arc graphics type. See ``arc?`` for information @@ -87,7 +88,7 @@ def __init__(self, x, y, r1, r2, angle, s1, s2, options): self.s1 = float(s1) self.s2 = float(s2) if self.s2 < self.s1: - self.s1,self.s2=self.s2,self.s1 + self.s1, self.s2 = self.s2, self.s1 GraphicPrimitive.__init__(self, options) def get_minmax_data(self): @@ -126,91 +127,113 @@ def get_minmax_data(self): """ from sage.plot.plot import minmax_data - twopi = 2*pi + twopi = 2 * pi s1 = self.s1 s2 = self.s2 - s = s2-s1 - s1 = fmod(s1,twopi) - if s1 < 0: s1 += twopi - s2 = fmod(s1 + s,twopi) - if s2 < 0: s2 += twopi + s = s2 - s1 + s1 = fmod(s1, twopi) + if s1 < 0: + s1 += twopi + s2 = fmod(s1 + s, twopi) + if s2 < 0: + s2 += twopi r1 = self.r1 r2 = self.r2 - angle = fmod(self.angle,twopi) - if angle < 0: angle += twopi + angle = fmod(self.angle, twopi) + if angle < 0: + angle += twopi epsilon = float(0.0000001) cos_angle = cos(angle) sin_angle = sin(angle) - if cos_angle > 1-epsilon: - xmin=-r1; ymin=-r2 - xmax=r1; ymax=r2 - axmin = pi; axmax = 0 - aymin = 3*pi/2; aymax = pi/2 - - elif cos_angle < -1+epsilon: - xmin=-r1; ymin=-r2 - xmax=r1; ymax=r2 - axmin=0; axmax=pi - aymin=pi/2; aymax=3*pi/2 - - elif sin_angle > 1-epsilon: - xmin=-r2; ymin=-r1 - xmax=r2; ymax=r1 - axmin = pi/2; axmax = 3*pi/2 - aymin = pi; aymax = 0 - - elif sin_angle < -1+epsilon: - xmin=-r2; ymin=-r1 - xmax=r2; ymax=r1 - axmin = 3*pi/2; axmax = pi/2 - aymin = 0; aymax = pi + if cos_angle > 1 - epsilon: + xmin = -r1 + ymin = -r2 + xmax = r1 + ymax = r2 + axmin = pi + axmax = 0 + aymin = 3 * pi / 2 + aymax = pi / 2 + + elif cos_angle < -1 + epsilon: + xmin = -r1 + ymin = -r2 + xmax = r1 + ymax = r2 + axmin = 0 + axmax = pi + aymin = pi / 2 + aymax = 3 * pi / 2 + + elif sin_angle > 1 - epsilon: + xmin = -r2 + ymin = -r1 + xmax = r2 + ymax = r1 + axmin = pi / 2 + axmax = 3 * pi / 2 + aymin = pi + aymax = 0 + + elif sin_angle < -1 + epsilon: + xmin = -r2 + ymin = -r1 + xmax = r2 + ymax = r1 + axmin = 3 * pi / 2 + axmax = pi / 2 + aymin = 0 + aymax = pi else: tan_angle = sin_angle / cos_angle - axmax = atan(-r2/r1*tan_angle) - if axmax < 0: axmax += twopi - xmax = ( - r1 * cos_angle * cos(axmax) - - r2 * sin_angle * sin(axmax)) + axmax = atan(-r2 / r1 * tan_angle) + if axmax < 0: + axmax += twopi + xmax = (r1 * cos_angle * cos(axmax) - + r2 * sin_angle * sin(axmax)) if xmax < 0: xmax = -xmax - axmax = fmod(axmax+pi,twopi) + axmax = fmod(axmax + pi, twopi) xmin = -xmax - axmin = fmod(axmax + pi,twopi) + axmin = fmod(axmax + pi, twopi) - aymax = atan(r2/(r1*tan_angle)) - if aymax < 0: aymax += twopi - ymax = ( - r1 * sin_angle * cos(aymax) + - r2 * cos_angle * sin(aymax)) + aymax = atan(r2 / (r1 * tan_angle)) + if aymax < 0: + aymax += twopi + ymax = (r1 * sin_angle * cos(aymax) + + r2 * cos_angle * sin(aymax)) if ymax < 0: ymax = -ymax - aymax = fmod(aymax+pi,twopi) + aymax = fmod(aymax + pi, twopi) ymin = -ymax aymin = fmod(aymax + pi, twopi) - if s < twopi-epsilon: # bb determined by the sector - def is_cyclic_ordered(x1,x2,x3): - return ( - (x1 < x2 and x2 < x3) or - (x2 < x3 and x3 < x1) or - (x3 < x1 and x1 < x2)) - - x1 = cos_angle*r1*cos(s1) - sin_angle*r2*sin(s1) - x2 = cos_angle*r1*cos(s2) - sin_angle*r2*sin(s2) - y1 = sin_angle*r1*cos(s1) + cos_angle*r2*sin(s1) - y2 = sin_angle*r1*cos(s2) + cos_angle*r2*sin(s2) - - if is_cyclic_ordered(s1,s2,axmin): xmin = min(x1,x2) - if is_cyclic_ordered(s1,s2,aymin): ymin = min(y1,y2) - if is_cyclic_ordered(s1,s2,axmax): xmax = max(x1,x2) - if is_cyclic_ordered(s1,s2,aymax): ymax = max(y1,y2) + if s < twopi - epsilon: # bb determined by the sector + def is_cyclic_ordered(x1, x2, x3): + return ((x1 < x2 and x2 < x3) or + (x2 < x3 and x3 < x1) or + (x3 < x1 and x1 < x2)) + + x1 = cos_angle * r1 * cos(s1) - sin_angle * r2 * sin(s1) + x2 = cos_angle * r1 * cos(s2) - sin_angle * r2 * sin(s2) + y1 = sin_angle * r1 * cos(s1) + cos_angle * r2 * sin(s1) + y2 = sin_angle * r1 * cos(s2) + cos_angle * r2 * sin(s2) + + if is_cyclic_ordered(s1, s2, axmin): + xmin = min(x1, x2) + if is_cyclic_ordered(s1, s2, aymin): + ymin = min(y1, y2) + if is_cyclic_ordered(s1, s2, axmax): + xmax = max(x1, x2) + if is_cyclic_ordered(s1, s2, aymax): + ymax = max(y1, y2) return minmax_data([self.x + xmin, self.x + xmax], [self.y + ymin, self.y + ymax], @@ -226,15 +249,75 @@ def _allowed_options(self): sage: p[0]._allowed_options()['alpha'] 'How transparent the figure is.' """ - return {'alpha':'How transparent the figure is.', - 'thickness':'How thick the border of the arc is.', - 'hue':'The color given as a hue.', - 'rgbcolor':'The color', - 'zorder':'2D only: The layer level in which to draw', - 'linestyle':"2D only: The style of the line, which is one of " + return {'alpha': 'How transparent the figure is.', + 'thickness': 'How thick the border of the arc is.', + 'hue': 'The color given as a hue.', + 'rgbcolor': 'The color', + 'zorder': '2D only: The layer level in which to draw', + 'linestyle': "2D only: The style of the line, which is one of " "'dashed', 'dotted', 'solid', 'dashdot', or '--', ':', '-', '-.', " "respectively."} + def _matplotlib_arc(self): + """ + Return ``self`` as a matplotlib arc object. + + EXAMPLES:: + + sage: from sage.plot.arc import Arc + sage: Arc(2,3,2.2,2.2,0,2,3,{})._matplotlib_arc() + + """ + import matplotlib.patches as patches + p = patches.Arc((self.x, self.y), + 2. * self.r1, + 2. * self.r2, + fmod(self.angle, 2 * pi) * (180. / pi), + self.s1 * (180. / pi), + self.s2 * (180. / pi)) + return p + + def bezier_path(self): + """ + Return ``self`` as a Bezier path. + + This is needed to concatenate arcs, in order to + create hyperbolic polygons. + + EXAMPLES:: + + sage: from sage.plot.arc import Arc + sage: op = {'alpha':1,'thickness':1,'rgbcolor':'blue','zorder':0, + ....: 'linestyle':'--'} + sage: Arc(2,3,2.2,2.2,0,2,3,op).bezier_path() + Graphics object consisting of 1 graphics primitive + + sage: a = arc((0,0),2,1,0,(pi/5,pi/2+pi/12), linestyle="--", color="red") + sage: b = a[0].bezier_path() + sage: b[0] + Bezier path from (1.618..., 0.5877...) to (-0.5176..., 0.9659...) + """ + from sage.plot.bezier_path import BezierPath + from sage.plot.graphics import Graphics + ma = self._matplotlib_arc() + transform = ma.get_transform().get_matrix() + cA, cC, cE = transform[0] + cB, cD, cF = transform[1] + points = [] + for u in ma._path.vertices: + x, y = list(u) + points += [(cA * x + cC * y + cE, cB * x + cD * y + cF)] + cutlist = [points[0: 4]] + N = 4 + while N < len(points): + cutlist += [points[N: N + 3]] + N += 3 + g = Graphics() + opt = self.options() + opt['fill'] = False + g.add_primitive(BezierPath(cutlist, opt)) + return g + def _repr_(self): """ String representation of ``Arc`` primitive. @@ -245,7 +328,7 @@ def _repr_(self): sage: print Arc(2,3,2.2,2.2,0,2,3,{}) Arc with center (2.0,3.0) radii (2.2,2.2) angle 0.0 inside the sector (2.0,3.0) """ - return "Arc with center (%s,%s) radii (%s,%s) angle %s inside the sector (%s,%s)" %(self.x,self.y,self.r1,self.r2,self.angle,self.s1,self.s2) + return "Arc with center (%s,%s) radii (%s,%s) angle %s inside the sector (%s,%s)" % (self.x, self.y, self.r1, self.r2, self.angle, self.s1, self.s2) def _render_on_subplot(self, subplot): """ @@ -254,26 +337,19 @@ def _render_on_subplot(self, subplot): sage: A = arc((1,1),3,4,pi/4,(pi,4*pi/3)); A Graphics object consisting of 1 graphics primitive """ - import matplotlib.patches as patches from sage.plot.misc import get_matplotlib_linestyle - options = self.options() - p = patches.Arc( - (self.x,self.y), - 2.*self.r1, - 2.*self.r2, - fmod(self.angle,2*pi)*(180./pi), - self.s1*(180./pi), - self.s2*(180./pi)) + p = self._matplotlib_arc() p.set_linewidth(float(options['thickness'])) a = float(options['alpha']) p.set_alpha(a) - z = int(options.pop('zorder',1)) + z = int(options.pop('zorder', 1)) p.set_zorder(z) c = to_mpl_color(options['rgbcolor']) - p.set_linestyle(get_matplotlib_linestyle(options['linestyle'],return_type='long')) + p.set_linestyle(get_matplotlib_linestyle(options['linestyle'], + return_type='long')) p.set_edgecolor(c) subplot.add_patch(p) @@ -289,10 +365,11 @@ def plot3d(self): """ raise NotImplementedError + @rename_keyword(color='rgbcolor') -@options(alpha=1, thickness=1, linestyle='solid', zorder=5,rgbcolor='blue', +@options(alpha=1, thickness=1, linestyle='solid', zorder=5, rgbcolor='blue', aspect_ratio=1.0) -def arc(center, r1, r2=None, angle=0.0, sector=(0.0,2*pi), **options): +def arc(center, r1, r2=None, angle=0.0, sector=(0.0, 2 * pi), **options): r""" An arc (that is a portion of a circle or an ellipse) @@ -372,19 +449,19 @@ def arc(center, r1, r2=None, angle=0.0, sector=(0.0,2*pi), **options): if scale == 'semilogy' or scale == 'semilogx': options['aspect_ratio'] = 'automatic' - if len(center)==2: - if r2 is None: r2 = r1 + if len(center) == 2: + if r2 is None: + r2 = r1 g = Graphics() g._set_extra_kwds(Graphics._extract_kwds_for_show(options)) if len(sector) != 2: raise ValueError("the sector must consist of two angles") g.add_primitive(Arc( - center[0],center[1], - r1,r2, + center[0], center[1], + r1, r2, angle, - sector[0],sector[1], + sector[0], sector[1], options)) return g - elif len(center)==3: + elif len(center) == 3: raise NotImplementedError - diff --git a/src/sage/plot/hyperbolic_arc.py b/src/sage/plot/hyperbolic_arc.py index 1e658fc84d5..5b1ac5d5739 100644 --- a/src/sage/plot/hyperbolic_arc.py +++ b/src/sage/plot/hyperbolic_arc.py @@ -7,6 +7,7 @@ """ #***************************************************************************** # Copyright (C) 2011 Hartmut Monien , +# 2015 Stefan Kraemer # # Distributed under the terms of the GNU General Public License (GPL) # @@ -70,20 +71,21 @@ def _hyperbolic_arc(self, z0, z3, first=False): the hyperbolic arc between the complex numbers z0 and z3 in the hyperbolic plane. """ - if (z0-z3).real() == 0: + z0, z3 = (CC(z0), CC(z3)) + p = (abs(z0)*abs(z0)-abs(z3)*abs(z3))/(z0-z3).real()/2 + r = abs(z0-p) + + if abs(z3-z0)/r < 0.1: self.path.append([(z0.real(),z0.imag()), (z3.real(),z3.imag())]) return - z0, z3 = (CC(z0), CC(z3)) + if z0.imag() == 0 and z3.imag() == 0: p = (z0.real()+z3.real())/2 - r = abs(z0-p) zm = CC(p, r) self._hyperbolic_arc(z0, zm, first) self._hyperbolic_arc(zm, z3) return else: - p = (abs(z0)*abs(z0)-abs(z3)*abs(z3))/(z0-z3).real()/2 - r = abs(z0-p) zm = ((z0+z3)/2-p)/abs((z0+z3)/2-p)*r+p t = (8*zm-4*(z0+z3)).imag()/3/(z3-z0).real() z1 = z0 + t*CC(z0.imag(), (p-z0.real())) diff --git a/src/sage/plot/hyperbolic_polygon.py b/src/sage/plot/hyperbolic_polygon.py index e6465183e04..3bfbdcb3404 100644 --- a/src/sage/plot/hyperbolic_polygon.py +++ b/src/sage/plot/hyperbolic_polygon.py @@ -8,7 +8,8 @@ """ #***************************************************************************** # Copyright (C) 2011 Hartmut Monien , -# 2014 Vincent Delecroix <20100.delecroix@gmail.com> +# 2014 Vincent Delecroix <20100.delecroix@gmail.com>, +# 2015 Stefan Kraemer # # Distributed under the terms of the GNU General Public License (GPL) # @@ -85,20 +86,21 @@ def _hyperbolic_arc(self, z0, z3, first=False): the hyperbolic arc between the complex numbers z0 and z3 in the hyperbolic plane. """ - if (z0-z3).real() == 0: + z0, z3 = (CC(z0), CC(z3)) + p = (abs(z0)*abs(z0)-abs(z3)*abs(z3))/(z0-z3).real()/2 + r = abs(z0-p) + + if abs(z3-z0)/r < 0.1: self.path.append([(z0.real(), z0.imag()), (z3.real(), z3.imag())]) return - z0, z3 = (CC(z0), CC(z3)) + if z0.imag() == 0 and z3.imag() == 0: p = (z0.real()+z3.real())/2 - r = abs(z0-p) zm = CC(p, r) self._hyperbolic_arc(z0, zm, first) self._hyperbolic_arc(zm, z3) return else: - p = (abs(z0)*abs(z0)-abs(z3)*abs(z3))/(z0-z3).real()/2 - r = abs(z0-p) zm = ((z0+z3)/2-p)/abs((z0+z3)/2-p)*r+p t = (8*zm-4*(z0+z3)).imag()/3/(z3-z0).real() z1 = z0 + t*CC(z0.imag(), (p-z0.real())) diff --git a/src/sage/quadratic_forms/genera/genus.py b/src/sage/quadratic_forms/genera/genus.py index 60f2f8f7c74..1f978f0f1ad 100644 --- a/src/sage/quadratic_forms/genera/genus.py +++ b/src/sage/quadratic_forms/genera/genus.py @@ -17,24 +17,23 @@ from sage.rings.integer import Integer from sage.rings.finite_rings.constructor import FiniteField + def Genus(A): - """ - Given a nonsingular symmetric matrix A, return the genus of A. + r""" + Given a nonsingular symmetric matrix `A`, return the genus of `A`. INPUT: - - A -- a symmetric matrix with coefficients in ZZ + - `A` -- a symmetric matrix with coefficients in `\ZZ` OUTPUT: - A GenusSymbol_global_ring object, encoding the Conway-Sloane - genus symbol of the quadratic form whose Gram matrix is A. + A ``GenusSymbol_global_ring`` object, encoding the Conway-Sloane + genus symbol of the quadratic form whose Gram matrix is `A`. EXAMPLES:: - sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring sage: from sage.quadratic_forms.genera.genus import Genus - sage: A = Matrix(ZZ, 2, 2, [1,1,1,2]) sage: Genus(A) Genus of [1 1] @@ -43,7 +42,6 @@ def Genus(A): return GenusSymbol_global_ring(A) - def LocalGenusSymbol(A,p): """ Given a nonsingular symmetric matrix A, return the local symbol of A at the prime p. @@ -55,8 +53,8 @@ def LocalGenusSymbol(A,p): OUTPUT: - A Genus_Symbol_p_adic_ring object, encoding the Conway-Sloane - genus symbol at p of the quadratic form whose Gram matrix is A. + A Genus_Symbol_p_adic_ring object, encoding the Conway-Sloane + genus symbol at p of the quadratic form whose Gram matrix is A. EXAMPLES:: @@ -91,7 +89,7 @@ def is_GlobalGenus(G): OUTPUT: - boolean + boolean EXAMPLES:: @@ -144,7 +142,7 @@ def is_2_adic_genus(genus_symbol_quintuple_list): OUTPUT: - boolean + boolean EXAMPLES:: @@ -209,7 +207,7 @@ def canonical_2_adic_compartments(genus_symbol_quintuple_list): OUTPUT: - a list of lists of integers. + a list of lists of integers. EXAMPLES:: @@ -283,7 +281,7 @@ def canonical_2_adic_trains(genus_symbol_quintuple_list, compartments=None): OUTPUT: - a list of lists of distinct integers. + a list of lists of distinct integers. EXAMPLES:: @@ -325,12 +323,13 @@ def canonical_2_adic_trains(genus_symbol_quintuple_list, compartments=None): sage: canonical_2_adic_trains(G2.symbol_tuple_list()) [] - NOTES: + .. NOTE:: See Conway-Sloane 3rd edition, pp. 381-382 for definitions and examples. - TO DO: - - Add a non-trivial example in the doctest here! + .. TODO:: + + Add a non-trivial example in the doctest here! """ ## Recompute compartments if none are passed. if compartments is None: @@ -384,7 +383,7 @@ def canonical_2_adic_reduction(genus_symbol_quintuple_list): OUTPUT: - a list of lists of distinct integers. + a list of lists of distinct integers. EXAMPLES:: @@ -415,12 +414,13 @@ def canonical_2_adic_reduction(genus_symbol_quintuple_list): sage: canonical_2_adic_reduction(G2.symbol_tuple_list()) [[0, 2, -1, 0, 0]] - NOTES: + .. NOTE:: See Conway-Sloane 3rd edition, pp. 381-382 for definitions and examples. - TO DO: - - Add an example where sign walking occurs! + .. TODO:: + + Add an example where sign walking occurs! """ canonical_symbol = genus_symbol_quintuple_list # Canonical determinants: @@ -469,7 +469,7 @@ def basis_complement(B): OUTPUT: - a rectangular matrix over a field + a rectangular matrix over a field EXAMPLES:: @@ -515,7 +515,7 @@ def signature_pair_of_matrix(A): OUTPUT: - a pair (tuple) of integers. + a pair (tuple) of integers. EXAMPLES:: @@ -564,7 +564,10 @@ def p_adic_symbol(A, p, val): val = valuation of the maximal elementary divisor of A needed to obtain enough precision calculation is modulo p to the val+3 - TODO: Some description of the definition of the genus symbol. + + .. TODO:: + + Some description of the definition of the genus symbol. INPUT: @@ -574,7 +577,7 @@ def p_adic_symbol(A, p, val): OUTPUT: - a list of lists of integers + a list of lists of integers EXAMPLES:: @@ -638,7 +641,7 @@ def is_even_matrix(A): OUTPUT: - a pair of the form (boolean, integer) + a pair of the form (boolean, integer) EXAMPLES:: @@ -671,8 +674,8 @@ def split_odd(A): OUTPUT: - a pair (u, B) consisting of an odd integer u and an odd - integral symmetric matrix B. + a pair (u, B) consisting of an odd integer u and an odd + integral symmetric matrix B. EXAMPLES:: @@ -764,7 +767,7 @@ def trace_diag_mod_8(A): OUTPUT: - an integer + an integer EXAMPLES:: @@ -816,7 +819,7 @@ def two_adic_symbol(A, val): OUTPUT: - a list of lists of integers (representing a Conway-Sloane 2-adic symbol) + a list of lists of integers (representing a Conway-Sloane 2-adic symbol) EXAMPLES:: @@ -890,29 +893,6 @@ def two_adic_symbol(A, val): return [ [s[0]+m0] + s[1:] for s in sym + two_adic_symbol(A, val) ] - - - -## Removed because it was unused and undocumented! -# -#def is_trivial_symbol(p, sym): -# """ -# """ -# if len(sym) != 1: -# return False -# if sym[0] != 0 or sym[2] != 1: -# return False -# if p != 2: -# return True -# return sym[3] == 1 and sym[1] % 8 == sym[4] - - - - - - - - class Genus_Symbol_p_adic_ring(object): """ Local genus symbol over a p-adic ring. @@ -924,19 +904,19 @@ def __init__(self, prime, symbol, check = True): The genus symbol of a component p^m*A for odd prime = p is of the form (m,n,d), where - m = valuation of the component - n = rank of A - d = det(A) in {1,u} for normalized quadratic non-residue u. + - m = valuation of the component + - n = rank of A + - d = det(A) in {1,u} for normalized quadratic non-residue u. The genus symbol of a component 2^m*A is of the form (m,n,s,d,o), where - m = valuation of the component - n = rank of A - d = det(A) in {1,3,5,7} - s = 0 (or 1) if even (or odd) - o = oddity of A (= 0 if s = 0) in Z/8Z - = the trace of the diagonalization of A + - m = valuation of the component + - n = rank of A + - d = det(A) in {1,3,5,7} + - s = 0 (or 1) if even (or odd) + - o = oddity of A (= 0 if s = 0) in Z/8Z + = the trace of the diagonalization of A The genus symbol is a list of such symbols (ordered by m) for each of the Jordan blocks A_1,...,A_t. @@ -949,7 +929,6 @@ def __init__(self, prime, symbol, check = True): doubling conventions straight throughout! This is especially noticeable in the determinant and excess methods!! - INPUT: - prime -- a prime integer > 0 @@ -958,7 +937,7 @@ def __init__(self, prime, symbol, check = True): OUTPUT: - None + None EXAMPLES:: @@ -992,31 +971,29 @@ def __init__(self, prime, symbol, check = True): self._canonical_symbol = None def __repr__(self): - """ + r""" Gives a string representation for the p-adic genus symbol INPUT: - None + None OUTPUT: - a string + a string EXAMPLES:: sage: from sage.quadratic_forms.genera.genus import two_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring - sage: A = diagonal_matrix(ZZ, [1,2,3,4]) sage: s2 = two_adic_symbol(A, 2); s2 [[0, 2, 3, 1, 4], [1, 1, 1, 1, 1], [2, 1, 1, 1, 1]] sage: G = Genus_Symbol_p_adic_ring(2, s2) sage: G.__repr__() 'Genus symbol at 2 : [[0, 2, 3, 1, 4], [1, 1, 1, 1, 1], [2, 1, 1, 1, 1]]' - """ - return "Genus symbol at %s : %s"%(self._prime, self._symbol) + return "Genus symbol at %s : %s" % (self._prime, self._symbol) def __eq__(self, other): @@ -1025,11 +1002,11 @@ def __eq__(self, other): INPUT: - a Genus_Symbol_p_adic_ring object + a Genus_Symbol_p_adic_ring object OUTPUT: - boolean + boolean EXAMPLES:: @@ -1064,11 +1041,13 @@ def __ne__(self, other): INPUT: - a Genus_Symbol_p_adic_ring object + a ``Genus_Symbol_p_adic_ring`` object OUTPUT: - boolean + boolean + + EXAMPLES:: sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -1109,11 +1088,11 @@ def canonical_symbol(self): INPUT: - None + None OUTPUT: - a list of lists of integers + a list of lists of integers EXAMPLES:: @@ -1156,13 +1135,13 @@ def canonical_symbol(self): sage: G3.canonical_symbol() [[0, 3, 1], [1, 1, -1]] + .. NOTE:: - NOTES: + See Conway-Sloane 3rd edition, pp. 381-382 for definitions and examples. - See Conway-Sloane 3rd edition, pp. 381-382 for definitions and examples. + .. TODO:: - TO DO: - - Add an example where sign walking occurs! + Add an example where sign walking occurs! """ symbol = self._symbol if self._prime == 2: @@ -1180,11 +1159,11 @@ def symbol_tuple_list(self): INPUT: - None + None OUTPUT: - list of lists of integers + list of lists of integers EXAMPLES:: @@ -1220,11 +1199,11 @@ def number_of_blocks(self): INPUT: - None + None OUTPUT: - integer >= 0 + integer >= 0 EXAMPLES:: @@ -1258,11 +1237,11 @@ def determinant(self): INPUT: - None + None OUTPUT: - an integer + an integer EXAMPLES:: @@ -1291,16 +1270,17 @@ def rank(self): """ Returns the dimension of a quadratic form associated to this genus symbol. - TO DO: DELETE THIS METHOD IN FAVOR OF THE dimension() METHOD BELOW! + .. TODO:: + DELETE THIS METHOD IN FAVOR OF THE dimension() METHOD BELOW! INPUT: - None + None OUTPUT: - an integer >= 0 + an integer >= 0 EXAMPLES:: @@ -1324,18 +1304,17 @@ def rank(self): """ return sum([ s[1] for s in self._symbol ]) - def dimension(self): """ Returns the dimension of a quadratic form associated to this genus symbol. INPUT: - None + None OUTPUT: - an integer >= 0 + an integer >= 0 EXAMPLES:: @@ -1372,15 +1351,15 @@ def excess(self): REFERENCE: - Conway and Sloane Book, 3rd edition, pp 370-371. + Conway and Sloane Book, 3rd edition, pp 370-371. INPUT: - None + None OUTPUT: - an integer + an integer EXAMPLES:: @@ -1448,11 +1427,11 @@ def trains(self): INPUT: - None + None OUTPUT: - a list of integers >= 0 + a list of integers >= 0 EXAMPLES:: @@ -1483,11 +1462,11 @@ def compartments(self): INPUT: - None + None OUTPUT: - a list of integers >= 0 + a list of integers >= 0 EXAMPLES:: @@ -1534,7 +1513,7 @@ def __init__(self, A, max_elem_divisors=None): OUTPUT: - None + None EXAMPLES:: @@ -1565,29 +1544,26 @@ def __init__(self, A, max_elem_divisors=None): def __repr__(self): - """ + r""" Returns a string representing the global genus symbol. INPUT: - None + None OUTPUT: - a string + a string EXAMPLES:: sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring - sage: A = DiagonalQuadraticForm(ZZ, [1,2,3,4]).Hessian_matrix() sage: GS = GenusSymbol_global_ring(A) sage: GS.__repr__() 'Genus of [2 0 0 0]\n[0 4 0 0]\n[0 0 6 0]\n[0 0 0 8]' - """ - return "Genus of %s"%self._representative - + return "Genus of %s" % self._representative def __eq__(self, other): @@ -1596,11 +1572,11 @@ def __eq__(self, other): INPUT: - a GenusSymbol_global_ring object + a ``GenusSymbol_global_ring`` object OUTPUT: - boolean + boolean EXAMPLES:: @@ -1642,11 +1618,11 @@ def __ne__(self, other): INPUT: - a GenusSymbol_global_ring object + a ``GenusSymbol_global_ring`` object OUTPUT: - boolean + boolean EXAMPLES:: @@ -1681,11 +1657,11 @@ def signature_pair_of_matrix(self): INPUT: - None + None OUTPUT: - a pair of integers (p, n) each >= 0 + a pair of integers (p, n) each >= 0 EXAMPLES:: @@ -1709,11 +1685,11 @@ def determinant(self): INPUT: - None + None OUTPUT: - an integer + an integer EXAMPLES:: diff --git a/src/sage/repl/display/fancy_repr.py b/src/sage/repl/display/fancy_repr.py index 31115a7b703..18f0906747b 100644 --- a/src/sage/repl/display/fancy_repr.py +++ b/src/sage/repl/display/fancy_repr.py @@ -317,11 +317,36 @@ def __call__(self, obj, p, cycle): sage: format_list = TallListRepr().format_string sage: format_list([1, 2, identity_matrix(2)]) '[\n [1 0]\n1, 2, [0 1]\n]' + + Check that :trac:`18743` is fixed:: + + sage: class Foo(object): + ....: def __repr__(self): + ....: return '''BBB AA RRR + ....: B B A A R R + ....: BBB AAAA RRR + ....: B B A A R R + ....: BBB A A R R''' + ....: def _repr_option(self, key): + ....: return key == 'ascii_art' + sage: F = Foo() + sage: [F, F] + [ + BBB AA RRR BBB AA RRR + B B A A R R B B A A R R + BBB AAAA RRR BBB AAAA RRR + B B A A R R B B A A R R + BBB A A R R, BBB A A R R + ] """ if not (isinstance(obj, (tuple, list)) and len(obj) > 0): return False ascii_art_repr = False for o in obj: + try: + ascii_art_repr = ascii_art_repr or o._repr_option('ascii_art') + except (AttributeError, TypeError): + pass try: ascii_art_repr = ascii_art_repr or o.parent()._repr_option('element_ascii_art') except (AttributeError, TypeError): diff --git a/src/sage/repl/image.py b/src/sage/repl/image.py index 34e065a094c..174703ca40d 100644 --- a/src/sage/repl/image.py +++ b/src/sage/repl/image.py @@ -14,8 +14,9 @@ sage: from sage.repl.image import Image sage: img = Image('RGB', (256, 256), 'white') sage: pixels = img.pixels() - sage: for x, y in CartesianProduct(range(img.width()), range(img.height())): - ....: pixels[x, y] = (x, y, 100) + sage: for x in range(img.width()): + ....: for y in range(img.height()): + ....: pixels[x, y] = (x, y, 100) sage: img 256x256px 24-bit RGB image sage: type(img) diff --git a/src/sage/repl/ipython_kernel/install.py b/src/sage/repl/ipython_kernel/install.py index 55e03622122..fe8aafbce85 100644 --- a/src/sage/repl/ipython_kernel/install.py +++ b/src/sage/repl/ipython_kernel/install.py @@ -1,39 +1,38 @@ """ -Installing the Sage IPython Kernel +Installing the SageMath Jupyter Kernel and extensions -Kernels have to register themselves with IPython so that they appear -in the IPython notebook's kernel drop-down. This is done by +Kernels have to register themselves with Jupyter so that they appear +in the Jupyter notebook's kernel drop-down. This is done by :class:`SageKernelSpec`. """ import os import errno -from jupyter_client.kernelspec import get_kernel_spec, install_kernel_spec -from IPython.paths import get_ipython_dir - from sage.env import ( SAGE_ROOT, SAGE_DOC, SAGE_LOCAL, SAGE_EXTCODE, SAGE_VERSION ) -from sage.misc.temporary_file import tmp_dir +from jupyter_core.paths import ENV_JUPYTER_PATH +JUPYTER_PATH = ENV_JUPYTER_PATH[0] class SageKernelSpec(object): def __init__(self): """ - Utility to manage Sage kernels + Utility to manage SageMath kernels and extensions EXAMPLES:: sage: from sage.repl.ipython_kernel.install import SageKernelSpec sage: spec = SageKernelSpec() sage: spec._display_name # random output - 'Sage 6.6.beta2' + 'SageMath 6.9' """ - self._display_name = 'Sage {0}'.format(SAGE_VERSION) - self._ipython_dir = get_ipython_dir() + self._display_name = 'SageMath {0}'.format(SAGE_VERSION) + self.nbextensions_dir = os.path.join(JUPYTER_PATH, "nbextensions") + self.kernel_dir = os.path.join(JUPYTER_PATH, "kernels", self.identifier()) self._mkdirs() def _mkdirs(self): @@ -45,40 +44,33 @@ def _mkdirs(self): sage: from sage.repl.ipython_kernel.install import SageKernelSpec sage: spec = SageKernelSpec() sage: spec._mkdirs() - sage: nbextensions = os.path.join(spec._ipython_dir, 'nbextensions') - sage: os.path.exists(nbextensions) + sage: os.path.isdir(spec.nbextensions_dir) True """ - def mkdir_p(*path_components): - path = os.path.join(*path_components) + def mkdir_p(path): try: os.makedirs(path) - except OSError as err: - if err.errno == errno.EEXIST and os.path.isdir(path): - pass - else: + except OSError: + if not os.path.isdir(path): raise - mkdir_p(self._ipython_dir, 'nbextensions') + mkdir_p(self.nbextensions_dir) + mkdir_p(self.kernel_dir) @classmethod - def identifier(self): + def identifier(cls): """ - Internal identifier for the Sage kernel - - OUTPUT: + Internal identifier for the SageMath kernel - String. + OUTPUT: the string ``"sagemath"``. EXAMPLES:: sage: from sage.repl.ipython_kernel.install import SageKernelSpec - sage: SageKernelSpec.identifier() # random output - 'sage_6_6_beta3' - sage: SageKernelSpec.identifier().startswith('sage_') - True + sage: SageKernelSpec.identifier() + 'sagemath' """ - return 'Sage {0}'.format(SAGE_VERSION).lower().replace(' ', '_').replace('.', '_') - + return 'sagemath' + def symlink(self, src, dst): """ Symlink ``src`` to ``dst`` @@ -103,38 +95,48 @@ def symlink(self, src, dst): if err.errno == errno.EEXIST: return os.symlink(src, dst) - + def use_local_mathjax(self): """ - Symlink Sage's Mathjax Install to the IPython notebook. + Symlink SageMath's Mathjax install to the Jupyter notebook. EXAMPLES:: sage: from sage.repl.ipython_kernel.install import SageKernelSpec - sage: from IPython.paths import get_ipython_dir sage: spec = SageKernelSpec() sage: spec.use_local_mathjax() - sage: ipython_dir = get_ipython_dir() - sage: mathjax = os.path.join(ipython_dir, 'nbextensions', 'mathjax') - sage: os.path.exists(mathjax) + sage: mathjax = os.path.join(spec.nbextensions_dir, 'mathjax') + sage: os.path.isdir(mathjax) True """ src = os.path.join(SAGE_LOCAL, 'share', 'mathjax') - dst = os.path.join(self._ipython_dir, 'nbextensions', 'mathjax') + dst = os.path.join(self.nbextensions_dir, 'mathjax') self.symlink(src, dst) def use_local_jsmol(self): + """ + Symlink jsmol to the Jupyter notebook. + + EXAMPLES:: + + sage: from sage.repl.ipython_kernel.install import SageKernelSpec + sage: spec = SageKernelSpec() + sage: spec.use_local_jsmol() + sage: jsmol = os.path.join(spec.nbextensions_dir, 'jsmol') + sage: os.path.isdir(jsmol) + True + """ src = os.path.join(SAGE_LOCAL, 'share', 'jsmol') - dst = os.path.join(self._ipython_dir, 'nbextensions', 'jsmol') + dst = os.path.join(self.nbextensions_dir, 'jsmol') self.symlink(src, dst) def _kernel_cmd(self): """ - Helper to construct the Sage kernel command. - + Helper to construct the SageMath kernel command. + OUTPUT: - List of strings. The command to start a new Sage kernel. + List of strings. The command to start a new SageMath kernel. EXAMPLES:: @@ -142,7 +144,7 @@ def _kernel_cmd(self): sage: spec = SageKernelSpec() sage: spec._kernel_cmd() ['/.../sage', - '-python', + '--python', '-m', 'sage.repl.ipython_kernel', '-f', @@ -150,40 +152,34 @@ def _kernel_cmd(self): """ return [ os.path.join(SAGE_ROOT, 'sage'), - '-python', + '--python', '-m', 'sage.repl.ipython_kernel', '-f', '{connection_file}', ] - + def kernel_spec(self): """ Return the kernel spec as Python dictionary OUTPUT: - A dictionary. See the IPython documentation for details. + A dictionary. See the Jupyter documentation for details. EXAMPLES:: sage: from sage.repl.ipython_kernel.install import SageKernelSpec sage: spec = SageKernelSpec() sage: spec.kernel_spec() - {'argv': ..., 'display_name': 'Sage ...'} + {'argv': ..., 'display_name': 'SageMath ...'} """ return dict( argv=self._kernel_cmd(), display_name=self._display_name, ) - + def _install_spec(self): """ - Install the Sage IPython kernel - - It is safe to call this method multiple times, only one Sage - kernel spec is ever installed for any given Sage - version. However, it resets the IPython kernel spec directory - so additional resources symlinked there are lost. See - :meth:`symlink_resources`. + Install the SageMath Jupyter kernel EXAMPLES:: @@ -191,21 +187,17 @@ def _install_spec(self): sage: spec = SageKernelSpec() sage: spec._install_spec() # not tested """ + jsonfile = os.path.join(self.kernel_dir, "kernel.json") import json - temp = tmp_dir() - kernel_spec = os.path.join(temp, 'kernel.json') - with open(kernel_spec, 'w') as f: + with open(jsonfile, 'w') as f: json.dump(self.kernel_spec(), f) - identifier = self.identifier() - install_kernel_spec(temp, identifier, user=True, replace=True) - self._spec = get_kernel_spec(identifier) def _symlink_resources(self): """ Symlink miscellaneous resources - This method symlinks additional resources (like the Sage - documentation) into the Sage kernel directory. This is + This method symlinks additional resources (like the SageMath + documentation) into the SageMath kernel directory. This is necessary to make the help links in the notebook work. EXAMPLES:: @@ -215,25 +207,23 @@ def _symlink_resources(self): sage: spec._install_spec() # not tested sage: spec._symlink_resources() # not tested """ - assert self._spec, 'call _install_spec() first' - spec_dir = self._spec.resource_dir path = os.path.join(SAGE_EXTCODE, 'notebook-ipython') for filename in os.listdir(path): self.symlink( os.path.join(path, filename), - os.path.join(spec_dir, filename) + os.path.join(self.kernel_dir, filename) ) self.symlink( os.path.join(SAGE_DOC, 'output', 'html', 'en'), - os.path.join(spec_dir, 'doc') + os.path.join(self.kernel_dir, 'doc') ) - + @classmethod def update(cls): """ - Configure the IPython notebook for the Sage kernel - - This method does everything necessary to use the Sage kernel, + Configure the Jupyter notebook for the SageMath kernel + + This method does everything necessary to use the SageMath kernel, you should never need to call any of the other methods directly. @@ -249,7 +239,7 @@ def update(cls): instance._install_spec() instance._symlink_resources() - + def have_prerequisites(debug=True): """ Check that we have all prerequisites to run the Jupyter notebook. diff --git a/src/sage/repl/ipython_kernel/kernel.py b/src/sage/repl/ipython_kernel/kernel.py index 9b00d8e25c9..eb61032dbd7 100644 --- a/src/sage/repl/ipython_kernel/kernel.py +++ b/src/sage/repl/ipython_kernel/kernel.py @@ -1,8 +1,8 @@ """ The Sage ZMQ Kernel -Version of the IPython kernel when running Sage inside the IPython -notebook or remote IPython sessions. +Version of the Jupyter kernel when running Sage inside the Jupyter +notebook or remote Jupyter sessions. """ #***************************************************************************** @@ -27,7 +27,7 @@ class SageZMQInteractiveShell(SageNotebookInteractiveShell, ZMQInteractiveShell) pass -class SageKernel(IPythonKernel): +class SageKernel(IPythonKernel): implementation = 'sage' implementation_version = SAGE_VERSION @@ -35,11 +35,11 @@ class SageKernel(IPythonKernel): def __init__(self, **kwds): """ - The Sage IPython Kernel + The Sage Jupyter Kernel INPUT: - See the IPython documentation + See the Jupyter documentation EXAMPLES:: @@ -54,8 +54,8 @@ def __init__(self, **kwds): def banner(self): r""" The Sage Banner - - The value of this property is displayed in the IPython + + The value of this property is displayed in the Jupyter notebook. OUTPUT: @@ -75,11 +75,11 @@ def banner(self): @property def help_links(self): r""" - Help in the IPython Notebook - + Help in the Jupyter Notebook + OUTPUT: - See the IPython documentation. + See the Jupyter documentation. EXAMPLES:: @@ -87,12 +87,12 @@ def help_links(self): sage: sk = SageKernel.__new__(SageKernel) sage: sk.help_links [{'text': 'Sage Documentation', - 'url': '/kernelspecs/sage_.../doc/index.html'}, + 'url': '../kernelspecs/sagemath/doc/index.html'}, ...] """ from sage.repl.ipython_kernel.install import SageKernelSpec identifier = SageKernelSpec.identifier() - kernel_url = lambda x: '/kernelspecs/{0}/{1}'.format(identifier, x) + kernel_url = lambda x: '../kernelspecs/{0}/{1}'.format(identifier, x) return [ { 'text': 'Sage Documentation', @@ -119,7 +119,7 @@ def help_links(self): 'url': kernel_url('doc/reference/index.html'), }, { - 'text': 'Developers Guide', + 'text': "Developer's Guide", 'url': kernel_url('doc/developer/index.html'), }, { diff --git a/src/sage/rings/all.py b/src/sage/rings/all.py index b3334d99b73..b14ecf33ba7 100644 --- a/src/sage/rings/all.py +++ b/src/sage/rings/all.py @@ -179,3 +179,6 @@ Hirzebruch_Jung_continued_fraction_list) # and deprecated continued fractions from sage.rings.contfrac import (CFF, ContinuedFractionField) + +# asymptotic ring +from asymptotic.all import * diff --git a/src/sage/rings/asymptotic/all.py b/src/sage/rings/asymptotic/all.py index e69de29bb2d..daf2b157307 100644 --- a/src/sage/rings/asymptotic/all.py +++ b/src/sage/rings/asymptotic/all.py @@ -0,0 +1,2 @@ +from sage.misc.lazy_import import lazy_import +lazy_import('sage.rings.asymptotic.asymptotic_ring', 'AsymptoticRing') diff --git a/src/sage/rings/asymptotic/asymptotic_ring.py b/src/sage/rings/asymptotic/asymptotic_ring.py new file mode 100644 index 00000000000..a2bacd54ade --- /dev/null +++ b/src/sage/rings/asymptotic/asymptotic_ring.py @@ -0,0 +1,3295 @@ +r""" +Asymptotic Ring + +This module provides a ring (called :class:`AsymptoticRing`) for +computations with :wikipedia:`asymptotic expansions `. + + +.. _asymptotic_ring_definition: + +(Informal) Definition +===================== + +An asymptotic expansion is a sum such as + +.. MATH:: + + 5z^3 + 4z^2 + O(z) + +as `z \to \infty` or + +.. MATH:: + + 3x^{42}y^2 + 7x^3y^3 + O(x^2) + O(y) + +as `x` and `y` tend to `\infty`. It is a truncated series (after a +finite number of terms), which approximates a function. + +The summands of the asymptotic expansions are partially ordered. In +this module these summands are the following: + +- Exact terms `c\cdot g` with a coefficient `c` and an element `g` of + a growth group (:ref:`see below `). + +- `O`-terms `O(g)` (see :wikipedia:`Big O notation `; + also called *Bachmann--Landau notation*) for a growth group + element `g` (:ref:`again see below `). + +See +:wikipedia:`the Wikipedia article on asymptotic expansions ` +for more details. +Further examples of such elements can be found :ref:`here `. + + +.. _asymptotic_ring_growth: + +Growth Groups and Elements +-------------------------- + +The elements of a :doc:`growth group ` are equipped with +a partial order and usually contain a variable. Examples---the order +is described below these examples---are + +- elements of the form `z^q` for some integer or rational `q` + (growth groups with :ref:`description strings ` + ``z^ZZ`` or ``z^QQ``), + +- elements of the form `\log(z)^q` for some integer or rational `q` + (growth groups ``log(z)^ZZ`` or ``log(z)^QQ``), + +- elements of the form `a^z` for some + rational `a` (growth group ``QQ^z``), or + +- more sophisticated constructions like products + `x^r \cdot \log(x)^s \cdot a^y \cdot y^q` + (this corresponds to an element of the growth group + ``x^QQ * log(x)^ZZ * QQ^y * y^QQ``). + +The order in all these examples is induced by the magnitude of the +elements as `x`, `y`, or `z` (independently) tend to `\infty`. For +elements only using the variable `z` this means that `g_1 \leq g_2` if + +.. MATH:: + + \lim_{z\to\infty} \frac{g_1}{g_2} \leq 1. + +.. NOTE:: + + Asymptotic rings where the variable tend to some value distinct from + `\infty` are not yet implemented. + +To find out more about + +- growth groups, + +- on how they are created and + +- about the above used *descriptions strings* + +see the top of the module :doc:`growth group `. + + +.. WARNING:: + + As this code is experimental, a warning is thrown when an + asymptotic ring (or an associated structure) is created for the + first time in a session (see + :class:`sage.misc.superseded.experimental`). + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^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/17601 for details. + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: T = GenericTermMonoid(G, ZZ) + sage: R. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=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/17601 for details. + + +.. _asymptotic_ring_intro: + +Introductory Examples +===================== + +We start this series of examples by defining two asymptotic rings. + + +Two Rings +--------- + +A Univariate Asymptotic Ring +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +First, we construct the following (very simple) asymptotic ring in the variable `z`:: + + sage: A. = AsymptoticRing(growth_group='z^QQ', coefficient_ring=ZZ); A + Asymptotic Ring over Integer Ring + +A typical element of this ring is +:: + + sage: A.an_element() + z^(3/2) + O(z^(1/2)) + +This element consists of two summands: the exact term with coefficient +`1` and growth `z^{3/2}` and the `O`-term `O(z^{1/2})`. Note that the +growth of `z^{3/2}` is larger than the growth of `z^{1/2}` as +`z\to\infty`, thus this expansion cannot be simplified (which would +be done automatically, see below). + +Elements can be constructed via the generator `z` and the function +:func:`~sage.rings.big_oh.O`, for example + +:: + + sage: 4*z^2 + O(z) + 4*z^2 + O(z) + +A Multivariate Asymptotic Ring +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Next, we construct a more sophisticated asymptotic ring in the +variables `x` and `y` by +:: + + sage: B. = AsymptoticRing(growth_group='x^QQ * log(x)^ZZ * QQ^y * y^QQ', coefficient_ring=QQ); B + Asymptotic Ring over Rational Field + +Again, we can look at a typical (nontrivial) element:: + + sage: B.an_element() + 1/8*x^(3/2)*log(x)^3*(1/8)^y*y^(3/2) + O(x^(1/2)*log(x)*(1/2)^y*y^(1/2)) + +Again, elements can be created using the generators `x` and `y`, as well as +the function :func:`~sage.rings.big_oh.O`:: + + sage: log(x)*y/42 + O(1/2^y) + 1/42*log(x)*y + O((1/2)^y) + +Arithmetical Operations +----------------------- + +In this section we explain how to perform various arithmetical +operations with the elements of the asymptotic rings constructed +above. + + +The Ring Operations Plus and Times +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We start our calculations in the ring +:: + + sage: A + Asymptotic Ring over Integer Ring + +Of course, we can perform the usual ring operations `+` and `*`:: + + sage: z^2 + 3*z*(1-z) + -2*z^2 + 3*z + sage: (3*z + 2)^3 + 27*z^3 + 54*z^2 + 36*z + 8 + +In addition to that, special powers---our growth group ``z^QQ`` allows +the exponents to be out of `\QQ`---can also be computed:: + + sage: (z^(5/2)+z^(1/7)) * z^(-1/5) + z^(23/10) + z^(-2/35) + +The central concepts of computations with asymptotic expansions is +that the `O`-notation can be used. For example, we have +:: + + sage: z^3 + z^2 + z + O(z^2) + z^3 + O(z^2) + +where the result is simplified automatically. A more sophisticated example is +:: + + sage: (z+2*z^2+3*z^3+4*z^4) * (O(z)+z^2) + 4*z^6 + O(z^5) + + +Division +^^^^^^^^ + +The asymptotic expansions support division. For example, we can +expand `1/(z-1)` to a geometric series:: + + sage: 1 / (z-1) + z^(-1) + z^(-2) + z^(-3) + z^(-4) + ... + z^(-20) + O(z^(-21)) + +A default precision (parameter ``default_prec`` of +:class:`AsymptoticRing`) is predefined. Thus, only the first `20` +summands are calculated. However, if we only want the first `5` exact +terms, we cut of the rest by using +:: + + sage: (1 / (z-1)).truncate(5) + z^(-1) + z^(-2) + z^(-3) + z^(-4) + z^(-5) + O(z^(-6)) + +or + +:: + + sage: 1 / (z-1) + O(z^(-6)) + z^(-1) + z^(-2) + z^(-3) + z^(-4) + z^(-5) + O(z^(-6)) + +Of course, we can work with more complicated expansions as well:: + + sage: (4*z+1) / (z^3+z^2+z+O(z^0)) + 4*z^(-2) - 3*z^(-3) - z^(-4) + O(z^(-5)) + +Not all elements are invertible, for instance, + +:: + + sage: 1 / O(z) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot invert O(z). + +is not invertible, since it includes `0`. + + +Powers, Expontials and Logarithms +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +It works as simple as it can be; just use the usual operators ``^``, +``exp`` and ``log``. For example, we obtain the usual series expansion +of the logarithm +:: + + sage: -log(1-1/z) + z^(-1) + 1/2*z^(-2) + 1/3*z^(-3) + ... + O(z^(-21)) + +as `z \to \infty`. + +Similarly, we can apply the exponential function of an asymptotic expansion:: + + sage: exp(1/z) + 1 + z^(-1) + 1/2*z^(-2) + 1/6*z^(-3) + 1/24*z^(-4) + ... + O(z^(-20)) + +Arbitrary powers work as well; for example, we have +:: + + sage: (1 + 1/z + O(1/z^5))^(1 + 1/z) + 1 + z^(-1) + z^(-2) + 1/2*z^(-3) + 1/3*z^(-4) + O(z^(-5)) + + +Multivariate Arithmetic +^^^^^^^^^^^^^^^^^^^^^^^ + +Now let us move on to arithmetic in the multivariate ring + +:: + + sage: B + Asymptotic Ring over Rational Field + +.. TODO:: + + write this part + + +More Examples +============= + + +The mathematical constant e as a limit +-------------------------------------- + +The base of the natural logarithm `e` satisfies the equation + +.. MATH:: + + e = \lim_{n\to\infty} \left(1+\frac{1}{n}\right)^n + +By using asymptotic expansions, we obtain the more precise result +:: + + sage: E. = AsymptoticRing(growth_group='n^ZZ', coefficient_ring=SR, default_prec=5); E + Asymptotic Ring over Symbolic Ring + sage: (1 + 1/n)^n + e - 1/2*e*n^(-1) + 11/24*e*n^(-2) - 7/16*e*n^(-3) + 2447/5760*e*n^(-4) + O(n^(-5)) + + +Selected Technical Details +========================== + + +Coercions and Functorial Constructions +-------------------------------------- + +The :class:`AsymptoticRing` fully supports +`coercion <../../../../coercion/index.html>`_. For example, the coefficient ring is automatically extended when needed:: + + sage: A + Asymptotic Ring over Integer Ring + sage: (z + 1/2).parent() + Asymptotic Ring over Rational Field + +Here, the coefficient ring was extended to allow `1/2` as a +coefficent. Another example is +:: + + sage: C. = AsymptoticRing(growth_group='c^ZZ', coefficient_ring=ZZ['e']) + sage: C.an_element() + e^3*c^3 + O(c) + sage: C.an_element() / 7 + 1/7*e^3*c^3 + O(c) + +Here the result's coefficient ring is the newly found +:: + + sage: (C.an_element() / 7).parent() + Asymptotic Ring over + Univariate Polynomial Ring in e over Rational Field + +Not only the coefficient ring can be extended, but the growth group as +well. For example, we can add/multiply elements of the asymptotic +rings ``A`` and ``C`` to get an expansion of new asymptotic ring:: + + sage: r = c*z + c/2 + O(z); r + c*z + 1/2*c + O(z) + sage: r.parent() + Asymptotic Ring over + Univariate Polynomial Ring in e over Rational Field + + +Data Structures +--------------- + +The summands of an +:class:`asymptotic expansion ` are wrapped +:doc:`growth group elements `. +This wrapping is done by the +:doc:`term monoid module `. +However, inside an +:class:`asymptotic expansion ` these summands +(terms) are stored together with their growth-relationship, i.e., each +summand knows its direct predecessors and successors. As a data +structure a special poset (namely a +:mod:`mutable poset `) +is used. We can have a look at this:: + + sage: b = x^3*y + x^2*y + x*y^2 + O(x) + O(y) + sage: print b.summands.repr_full(reverse=True) + poset(x*y^2, x^3*y, x^2*y, O(x), O(y)) + +-- oo + | +-- no successors + | +-- predecessors: x*y^2, x^3*y + +-- x*y^2 + | +-- successors: oo + | +-- predecessors: O(x), O(y) + +-- x^3*y + | +-- successors: oo + | +-- predecessors: x^2*y + +-- x^2*y + | +-- successors: x^3*y + | +-- predecessors: O(x), O(y) + +-- O(x) + | +-- successors: x*y^2, x^2*y + | +-- predecessors: null + +-- O(y) + | +-- successors: x*y^2, x^2*y + | +-- predecessors: null + +-- null + | +-- successors: O(x), O(y) + | +-- no predecessors + + +Various +======= + +AUTHORS: + +- Benjamin Hackl (2015) +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + + +Classes and Methods +=================== +""" + +# ***************************************************************************** +# Copyright (C) 2015 Benjamin Hackl +# 2015 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 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.rings.ring import Algebra +from sage.structure.element import CommutativeAlgebraElement +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.superseded import experimental + +class AsymptoticExpansion(CommutativeAlgebraElement): + r""" + Class for asymptotic expansions, i.e., the elements of an + :class:`AsymptoticRing`. + + INPUT: + + - ``parent`` -- the parent of the asymptotic expansion. + + - ``summands`` -- the summands as a + :class:`~sage.data_structures.mutable_poset.MutablePoset`, which + represents the underlying structure. + + - ``simplify`` -- a boolean (default: ``True``). It controls + automatic simplification (absorption) of the asymptotic expansion. + + - ``convert`` -- a boolean (default: ``True``). If set, then the + ``summands`` are converted to the asymptotic ring (the parent of this + expansion). If not, then the summands are taken as they are. In + that case, the caller must ensure that the parent of the terms is + set correctly. + + EXAMPLES: + + There are several ways to create asymptotic expansions; usually + this is done by using the corresponding :class:`asymptotic rings `:: + + sage: R_x. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ); R_x + Asymptotic Ring over Rational Field + sage: R_y. = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=ZZ); R_y + Asymptotic Ring over Integer Ring + + At this point, `x` and `y` are already asymptotic expansions:: + + sage: type(x) + + + The usual ring operations, but allowing rational exponents (growth + group ``x^QQ``) can be performed:: + + sage: x^2 + 3*(x - x^(2/5)) + x^2 + 3*x - 3*x^(2/5) + sage: (3*x^(1/3) + 2)^3 + 27*x + 54*x^(2/3) + 36*x^(1/3) + 8 + + One of the central ideas behind computing with asymptotic + expansions is that the `O`-notation (see + :wikipedia:`Big_O_notation`) can be used. For example, we have:: + + sage: (x+2*x^2+3*x^3+4*x^4) * (O(x)+x^2) + 4*x^6 + O(x^5) + + In particular, :meth:`~sage.rings.big_oh.O` can be used to + construct the asymptotic expansions. With the help of the + :meth:`summands`, we can also have a look at the inner structure + of an asymptotic expansion:: + + sage: expr1 = x + 2*x^2 + 3*x^3 + 4*x^4; expr2 = O(x) + x^2 + sage: print(expr1.summands.repr_full()) + poset(x, 2*x^2, 3*x^3, 4*x^4) + +-- null + | +-- no predecessors + | +-- successors: x + +-- x + | +-- predecessors: null + | +-- successors: 2*x^2 + +-- 2*x^2 + | +-- predecessors: x + | +-- successors: 3*x^3 + +-- 3*x^3 + | +-- predecessors: 2*x^2 + | +-- successors: 4*x^4 + +-- 4*x^4 + | +-- predecessors: 3*x^3 + | +-- successors: oo + +-- oo + | +-- predecessors: 4*x^4 + | +-- no successors + sage: print(expr2.summands.repr_full()) + poset(O(x), x^2) + +-- null + | +-- no predecessors + | +-- successors: O(x) + +-- O(x) + | +-- predecessors: null + | +-- successors: x^2 + +-- x^2 + | +-- predecessors: O(x) + | +-- successors: oo + +-- oo + | +-- predecessors: x^2 + | +-- no successors + sage: print((expr1 * expr2).summands.repr_full()) + poset(O(x^5), 4*x^6) + +-- null + | +-- no predecessors + | +-- successors: O(x^5) + +-- O(x^5) + | +-- predecessors: null + | +-- successors: 4*x^6 + +-- 4*x^6 + | +-- predecessors: O(x^5) + | +-- successors: oo + +-- oo + | +-- predecessors: 4*x^6 + | +-- no successors + + In addition to the monomial growth elements from above, we can + also compute with logarithmic terms (simply by constructing the + appropriate growth group):: + + sage: R_log = AsymptoticRing(growth_group='log(x)^QQ', coefficient_ring=QQ) + sage: lx = R_log(log(SR.var('x'))) + sage: (O(lx) + lx^3)^4 + log(x)^12 + O(log(x)^10) + + .. SEEALSO:: + + :doc:`growth_group`, + :doc:`term_monoid`, + :mod:`~sage.data_structures.mutable_poset`. + """ + def __init__(self, parent, summands, simplify=True, convert=True): + r""" + See :class:`AsymptoticExpansion` for more information. + + TESTS:: + + sage: R_x. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: R_y. = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=ZZ) + sage: R_x is R_y + False + sage: ex1 = x + 2*x^2 + 3*x^3 + 4*x^4 + 5*x^5 + sage: ex2 = x + O(R_x(1)) + sage: ex1 * ex2 + 5*x^6 + O(x^5) + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = TermMonoid('O', G, ZZ); ET = TermMonoid('exact', G, ZZ) + sage: R = AsymptoticRing(G, ZZ) + sage: lst = [ET(x, 1), ET(x^2, 2), OT(x^3), ET(x^4, 4)] + sage: expr = R(lst, simplify=False); expr # indirect doctest + 4*x^4 + O(x^3) + 2*x^2 + x + sage: print expr.summands.repr_full() + poset(x, 2*x^2, O(x^3), 4*x^4) + +-- null + | +-- no predecessors + | +-- successors: x + +-- x + | +-- predecessors: null + | +-- successors: 2*x^2 + +-- 2*x^2 + | +-- predecessors: x + | +-- successors: O(x^3) + +-- O(x^3) + | +-- predecessors: 2*x^2 + | +-- successors: 4*x^4 + +-- 4*x^4 + | +-- predecessors: O(x^3) + | +-- successors: oo + +-- oo + | +-- predecessors: 4*x^4 + | +-- no successors + sage: expr._simplify_(); expr + 4*x^4 + O(x^3) + sage: print expr.summands.repr_full() + poset(O(x^3), 4*x^4) + +-- null + | +-- no predecessors + | +-- successors: O(x^3) + +-- O(x^3) + | +-- predecessors: null + | +-- successors: 4*x^4 + +-- 4*x^4 + | +-- predecessors: O(x^3) + | +-- successors: oo + +-- oo + | +-- predecessors: 4*x^4 + | +-- no successors + sage: R(lst, simplify=True) # indirect doctest + 4*x^4 + O(x^3) + + :: + + sage: R. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ) + sage: e = R(x^2 + O(x)) + sage: from sage.rings.asymptotic.asymptotic_ring import AsymptoticExpansion + sage: S = AsymptoticRing(growth_group='x^QQ', coefficient_ring=ZZ) + sage: for s in AsymptoticExpansion(S, e.summands).summands.elements_topological(): + ....: print s.parent() + O-Term Monoid x^QQ with implicit coefficients in Integer Ring + Exact Term Monoid x^QQ with coefficients in Integer Ring + sage: for s in AsymptoticExpansion(S, e.summands, + ....: convert=False).summands.elements_topological(): + ....: print s.parent() + O-Term Monoid x^QQ with implicit coefficients in Rational Field + Exact Term Monoid x^QQ with coefficients in Rational Field + + :: + + sage: AsymptoticExpansion(S, R(1/2).summands) + Traceback (most recent call last): + ... + ValueError: Cannot include 1/2 with parent + Exact Term Monoid x^QQ with coefficients in Rational Field in + Asymptotic Ring over Integer Ring + > *previous* ValueError: 1/2 is not a coefficient in + Exact Term Monoid x^QQ with coefficients in Integer Ring. + """ + super(AsymptoticExpansion, self).__init__(parent=parent) + + from sage.data_structures.mutable_poset import MutablePoset + if not isinstance(summands, MutablePoset): + raise TypeError('Summands %s are not in a mutable poset as expected ' + 'when creating an element of %s.' % (summands, parent)) + + if convert: + from misc import combine_exceptions + from term_monoid import TermMonoid + def convert_terms(element): + T = TermMonoid(term=element.parent(), asymptotic_ring=parent) + try: + return T(element) + except (ValueError, TypeError) as e: + raise combine_exceptions( + ValueError('Cannot include %s with parent %s in %s' % + (element, element.parent(), parent)), e) + new_summands = summands.copy() + new_summands.map(convert_terms, topological=True, reverse=True) + self._summands_ = new_summands + else: + self._summands_ = summands + + if simplify: + self._simplify_() + + + @property + def summands(self): + r""" + The summands of this asymptotic expansion stored in the + underlying data structure (a + :class:`~sage.data_structures.mutable_poset.MutablePoset`). + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: expr = 7*x^12 + x^5 + O(x^3) + sage: expr.summands + poset(O(x^3), x^5, 7*x^12) + + .. SEEALSO:: + + :class:`sage.data_structures.mutable_poset.MutablePoset` + """ + return self._summands_ + + + def __hash__(self): + r""" + A hash value for this element. + + .. WARNING:: + + This hash value uses the string representation and might not be + always right. + + TESTS:: + + sage: R_log = AsymptoticRing(growth_group='log(x)^QQ', coefficient_ring=QQ) + sage: lx = R_log(log(SR.var('x'))) + sage: elt = (O(lx) + lx^3)^4 + sage: hash(elt) # random + -4395085054568712393 + """ + return hash(str(self)) + + + def __nonzero__(self): + r""" + Return whether this asymptotic expansion is not identically zero. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + TESTS:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: bool(R(0)) # indirect doctest + False + sage: bool(x) # indirect doctest + True + sage: bool(7*x^12 + x^5 + O(x^3)) # indirect doctest + True + """ + return bool(self._summands_) + + + def __eq__(self, other): + r""" + Return whether this asymptotic expansion is equal to ``other``. + + INPUT: + + - ``other`` -- an object. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + EXAMPLES:: + + sage: R. = AsymptoticRing('x^ZZ', QQ) + sage: (1 + 2*x + 3*x^2) == (3*x^2 + 2*x + 1) # indirect doctest + True + sage: O(x) == O(x) + False + + TESTS:: + + sage: x == None + False + + :: + + sage: x == 'x' + False + """ + if other is None: + return False + try: + return not bool(self - other) + except (TypeError, ValueError): + return False + + + def __ne__(self, other): + r""" + Return whether this asymptotic expansion is not equal to ``other``. + + INPUT: + + - ``other`` -- an object. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + EXAMPLES:: + + sage: R. = AsymptoticRing('x^ZZ', QQ) + sage: (1 + 2*x + 3*x^2) != (3*x^2 + 2*x + 1) # indirect doctest + False + sage: O(x) != O(x) + True + + TESTS:: + + sage: x != None + True + """ + return not self == other + + + def has_same_summands(self, other): + r""" + Return whether this asymptotic expansion and ``other`` have the + same summands. + + INPUT: + + - ``other`` -- an asymptotic expansion. + + OUTPUT: + + A boolean. + + .. NOTE:: + + While for example ``O(x) == O(x)`` yields ``False``, + these expansions *do* have the same summands and this method + returns ``True``. + + Moreover, this method uses the coercion model in order to + find a common parent for this asymptotic expansion and + ``other``. + + EXAMPLES:: + + sage: R_ZZ. = AsymptoticRing('x^ZZ', ZZ) + sage: R_QQ. = AsymptoticRing('x^ZZ', QQ) + sage: sum(x_ZZ^k for k in range(5)) == sum(x_QQ^k for k in range(5)) # indirect doctest + True + sage: O(x_ZZ) == O(x_QQ) + False + + TESTS:: + + sage: x_ZZ.has_same_summands(None) + False + """ + if other is None: + return False + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._has_same_summands_(other) + + from sage.structure.element import get_coercion_model + return get_coercion_model().bin_op(self, other, + lambda self, other: + self._has_same_summands_(other)) + + + def _has_same_summands_(self, other): + r""" + Return whether this :class:`AsymptoticExpansion` has the same + summands as ``other``. + + INPUT: + + - ``other`` -- an :class:`AsymptoticExpansion`. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method compares two :class:`AsymptoticExpansion` + with the same parent. + + EXAMPLES:: + + sage: R. = AsymptoticRing('x^ZZ', QQ) + sage: O(x).has_same_summands(O(x)) + True + sage: (1 + x + 2*x^2).has_same_summands(2*x^2 + O(x)) # indirect doctest + False + """ + if len(self.summands) != len(other.summands): + return False + from itertools import izip + return all(s == o for s, o in + izip(self.summands.elements_topological(), + other.summands.elements_topological())) + + + def _simplify_(self): + r""" + Simplify this asymptotic expansion. + + INPUT: + + Nothing. + + OUTPUT: + + Nothing, but modifies this asymptotic expansion. + + .. NOTE:: + + This method is usually called during initialization of + this asymptotic expansion. + + .. NOTE:: + + This asymptotic expansion is simplified by letting + `O`-terms that are included in this expansion absorb all + terms with smaller growth. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: OT = TermMonoid('O', G, ZZ); ET = TermMonoid('exact', G, ZZ) + sage: R = AsymptoticRing(G, ZZ) + sage: lst = [ET(x, 1), ET(x^2, 2), OT(x^3), ET(x^4, 4)] + sage: expr = R(lst, simplify=False); expr # indirect doctest + 4*x^4 + O(x^3) + 2*x^2 + x + sage: expr._simplify_(); expr + 4*x^4 + O(x^3) + sage: R(lst) # indirect doctest + 4*x^4 + O(x^3) + """ + self._summands_.merge(reverse=True) + + + def _repr_(self): + r""" + A representation string for this asymptotic expansion. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: (5*x^2+12*x) * (x^3+O(x)) # indirect doctest + 5*x^5 + 12*x^4 + O(x^3) + sage: (5*x^2-12*x) * (x^3+O(x)) # indirect doctest + 5*x^5 - 12*x^4 + O(x^3) + """ + s = ' + '.join(repr(elem) for elem in + self.summands.elements_topological(reverse=True)) + s = s.replace('+ -', '- ') + if not s: + return '0' + return s + + + def _add_(self, other): + r""" + Add ``other`` to this asymptotic expansion. + + INPUT: + + - ``other`` -- an :class:`AsymptoticExpansion`. + + OUTPUT: + + The sum as an :class:`AsymptoticExpansion`. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: expr1 = x^123; expr2 = x^321 + sage: expr1._add_(expr2) + x^321 + x^123 + sage: expr1 + expr2 # indirect doctest + x^321 + x^123 + + If an `O`-term is added to an asymptotic expansion, then + the `O`-term absorbs everything it can:: + + sage: x^123 + x^321 + O(x^555) # indirect doctest + O(x^555) + + TESTS:: + + sage: x + O(x) + O(x) + sage: O(x) + x + O(x) + """ + return self.parent()(self.summands.union(other.summands), + simplify=True, convert=False) + + + def _sub_(self, other): + r""" + Subtract ``other`` from this asymptotic expansion. + + INPUT: + + - ``other`` -- an :class:`AsymptoticExpansion`. + + OUTPUT: + + The difference as an :class:`AsymptoticExpansion`. + + .. NOTE:: + + Subtraction of two asymptotic expansions is implemented + by means of addition: `e_1 - e_2 = e_1 + (-1)\cdot e_2`. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: expr1 = x^123; expr2 = x^321 + sage: expr1 - expr2 # indirect doctest + -x^321 + x^123 + sage: O(x) - O(x) + O(x) + """ + return self + self.parent().coefficient_ring(-1)*other + + + def _mul_term_(self, term): + r""" + Helper method: multiply this asymptotic expansion by the + asymptotic term ``term``. + + INPUT: + + - ``term`` -- an asymptotic term (see + :doc:`term_monoid`). + + OUTPUT: + + The product as an :class:`AsymptoticExpansion`. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import OTermMonoid + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: T = OTermMonoid(R.growth_group, ZZ) + sage: expr = 10*x^2 + O(x) + sage: t = T(R.growth_group.gen()) + sage: expr._mul_term_(t) + O(x^3) + """ + from term_monoid import ExactTerm + simplify = not isinstance(term, ExactTerm) + return self.parent()(self.summands.mapped(lambda element: term * element), + simplify=simplify, convert=False) + + + def _mul_(self, other): + r""" + Multiply this asymptotic expansion by another asymptotic expansion ``other``. + + INPUT: + + - ``other`` -- an :class:`AsymptoticExpansion`. + + OUTPUT: + + The product as an :class:`AsymptoticExpansion`. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: ex1 = 5*x^12 + sage: ex2 = x^3 + O(x) + sage: ex1 * ex2 # indirect doctest + 5*x^15 + O(x^13) + + .. TODO:: + + The current implementation is the standard long + multiplication. More efficient variants like Karatsuba + multiplication, or methods that exploit the structure + of the underlying poset shall be implemented at a later + point. + """ + return sum(self._mul_term_(term_other) for + term_other in other.summands.elements()) + + + def _rmul_(self, other): + r""" + Multiply this asymptotic expansion by an element ``other`` of its + coefficient ring. + + INPUT: + + - ``other`` -- an element of the coefficient ring. + + OUTPUT: + + An :class:`AsymptoticExpansion`. + + TESTS:: + + sage: A. = AsymptoticRing(growth_group='QQ^a * a^QQ * log(a)^QQ', coefficient_ring=ZZ) + sage: 2*a # indirect doctest + 2*a + """ + if other.is_zero(): + return self.parent().zero() + + from term_monoid import TermMonoid + E = TermMonoid('exact', asymptotic_ring=self.parent()) + e = E(self.parent().growth_group.one(), coefficient=other) + return self._mul_term_(e) + + + _lmul_ = _rmul_ + + + def _div_(self, other): + r""" + Divide this element through ``other``. + + INPUT: + + - ``other`` -- an asymptotic expansion. + + OUTPUT: + + An asymptotic expansion. + + EXAMPLES:: + + sage: R. = AsymptoticRing('x^ZZ', QQ, default_prec=5) + sage: 1/x^42 + x^(-42) + sage: (1 + 4*x) / (x + 2*x^2) + 2*x^(-1) - 1/2*x^(-2) + 1/4*x^(-3) - 1/8*x^(-4) + 1/16*x^(-5) + O(x^(-6)) + sage: x / O(x) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot invert O(x). + """ + return self * ~other + + + def __invert__(self, precision=None): + r""" + Return the multiplicative inverse of this element. + + INPUT: + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. + + OUTPUT: + + An asymptotic expansion. + + .. WARNING:: + + Due to truncation of infinite expansions, the element + returned by this method might not fulfill + ``el * ~el == 1``. + + .. TODO:: + + As soon as `L`-terms are implemented, this + implementation has to be adapted as well in order to + yield correct results. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ, default_prec=4) + sage: ~x + x^(-1) + sage: ~(x^42) + x^(-42) + sage: ex = ~(1 + x); ex + x^(-1) - x^(-2) + x^(-3) - x^(-4) + O(x^(-5)) + sage: ex * (1+x) + 1 + O(x^(-4)) + sage: ~(1 + O(1/x)) + 1 + O(x^(-1)) + + TESTS:: + + sage: A. = AsymptoticRing(growth_group='a^ZZ', coefficient_ring=ZZ) + sage: (1 / a).parent() + Asymptotic Ring over Rational Field + sage: (a / 2).parent() + Asymptotic Ring over Rational Field + + :: + + sage: ~A(0) + Traceback (most recent call last): + ... + ZeroDivisionError: Division by zero in 0. + + :: + + sage: B. = AsymptoticRing(growth_group='s^ZZ * t^ZZ', coefficient_ring=QQ) + sage: ~(s + t) + Traceback (most recent call last): + ... + ValueError: Expansion s + t cannot be inverted since there are + several maximal elements s, t. + """ + if not self.summands: + raise ZeroDivisionError('Division by zero in %s.' % (self,)) + + elif len(self.summands) == 1: + element = next(self.summands.elements()) + return self.parent()._create_element_in_extension_( + ~element, element.parent()) + + max_elem = tuple(self.summands.maximal_elements()) + if len(max_elem) != 1: + raise ValueError('Expansion %s cannot be inverted since there ' + 'are several maximal elements %s.' % + (self, ', '.join(str(e) for e in + sorted(max_elem, key=str)))) + max_elem = max_elem[0] + + imax_elem = ~max_elem + if imax_elem.parent() is max_elem.parent(): + new_self = self + else: + new_self = self.parent()._create_element_in_extension_( + imax_elem, max_elem.parent()).parent()(self) + + one = new_self.parent().one() + geom = one - new_self._mul_term_(imax_elem) + + expanding = True + result = one + while expanding: + new_result = (geom*result + one).truncate(precision=precision) + if new_result.has_same_summands(result): + expanding = False + result = new_result + return result._mul_term_(imax_elem) + + + invert = __invert__ + + + def truncate(self, precision=None): + r""" + Truncate this asymptotic expansion. + + INPUT: + + - ``precision`` -- a positive integer or ``None``. Number of + summands that are kept. If ``None`` (default value) is + given, then ``default_prec`` from the parent is used. + + OUTPUT: + + An asymptotic expansion. + + .. NOTE:: + + For example, truncating an asymptotic expansion with + ``precision=20`` does not yield an expansion with exactly 20 + summands! Rather than that, it keeps the 20 summands + with the largest growth, and adds appropriate + `O`-Terms. + + EXAMPLES:: + + sage: R. = AsymptoticRing('x^ZZ', QQ) + sage: ex = sum(x^k for k in range(5)); ex + x^4 + x^3 + x^2 + x + 1 + sage: ex.truncate(precision=2) + x^4 + x^3 + O(x^2) + sage: ex.truncate(precision=0) + O(x^4) + sage: ex.truncate() + x^4 + x^3 + x^2 + x + 1 + """ + if precision is None: + precision = self.parent().default_prec + + if len(self.summands) <= precision: + return self + + summands = self.summands.copy() + from term_monoid import TermMonoid + def convert_terms(element): + if convert_terms.count < precision: + convert_terms.count += 1 + return element + T = TermMonoid(term='O', asymptotic_ring=self.parent()) + return T(element) + convert_terms.count = 0 + summands.map(convert_terms, topological=True, reverse=True) + return self.parent()(summands, simplify=True, convert=False) + + + def __pow__(self, exponent, precision=None): + r""" + Calculate the power of this asymptotic expansion to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. + + OUTPUT: + + An asymptotic expansion. + + TESTS:: + + sage: R_QQ. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ) + sage: x^(1/7) + x^(1/7) + sage: R_ZZ. = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=ZZ) + sage: y^(1/7) + y^(1/7) + sage: (y^(1/7)).parent() + Asymptotic Ring over Rational Field + sage: (x^(1/2) + O(x^0))^15 + x^(15/2) + O(x^7) + sage: (y^2 + O(y))^(1/2) # not tested, see #19316 + y + O(1) + sage: (y^2 + O(y))^(-2) + y^(-4) + O(y^(-5)) + + :: + + sage: B. = AsymptoticRing(growth_group='z^QQ * log(z)^QQ', coefficient_ring=QQ) + sage: (z^2 + O(z))^(1/2) + z + O(1) + + :: + + sage: A. = AsymptoticRing('QQ^x * x^SR * log(x)^ZZ', QQ) + sage: x * 2^x + 2^x*x + sage: 5^x * 2^x + 10^x + sage: 2^log(x) + x^(log(2)) + sage: 2^(x + 1/x) + 2^x + log(2)*2^x*x^(-1) + 1/2*log(2)^2*2^x*x^(-2) + ... + O(2^x*x^(-20)) + sage: _.parent() + Asymptotic Ring over Symbolic Ring + + See :trac:`19110`:: + + sage: O(x)^(-1) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take O(x) to exponent -1. + > *previous* ZeroDivisionError: rational division by zero + + :: + + sage: B. = AsymptoticRing(growth_group='z^QQ * log(z)^QQ', coefficient_ring=QQ, default_prec=5) + sage: z^(1+1/z) + z + log(z) + 1/2*z^(-1)*log(z)^2 + 1/6*z^(-2)*log(z)^3 + + 1/24*z^(-3)*log(z)^4 + O(z^(-4)*log(z)^5) + + :: + + sage: B(0)^(-7) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take 0 to the negative exponent -7. + sage: B(0)^SR.var('a') + Traceback (most recent call last): + ... + NotImplementedError: Taking 0 to the exponent a not implemented. + + :: + + sage: C. = AsymptoticRing(growth_group='s^QQ * t^QQ', coefficient_ring=QQ) + sage: (s + t)^s + Traceback (most recent call last): + ... + ValueError: Cannot take s + t to the exponent s. + > *previous* ValueError: log(s + t) cannot be constructed since + there are several maximal elements s, t. + """ + if not self.summands: + if exponent == 0: + return self.parent().one() + elif exponent > 0: + return self.parent().zero() + elif exponent < 0: + raise ZeroDivisionError('Cannot take %s to the negative exponent %s.' % + (self, exponent)) + else: + raise NotImplementedError('Taking %s to the exponent %s not implemented.' % + (self, exponent)) + + elif len(self.summands) == 1: + element = next(self.summands.elements()) + if isinstance(exponent, AsymptoticExpansion) and element.is_constant(): + return exponent.rpow(base=element.coefficient, precision=precision) + try: + return self.parent()._create_element_in_extension_( + element ** exponent, element.parent()) + except (ArithmeticError, TypeError, ValueError): + if not isinstance(exponent, AsymptoticExpansion): + raise + + from sage.rings.integer_ring import ZZ + try: + exponent = ZZ(exponent) + except (TypeError, ValueError): + pass + else: + return super(AsymptoticExpansion, self).__pow__(exponent) + + try: + return (exponent * self.log(precision=precision)).exp(precision=precision) + except (TypeError, ValueError, ZeroDivisionError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('Cannot take %s to the exponent %s.' % (self, exponent)), e) + + + pow = __pow__ + + + def O(self): + r""" + Convert all terms in this asymptotic expansion to `O`-terms. + + INPUT: + + Nothing. + + OUTPUT: + + An asymptotic expansion. + + EXAMPLES:: + + sage: AR. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: O(x) + O(x) + sage: type(O(x)) + + sage: expr = 42*x^42 + x^10 + O(x^2); expr + 42*x^42 + x^10 + O(x^2) + sage: expr.O() + O(x^42) + sage: O(AR(0)) + 0 + sage: (2*x).O() + O(x) + + .. SEEALSO:: + + :func:`sage.rings.power_series_ring.PowerSeriesRing`, + :func:`sage.rings.laurent_series_ring.LaurentSeriesRing`. + """ + return sum(self.parent().create_summand('O', growth=element) + for element in self.summands.maximal_elements()) + + + def log(self, base=None, precision=None): + r""" + The logarithm of this asymptotic expansion. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. + + OUTPUT: + + An asymptotic expansion. + + .. NOTE:: + + Computing the logarithm of an asymptotic expansion + is possible if and only if there is exactly one maximal + summand in the expansion. + + ALGORITHM: + + If the expansion has more than one summand, + the asymptotic expansion for `\log(1+t)` as `t` tends to `0` + is used. + + .. TODO:: + + As soon as `L`-terms are implemented, this + implementation has to be adapted as well in order to + yield correct results. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ * log(x)^ZZ', coefficient_ring=QQ) + sage: log(x) + log(x) + sage: log(x^2) + 2*log(x) + sage: log(x-1) + log(x) - x^(-1) - 1/2*x^(-2) - 1/3*x^(-3) - ... + O(x^(-21)) + + TESTS:: + + sage: log(R(1)) + 0 + sage: log(R(0)) + Traceback (most recent call last): + ... + ArithmeticError: Cannot compute log(0) in + Asymptotic Ring over Rational Field. + sage: C. = AsymptoticRing(growth_group='s^ZZ * t^ZZ', coefficient_ring=QQ) + sage: log(s + t) + Traceback (most recent call last): + ... + ValueError: log(s + t) cannot be constructed since there are + several maximal elements s, t. + """ + P = self.parent() + + if not self.summands: + raise ArithmeticError('Cannot compute log(0) in %s.' % (self.parent(),)) + + elif len(self.summands) == 1: + if self.is_one(): + return P.zero() + element = next(self.summands.elements()) + return sum(P._create_element_in_extension_(l, element.parent()) + for l in element.log_term(base=base)) + + max_elem = tuple(self.summands.maximal_elements()) + if len(max_elem) != 1: + raise ValueError('log(%s) cannot be constructed since there ' + 'are several maximal elements %s.' % + (self, ', '.join(str(e) for e in + sorted(max_elem, key=str)))) + max_elem = max_elem[0] + + imax_elem = ~max_elem + if imax_elem.parent() is max_elem.parent(): + new_self = self + else: + new_self = P._create_element_in_extension_( + imax_elem, max_elem.parent()).parent()(self) + + one = new_self.parent().one() + geom = one - new_self._mul_term_(imax_elem) + + from sage.rings.integer_ring import ZZ + expanding = True + result = -geom + geom_k = geom + k = ZZ(1) + while expanding: + k += ZZ(1) + geom_k *= geom + new_result = (result - geom_k * ~k).truncate(precision=precision) + if new_result.has_same_summands(result): + expanding = False + result = new_result + + result += new_self.parent()(max_elem).log() + if base: + from sage.functions.log import log + result = result / log(base) + return result + + + def is_little_o_of_one(self): + r""" + Return whether this expansion is of order `o(1)`. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: A. = AsymptoticRing('x^ZZ * log(x)^ZZ', QQ) + sage: (x^4 * log(x)^(-2) + x^(-4) * log(x)^2).is_little_o_of_one() + False + sage: (x^(-1) * log(x)^1234 + x^(-2) + O(x^(-3))).is_little_o_of_one() + True + sage: (log(x) - log(x-1)).is_little_o_of_one() + True + + :: + + sage: A. = AsymptoticRing('x^QQ * y^QQ * log(y)^ZZ', QQ) + sage: (x^(-1/16) * y^32 + x^32 * y^(-1/16)).is_little_o_of_one() + False + sage: (x^(-1) * y^(-3) + x^(-3) * y^(-1)).is_little_o_of_one() + True + sage: (x^(-1) * y / log(y)).is_little_o_of_one() + False + sage: (log(y-1)/log(y) - 1).is_little_o_of_one() + True + """ + return all(term.is_little_o_of_one() for term in self.summands.maximal_elements()) + + + def rpow(self, base, precision=None): + r""" + Return the power of ``base`` to this asymptotic expansion. + + INPUT: + + - ``base`` -- an element or ``'e'``. + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. + + OUTPUT: + + An asymptotic expansion. + + EXAMPLES:: + + sage: A. = AsymptoticRing('x^ZZ', QQ) + sage: (1/x).rpow('e', precision=5) + 1 + x^(-1) + 1/2*x^(-2) + 1/6*x^(-3) + 1/24*x^(-4) + O(x^(-5)) + + TESTS:: + + sage: x.rpow(SR.var('y')) + Traceback (most recent call last): + ... + ArithmeticError: Cannot construct y^x in Growth Group x^ZZ + > *previous* TypeError: unsupported operand parent(s) for '*': + 'Growth Group x^ZZ' and 'Growth Group SR^x' + """ + if isinstance(base, AsymptoticExpansion): + return base.__pow__(self, precision=precision) + + P = self.parent() + + # first: remove terms from a copy of this term such that a + # term in o(1) remains + + expr_o = self.summands.copy() + large_terms = [] + for term in self.summands.elements_topological(): + if not term.is_little_o_of_one(): + large_terms.append(term) + expr_o.remove(term.growth) + + expr_o = P(expr_o) + + # next: try to take the exponential function of the large elements + + try: + large_result = P.prod( + P._create_element_in_extension_(term.rpow(base), + term.parent()) + for term in large_terms) + except (TypeError, ValueError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('Cannot construct the power of %s to the ' + 'exponent %s in %s.' % + (base, self, self.parent())), e) + + # then: expand expr_o + + if not expr_o: + return large_result + + + if base == 'e': + geom = expr_o + else: + from sage.functions.log import log + geom = expr_o * log(base) + P = geom.parent() + + expanding = True + result = P.one() + geom_k = P.one() + from sage.rings.integer_ring import ZZ + k = ZZ(0) + while expanding: + k += ZZ(1) + geom_k *= geom + new_result = (result + geom_k / k.factorial()).truncate(precision=precision) + if new_result.has_same_summands(result): + expanding = False + result = new_result + + return result * large_result + + + def exp(self, precision=None): + r""" + Return the exponential of (i.e., the power of `e` to) this asymptotic expansion. + + INPUT: + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. + + OUTPUT: + + An asymptotic expansion. + + .. NOTE:: + + The exponential function of this expansion can only be + computed exactly if the respective growth element can be + constructed in the underlying growth group. + + ALGORITHM: + + If the corresponding growth can be constructed, return + the exact exponential function. Otherwise, if this term + is `o(1)`, try to expand the series and truncate + according to the given precision. + + .. TODO:: + + As soon as `L`-terms are implemented, this + implementation has to be adapted as well in order to + yield correct results. + + EXAMPLES:: + + sage: A. = AsymptoticRing('(e^x)^ZZ * x^ZZ * log(x)^ZZ', SR) + sage: exp(x) + e^x + sage: exp(2*x) + (e^x)^2 + sage: exp(x + log(x)) + e^x*x + + :: + + sage: (x^(-1)).exp(precision=7) + 1 + x^(-1) + 1/2*x^(-2) + 1/6*x^(-3) + ... + O(x^(-7)) + + TESTS:: + + sage: A. = AsymptoticRing('(e^x)^ZZ * x^QQ * log(x)^QQ', SR) + sage: exp(log(x)) + x + sage: log(exp(x)) + x + + :: + + sage: exp(x+1) + e*e^x + """ + return self.rpow('e', precision=precision) + + + def substitute(self, rules=None, domain=None, **kwds): + r""" + Substitute the given ``rules`` in this asymptotic expansion. + + INPUT: + + - ``rules`` -- a dictionary. + + - ``kwds`` -- keyword arguments will be added to the + substitution ``rules``. + + - ``domain`` -- (default: ``None``) a parent. The neutral + elements `0` and `1` (rules for the keys ``'_zero_'`` and + ``'_one_'``, see note box below) are taken out of this + domain. If ``None``, then this is determined automatically. + + OUTPUT: + + An object. + + .. NOTE:: + + The neutral element of the asymptotic ring is replaced by + the value to the key ``'_zero_'``; the neutral element of + the growth group is replaced by the value to the key + ``'_one_'``. + + EXAMPLES:: + + sage: A. = AsymptoticRing(growth_group='(e^x)^QQ * x^ZZ * log(x)^ZZ', coefficient_ring=QQ, default_prec=5) + + :: + + sage: (e^x * x^2 + log(x)).subs(x=SR('s')) + s^2*e^s + log(s) + sage: _.parent() + Symbolic Ring + + :: + + sage: (x^3 + x + log(x)).subs(x=x+5).truncate(5) + x^3 + 15*x^2 + 76*x + log(x) + 130 + O(x^(-1)) + sage: _.parent() + Asymptotic Ring <(e^x)^QQ * x^ZZ * log(x)^ZZ> over Rational Field + + :: + + sage: (e^x * x^2 + log(x)).subs(x=2*x) + 4*(e^x)^2*x^2 + log(x) + log(2) + sage: _.parent() + Asymptotic Ring <(e^x)^QQ * x^QQ * log(x)^QQ> over Symbolic Ring + + :: + + sage: (x^2 + log(x)).subs(x=4*x+2).truncate(5) + 16*x^2 + 16*x + log(x) + log(4) + 4 + 1/2*x^(-1) + O(x^(-2)) + sage: _.parent() + Asymptotic Ring <(e^x)^QQ * x^ZZ * log(x)^ZZ> over Symbolic Ring + + :: + + sage: (e^x * x^2 + log(x)).subs(x=RIF(pi)) + 229.534211738584? + sage: _.parent() + Real Interval Field with 53 bits of precision + + .. SEEALSO:: + + :meth:`sage.symbolic.expression.Expression.subs` + + TESTS:: + + sage: x.subs({'y': -1}) + Traceback (most recent call last): + ... + ValueError: Cannot substitute y in x since it is not a generator of + Asymptotic Ring <(e^x)^QQ * x^ZZ * log(x)^ZZ> over Rational Field. + sage: B. = AsymptoticRing(growth_group='u^QQ * v^QQ * w^QQ', coefficient_ring=QQ) + sage: (1/u).subs({'u': 0}) + Traceback (most recent call last): + ... + TypeError: Cannot apply the substitution rules {u: 0} on u^(-1) in + Asymptotic Ring over Rational Field. + > *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Asymptotic Ring over Rational Field. + >> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Exact Term Monoid u^QQ * v^QQ * w^QQ with coefficients in Rational Field. + >...> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Growth Group u^QQ * v^QQ * w^QQ. + >...> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Growth Group u^QQ. + >...> *previous* ZeroDivisionError: rational division by zero + sage: (1/u).subs({'u': 0, 'v': SR.var('v')}) + Traceback (most recent call last): + ... + TypeError: Cannot apply the substitution rules {u: 0, v: v} on u^(-1) in + Asymptotic Ring over Rational Field. + > *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Asymptotic Ring over Rational Field. + >> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Exact Term Monoid u^QQ * v^QQ * w^QQ with coefficients in Rational Field. + >...> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Growth Group u^QQ * v^QQ * w^QQ. + >...> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Growth Group u^QQ. + >...> *previous* ZeroDivisionError: rational division by zero + + :: + + sage: u.subs({u: 0, 'v': SR.var('v')}) + 0 + sage: v.subs({u: 0, 'v': SR.var('v')}) + v + sage: _.parent() + Symbolic Ring + + :: + + sage: u.subs({SR.var('u'): -1}) + Traceback (most recent call last): + ... + TypeError: Cannot substitute u in u since it is neither an + asymptotic expansion nor a string + (but a ). + + :: + + sage: u.subs({u: 1, 'u': 1}) + 1 + sage: u.subs({u: 1}, u=1) + 1 + sage: u.subs({u: 1, 'u': 2}) + Traceback (most recent call last): + ... + ValueError: Cannot substitute in u: duplicate key u. + sage: u.subs({u: 1}, u=3) + Traceback (most recent call last): + ... + ValueError: Cannot substitute in u: duplicate key u. + """ + # check if nothing to do + if not rules and not kwds: + return self + + # init and process keyword arguments + gens = self.parent().gens() + locals = kwds or dict() + + # update with rules + if isinstance(rules, dict): + for k, v in rules.iteritems(): + if not isinstance(k, str) and k not in gens: + raise TypeError('Cannot substitute %s in %s ' + 'since it is neither an ' + 'asymptotic expansion ' + 'nor a string (but a %s).' % + (k, self, type(k))) + k = str(k) + if k in locals and locals[k] != v: + raise ValueError('Cannot substitute in %s: ' + 'duplicate key %s.' % (self, k)) + locals[k] = v + elif rules is not None: + raise TypeError('Substitution rules %s have to be a dictionary.' % + (rules,)) + + # fill up missing rules + for g in gens: + locals.setdefault(str(g), g) + + # check if all keys are generators + gens_str = tuple(str(g) for g in gens) + for k in locals: + if str(k) not in gens_str: + raise ValueError('Cannot substitute %s in %s ' + 'since it is not a generator of %s.' % + (k, self, self.parent())) + + # determine 0 and 1 + if domain is None and \ + ('_zero_' not in locals or '_one_' not in locals): + P = self.parent() + for g in gens: + G = locals[str(g)].parent() + if G is not P: + domain = G + break + else: + domain = P + locals.setdefault('_zero_', domain.zero()) + locals.setdefault('_one_', domain.one()) + + # do the actual substitution + try: + return self._substitute_(locals) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import combine_exceptions + rules = '{' + ', '.join( + '%s: %s' % (k, v) + for k, v in sorted(locals.iteritems(), + key=lambda k: str(k[0])) + if not k.startswith('_') and + not any(k == str(g) and v is g for g in gens)) + '}' + raise combine_exceptions( + TypeError('Cannot apply the substitution rules %s on %s ' + 'in %s.' % (rules, self, self.parent())), e) + + + subs = substitute + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this asymptotic expansion. + + INPUT: + + - ``rules`` -- a dictionary. + The neutral element of the asymptotic ring is replaced by the value + to key ``'_zero_'``. + + OUTPUT: + + An object. + + TESTS:: + + sage: A. = AsymptoticRing(growth_group='z^QQ', coefficient_ring=QQ) + sage: z._substitute_({'z': SR.var('a')}) + a + sage: _.parent() + Symbolic Ring + sage: A(0)._substitute_({'_zero_': 'zero'}) + 'zero' + sage: (1/z)._substitute_({'z': 4}) + 1/4 + sage: _.parent() + Rational Field + sage: (1/z)._substitute_({'z': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in z^(-1) in + Asymptotic Ring over Rational Field. + > *previous* ZeroDivisionError: Cannot substitute in z^(-1) in + Exact Term Monoid z^QQ with coefficients in Rational Field. + >> *previous* ZeroDivisionError: Cannot substitute in z^(-1) in + Growth Group z^QQ. + >...> *previous* ZeroDivisionError: rational division by zero + """ + if not self.summands: + return rules['_zero_'] + from sage.symbolic.operators import add_vararg + try: + return add_vararg( + *tuple(s._substitute_(rules) + for s in self.summands.elements_topological())) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + + def symbolic_expression(self, R=None): + r""" + Return this asymptotic expansion as a symbolic expression. + + INPUT: + + - ``R`` -- (a subring of) the symbolic ring or ``None``. + The output is will be an element of ``R``. If ``None``, + then the symbolic ring is used. + + OUTPUT: + + A symbolic expression. + + EXAMPLES:: + + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^QQ * log(y)^QQ * QQ^z * z^QQ', coefficient_ring=QQ) + sage: SR(A.an_element()) # indirect doctest + 1/8*(1/8)^z*x^3*y^(3/2)*z^(3/2)*log(y)^(3/2) + + Order((1/2)^z*x*sqrt(y)*sqrt(z)*sqrt(log(y))) + + TESTS:: + + sage: a = A.an_element(); a + 1/8*x^3*y^(3/2)*log(y)^(3/2)*(1/8)^z*z^(3/2) + + O(x*y^(1/2)*log(y)^(1/2)*(1/2)^z*z^(1/2)) + sage: a.symbolic_expression() + 1/8*(1/8)^z*x^3*y^(3/2)*z^(3/2)*log(y)^(3/2) + + Order((1/2)^z*x*sqrt(y)*sqrt(z)*sqrt(log(y))) + sage: _.parent() + Symbolic Ring + + :: + + sage: from sage.symbolic.ring import SymbolicRing + sage: class MySymbolicRing(SymbolicRing): + ....: pass + sage: mySR = MySymbolicRing() + sage: a.symbolic_expression(mySR).parent() is mySR + True + """ + if R is None: + from sage.symbolic.ring import SR + R = SR + + return self.substitute(dict((g, R(R.var(str(g)))) + for g in self.parent().gens()), + domain=R) + + + _symbolic_ = symbolic_expression # will be used by SR._element_constructor_ + + +class AsymptoticRing(Algebra, UniqueRepresentation): + r""" + A ring consisting of :class:`asymptotic expansions `. + + INPUT: + + - ``growth_group`` -- either a partially ordered group (see + :doc:`growth_group`) or a string + describing such a growth group (see + :class:`~sage.rings.asymptotic.growth_group.GrowthGroupFactory`). + + - ``coefficient_ring`` -- the ring which contains the + coefficients of the expansions. + + - ``default_prec`` -- a positive integer. This is the number of + summands that are kept before truncating an infinite series. + + - ``category`` -- the category of the parent can be specified + in order to broaden the base structure. It has to be a + subcategory of ``Category of rings``. This is also the default + category if ``None`` is specified. + + EXAMPLES: + + We begin with the construction of an asymptotic ring in various + ways. First, we simply pass a string specifying the underlying + growth group:: + + sage: R1_x. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ); R1_x + Asymptotic Ring over Rational Field + sage: x + x + + This is equivalent to the following code, which explicitly + specifies the underlying growth group:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_QQ = GrowthGroup('x^QQ') + sage: R2_x. = AsymptoticRing(growth_group=G_QQ, coefficient_ring=QQ); R2_x + Asymptotic Ring over Rational Field + + Of course, the coefficient ring of the asymptotic ring and the + base ring of the underlying growth group do not need to + coincide:: + + sage: R_ZZ_x. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=ZZ); R_ZZ_x + Asymptotic Ring over Integer Ring + + Note, we can also create and use logarithmic growth groups:: + + sage: R_log = AsymptoticRing(growth_group='log(x)^ZZ', coefficient_ring=QQ); R_log + Asymptotic Ring over Rational Field + + Other growth groups are available. See :doc:`asymptotic_ring` for + more examples. + + Below there are some technical details. + + According to the conventions for parents, uniqueness is ensured:: + + sage: R1_x is R2_x + True + + Furthermore, the coercion framework is also involved. Coercion + between two asymptotic rings is possible (given that the + underlying growth groups and coefficient rings are chosen + appropriately):: + + sage: R1_x.has_coerce_map_from(R_ZZ_x) + True + + Additionally, for the sake of convenience, the coefficient ring + also coerces into the asymptotic ring (representing constant + quantities):: + + sage: R1_x.has_coerce_map_from(QQ) + True + + TESTS:: + + sage: from sage.rings.asymptotic.asymptotic_ring import AsymptoticRing as AR_class + sage: class AR(AR_class): + ....: class Element(AR_class.Element): + ....: __eq__ = AR_class.Element.has_same_summands + sage: A = AR(growth_group='z^QQ', coefficient_ring=QQ) + sage: from itertools import islice + sage: TestSuite(A).run( # not tested # long time # see #19424 + ....: verbose=True, + ....: elements=tuple(islice(A.some_elements(), 10)), + ....: skip=('_test_some_elements', # to many elements + ....: '_test_distributivity')) # due to cancellations: O(z) != O(z^2) + """ + + # enable the category framework for elements + Element = AsymptoticExpansion + + + @staticmethod + def __classcall__(cls, growth_group=None, coefficient_ring=None, + names=None, category=None, default_prec=None): + r""" + Normalizes the input in order to ensure a unique + representation of the parent. + + For more information see :class:`AsymptoticRing`. + + EXAMPLES: + + ``__classcall__`` unifies the input to the constructor of + :class:`AsymptoticRing` such that the instances generated + are unique. Also, this enables the use of the generation + framework:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: MG = GrowthGroup('x^ZZ') + sage: AR1 = AsymptoticRing(growth_group=MG, coefficient_ring=ZZ) + sage: AR2. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR1 is AR2 + True + + The bracket notation can only be used if the growth group + has a generator:: + + sage: AR. = AsymptoticRing(growth_group='log(x)^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Growth Group log(x)^ZZ does not provide any + generators but name 'lx' given. + + The names of the generators have to agree with the names used in + the growth group except for univariate rings:: + + sage: A. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ); A + Asymptotic Ring over Integer Ring + sage: icecream + x + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ); A + Asymptotic Ring over Integer Ring + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Names 'y', 'x' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Names 'a', 'b' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Names 'x', 'b' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Name 'x' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Names 'x', 'y', 'z' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + + TESTS:: + + sage: AsymptoticRing(growth_group=None, coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Growth group not specified. Cannot continue. + sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring=None) + Traceback (most recent call last): + ... + ValueError: Coefficient ring not specified. Cannot continue. + sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring='icecream') + Traceback (most recent call last): + ... + ValueError: icecream is not a ring. Cannot continue. + """ + from sage.categories.sets_cat import Sets + from sage.categories.rings import Rings + + Sets_parent_class = Sets().parent_class + while issubclass(cls, Sets_parent_class): + cls = cls.__base__ + + if isinstance(growth_group, str): + from growth_group import GrowthGroup + growth_group = GrowthGroup(growth_group) + + if growth_group is None: + raise ValueError('Growth group not specified. Cannot continue.') + + if coefficient_ring is None: + raise ValueError('Coefficient ring not specified. Cannot continue.') + if coefficient_ring not in Rings(): + raise ValueError('%s is not a ring. Cannot continue.' % (coefficient_ring,)) + + strgens = tuple(str(g) for g in growth_group.gens_monomial()) + def format_names(N): + return ('s ' if len(N) != 1 else ' ') + ', '.join("'%s'" % n for n in N) + if names and not strgens: + raise ValueError('%s does not provide any generators but name%s given.' % + (growth_group, format_names(names))) + elif names is not None and len(names) == 1 and len(strgens) == 1: + pass + elif names is not None and names != strgens: + raise ValueError('Name%s do not coincide with generator%s of %s.' % + (format_names(names), format_names(strgens), growth_group)) + + if category is None: + from sage.categories.commutative_algebras import CommutativeAlgebras + from sage.categories.rings import Rings + category = CommutativeAlgebras(Rings()) + + if default_prec is None: + from sage.misc.defaults import series_precision + default_prec = series_precision() + + return super(AsymptoticRing, + cls).__classcall__(cls, growth_group, coefficient_ring, + category=category, + default_prec=default_prec) + + + @experimental(trac_number=17601) + def __init__(self, growth_group, coefficient_ring, category, default_prec): + r""" + See :class:`AsymptoticRing` for more information. + + TESTS:: + + sage: R1 = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ); R1 + Asymptotic Ring over Integer Ring + sage: R2. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ); R2 + Asymptotic Ring over Rational Field + sage: R1 is R2 + False + + :: + + sage: R3 = AsymptoticRing('x^ZZ') + Traceback (most recent call last): + ... + ValueError: Coefficient ring not specified. Cannot continue. + """ + self._coefficient_ring_ = coefficient_ring + self._growth_group_ = growth_group + self._default_prec_ = default_prec + super(AsymptoticRing, self).__init__(base_ring=coefficient_ring, + category=category) + + + @property + def growth_group(self): + r""" + The growth group of this asymptotic ring. + + EXAMPLES:: + + sage: AR = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR.growth_group + Growth Group x^ZZ + + .. SEEALSO:: + + :doc:`growth_group` + """ + return self._growth_group_ + + + @property + def coefficient_ring(self): + r""" + The coefficient ring of this asymptotic ring. + + EXAMPLES:: + + sage: AR = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR.coefficient_ring + Integer Ring + """ + return self._coefficient_ring_ + + + @property + def default_prec(self): + r""" + The default precision of this asymptotic ring. + + This is the parameter used to determine how many summands + are kept before truncating an infinite series (which occur + when inverting asymptotic expansions). + + EXAMPLES:: + + sage: AR = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR.default_prec + 20 + sage: AR = AsymptoticRing('x^ZZ', ZZ, default_prec=123) + sage: AR.default_prec + 123 + """ + return self._default_prec_ + + + def change_parameter(self, **kwds): + r""" + Return an asymptotic ring with a change in one or more of the given parameters. + + INPUT: + + - ``growth_group`` -- (default: ``None``) the new growth group. + + - ``coefficient_ring`` -- (default: ``None``) the new coefficient ring. + + - ``category`` -- (default: ``None``) the new category. + + - ``default_prec`` -- (default: ``None``) the new default precision. + + OUTPUT: + + An asymptotic ring. + + EXAMPLES:: + + sage: A = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: A.change_parameter(coefficient_ring=QQ) + Asymptotic Ring over Rational Field + + TESTS:: + + sage: A.change_parameter(coefficient_ring=ZZ) is A + True + """ + parameters = ('growth_group', 'coefficient_ring', 'default_prec') + values = dict() + for parameter in parameters: + values[parameter] = kwds.get(parameter, getattr(self, parameter)) + values['category'] = self.category() + if isinstance(values['growth_group'], str): + from growth_group import GrowthGroup + values['growth_group'] = GrowthGroup(values['growth_group']) + if all(values[parameter] is getattr(self, parameter) + for parameter in parameters) and values['category'] is self.category(): + return self + from misc import underlying_class + return underlying_class(self)(**values) + + + @staticmethod + def _create_empty_summands_(): + r""" + Create an empty data structure suitable for storing and working + with summands. + + INPUT: + + Nothing. + + OUTPUT: + + A :class:`~sage.data_structures.mutable_poset.MutablePoset`. + + TESTS:: + + sage: AsymptoticRing._create_empty_summands_() + poset() + """ + from sage.data_structures.mutable_poset import MutablePoset + from term_monoid import can_absorb, absorption + return MutablePoset(key=lambda element: element.growth, + can_merge=can_absorb, + merge=absorption) + + + def _create_element_in_extension_(self, term, old_term_parent=None): + r""" + Create an element in an extension of this asymptotic ring which + is chosen according to the input. + + INPUT: + + - ``term`` -- the element data. + + - ``old_term_parent`` -- the parent of ``term`` is compared to this + parent. If both are the same or ``old_parent`` is ``None``, + then the result is an expansion in this (``self``) asymptotic ring. + + OUTPUT: + + An element. + + EXAMPLES:: + + sage: A = AsymptoticRing('z^ZZ', ZZ) + sage: a = next(A.an_element().summands.elements_topological()) + sage: B = AsymptoticRing('z^QQ', QQ) + sage: b = next(B.an_element().summands.elements_topological()) + sage: c = A._create_element_in_extension_(a, a.parent()) + sage: next(c.summands.elements_topological()).parent() + O-Term Monoid z^ZZ with implicit coefficients in Integer Ring + sage: c = A._create_element_in_extension_(b, a.parent()) + sage: next(c.summands.elements_topological()).parent() + O-Term Monoid z^QQ with implicit coefficients in Rational Field + + TESTS:: + + sage: c = A._create_element_in_extension_(b, None) + sage: next(c.summands.elements_topological()).parent() + O-Term Monoid z^QQ with implicit coefficients in Rational Field + """ + if old_term_parent is None or term.parent() is old_term_parent: + parent = self + else: + # Insert an 'if' here once terms can have different + # coefficient rings, as this will be for L-terms. + parent = self.change_parameter( + growth_group=term.parent().growth_group, + coefficient_ring=term.parent().coefficient_ring) + return parent(term, simplify=False, convert=False) + + + def _element_constructor_(self, data, simplify=True, convert=True): + r""" + Convert a given object to this asymptotic ring. + + INPUT: + + - ``data`` -- an object representing the element to be + initialized. + + - ``simplify`` -- (default: ``True``) if set, then the constructed + element is simplified (terms are absorbed) automatically. + + - ``convert`` -- (default: ``True``) passed on to the element + constructor. If set, then the ``summands`` are converted to + the asymptotic ring (the parent of this expansion). If not, + then the summands are taken as they are. In that case, the + caller must ensure that the parent of the terms is set + correctly. + + OUTPUT: + + An element of this asymptotic ring. + + TESTS:: + + sage: AR = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR(5) # indirect doctest + 5 + sage: AR(3*x^2) # indirect doctest + 3*x^2 + sage: x = ZZ['x'].gen(); x.parent() + Univariate Polynomial Ring in x over Integer Ring + sage: AR(x) + x + sage: y = ZZ['y'].gen(); AR(y) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Polynomial y is not in + Asymptotic Ring over Integer Ring + > *previous* ValueError: Growth y is not in + Exact Term Monoid x^ZZ with coefficients in Integer Ring. + >> *previous* ValueError: y is not in Growth Group x^ZZ. + + :: + + sage: A = AsymptoticRing(growth_group='p^ZZ', coefficient_ring=QQ) + sage: P.

= QQ[] + sage: A(p) # indirect doctest + p + sage: A(p^11) # indirect doctest + p^11 + sage: A(2*p^11) # indirect doctest + 2*p^11 + sage: A(3*p^4 + 7/3*p - 8) # indirect doctest + 3*p^4 + 7/3*p - 8 + + :: + + sage: S = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=QQ) + sage: var('x, y') + (x, y) + sage: S(x + y) # indirect doctest + x + y + sage: S(2*x - 4*x*y^6) # indirect doctest + -4*x*y^6 + 2*x + + :: + + sage: A. = AsymptoticRing('a^ZZ * b^ZZ', QQ) + sage: 1/a + a^(-1) + + :: + + sage: P. = ZZ[] + sage: A(a + b) + a + b + sage: A(a + c) + Traceback (most recent call last): + ... + ValueError: Polynomial a + c is not in + Asymptotic Ring over Rational Field + > *previous* ValueError: Growth c is not in + Exact Term Monoid a^ZZ * b^ZZ with coefficients in Rational Field. + >> *previous* ValueError: c is not in Growth Group a^ZZ * b^ZZ. + >...> *previous* ValueError: c is not in any of the factors of + Growth Group a^ZZ * b^ZZ + + :: + + sage: M = AsymptoticRing('m^ZZ', ZZ) + sage: N = AsymptoticRing('n^ZZ', QQ) + sage: N(M.an_element()) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Cannot include m^3 with parent + Exact Term Monoid m^ZZ with coefficients in Integer Ring + in Asymptotic Ring over Rational Field + > *previous* ValueError: m^3 is not in Growth Group n^ZZ + + :: + + sage: M([1]) # indirect doctest + Traceback (most recent call last): + ... + TypeError: Not all list entries of [1] are asymptotic terms, + so cannot create an asymptotic expansion in + Asymptotic Ring over Integer Ring. + sage: M(SR.var('a') + 1) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Symbolic expression a + 1 is not in + Asymptotic Ring over Integer Ring. + > *previous* ValueError: a is not in + Exact Term Monoid m^ZZ with coefficients in Integer Ring. + >> *previous* ValueError: Factor a of a is neither a coefficient + (in Integer Ring) nor growth (in Growth Group m^ZZ). + """ + from sage.data_structures.mutable_poset import MutablePoset + if isinstance(data, MutablePoset): + return self.element_class(self, data, simplify=simplify, convert=convert) + + if type(data) == self.element_class and data.parent() == self: + return data + + if isinstance(data, AsymptoticExpansion): + return self.element_class(self, data.summands, + simplify=simplify, convert=convert) + + from term_monoid import GenericTerm + if isinstance(data, GenericTerm): + data = (data,) + + if isinstance(data, (list, tuple)): + if not all(isinstance(elem, GenericTerm) for elem in data): + raise TypeError('Not all list entries of %s ' + 'are asymptotic terms, so cannot create an ' + 'asymptotic expansion in %s.' % (data, self)) + summands = AsymptoticRing._create_empty_summands_() + summands.union_update(data) + return self.element_class(self, summands, + simplify=simplify, convert=convert) + + if not data: + summands = AsymptoticRing._create_empty_summands_() + return self.element_class(self, summands, + simplify=simplify, convert=False) + + try: + P = data.parent() + except AttributeError: + return self.create_summand('exact', data) + + from misc import combine_exceptions + from sage.symbolic.ring import SymbolicRing + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + from sage.rings.polynomial.multi_polynomial_ring_generic import is_MPolynomialRing + from sage.rings.power_series_ring import is_PowerSeriesRing + + if isinstance(P, SymbolicRing): + from sage.symbolic.operators import add_vararg + if data.operator() == add_vararg: + summands = [] + for summand in data.operands(): + # TODO: check if summand is an O-Term here + # (see #19425, #19426) + try: + summands.append(self.create_summand('exact', summand)) + except ValueError as e: + raise combine_exceptions( + ValueError('Symbolic expression %s is not in %s.' % + (data, self)), e) + return sum(summands) + + elif is_PolynomialRing(P): + p = P.gen() + try: + return sum(self.create_summand('exact', growth=p**i, + coefficient=c) + for i, c in enumerate(data)) + except ValueError as e: + raise combine_exceptions( + ValueError('Polynomial %s is not in %s' % (data, self)), e) + + elif is_MPolynomialRing(P): + try: + return sum(self.create_summand('exact', growth=g, coefficient=c) + for c, g in iter(data)) + except ValueError as e: + raise combine_exceptions( + ValueError('Polynomial %s is not in %s' % (data, self)), e) + + elif is_PowerSeriesRing(P): + raise NotImplementedError( + 'Cannot convert %s from the %s to an asymptotic expansion ' + 'in %s, since growths at other points than +oo are not yet ' + 'supported.' % (data, P, self)) + # Delete lines above as soon as we can deal with growths + # other than the that at going to +oo. + p = P.gen() + try: + result = self(data.polynomial()) + except ValueError as e: + raise combine_exceptions( + ValueError('Powerseries %s is not in %s' % (data, self)), e) + prec = data.precision_absolute() + if prec < sage.rings.infinity.PlusInfinity(): + try: + result += self.create_summand('O', growth=p**prec) + except ValueError as e: + raise combine_exceptions( + ValueError('Powerseries %s is not in %s' % + (data, self)), e) + return result + + return self.create_summand('exact', data) + + + def _coerce_map_from_(self, R): + r""" + Return whether ``R`` coerces into this asymptotic ring. + + INPUT: + + - ``R`` -- a parent. + + OUTPUT: + + A boolean. + + .. NOTE:: + + There are two possible cases: either ``R`` coerces in the + :meth:`coefficient_ring` of this asymptotic ring, or ``R`` + itself is an asymptotic ring, where both the + :meth:`growth_group` and the :meth:`coefficient_ring` coerce into + the :meth:`growth_group` and the :meth:`coefficient_ring` of this + asymptotic ring, respectively. + + TESTS:: + + sage: AR_ZZ = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ); AR_ZZ + Asymptotic Ring over Integer Ring + sage: x_ZZ = AR_ZZ.gen() + sage: AR_QQ = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ); AR_QQ + Asymptotic Ring over Rational Field + sage: x_QQ = AR_QQ.gen() + sage: AR_QQ.has_coerce_map_from(AR_ZZ) # indirect doctest + True + sage: x_ZZ * x_QQ + x^2 + + :: + + sage: AR_QQ.has_coerce_map_from(QQ) + True + sage: AR_QQ.has_coerce_map_from(ZZ) + True + """ + from sage.data_structures.mutable_poset import MutablePoset + if R == MutablePoset: + return + if self.coefficient_ring.has_coerce_map_from(R): + return True + if self.growth_group.has_coerce_map_from(R): + return True + elif isinstance(R, AsymptoticRing): + if self.growth_group.has_coerce_map_from(R.growth_group) and \ + self.coefficient_ring.has_coerce_map_from(R.coefficient_ring): + return True + + + def _repr_(self): + r""" + A representation string of this asymptotic ring. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: AR = AsymptoticRing(growth_group='x^ZZ', + ....: coefficient_ring=ZZ) + sage: repr(AR) # indirect doctest + 'Asymptotic Ring over Integer Ring' + """ + try: + G = '<' + self.growth_group._repr_(condense=True) + '>' + except TypeError: + G = repr(self.growth_group) + return 'Asymptotic Ring %s over %s' % (G, self.coefficient_ring) + + + def _an_element_(self): + r""" + Return an element of this asymptotic ring. + + INPUT: + + Nothing. + + OUTPUT: + + An :class:`AsymptoticExpansion`. + + EXAMPLES:: + + sage: AsymptoticRing(growth_group='z^QQ', coefficient_ring=ZZ).an_element() + z^(3/2) + O(z^(1/2)) + sage: AsymptoticRing(growth_group='z^ZZ', coefficient_ring=QQ).an_element() + 1/8*z^3 + O(z) + sage: AsymptoticRing(growth_group='z^QQ', coefficient_ring=QQ).an_element() + 1/8*z^(3/2) + O(z^(1/2)) + """ + from term_monoid import TermMonoid + E = TermMonoid('exact', asymptotic_ring=self) + O = TermMonoid('O', asymptotic_ring=self) + return self(E.an_element(), simplify=False, convert=False)**3 + \ + self(O.an_element(), simplify=False, convert=False) + + + def some_elements(self): + r""" + Return some elements of this term monoid. + + See :class:`TestSuite` for a typical use case. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from itertools import islice + sage: A = AsymptoticRing(growth_group='z^QQ', coefficient_ring=ZZ) + sage: tuple(islice(A.some_elements(), 10)) + (z^(3/2) + O(z^(1/2)), + O(z^(1/2)), + z^(3/2) + O(z^(-1/2)), + -z^(3/2) + O(z^(1/2)), + O(z^(-1/2)), + O(z^2), + z^6 + O(z^(1/2)), + -z^(3/2) + O(z^(-1/2)), + O(z^2), + z^(3/2) + O(z^(-2))) + """ + from sage.misc.mrange import cantor_product + from term_monoid import TermMonoid + E = TermMonoid('exact', asymptotic_ring=self) + O = TermMonoid('O', asymptotic_ring=self) + return iter(self(e, simplify=False, convert=False)**3 + + self(o, simplify=False, convert=False) + for e, o in cantor_product( + E.some_elements(), O.some_elements())) + + + def gens(self): + r""" + Return a tuple with generators of this asymptotic ring. + + INPUT: + + Nothing. + + OUTPUT: + + A tuple of asymptotic expansions. + + .. NOTE:: + + Generators do not necessarily exist. This depends on the + underlying growth group. For example, + :class:`monomial growth groups ` + have a generator, and exponential growth groups + do not. + + EXAMPLES:: + + sage: AR. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR.gens() + (x,) + sage: B. = AsymptoticRing(growth_group='y^ZZ * z^ZZ', coefficient_ring=QQ) + sage: B.gens() + (y, z) + """ + return tuple(self.create_summand('exact', + growth=g, + coefficient=self.coefficient_ring(1)) + for g in self.growth_group.gens_monomial()) + + + def gen(self, n=0): + r""" + Return the ``n``-th generator of this asymptotic ring. + + INPUT: + + - ``n`` -- (default: `0`) a non-negative integer. + + OUTPUT: + + An asymptotic expansion. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: R.gen() + x + """ + return self.gens()[n] + + + def ngens(self): + r""" + Return the number of generators of this asymptotic ring. + + INPUT: + + Nothing. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: AR. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR.ngens() + 1 + """ + return len(self.growth_group.gens_monomial()) + + + def create_summand(self, type, data=None, **kwds): + r""" + Create a simple asymptotic expansion consisting of a single + summand. + + INPUT: + + - ``type`` -- 'O' or 'exact'. + + - ``data`` -- the element out of which a summand has to be created. + + - ``growth`` -- an element of the :meth:`growth_group`. + + - ``coefficient`` -- an element of the :meth:`coefficient_ring`. + + .. NOTE:: + + Either ``growth`` and ``coefficient`` or ``data`` have to + be specified. + + OUTPUT: + + An asymptotic expansion. + + .. NOTE:: + + This method calls the factory :class:`TermMonoid + ` + with the appropriate arguments. + + EXAMPLES:: + + sage: R = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: R.create_summand('O', x^2) + O(x^2) + sage: R.create_summand('exact', growth=x^456, coefficient=123) + 123*x^456 + sage: R.create_summand('exact', data=12*x^13) + 12*x^13 + + TESTS:: + + sage: R.create_summand('exact', data='12*x^13') + 12*x^13 + sage: R.create_summand('exact', data='x^13 * 12') + 12*x^13 + sage: R.create_summand('exact', data='x^13') + x^13 + sage: R.create_summand('exact', data='12') + 12 + sage: R.create_summand('exact', data=12) + 12 + + :: + + sage: R.create_summand('O', growth=42*x^2, coefficient=1) + Traceback (most recent call last): + ... + ValueError: Growth 42*x^2 is not in O-Term Monoid x^ZZ with implicit coefficients in Integer Ring. + > *previous* ValueError: 42*x^2 is not in Growth Group x^ZZ. + + :: + + sage: AR. = AsymptoticRing('z^QQ', QQ) + sage: AR.create_summand('exact', growth='z^2') + Traceback (most recent call last): + ... + TypeError: Cannot create exact term: only 'growth' but + no 'coefficient' specified. + """ + from term_monoid import TermMonoid + TM = TermMonoid(type, asymptotic_ring=self) + + if data is None: + try: + data = kwds.pop('growth') + except KeyError: + raise TypeError("Neither 'data' nor 'growth' are specified.") + if type == 'exact' and kwds.get('coefficient') is None: + raise TypeError("Cannot create exact term: only 'growth' " + "but no 'coefficient' specified.") + + + if type == 'exact' and kwds.get('coefficient') == 0: + return self.zero() + + return self(TM(data, **kwds), simplify=False, convert=False) + + + def variable_names(self): + r""" + Return the names of the variables. + + OUTPUT: + + A tuple of strings. + + EXAMPLES:: + + sage: A = AsymptoticRing(growth_group='x^ZZ * QQ^y', coefficient_ring=QQ) + sage: A.variable_names() + ('x', 'y') + """ + return self.growth_group.variable_names() + + + def construction(self): + r""" + Return the construction of this asymptotic ring. + + OUTPUT: + + A pair whose first entry is an + :class:`asymptotic ring construction functor ` + and its second entry the coefficient ring. + + EXAMPLES:: + + sage: A = AsymptoticRing(growth_group='x^ZZ * QQ^y', coefficient_ring=QQ) + sage: A.construction() + (AsymptoticRing, Rational Field) + + .. SEEALSO:: + + :doc:`asymptotic_ring`, + :class:`AsymptoticRing`, + :class:`AsymptoticRingFunctor`. + """ + return AsymptoticRingFunctor(self.growth_group), self.coefficient_ring + + +from sage.categories.pushout import ConstructionFunctor +class AsymptoticRingFunctor(ConstructionFunctor): + r""" + A :class:`construction functor ` + for :class:`asymptotic rings `. + + INPUT: + + - ``growth_group`` -- a partially ordered group (see + :class:`AsymptoticRing` or + :doc:`growth_group` for details). + + EXAMPLES:: + + sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ).construction() # indirect doctest + (AsymptoticRing, Rational Field) + + .. SEEALSO:: + + :doc:`asymptotic_ring`, + :class:`AsymptoticRing`, + :class:`sage.rings.asymptotic.growth_group.AbstractGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.growth_group.ExponentialGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.growth_group.MonomialGrowthGroupFunctor`, + :class:`sage.categories.pushout.ConstructionFunctor`. + + TESTS:: + + sage: X = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: Y = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=QQ) + sage: cm = sage.structure.element.get_coercion_model() + sage: cm.record_exceptions() + sage: cm.common_parent(X, Y) + Asymptotic Ring over Rational Field + sage: sage.structure.element.coercion_traceback() # not tested + + :: + + sage: from sage.categories.pushout import pushout + sage: pushout(AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ), QQ) + Asymptotic Ring over Rational Field + """ + + rank = 13 + + + def __init__(self, growth_group): + r""" + See :class:`AsymptoticRingFunctor` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.asymptotic_ring import AsymptoticRingFunctor + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: AsymptoticRingFunctor(GrowthGroup('x^ZZ')) + AsymptoticRing + """ + self.growth_group = growth_group + + from sage.categories.rings import Rings + super(ConstructionFunctor, self).__init__( + Rings(), Rings()) + + + def _repr_(self): + r""" + Return a representation string of this functor. + + OUTPUT: + + A string. + + TESTS:: + + sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ).construction()[0] # indirect doctest + AsymptoticRing + """ + return 'AsymptoticRing<%s>' % (self.growth_group._repr_(condense=True),) + + + def _apply_functor(self, coefficient_ring): + r""" + Apply this functor to the given ``coefficient_ring``. + + INPUT: + + - ``base`` - anything :class:`~sage.rings.asymptotic.growth_group.MonomialGrowthGroup` accepts. + + OUTPUT: + + An :class:`AsymptoticRing`. + + EXAMPLES:: + + sage: A = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: F, C = A.construction() + sage: F(C) # indirect doctest + Asymptotic Ring over Rational Field + """ + return AsymptoticRing(growth_group=self.growth_group, + coefficient_ring=coefficient_ring) + + + def merge(self, other): + r""" + Merge this functor with ``other`` if possible. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A functor or ``None``. + + EXAMPLES:: + + sage: X = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: Y = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=QQ) + sage: F_X = X.construction()[0] + sage: F_Y = Y.construction()[0] + sage: F_X.merge(F_X) + AsymptoticRing + sage: F_X.merge(F_Y) + AsymptoticRing + """ + if self == other: + return self + + if isinstance(other, AsymptoticRingFunctor): + from sage.structure.element import get_coercion_model + cm = get_coercion_model() + try: + G = cm.common_parent(self.growth_group, other.growth_group) + except TypeError: + pass + else: + return AsymptoticRingFunctor(G) + + + def __eq__(self, other): + r""" + Return whether this functor is equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: X = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: Y = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=QQ) + sage: F_X = X.construction()[0] + sage: F_Y = Y.construction()[0] + sage: F_X == F_X + True + sage: F_X == F_Y + False + """ + return type(self) == type(other) and \ + self.growth_group == other.growth_group + + + def __ne__(self, other): + r""" + Return whether this functor is not equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: X = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: Y = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=QQ) + sage: F_X = X.construction()[0] + sage: F_Y = Y.construction()[0] + sage: F_X != F_X + False + sage: F_X != F_Y + True + """ + return not self.__eq__(other) diff --git a/src/sage/rings/asymptotic/growth_group.py b/src/sage/rings/asymptotic/growth_group.py index ba3c48032e7..ac2f3517f9c 100644 --- a/src/sage/rings/asymptotic/growth_group.py +++ b/src/sage/rings/asymptotic/growth_group.py @@ -1,28 +1,17 @@ r""" (Asymptotic) Growth Groups -This module adds support for (asymptotic) growth groups. Such groups -are equipped with a partial order: the elements can be seen as -functions, and their behavior as the argument(s) get large (tend to -`\infty`) is compared. +This module provides support for (asymptotic) growth groups. -Besides an abstract base class :class:`GenericGrowthGroup`, this module -contains concrete realizations of growth groups. At the moment there -is +Such groups are equipped with a partial order: the elements can be +seen as functions, and the behavior as their argument (or arguments) +gets large (tend to `\infty`) is compared. -- :class:`MonomialGrowthGroup` (whose elements are powers of a fixed symbol). +Growth groups are used for the calculations done in the +:doc:`asymptotic ring `. There, take a look at the +:ref:`informal definition `, where +examples of growth groups and elements are given as well. -More complex growth groups can be constructed via cartesian products -(to be implemented). - -These growth groups are used behind the scenes when performing -calculations in an asymptotic ring (to be implemented). - -AUTHORS: - -- Benjamin Hackl (2015-01): initial version -- Daniel Krenn (2015-05-29): initial version and review -- Benjamin Hackl (2015-07): short representation strings .. WARNING:: @@ -32,690 +21,2862 @@ TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ); G + sage: from sage.rings.asymptotic.growth_group import \ + ....: GenericGrowthGroup, GrowthGroup + sage: GenericGrowthGroup(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/17601 for details. Growth Group Generic(ZZ) - sage: G = agg.MonomialGrowthGroup(ZZ, 'x'); G + sage: GrowthGroup('x^ZZ * log(x)^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/17601 for details. - Growth Group x^ZZ -""" + Growth Group x^ZZ * log(x)^ZZ -#***************************************************************************** -# Copyright (C) 2014--2015 Benjamin Hackl -# 2014--2015 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 2 of the License, or -# (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +.. _growth_group_description: -import sage +Description of Growth Groups +============================ -def repr_short_to_parent(s): - r""" - Helper method for the growth group factory, which converts a short - representation string to a parent. +Many growth groups can be described by a string, which can also be used to +create them. For example, the string ``'x^QQ * log(x)^ZZ * QQ^y * y^QQ'`` +represents a growth group with the following properties: - INPUT: +- It is a growth group in the two variables `x` and `y`. - - ``s`` -- a string, short representation of a parent. +- Its elements are of the form - OUTPUT: + .. MATH:: - A parent. + x^r \cdot \log(x)^s \cdot a^y \cdot y^q - The possible short representations are shown in the examples below. + for `r\in\QQ`, `s\in\ZZ`, `a\in\QQ` and `q\in\QQ`. - EXAMPLES:: +- The order is with respect to `x\to\infty` and `y\to\infty` independently + of each other. - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.repr_short_to_parent('ZZ') - Integer Ring - sage: agg.repr_short_to_parent('QQ') - Rational Field - sage: agg.repr_short_to_parent('SR') - Symbolic Ring +- To compare such elements, they are split into parts belonging to + only one variable. In the example above, - TESTS:: + .. MATH:: - sage: agg.repr_short_to_parent('abcdef') - Traceback (most recent call last): - ... - ValueError: Cannot create a parent out of 'abcdef'. - """ - if s == 'ZZ': - return sage.rings.integer_ring.ZZ - elif s == 'QQ': - return sage.rings.rational_field.QQ - elif s == 'SR': - return sage.symbolic.ring.SR - else: - raise ValueError("Cannot create a parent out of '%s'." % (s,)) + x^{r_1} \cdot \log(x)^{s_1} \leq x^{r_2} \cdot \log(x)^{s_2} + if `(r_1, s_1) \leq (r_2, s_2)` lexicographically. This reflects the fact + that elements `x^r` are larger than elements `\log(x)^s` as `x\to\infty`. + The factors belonging to the variable `y` are compared analogously. -def parent_to_repr_short(P): - r""" - Helper method which generates a short(er) representation string - out of a parent. + The results of these comparisons are then put together using the + :wikipedia:`product order `, i.e., `\leq` if each component + satisfies `\leq`. - INPUT: - - ``P`` -- a parent. +Each description string consists of ordered factors---yes, this means +``*`` is noncommutative---of strings describing "elementary" growth +groups (see the examples below). As stated in the example above, these +factors are split by their variable; factors with the same variable are +grouped. Reading such factors from left to right determines the order: +Comparing elements of two factors (growth groups) `L` and `R`, then all +elements of `L` are considered to be larger than each element of `R`. - OUTPUT: - A string. +.. _growth_group_creating: - EXAMPLES:: +Creating a Growth Group +======================= - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.parent_to_repr_short(ZZ) - 'ZZ' - sage: agg.parent_to_repr_short(QQ) - 'QQ' - sage: agg.parent_to_repr_short(SR) - 'SR' - sage: agg.parent_to_repr_short(ZZ[x]) - '(Univariate Polynomial Ring in x over Integer Ring)' - """ - if P is sage.rings.integer_ring.ZZ: - return 'ZZ' - elif P is sage.rings.rational_field.QQ: - return 'QQ' - elif P is sage.symbolic.ring.SR: - return 'SR' - else: - rep = repr(P) - if ' ' in rep: - rep = '(' + rep + ')' - return rep +For many purposes the factory ``GrowthGroup`` (see +:class:`GrowthGroupFactory`) is the most convenient way to generate a +growth group. +:: + sage: from sage.rings.asymptotic.growth_group import GrowthGroup -class GenericGrowthElement(sage.structure.element.MultiplicativeGroupElement): - r""" - A basic implementation of a generic growth element. +Here are some examples:: - Growth elements form a group by multiplication, and (some of) the - elements can be compared to each other, i.e., all elements form a - poset. + sage: GrowthGroup('z^ZZ') + Growth Group z^ZZ + sage: M = GrowthGroup('z^QQ'); M + Growth Group z^QQ - INPUT: +Each of these two generated groups is a :class:`MonomialGrowthGroup`, +whose elements are powers of a fixed symbol (above ``'z'``). +For the order of the elements it is assumed that `z\to\infty`. - - ``parent`` -- a :class:`GenericGrowthGroup`. +.. NOTE:: - - ``raw_element`` -- an element from the base of the parent. + Growth groups where the variable tend to some value distinct from + `\infty` are not yet implemented. - EXAMPLES:: +To create elements of `M`, a generator can be used:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: g = agg.GenericGrowthElement(G, 42); g - GenericGrowthElement(42) - sage: g.parent() - Growth Group Generic(ZZ) - sage: G(raw_element=42) == g - True - """ + sage: z = M.gen() + sage: z^(3/5) + z^(3/5) - def __init__(self, parent, raw_element): - r""" - See :class:`GenericGrowthElement` for more information. +Strings can also be parsed:: - EXAMPLES:: + sage: M('z^7') + z^7 - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: G(raw_element=42) - GenericGrowthElement(42) +Similarly, we can construct logarithmic factors by:: - TESTS:: + sage: GrowthGroup('log(z)^QQ') + Growth Group log(z)^QQ - sage: G(raw_element=42).category() - Category of elements of Growth Group Generic(ZZ) +which again creates a +:class:`MonomialGrowthGroup`. An :class:`ExponentialGrowthGroup` is generated in the same way. Our factory gives +:: - :: + sage: E = GrowthGroup('QQ^z'); E + Growth Group QQ^z - sage: agg.GenericGrowthElement(None, 0) - Traceback (most recent call last): - ... - ValueError: The parent must be provided - """ - if parent is None: - raise ValueError('The parent must be provided') - super(GenericGrowthElement, self).__init__(parent=parent) +and a typical element looks like this:: - self._raw_element_ = parent.base()(raw_element) + sage: E.an_element() + (1/2)^z +More complex groups are created in a similar fashion. For example +:: - def _repr_(self): - r""" - A representation string for this generic element. + sage: C = GrowthGroup('QQ^z * z^QQ * log(z)^QQ'); C + Growth Group QQ^z * z^QQ * log(z)^QQ - INPUT: +This contains elements of the form +:: - Nothing. + sage: C.an_element() + (1/2)^z*z^(1/2)*log(z)^(1/2) - OUTPUT: +The group `C` itself is a cartesian product; to be precise a +:class:`~sage.rings.asymptotic.growth_group_cartesian.UnivariateProduct`. We +can see its factors:: - A string. + sage: C.cartesian_factors() + (Growth Group QQ^z, Growth Group z^QQ, Growth Group log(z)^QQ) - EXAMPLES:: +Multivariate constructions are also possible:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: G(raw_element=42) # indirect doctest - GenericGrowthElement(42) - """ - return 'GenericGrowthElement(%s)' % (self._raw_element_,) + sage: GrowthGroup('x^QQ * y^QQ') + Growth Group x^QQ * y^QQ +This gives a +:class:`~sage.rings.asymptotic.growth_group_cartesian.MultivariateProduct`. - def __hash__(self): - r""" - Return the hash of this element. +Both these cartesian products are derived from the class +:class:`~sage.rings.asymptotic.growth_group_cartesian.GenericProduct`. Moreover +all growth groups have the abstract base class +:class:`GenericGrowthGroup` in common. - INPUT: +Some Examples +^^^^^^^^^^^^^ - Nothing. +:: - OUTPUT: + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_x = GrowthGroup('x^ZZ'); G_x + Growth Group x^ZZ + sage: G_xy = GrowthGroup('x^ZZ * y^ZZ'); G_xy + Growth Group x^ZZ * y^ZZ + sage: G_xy.an_element() + x*y + sage: x = G_xy('x'); y = G_xy('y') + sage: x^2 + x^2 + sage: elem = x^21*y^21; elem^2 + x^42*y^42 - An integer. +A monomial growth group itself is totally ordered, all elements +are comparable. However, this does **not** hold for cartesian +products:: - EXAMPLES:: + sage: e1 = x^2*y; e2 = x*y^2 + sage: e1 <= e2 or e2 <= e1 + False - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ); - sage: hash(G(raw_element=42)) # random - 5656565656565656 - """ - return hash((self.parent(), self._raw_element_)) +In terms of uniqueness, we have the following behaviour:: + sage: GrowthGroup('x^ZZ * y^ZZ') is GrowthGroup('y^ZZ * x^ZZ') + True - def _mul_(self, other): - r""" - Abstract multiplication method for generic elements. +The above is ``True`` since the order of the factors does not play a role here; they use different variables. But when using the same variable, it plays a role:: - INPUT: + sage: GrowthGroup('x^ZZ * log(x)^ZZ') is GrowthGroup('log(x)^ZZ * x^ZZ') + False - - ``other`` -- a :class:`GenericGrowthElement`. +In this case the components are ordered lexicographically, which +means that in the second growth group, ``log(x)`` is assumed to +grow faster than ``x`` (which is nonsense, mathematically). See +:class:`CartesianProduct ` +for more details or see :ref:`above ` +for a more extensive description. - OUTPUT: +Short notation also allows the construction of more complicated +growth groups:: - A :class:`GenericGrowthElement` representing the product with - ``other``. + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^QQ * y^QQ') + sage: G.an_element() + (1/2)^x*x*log(x)^(1/2)*y^(1/2) + sage: x, y = var('x y') + sage: G(2^x * log(x) * y^(1/2)) * G(x^(-5) * 5^x * y^(1/3)) + 10^x*x^(-5)*log(x)*y^(5/6) - .. NOTE:: +AUTHORS: - Inherited classes must override this. +- Benjamin Hackl (2015) +- Daniel Krenn (2015) - EXAMPLES:: +ACKNOWLEDGEMENT: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: g = G.an_element() - sage: g * g - Traceback (most recent call last): - ... - NotImplementedError: Only implemented in concrete realizations. - """ - raise NotImplementedError('Only implemented in concrete realizations.') +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. +- Benjamin Hackl is supported by the Google Summer of Code 2015. - def _div_(self, other): - r""" - Divide this growth element by another one. +Classes and Methods +=================== +""" - INPUT: +#***************************************************************************** +# Copyright (C) 2014--2015 Benjamin Hackl +# 2014--2015 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 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** - - ``other`` -- an instance of :class:`GenericGrowthElement`. +import sage +from sage.misc.lazy_import import lazy_import +lazy_import('sage.rings.asymptotic.growth_group_cartesian', 'CartesianProductGrowthGroups') - OUTPUT: - An instance of :class:`GenericGrowthElement`. +class Variable(sage.structure.unique_representation.CachedRepresentation, + sage.structure.sage_object.SageObject): + r""" + A class managing the variable of a growth group. - .. NOTE:: + INPUT: - This method is called by the coercion framework, thus, it can be - assumed that this element is of the same type as ``other``. - The output will be of the same type as well. + - ``var`` -- an object whose representation string is used as the + variable. It has to be a valid Python identifier. ``var`` can + also be a tuple (or other iterable) of such objects. - EXAMPLES:: + - ``repr`` -- (default: ``None``) if specified, then this string + will be displayed instead of ``var``. Use this to get + e.g. ``log(x)^ZZ``: ``var`` is then used to specify the variable `x`. - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: e1 = P(raw_element=2) - sage: e2 = e1._div_(P.gen()); e2 - x - sage: e2 == e1 / P.gen() - True - """ - return self._mul_(~other) + - ``ignore`` -- (default: ``None``) a tuple (or other iterable) + of strings which are not variables. + TESTS:: - def __pow__(self, power): - r""" - Raises this growth element to the given ``power``. + sage: from sage.rings.asymptotic.growth_group import Variable + sage: v = Variable('x'); repr(v), v.variable_names() + ('x', ('x',)) + sage: v = Variable('x1'); repr(v), v.variable_names() + ('x1', ('x1',)) + sage: v = Variable('x_42'); repr(v), v.variable_names() + ('x_42', ('x_42',)) + sage: v = Variable(' x'); repr(v), v.variable_names() + ('x', ('x',)) + sage: v = Variable('x '); repr(v), v.variable_names() + ('x', ('x',)) + sage: v = Variable(''); repr(v), v.variable_names() + ('', ()) + + :: + + sage: v = Variable(('x', 'y')); repr(v), v.variable_names() + ('x, y', ('x', 'y')) + sage: v = Variable(('x', 'log(y)')); repr(v), v.variable_names() + ('x, log(y)', ('x', 'y')) + sage: v = Variable(('x', 'log(x)')); repr(v), v.variable_names() + Traceback (most recent call last): + ... + ValueError: Variable names ('x', 'x') are not pairwise distinct. - INPUT: + :: - - ``power`` -- a number. This can be anything that is a - valid right hand side of ``*`` with elements of the - parent's base. + sage: v = Variable('log(x)'); repr(v), v.variable_names() + ('log(x)', ('x',)) + sage: v = Variable('log(log(x))'); repr(v), v.variable_names() + ('log(log(x))', ('x',)) - OUTPUT: + :: - The result of this exponentiation. + sage: v = Variable('x', repr='log(x)'); repr(v), v.variable_names() + ('log(x)', ('x',)) - EXAMPLES:: + :: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: G.an_element()^7 + sage: v = Variable('e^x', ignore=('e',)); repr(v), v.variable_names() + ('e^x', ('x',)) + + :: + + sage: v = Variable('(e^n)', ignore=('e',)); repr(v), v.variable_names() + ('e^n', ('n',)) + sage: v = Variable('(e^(n*log(n)))', ignore=('e',)); repr(v), v.variable_names() + ('e^(n*log(n))', ('n',)) + """ + def __init__(self, var, repr=None, ignore=None): + r""" + See :class:`Variable` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('blub') + blub + sage: Variable('blub') is Variable('blub') + True + + :: + + sage: Variable('(:-)') Traceback (most recent call last): ... - NotImplementedError: Only implemented in concrete realizations. + TypeError: Malformed expression: : !!! - + sage: Variable('(:-)', repr='icecream') + Traceback (most recent call last): + ... + ValueError: ':-' is not a valid name for a variable. """ - raise NotImplementedError('Only implemented in concrete realizations.') + from sage.symbolic.ring import isidentifier + from misc import split_str_by_op + + if not isinstance(var, (list, tuple)): + var = (var,) + var = tuple(''.join(split_str_by_op(str(v), None)) for v in var) # we strip off parentheses + + if ignore is None: + ignore = tuple() + + if repr is None: + var_bases = tuple(i for i in sum(iter( + self.extract_variable_names(v) + if not isidentifier(v) else (v,) + for v in var), tuple()) if i not in ignore) + var_repr = ', '.join(var) + else: + for v in var: + if not isidentifier(v): + raise ValueError("'%s' is not a valid name for a variable." % (v,)) + var_bases = var + var_repr = str(repr).strip() + + if len(var_bases) != len(set(var_bases)): + raise ValueError('Variable names %s are not pairwise distinct.' % + (var_bases,)) + self.var_bases = var_bases + self.var_repr = var_repr + + + def __hash__(self): + r""" + Return the hash of this variable. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: hash(Variable('blub')) # random + -123456789 + """ + return hash((self.var_repr,) + self.var_bases) def __eq__(self, other): r""" - Return if this growth element is equal to ``other``. + Compare whether this variable equals ``other``. INPUT: - - ``other`` -- an element. + - ``other`` -- another variable. OUTPUT: A boolean. - .. NOTE:: + TESTS:: - This function uses the coercion model to find a common - parent for the two operands. + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x') == Variable('x') + True + sage: Variable('x') == Variable('y') + False + """ + return self.var_repr == other.var_repr and self.var_bases == other.var_bases - The comparison of two elements with the same parent is done in - :meth:`_eq_`. - EXAMPLES:: + def __ne__(self, other): + r""" + Return whether this variable does not equal ``other``. - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: G.an_element() == G.an_element() - True - sage: G(raw_element=42) == G(raw_element=7) - False + INPUT: - :: + - ``other`` -- another variable. - sage: G_ZZ = agg.GenericGrowthGroup(ZZ) - sage: G_QQ = agg.GenericGrowthGroup(QQ) - sage: G_ZZ(raw_element=1) == G_QQ(raw_element=1) - True + OUTPUT: - :: + A boolean. - sage: P_ZZ = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P_QQ = agg.MonomialGrowthGroup(QQ, 'x') - sage: P_ZZ.gen() == P_QQ.gen() - True - sage: ~P_ZZ.gen() == P_ZZ.gen() + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x') != Variable('x') False - sage: ~P_ZZ(1) == P_ZZ(1) + sage: Variable('x') != Variable('y') True """ - from sage.structure.element import have_same_parent - if have_same_parent(self, other): - return self._eq_(other) - - from sage.structure.element import get_coercion_model - import operator - try: - return get_coercion_model().bin_op(self, other, operator.eq) - except TypeError: - return False + return not self == other - def _eq_(self, other): + def _repr_(self): r""" - Return if this :class:`GenericGrowthElement` is equal to ``other``. + Return a representation string of this variable. - INPUT: + TESTS:: - - ``other`` -- a :class:`GenericGrowthElement`. + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('blub') # indirect doctest + blub + """ + return self.var_repr - OUTPUT: - A boolean. + def variable_names(self): + r""" + Return the names of the variables. - .. NOTE:: + OUTPUT: - This function compares two instances of - :class:`GenericGrowthElement`. + A tuple of strings. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: e1 = P(raw_element=1) - sage: e1._eq_(P.gen()) - True - sage: e2 = e1^4 - sage: e2 == e1^2 * e1 * e1 - True - sage: e2 == e1 - False + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x').variable_names() + ('x',) + sage: Variable('log(x)').variable_names() + ('x',) """ - return self._raw_element_ == other._raw_element_ + return self.var_bases - def __le__(self, other): + def is_monomial(self): r""" - Return if this growth element is at most (less than or equal - to) ``other``. - - INPUT: - - - ``other`` -- an element. + Return whether this is a monomial variable. OUTPUT: A boolean. - .. NOTE:: - - This function uses the coercion model to find a common - parent for the two operands. - - The comparison of two elements with the same parent is done in - :meth:`_le_`. - EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P_ZZ = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P_QQ = agg.MonomialGrowthGroup(QQ, 'x') - sage: P_ZZ.gen() <= P_QQ.gen()^2 - True - sage: ~P_ZZ.gen() <= P_ZZ.gen() + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x').is_monomial() True + sage: Variable('log(x)').is_monomial() + False """ - from sage.structure.element import have_same_parent - if have_same_parent(self, other): - return self._le_(other) - - from sage.structure.element import get_coercion_model - import operator - try: - return get_coercion_model().bin_op(self, other, operator.le) - except TypeError: - return False + return len(self.var_bases) == 1 and self.var_bases[0] == self.var_repr - def _le_(self, other): + @staticmethod + def extract_variable_names(s): r""" - Return if this :class:`GenericGrowthElement` is at most (less - than or equal to) ``other``. + Determine the name of the variable for the given string. INPUT: - - ``other`` -- a :class:`GenericGrowthElement`. + - ``s`` -- a string. + + OUTPUT: + + A tuple of strings. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable.extract_variable_names('') + () + sage: Variable.extract_variable_names('x') + ('x',) + sage: Variable.extract_variable_names('exp(x)') + ('x',) + sage: Variable.extract_variable_names('sin(cos(ln(x)))') + ('x',) + + :: + + sage: Variable.extract_variable_names('log(77w)') + ('w',) + sage: Variable.extract_variable_names('log(x') + Traceback (most recent call last): + .... + TypeError: Bad function call: log(x !!! + sage: Variable.extract_variable_names('x)') + Traceback (most recent call last): + .... + TypeError: Malformed expression: x) !!! + sage: Variable.extract_variable_names('log)x(') + Traceback (most recent call last): + .... + TypeError: Malformed expression: log) !!! x( + sage: Variable.extract_variable_names('log(x)+y') + ('x', 'y') + sage: Variable.extract_variable_names('icecream(summer)') + ('summer',) + + :: + + sage: Variable.extract_variable_names('a + b') + ('a', 'b') + sage: Variable.extract_variable_names('a+b') + ('a', 'b') + sage: Variable.extract_variable_names('a +b') + ('a', 'b') + sage: Variable.extract_variable_names('+a') + ('a',) + sage: Variable.extract_variable_names('a+') + Traceback (most recent call last): + ... + TypeError: Malformed expression: a+ !!! + sage: Variable.extract_variable_names('b!') + ('b',) + sage: Variable.extract_variable_names('-a') + ('a',) + sage: Variable.extract_variable_names('a*b') + ('a', 'b') + sage: Variable.extract_variable_names('2^q') + ('q',) + sage: Variable.extract_variable_names('77') + () + + :: + + sage: Variable.extract_variable_names('a + (b + c) + d') + ('a', 'b', 'c', 'd') + """ + from sage.symbolic.ring import SR + if s == '': + return () + return tuple(str(s) for s in SR(s).variables()) + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this variable. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x^2')._substitute_({'x': SR.var('z')}) + z^2 + sage: _.parent() + Symbolic Ring + + :: + + sage: Variable('1/x')._substitute_({'x': 'z'}) + Traceback (most recent call last): + ... + TypeError: Cannot substitute in 1/x in + . + > *previous* TypeError: unsupported operand parent(s) for '/': + 'Integer Ring' and '' + sage: Variable('1/x')._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in 1/x in + . + > *previous* ZeroDivisionError: rational division by zero + """ + from sage.misc.sage_eval import sage_eval + try: + return sage_eval(self.var_repr, locals=rules) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + +# The following function is used in the classes GenericGrowthElement and +# GenericProduct.Element as a method. +def _is_lt_one_(self): + r""" + Return whether this element is less than `1`. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ'); x = G(x) + sage: (x^42).is_lt_one() # indirect doctest + False + sage: (x^(-42)).is_lt_one() # indirect doctest + True + """ + one = self.parent().one() + return self <= one and self != one + + +# The following function is used in the classes GenericGrowthElement and +# GenericProduct.Element as a method. +def _log_(self, base=None): + r""" + Return the logarithm of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ') + sage: x, = G.gens_monomial() + sage: log(x) # indirect doctest + log(x) + sage: log(x^5) # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: When calculating log(x^5) a factor 5 != 1 appeared, + which is not contained in Growth Group x^ZZ * log(x)^ZZ. + + :: + + sage: G = GrowthGroup('QQ^x * x^ZZ') + sage: x, = G.gens_monomial() + sage: el = x.rpow(2); el + 2^x + sage: log(el) # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: When calculating log(2^x) a factor log(2) != 1 + appeared, which is not contained in Growth Group QQ^x * x^ZZ. + sage: log(el, base=2) # indirect doctest + x + + :: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: x = GenericGrowthGroup(ZZ).an_element() + sage: log(x) # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Cannot determine logarithmized factorization of + GenericGrowthElement(1) in abstract base class. + + :: + + sage: x = GrowthGroup('x^ZZ').an_element() + sage: log(x) # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Cannot build log(x) since log(x) is not in + Growth Group x^ZZ. + + TESTS:: + + sage: G = GrowthGroup("(e^x)^QQ * x^ZZ") + sage: x, = G.gens_monomial() + sage: log(exp(x)) # indirect doctest + x + sage: G.one().log() # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: log(1) is zero, which is not contained in + Growth Group (e^x)^QQ * x^ZZ. + + :: + + sage: G = GrowthGroup("(e^x)^ZZ * x^ZZ") + sage: x, = G.gens_monomial() + sage: log(exp(x)) # indirect doctest + x + sage: G.one().log() # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: log(1) is zero, which is not contained in + Growth Group (e^x)^ZZ * x^ZZ. + + :: + + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ') + sage: x, y = G.gens_monomial() + sage: (x * y).log() # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Calculating log(x*y) results in a sum, + which is not contained in + Growth Group QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ. + """ + from misc import log_string + + log_factor = self.log_factor(base=base) + if not log_factor: + raise ArithmeticError('%s is zero, ' + 'which is not contained in %s.' % + (log_string(self, base), self.parent())) + + if len(log_factor) != 1: + raise ArithmeticError('Calculating %s results in a sum, ' + 'which is not contained in %s.' % + (log_string(self, base), self.parent())) + g, c = log_factor[0] + if c != 1: + raise ArithmeticError('When calculating %s a factor %s != 1 ' + 'appeared, which is not contained in %s.' % + (log_string(self, base), c, self.parent())) + return g + + +# The following function is used in the classes GenericGrowthElement and +# GenericProduct.Element as a method. +def _log_factor_(self, base=None): + r""" + Return the logarithm of the factorization of this + element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is a growth + element and the second a multiplicative coefficient. + + ALGORITHM: + + This function factors the given element and calculates + the logarithm of each of these factors. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ') + sage: x, y = G.gens_monomial() + sage: (x * y).log_factor() # indirect doctest + ((log(x), 1), (log(y), 1)) + sage: (x^123).log_factor() # indirect doctest + ((log(x), 123),) + sage: (G('2^x') * x^2).log_factor(base=2) # indirect doctest + ((x, 1), (log(x), 2/log(2))) + + :: + + sage: G(1).log_factor() + () + + :: + + sage: log(x).log_factor() # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Cannot build log(log(x)) since log(log(x)) is + not in Growth Group QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ. + + .. SEEALSO:: + + :meth:`~sage.rings.asymptotic.growth_group.GenericGrowthElement.factors`, + :meth:`~sage.rings.asymptotic.growth_group.GenericGrowthElement.log`. + + TESTS:: + + sage: G = GrowthGroup("(e^x)^ZZ * x^ZZ * log(x)^ZZ") + sage: x, = G.gens_monomial() + sage: (exp(x) * x).log_factor() # indirect doctest + ((x, 1), (log(x), 1)) + """ + log_factor = self._log_factor_(base=base) + + for g, c in log_factor: + if hasattr(g, 'parent') and \ + isinstance(g.parent(), GenericGrowthGroup): + continue + from misc import log_string + raise ArithmeticError('Cannot build %s since %s ' + 'is not in %s.' % (log_string(self, base), + g, self.parent())) + + return log_factor + + +# The following function is used in the classes GenericGrowthElement and +# GenericProduct.Element as a method. +def _rpow_(self, base): + r""" + Calculate the power of ``base`` to this element. + + INPUT: + + - ``base`` -- an element. + + OUTPUT: + + A growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x * x^ZZ') + sage: x = G('x') + sage: x.rpow(2) # indirect doctest + 2^x + sage: x.rpow(1/2) # indirect doctest + (1/2)^x + + :: + + sage: x.rpow(0) # indirect doctest + Traceback (most recent call last): + ... + ValueError: 0 is not an allowed base for calculating the power to x. + sage: (x^2).rpow(2) # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Cannot construct 2^(x^2) in Growth Group QQ^x * x^ZZ + > *previous* TypeError: unsupported operand parent(s) for '*': + 'Growth Group QQ^x * x^ZZ' and 'Growth Group ZZ^(x^2)' + + :: + + sage: G = GrowthGroup('QQ^(x*log(x)) * x^ZZ * log(x)^ZZ') + sage: x = G('x') + sage: (x * log(x)).rpow(2) # indirect doctest + 2^(x*log(x)) + """ + if base == 0: + raise ValueError('%s is not an allowed base for calculating the ' + 'power to %s.' % (base, self)) + + var = str(self) + + try: + element = self._rpow_element_(base) + except ValueError: + if base == 'e': + from sage.rings.integer_ring import ZZ + from misc import repr_op + M = MonomialGrowthGroup(ZZ, repr_op('e', '^', var), + ignore_variables=('e',)) + element = M(raw_element=ZZ(1)) + else: + E = ExponentialGrowthGroup(base.parent(), var) + element = E(raw_element=base) + + try: + return self.parent().one() * element + except (TypeError, ValueError) as e: + from misc import combine_exceptions, repr_op + raise combine_exceptions( + ArithmeticError('Cannot construct %s in %s' % + (repr_op(base, '^', var), self.parent())), e) + + +class GenericGrowthElement(sage.structure.element.MultiplicativeGroupElement): + r""" + A basic implementation of a generic growth element. + + Growth elements form a group by multiplication, and (some of) the + elements can be compared to each other, i.e., all elements form a + poset. + + INPUT: + + - ``parent`` -- a :class:`GenericGrowthGroup`. + + - ``raw_element`` -- an element from the base of the parent. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import (GenericGrowthGroup, + ....: GenericGrowthElement) + sage: G = GenericGrowthGroup(ZZ) + sage: g = GenericGrowthElement(G, 42); g + GenericGrowthElement(42) + sage: g.parent() + Growth Group Generic(ZZ) + sage: G(raw_element=42) == g + True + """ + + def __init__(self, parent, raw_element): + r""" + See :class:`GenericGrowthElement` for more information. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G(raw_element=42) + GenericGrowthElement(42) + + TESTS:: + + sage: G(raw_element=42).category() + Category of elements of Growth Group Generic(ZZ) + + :: + + sage: G = GenericGrowthGroup(ZZ) + sage: G(raw_element=42).category() + Category of elements of Growth Group Generic(ZZ) + + :: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthElement + sage: GenericGrowthElement(None, 0) + Traceback (most recent call last): + ... + ValueError: The parent must be provided + """ + if parent is None: + raise ValueError('The parent must be provided') + super(GenericGrowthElement, self).__init__(parent=parent) + + self._raw_element_ = parent.base()(raw_element) + + + def _repr_(self): + r""" + A representation string for this generic element. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G(raw_element=42) # indirect doctest + GenericGrowthElement(42) + sage: H = GenericGrowthGroup(ZZ, 'h') + sage: H(raw_element=42) # indirect doctest + GenericGrowthElement(42, h) + """ + vars = ', '.join(self.parent()._var_.variable_names()) + if vars: + vars = ', ' + vars + return 'GenericGrowthElement(%s%s)' % (self._raw_element_, vars) + + + def __hash__(self): + r""" + Return the hash of this element. + + INPUT: + + Nothing. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ); + sage: hash(G(raw_element=42)) # random + 5656565656565656 + """ + return hash((self.parent(), self._raw_element_)) + + + def _mul_(self, other): + r""" + Abstract multiplication method for generic elements. + + INPUT: + + - ``other`` -- a :class:`GenericGrowthElement`. + + OUTPUT: + + A :class:`GenericGrowthElement` representing the product with + ``other``. + + .. NOTE:: + + Inherited classes must override this. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: g = G.an_element() + sage: g*g + Traceback (most recent call last): + ... + NotImplementedError: Only implemented in concrete realizations. + """ + raise NotImplementedError('Only implemented in concrete realizations.') + + + def __invert__(self): + r""" + Return the inverse of this growth element. + + OUTPUT: + + An instance of :class:`GenericGrowthElement`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: ~G.an_element() + Traceback (most recent call last): + ... + NotImplementedError: Inversion of GenericGrowthElement(1) not implemented + (in this abstract method). + sage: G.an_element()^7 + Traceback (most recent call last): + ... + NotImplementedError: Only implemented in concrete realizations. + sage: P = GrowthGroup('x^ZZ') + sage: ~P.an_element() + x^(-1) + """ + raise NotImplementedError('Inversion of %s not implemented ' + '(in this abstract method).' % (self,)) + + + def __eq__(self, other): + r""" + Return whether this growth element is equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + The comparison of two elements with the same parent is done in + :meth:`_eq_`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G.an_element() == G.an_element() + True + sage: G(raw_element=42) == G(raw_element=7) + False + + :: + + sage: G_ZZ = GenericGrowthGroup(ZZ) + sage: G_QQ = GenericGrowthGroup(QQ) + sage: G_ZZ(raw_element=1) == G_QQ(raw_element=1) + True + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_ZZ = GrowthGroup('x^ZZ') + sage: P_QQ = GrowthGroup('x^QQ') + sage: P_ZZ.gen() == P_QQ.gen() + True + sage: ~P_ZZ.gen() == P_ZZ.gen() + False + sage: ~P_ZZ(1) == P_ZZ(1) + True + """ + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._eq_(other) + + from sage.structure.element import get_coercion_model + import operator + try: + return get_coercion_model().bin_op(self, other, operator.eq) + except TypeError: + return False + + + def _eq_(self, other): + r""" + Return whether this :class:`GenericGrowthElement` is equal to ``other``. + + INPUT: + + - ``other`` -- a :class:`GenericGrowthElement`. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function compares two instances of + :class:`GenericGrowthElement`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: e1 = P(raw_element=1) + sage: e1._eq_(P.gen()) + True + sage: e2 = e1^4 + sage: e2 == e1^2*e1*e1 + True + sage: e2 == e1 + False + """ + return self._raw_element_ == other._raw_element_ + + + def __ne__(self, other): + r""" + Return whether this growth element is not equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: G.one() != G(1) + False + sage: G.one() != G.one() + False + sage: G(1) != G(1) + False + """ + return not self == other + + + def __le__(self, other): + r""" + Return whether this growth element is at most (less than or equal + to) ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + The comparison of two elements with the same parent is done in + :meth:`_le_`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_ZZ = GrowthGroup('x^ZZ') + sage: P_QQ = GrowthGroup('x^QQ') + sage: P_ZZ.gen() <= P_QQ.gen()^2 + True + sage: ~P_ZZ.gen() <= P_ZZ.gen() + True + """ + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._le_(other) + + from sage.structure.element import get_coercion_model + import operator + try: + return get_coercion_model().bin_op(self, other, operator.le) + except TypeError: + return False + + + def _le_(self, other): + r""" + Return whether this :class:`GenericGrowthElement` is at most (less + than or equal to) ``other``. + + INPUT: + + - ``other`` -- a :class:`GenericGrowthElement`. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function compares two instances of + :class:`GenericGrowthElement`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: e1 = G(raw_element=1); e2 = G(raw_element=2) + sage: e1 <= e2 # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Only implemented in concrete realizations. + """ + raise NotImplementedError('Only implemented in concrete realizations.') + + + log = _log_ + log_factor = _log_factor_ + + + def _log_factor_(self, base=None): + r""" + Helper method for calculating the logarithm of the factorization + of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is either a growth + element or something out of which we can construct a growth element + and the second a multiplicative coefficient. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(QQ) + sage: G.an_element().log_factor() # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Cannot determine logarithmized factorization of + GenericGrowthElement(1/2) in abstract base class. + """ + raise NotImplementedError('Cannot determine logarithmized factorization ' + 'of %s in abstract base class.' % (self,)) + + + rpow = _rpow_ + + + def _rpow_element_(self, base): + r""" + Return an element which is the power of ``base`` to this + element. + + INPUT: + + - ``base`` -- an element. + + OUTPUT: + + Nothing since a ``ValueError`` is raised in this generic method. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x') + sage: x = G(raw_element=3) + sage: x._rpow_element_(2) is None + Traceback (most recent call last): + ... + ValueError: Cannot compute 2 to the generic element 3^x. + """ + raise ValueError('Cannot compute %s to the generic element %s.' % + (base, self)) + + + def factors(self): + r""" + Return the atomic factors of this growth element. An atomic factor + cannot be split further. + + INPUT: + + Nothing. + + OUTPUT: + + A tuple of growth elements. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: G.an_element().factors() + (x,) + """ + return (self,) + + + is_lt_one = _is_lt_one_ + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this generic growth element. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G(raw_element=42)._substitute_({}) + Traceback (most recent call last): + ... + TypeError: Cannot substitute in GenericGrowthElement(42) in + Growth Group Generic(ZZ). + > *previous* TypeError: Cannot substitute in the abstract base class + Growth Group Generic(ZZ). + """ + from misc import substitute_raise_exception + substitute_raise_exception(self, TypeError( + 'Cannot substitute in the abstract ' + 'base class %s.' % (self.parent(),))) + + +class GenericGrowthGroup( + sage.structure.unique_representation.UniqueRepresentation, + sage.structure.parent.Parent): + r""" + A basic implementation for growth groups. + + INPUT: + + - ``base`` -- one of SageMath's parents, out of which the elements + get their data (``raw_element``). + + - ``category`` -- (default: ``None``) the category of the newly + created growth group. It has to be a subcategory of ``Join of + Category of groups and Category of posets``. This is also the + default category if ``None`` is specified. + + - ``ignore_variables`` -- (default: ``None``) a tuple (or other + iterable) of strings. The specified names are not considered as + variables. + + .. NOTE:: + + This class should be derived for concrete implementations. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ); G + Growth Group Generic(ZZ) + + .. SEEALSO:: + + :class:`MonomialGrowthGroup`, + :class:`ExponentialGrowthGroup` + """ + # TODO: implement some sort of 'assume', where basic assumptions + # for the variables can be stored. --> within the cartesian product + + # enable the category framework for elements + Element = GenericGrowthElement + + + # set everything up to determine category + from sage.categories.sets_cat import Sets + from sage.categories.posets import Posets + from sage.categories.magmas import Magmas + from sage.categories.additive_magmas import AdditiveMagmas + + _determine_category_subcategory_mapping_ = [ + (Sets(), Sets(), True), + (Posets(), Posets(), False)] + + _determine_category_axiom_mapping_ = [] + + + @staticmethod + def __classcall__(cls, base, var=None, category=None, ignore_variables=None): + r""" + Normalizes the input in order to ensure a unique + representation. + + For more information see :class:`GenericGrowthGroup`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: P1 = MonomialGrowthGroup(ZZ, 'x') + sage: P2 = MonomialGrowthGroup(ZZ, ZZ['x'].gen()) + sage: P3 = MonomialGrowthGroup(ZZ, SR.var('x')) + sage: P1 is P2 and P2 is P3 + True + sage: P4 = MonomialGrowthGroup(ZZ, buffer('xylophone', 0, 1)) + sage: P1 is P4 + True + sage: P5 = MonomialGrowthGroup(ZZ, 'x ') + sage: P1 is P5 + True + + :: + + sage: L1 = MonomialGrowthGroup(QQ, log(x)) + sage: L2 = MonomialGrowthGroup(QQ, 'log(x)') + sage: L1 is L2 + True + + Test determining of the category (GenericGrowthGroup):: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(ZZ, 'x').category() # indirect doctest + Category of posets + sage: GenericGrowthGroup(ZZ, 'x', category=Groups()).category() # indirect doctest + Category of groups + + Test determining of the category (MonomialGrowthGroup):: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: MonomialGrowthGroup(ZZ, 'x').category() # indirect doctest + Join of Category of commutative groups and Category of posets + sage: MonomialGrowthGroup(ZZ, 'x', category=Monoids()).category() # indirect doctest + Category of monoids + sage: W = Words([0, 1]) + sage: W.category() + Category of sets + sage: MonomialGrowthGroup(W, 'x').category() # indirect doctest + Category of sets + + Test determining of the category (ExponentialGrowthGroup):: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: ExponentialGrowthGroup(ZZ, 'x').category() # indirect doctest + Join of Category of commutative monoids and Category of posets + sage: ExponentialGrowthGroup(QQ, 'x').category() # indirect doctest + Join of Category of commutative groups and Category of posets + sage: ExponentialGrowthGroup(ZZ, 'x', category=Groups()).category() # indirect doctest + Category of groups + sage: ExponentialGrowthGroup(QQ, 'x', category=Monoids()).category() # indirect doctest + Category of monoids + """ + if not isinstance(base, sage.structure.parent.Parent): + raise TypeError('%s is not a valid base.' % (base,)) + + if var is None: + var = Variable('') + elif not isinstance(var, Variable): + var = Variable(var, ignore=ignore_variables) + + from sage.categories.posets import Posets + if category is None: + # The following block can be removed once #19269 is fixed. + from sage.rings.integer_ring import ZZ + from sage.rings.rational_field import QQ + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if base is ZZ or base is QQ or \ + is_PolynomialRing(base) and \ + (base.base_ring() is ZZ or base.base_ring() is QQ): + initial_category = Posets() + else: + initial_category = None + + from misc import transform_category + category = transform_category( + base.category(), + cls._determine_category_subcategory_mapping_, + cls._determine_category_axiom_mapping_, + initial_category=initial_category) + + return super(GenericGrowthGroup, cls).__classcall__( + cls, base, var, category) + + + @sage.misc.superseded.experimental(trac_number=17601) + def __init__(self, base, var, category): + r""" + See :class:`GenericGrowthElement` for more information. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(ZZ).category() + Category of posets + + :: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: MonomialGrowthGroup(ZZ, 'x') + Growth Group x^ZZ + sage: MonomialGrowthGroup(QQ, SR.var('n')) + Growth Group n^QQ + sage: MonomialGrowthGroup(ZZ, ZZ['y'].gen()) + Growth Group y^ZZ + sage: MonomialGrowthGroup(QQ, 'log(x)') + Growth Group log(x)^QQ + + :: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: ExponentialGrowthGroup(QQ, 'x') + Growth Group QQ^x + sage: ExponentialGrowthGroup(SR, ZZ['y'].gen()) + Growth Group SR^y + + TESTS:: + + sage: G = GenericGrowthGroup(ZZ) + sage: G.is_parent_of(G(raw_element=42)) + True + sage: G2 = GenericGrowthGroup(ZZ, category=FiniteGroups() & Posets()) + sage: G2.category() + Join of Category of finite groups and Category of finite posets + + :: + + sage: G = GenericGrowthGroup('42') + Traceback (most recent call last): + ... + TypeError: 42 is not a valid base. + + :: + + sage: MonomialGrowthGroup('x', ZZ) + Traceback (most recent call last): + ... + TypeError: x is not a valid base. + sage: MonomialGrowthGroup('x', 'y') + Traceback (most recent call last): + ... + TypeError: x is not a valid base. + + :: + + sage: ExponentialGrowthGroup('x', ZZ) + Traceback (most recent call last): + ... + TypeError: x is not a valid base. + sage: ExponentialGrowthGroup('x', 'y') + Traceback (most recent call last): + ... + TypeError: x is not a valid base. + + """ + self._var_ = var + super(GenericGrowthGroup, self).__init__(category=category, + base=base) + + + def _repr_short_(self): + r""" + A short representation string of this abstract growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(QQ)._repr_short_() + 'Generic(QQ)' + sage: GenericGrowthGroup(QQ) + Growth Group Generic(QQ) + sage: GenericGrowthGroup(QQ, ('a', 'b')) + Growth Group Generic(QQ, a, b) + """ + from misc import parent_to_repr_short + vars = ', '.join(self._var_.variable_names()) + if vars: + vars = ', ' + vars + return 'Generic(%s%s)' % (parent_to_repr_short(self.base()), vars) + + + def _repr_(self, condense=False): + r""" + A representations string of this growth group. + + INPUT: + + - ``condense`` -- (default: ``False``) if set, then a shorter + output is returned, e.g. the prefix-string ``Growth Group`` + is not show in this case. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ') # indirect doctest + Growth Group x^ZZ + sage: GrowthGroup('log(x)^QQ') # indirect doctest + Growth Group log(x)^QQ + + TESTS:: + + sage: GrowthGroup('log(x)^QQ')._repr_(condense=True) + 'log(x)^QQ' + """ + pre = 'Growth Group ' if not condense else '' + return '%s%s' % (pre, self._repr_short_()) + + + def __hash__(self): + r""" + Return the hash of this group. + + INPUT: + + Nothing. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import (GenericGrowthGroup, + ....: GrowthGroup) + sage: hash(GenericGrowthGroup(ZZ)) # random + 4242424242424242 + + :: + + sage: P = GrowthGroup('x^ZZ') + sage: hash(P) # random + -1234567890123456789 + + :: + + sage: P = GrowthGroup('QQ^x') + sage: hash(P) # random + -1234567890123456789 + """ + return hash((self.__class__, self.base(), self._var_)) + + + def _an_element_(self): + r""" + Return an element of ``self``. + + INPUT: + + Nothing. + + OUTPUT: + + An element of ``self``. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import (GenericGrowthGroup, + ....: GrowthGroup) + sage: GenericGrowthGroup(ZZ).an_element() # indirect doctest + GenericGrowthElement(1) + sage: GrowthGroup('z^ZZ').an_element() # indirect doctest + z + sage: GrowthGroup('log(z)^QQ').an_element() # indirect doctest + log(z)^(1/2) + sage: GrowthGroup('QQ^(x*log(x))').an_element() # indirect doctest + (1/2)^(x*log(x)) + """ + return self.element_class(self, self.base().an_element()) + + + def some_elements(self): + r""" + Return some elements of this growth group. + + See :class:`TestSuite` for a typical use case. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: tuple(GrowthGroup('z^ZZ').some_elements()) + (1, z, z^(-1), z^2, z^(-2), z^3, z^(-3), + z^4, z^(-4), z^5, z^(-5), ...) + sage: tuple(GrowthGroup('z^QQ').some_elements()) + (z^(1/2), z^(-1/2), z^2, z^(-2), + 1, z, z^(-1), z^42, + z^(2/3), z^(-2/3), z^(3/2), z^(-3/2), + z^(4/5), z^(-4/5), z^(5/4), z^(-5/4), ...) + """ + return iter(self.element_class(self, e) + for e in self.base().some_elements()) + + + def _create_element_in_extension_(self, raw_element): + r""" + Create an element in an extension of this growth group which + is chosen according to the input ``raw_element``. + + INPUT: + + - ``raw_element`` -- the element data. + + OUTPUT: + + An element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: G._create_element_in_extension_(3).parent() + Growth Group z^ZZ + sage: G._create_element_in_extension_(1/2).parent() + Growth Group z^QQ + """ + if raw_element.parent() is self.base(): + parent = self + else: + from misc import underlying_class + parent = underlying_class(self)(raw_element.parent(), self._var_, + category=self.category()) + return parent(raw_element=raw_element) + + + def le(self, left, right): + r""" + Return whether the growth of ``left`` is at most (less than or + equal to) the growth of ``right``. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: x = G.gen() + sage: G.le(x, x^2) + True + sage: G.le(x^2, x) + False + sage: G.le(x^0, 1) + True + """ + return self(left) <= self(right) + + + def _element_constructor_(self, data, raw_element=None): + r""" + Convert a given object to this growth group. + + INPUT: + + - ``data`` -- an object representing the element to be + initialized. + + - ``raw_element`` -- (default: ``None``) if given, then this is + directly passed to the element constructor (i.e., no conversion + is performed). + + OUTPUT: + + An element of this growth group. + + .. NOTE:: + + Either ``data`` or ``raw_element`` has to be given. If + ``raw_element`` is specified, then no positional argument + may be passed. + + This method calls :meth:`_convert_`, which does the actual + conversion from ``data``. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G_ZZ = GenericGrowthGroup(ZZ) + sage: z = G_ZZ(raw_element=42); z # indirect doctest + GenericGrowthElement(42) + sage: z is G_ZZ(z) # indirect doctest + True + + :: + + sage: G_QQ = GenericGrowthGroup(QQ) + sage: q = G_QQ(raw_element=42) # indirect doctest + sage: q is z + False + sage: G_ZZ(q) # indirect doctest + GenericGrowthElement(42) + sage: G_QQ(z) # indirect doctest + GenericGrowthElement(42) + sage: q is G_ZZ(q) # indirect doctest + False + + :: + + sage: G_ZZ() + Traceback (most recent call last): + ... + ValueError: No input specified. Cannot continue. + sage: G_ZZ('blub') # indirect doctest + Traceback (most recent call last): + ... + ValueError: blub is not in Growth Group Generic(ZZ). + sage: G_ZZ('x', raw_element=42) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Input is ambigous: x as well as raw_element=42 are specified. + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: x = GrowthGroup('x^ZZ')(raw_element=1) # indirect doctest + sage: G_y = GrowthGroup('y^ZZ') + sage: G_y(x) # indirect doctest + Traceback (most recent call last): + ... + ValueError: x is not in Growth Group y^ZZ. + + :: + + sage: GrowthGroup('QQ^x')(GrowthGroup('ZZ^x')('2^x')) + 2^x + """ + from misc import underlying_class, combine_exceptions + + if raw_element is None: + if isinstance(data, int) and data == 0: + raise ValueError('No input specified. Cannot continue.') + + elif isinstance(data, self.element_class): + if data.parent() == self: + return data + if self._var_ != data.parent()._var_: + raise ValueError('%s is not in %s.' % (data, self)) + raw_element = data._raw_element_ + + elif isinstance(data, self.Element): + if self._var_ == data.parent()._var_: + try: + raw_element = self.base()(data._raw_element_) + except (TypeError, ValueError) as e: + raise combine_exceptions( + ValueError('%s is not in %s.' % (data, self)), e) + + elif isinstance(data, GenericGrowthElement): + if data.is_one(): + return self.one() + + else: + raw_element = self._convert_(data) + + if raw_element is None: + raise ValueError('%s is not in %s.' % (data, self)) + elif not isinstance(data, int) or data != 0: + raise ValueError('Input is ambigous: ' + '%s as well as raw_element=%s ' + 'are specified.' % (data, raw_element)) + + return self.element_class(self, raw_element) + + + def _convert_(self, data): + r""" + Convert ``data`` to something the constructor of the + element class accepts (``raw_element``). + + INPUT: + + - ``data`` -- an object. + + OUTPUT: + + An element of the base ring or ``None`` (when no such element + can be constructed). + + .. NOTE:: + + This method always returns ``None`` in this abstract base + class, and should be overridden in inherited class. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G._convert_('icecream') is None + True + """ + pass + + + def _coerce_map_from_(self, S): + r""" + Return whether ``S`` coerces into this growth group. + + INPUT: + + - ``S`` -- a parent. OUTPUT: A boolean. + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: G_QQ = GrowthGroup('x^QQ') + sage: G_ZZ.has_coerce_map_from(G_QQ) # indirect doctest + False + sage: G_QQ.has_coerce_map_from(G_ZZ) # indirect doctest + True + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_x_ZZ = GrowthGroup('x^ZZ') + sage: P_x_QQ = GrowthGroup('x^QQ') + sage: P_x_ZZ.has_coerce_map_from(P_x_QQ) # indirect doctest + False + sage: P_x_QQ.has_coerce_map_from(P_x_ZZ) # indirect doctest + True + sage: P_y_ZZ = GrowthGroup('y^ZZ') + sage: P_y_ZZ.has_coerce_map_from(P_x_ZZ) # indirect doctest + False + sage: P_x_ZZ.has_coerce_map_from(P_y_ZZ) # indirect doctest + False + sage: P_y_ZZ.has_coerce_map_from(P_x_QQ) # indirect doctest + False + sage: P_x_QQ.has_coerce_map_from(P_y_ZZ) # indirect doctest + False + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_x_ZZ = GrowthGroup('ZZ^x') + sage: P_x_QQ = GrowthGroup('QQ^x') + sage: P_x_ZZ.has_coerce_map_from(P_x_QQ) # indirect doctest + False + sage: P_x_QQ.has_coerce_map_from(P_x_ZZ) # indirect doctest + True + sage: P_y_ZZ = GrowthGroup('ZZ^y') + sage: P_y_ZZ.has_coerce_map_from(P_x_ZZ) # indirect doctest + False + sage: P_x_ZZ.has_coerce_map_from(P_y_ZZ) # indirect doctest + False + sage: P_y_ZZ.has_coerce_map_from(P_x_QQ) # indirect doctest + False + sage: P_x_QQ.has_coerce_map_from(P_y_ZZ) # indirect doctest + False + + :: + + sage: GrowthGroup('x^QQ').has_coerce_map_from(GrowthGroup('QQ^x')) # indirect doctest + False + """ + from misc import underlying_class + if isinstance(S, underlying_class(self)) and self._var_ == S._var_: + if self.base().has_coerce_map_from(S.base()): + return True + + + def _pushout_(self, other): + r""" + Construct the pushout of this and the other growth group. This is called by + :func:`sage.categories.pushout.pushout`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.categories.pushout import pushout + sage: cm = sage.structure.element.get_coercion_model() + sage: A = GrowthGroup('QQ^x') + sage: B = GrowthGroup('y^ZZ') + + When using growth groups with disjoint variable lists, then a + pushout can be constructed:: + + sage: A._pushout_(B) + Growth Group QQ^x * y^ZZ + sage: cm.common_parent(A, B) + Growth Group QQ^x * y^ZZ + + In general, growth groups of the same variable cannot be + combined automatically, since there is no order relation between the two factors:: + + sage: C = GrowthGroup('x^QQ') + sage: cm.common_parent(A, C) + Traceback (most recent call last): + ... + TypeError: no common canonical parent for objects with parents: + 'Growth Group QQ^x' and 'Growth Group x^QQ' + + However, combining is possible if the factors with the same variable + overlap:: + + sage: cm.common_parent(GrowthGroup('x^ZZ * log(x)^ZZ'), GrowthGroup('exp(x)^ZZ * x^ZZ')) + Growth Group exp(x)^ZZ * x^ZZ * log(x)^ZZ + sage: cm.common_parent(GrowthGroup('x^ZZ * log(x)^ZZ'), GrowthGroup('y^ZZ * x^ZZ')) + Growth Group x^ZZ * log(x)^ZZ * y^ZZ + + :: + + sage: cm.common_parent(GrowthGroup('x^ZZ'), GrowthGroup('y^ZZ')) + Growth Group x^ZZ * y^ZZ + + :: + + sage: cm.record_exceptions() + sage: cm.common_parent(GrowthGroup('x^ZZ'), GrowthGroup('y^ZZ')) + Growth Group x^ZZ * y^ZZ + sage: sage.structure.element.coercion_traceback() # not tested + """ + if not isinstance(other, GenericGrowthGroup) and \ + not (other.construction() is not None and + isinstance(other.construction()[0], AbstractGrowthGroupFunctor)): + return + + if set(self.variable_names()).isdisjoint(set(other.variable_names())): + from sage.categories.cartesian_product import cartesian_product + return cartesian_product([self, other]) + + + def gens_monomial(self): + r""" + Return a tuple containing monomial generators of this growth + group. + + INPUT: + + Nothing. + + OUTPUT: + + An empty tuple. + .. NOTE:: - This function compares two instances of - :class:`GenericGrowthElement`. + A generator is called monomial generator if the variable + of the underlying growth group is a valid identifier. For + example, ``x^ZZ`` has ``x`` as a monomial generator, + while ``log(x)^ZZ`` or ``icecream(x)^ZZ`` do not have + monomial generators. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(ZZ).gens_monomial() + () + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^QQ').gens_monomial() + (x,) + sage: GrowthGroup('QQ^x').gens_monomial() + () + """ + return tuple() + + + def gens(self): + r""" + Return a tuple of all generators of this growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A tuple whose entries are growth elements. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P.gens() + (x,) + sage: GrowthGroup('log(x)^ZZ').gens() + (log(x),) + """ + return (self(raw_element=self.base().one()),) + + + def gen(self, n=0): + r""" + Return the `n`-th generator (as a group) of this growth group. + + INPUT: + + - ``n`` -- default: `0`. + + OUTPUT: + + A :class:`MonomialGrowthElement`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P.gen() + x + + :: + + sage: P = GrowthGroup('QQ^x') + sage: P.gen() + Traceback (most recent call last): + ... + IndexError: tuple index out of range + """ + return self.gens()[n] + + + def ngens(self): + r""" + Return the number of generators (as a group) of this growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A Python integer. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P.ngens() + 1 + sage: GrowthGroup('log(x)^ZZ').ngens() + 1 + + :: + + sage: P = GrowthGroup('QQ^x') + sage: P.ngens() + 0 + """ + return len(self.gens()) + + + def variable_names(self): + r""" + Return the names of the variables. + + OUTPUT: + + A tuple of strings. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(ZZ).variable_names() + () + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ').variable_names() + ('x',) + sage: GrowthGroup('log(x)^ZZ').variable_names() + ('x',) + + :: + + sage: GrowthGroup('QQ^x').variable_names() + ('x',) + sage: GrowthGroup('QQ^(x*log(x))').variable_names() + ('x',) + """ + return self._var_.variable_names() + + + CartesianProduct = CartesianProductGrowthGroups + + +from sage.categories.pushout import ConstructionFunctor +class AbstractGrowthGroupFunctor(ConstructionFunctor): + r""" + A base class for the functors constructing growth groups. + + INPUT: + + - ``var`` -- a string or list of strings (or anything else + :class:`Variable` accepts). + + - ``domain`` -- a category. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('z^QQ').construction()[0] # indirect doctest + MonomialGrowthGroup[z] + + .. SEEALSO:: + + :doc:`asymptotic_ring`, + :class:`ExponentialGrowthGroupFunctor`, + :class:`MonomialGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.asymptotic_ring.AsymptoticRingFunctor`, + :class:`sage.categories.pushout.ConstructionFunctor`. + """ + + _functor_name = 'AbstractGrowthGroup' + + rank = 13 + + def __init__(self, var, domain): + r""" + See :class:`AbstractGrowthGroupFunctor` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import AbstractGrowthGroupFunctor + sage: AbstractGrowthGroupFunctor('x', Groups()) + AbstractGrowthGroup[x] + """ + if var is None: + var = Variable('') + elif not isinstance(var, Variable): + var = Variable(var) + self.var = var + super(ConstructionFunctor, self).__init__( + domain, sage.categories.monoids.Monoids() & sage.categories.posets.Posets()) + + + def _repr_(self): + r""" + Return a representation string of this functor. + + OUTPUT: + + A string. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('QQ^t').construction()[0] # indirect doctest + ExponentialGrowthGroup[t] + """ + return '%s[%s]' % (self._functor_name, self.var) + + + def merge(self, other): + r""" + Merge this functor with ``other`` of possible. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A functor or ``None``. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F = GrowthGroup('QQ^t').construction()[0] + sage: G = GrowthGroup('t^QQ').construction()[0] + sage: F.merge(F) + ExponentialGrowthGroup[t] + sage: F.merge(G) is None + True + """ + if self == other: + return self + + + def __eq__(self, other): + r""" + Return whether this functor is equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F = GrowthGroup('QQ^t').construction()[0] + sage: G = GrowthGroup('t^QQ').construction()[0] + sage: F == F + True + sage: F == G + False + """ + return type(self) == type(other) and self.var == other.var + + + def __ne__(self, other): + r""" + Return whether this functor is not equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F = GrowthGroup('QQ^t').construction()[0] + sage: G = GrowthGroup('t^QQ').construction()[0] + sage: F != F + False + sage: F != G + True + """ + return not self == other + + +class MonomialGrowthElement(GenericGrowthElement): + r""" + An implementation of monomial growth elements. + + INPUT: + + - ``parent`` -- a :class:`MonomialGrowthGroup`. + + - ``raw_element`` -- an element from the base ring of the parent. + + This ``raw_element`` is the exponent of the created monomial + growth element. + + A monomial growth element represents a term of the type + `\operatorname{variable}^{\operatorname{exponent}}`. The multiplication + corresponds to the addition of the exponents. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: P = MonomialGrowthGroup(ZZ, 'x') + sage: e1 = P(1); e1 + 1 + sage: e2 = P(raw_element=2); e2 + x^2 + sage: e1 == e2 + False + sage: P.le(e1, e2) + True + sage: P.le(e1, P.gen()) and P.le(P.gen(), e2) + True + """ + + @property + def exponent(self): + r""" + The exponent of this growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P(x^42).exponent + 42 + """ + return self._raw_element_ + + + def _repr_(self): + r""" + A representation string for this monomial growth element. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^QQ') + sage: P(1)._repr_() + '1' + sage: P(x^5) # indirect doctest + x^5 + sage: P(x^(1/2)) # indirect doctest + x^(1/2) TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: e1 = G(raw_element=1); e2 = G(raw_element=2) - sage: e1 <= e2 # indirect doctest - Traceback (most recent call last): - ... - NotImplementedError: Only implemented in concrete realizations. + sage: P(x^-1) # indirect doctest + x^(-1) + sage: P(x^-42) # indirect doctest + x^(-42) """ - raise NotImplementedError('Only implemented in concrete realizations.') + from sage.rings.integer_ring import ZZ + from misc import repr_op + var = repr(self.parent()._var_) + if self.exponent.is_zero(): + return '1' + elif self.exponent.is_one(): + return var + elif self.exponent in ZZ and self.exponent > 0: + return repr_op(var, '^') + str(self.exponent) + else: + return repr_op(var, '^') + '(' + str(self.exponent) + ')' -class GenericGrowthGroup( - sage.structure.parent.Parent, - sage.structure.unique_representation.UniqueRepresentation): - r""" - A basic implementation for growth groups. - INPUT: + def _mul_(self, other): + r""" + Multiply this monomial growth element with another. - - ``base`` -- one of SageMath's parents, out of which the elements - get their data (``raw_element``). + INPUT: - - ``category`` -- (default: ``None``) the category of the newly - created growth group. It has to be a subcategory of ``Join of - Category of groups and Category of posets``. This is also the - default category if ``None`` is specified. + - ``other`` -- a :class:`MonomialGrowthElement` - .. NOTE:: + OUTPUT: - This class should be derived for concrete implementations. + The product as a :class:`MonomialGrowthElement`. - EXAMPLES:: + .. NOTE:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ); G - Growth Group Generic(ZZ) + Two monomial growth elements are multiplied by adding + their exponents. - .. SEEALSO:: + EXAMPLES:: - :class:`MonomialGrowthGroup` - """ - # TODO: implement some sort of 'assume', where basic assumptions - # for the variables can be stored. --> within the cartesian product + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: a = P(x^2) + sage: b = P(x^3) + sage: c = a._mul_(b); c + x^5 + sage: c == a*b + True + sage: a*b*a # indirect doctest + x^7 + """ + return self.parent()(raw_element=self.exponent + other.exponent) - # enable the category framework for elements - Element = GenericGrowthElement + def __invert__(self): + r""" + Return the multiplicative inverse of this monomial growth element. + + INPUT: - @sage.misc.superseded.experimental(trac_number=17601) - def __init__(self, base, category=None): + Nothing. + + OUTPUT: + + The multiplicative inverse as a :class:`MonomialGrowthElement`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: e1 = P(raw_element=2) + sage: e2 = e1.__invert__(); e2 + x^(-2) + sage: e2 == ~e1 + True + sage: Q = GrowthGroup('x^NN'); Q + Growth Group x^((Non negative integer semiring)) + sage: e3 = ~Q('x'); e3 + x^(-1) + sage: e3.parent() + Growth Group x^ZZ + """ + return self.parent()._create_element_in_extension_(-self.exponent) + + + def __pow__(self, exponent): r""" - See :class:`GenericGrowthElement` for more information. + Calculate the power of this growth element to the given ``exponent``. + + INPUT: + + - ``exponent`` -- a number. This can be anything that is a + valid right hand side of ``*`` with elements of the + parent's base. + + OUTPUT: + + The result of this exponentiation, a :class:`MonomialGrowthElement`. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.GenericGrowthGroup(ZZ).category() - Join of Category of groups and Category of posets + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: x = P.gen() + sage: a = x^7; a + x^7 + sage: a^(1/2) + x^(7/2) + sage: (a^(1/2)).parent() + Growth Group x^QQ + sage: a^(1/7) + x + sage: (a^(1/7)).parent() + Growth Group x^QQ + sage: P = GrowthGroup('x^QQ') + sage: b = P.gen()^(7/2); b + x^(7/2) + sage: b^12 + x^42 + """ + return self.parent()._create_element_in_extension_(self.exponent * exponent) + + + def _log_factor_(self, base=None): + r""" + Helper method for calculating the logarithm of the factorization + of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is either a growth + element or something out of which we can construct a growth element + and the second a multiplicative coefficient. TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: G.is_parent_of(G(raw_element=42)) - True - sage: G2 = agg.GenericGrowthGroup(ZZ, category=FiniteGroups() & Posets()) - sage: G2.category() - Join of Category of finite groups and Category of finite posets - sage: G3 = agg.GenericGrowthGroup(ZZ, category=Rings()) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^QQ') + sage: G('x').log_factor() # indirect doctest Traceback (most recent call last): ... - ValueError: (Category of rings,) is not a subcategory of - Join of Category of groups and Category of posets + ArithmeticError: Cannot build log(x) since log(x) is not in + Growth Group x^QQ. :: - sage: G = agg.GenericGrowthGroup('42') + sage: G = GrowthGroup('exp(x)^ZZ * x^ZZ') + sage: log(G('exp(x)'), base=2) Traceback (most recent call last): ... - TypeError: 42 is not a valid base + ArithmeticError: When calculating log(exp(x), base=2) a factor + 1/log(2) != 1 appeared, which is not contained in + Growth Group exp(x)^ZZ * x^ZZ. """ - if not isinstance(base, sage.structure.parent.Parent): - raise TypeError('%s is not a valid base' % (base,)) - from sage.categories.groups import Groups - from sage.categories.posets import Posets - - if category is None: - category = Groups() & Posets() + if self.is_one(): + return tuple() + coefficient = self.exponent + + var = str(self.parent()._var_) + + from misc import split_str_by_op + split = split_str_by_op(var, '^') + if len(split) == 2: + b, e = split + if base is None and b == 'e' or \ + base is not None and b == str(base): + return ((e, coefficient),) + + if var.startswith('exp('): + assert(var[-1] == ')') + v = var[4:-1] else: - if not isinstance(category, tuple): - category = (category,) - if not any(cat.is_subcategory(Groups() & Posets()) for cat in - category): - raise ValueError('%s is not a subcategory of %s' - % (category, Groups() & Posets())) - super(GenericGrowthGroup, self).__init__(category=category, - base=base) + v = 'log(%s)' % (var,) + if base is not None: + from sage.functions.log import log + coefficient = coefficient / log(base) + return ((v, coefficient),) - def _repr_short_(self): + + def _rpow_element_(self, base): r""" - A short representation string of this abstract growth group. + Return an element which is the power of ``base`` to this + element. INPUT: - Nothing. + - ``base`` -- an element. OUTPUT: - A string. + A growth element. - EXAMPLES:: + .. NOTE:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.GenericGrowthGroup(QQ)._repr_short_() - 'Generic(QQ)' - sage: agg.GenericGrowthGroup(QQ) - Growth Group Generic(QQ) + The parent of the result can be different from the parent + of this element. + + A ``ValueError`` is raised if the calculation is not possible + within this method. (Then the calling method should take care + of the calculation.) + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: x = G('x') + sage: x._rpow_element_(2) + Traceback (most recent call last): + ... + ValueError: Variable x is not a log of something. + + The previous example does not work since the result would not + live in a monomial growth group. When using + :meth:`~GenericGrowthElement.rpow`, this + case is handeled by the calling method :meth:`_rpow_`. + + :: + + sage: G = GrowthGroup('log(x)^ZZ') + sage: lx = G(raw_element=1); lx + log(x) + sage: rp = lx._rpow_element_('e'); rp + x + sage: rp.parent() + Growth Group x^ZZ + + :: + + sage: G = GrowthGroup('log(x)^SR') + sage: lx = G('log(x)') + sage: lx._rpow_element_(2) + x^(log(2)) """ - return 'Generic(%s)' % (parent_to_repr_short(self.base()),) + var = str(self.parent()._var_) + if not(var.startswith('log(') and self.exponent.is_one()): + raise ValueError('Variable %s is not a log of something.' % (var,)) + new_var = var[4:-1] + if base == 'e': + from sage.rings.integer_ring import ZZ + M = MonomialGrowthGroup(ZZ, new_var) + return M(raw_element=ZZ(1)) + else: + from sage.functions.log import log + new_exponent = log(base) + M = MonomialGrowthGroup(new_exponent.parent(), new_var) + return M(raw_element=new_exponent) - def _repr_(self, condense=False): + def _le_(self, other): r""" - A representations string of this growth group. + Return whether this :class:`MonomialGrowthElement` is at most + (less than or equal to) ``other``. INPUT: - - ``condense`` -- (default: ``False``) if set, then a shorter - output is returned, e.g. the prefix-string ``Growth Group`` - is not show in this case. + - ``other`` -- a :class:`MonomialGrowthElement`. OUTPUT: - A string. + A boolean. - EXAMPLES:: + .. NOTE:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.MonomialGrowthGroup(ZZ, 'x') # indirect doctest - Growth Group x^ZZ - sage: agg.MonomialGrowthGroup(QQ, 'log(x)') # indirect doctest - Growth Group log(x)^QQ + This function compares two instances of + :class:`MonomialGrowthElement`. TESTS:: - sage: agg.MonomialGrowthGroup(QQ, 'log(x)')._repr_(condense=True) - 'log(x)^QQ' + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_ZZ = GrowthGroup('x^ZZ') + sage: P_QQ = GrowthGroup('x^QQ') + sage: P_ZZ.gen() <= P_QQ.gen()^2 # indirect doctest + True """ - pre = 'Growth Group ' if not condense else '' - return '%s%s' % (pre, self._repr_short_()) + return self.exponent <= other.exponent - def __hash__(self): + def _substitute_(self, rules): r""" - Return the hash of this group. + Substitute the given ``rules`` in this monomial growth element. INPUT: - Nothing. + - ``rules`` -- a dictionary. + The neutral element of the group is replaced by the value + to key ``'_one_'``. OUTPUT: - An integer. + An object. - EXAMPLES:: + TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: hash(agg.GenericGrowthGroup(ZZ)) # random - 4242424242424242 + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: G(x^42)._substitute_({'x': SR.var('z')}) + z^42 + sage: _.parent() + Symbolic Ring + sage: G(x^3)._substitute_({'x': 2}) + 8 + sage: _.parent() + Integer Ring + sage: G(1 / x)._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in x^(-1) in Growth Group x^ZZ. + > *previous* ZeroDivisionError: rational division by zero + sage: G(1)._substitute_({'_one_': 'one'}) + 'one' """ - return hash((self.__class__, self.base())) + if self.is_one(): + return rules['_one_'] + try: + return self.parent()._var_._substitute_(rules) ** self.exponent + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) - def _an_element_(self): - r""" - Return an element of ``self``. +class MonomialGrowthGroup(GenericGrowthGroup): + r""" + A growth group dealing with powers of a fixed object/symbol. - INPUT: + The elements :class:`MonomialGrowthElement` of this group represent powers + of a fixed base; the group law is the multiplication, which corresponds + to the addition of the exponents of the monomials. - Nothing. + INPUT: - OUTPUT: + - ``base`` -- one of SageMath's parents, out of which the elements + get their data (``raw_element``). - An element of ``self``. + As monomials are represented by this group, the elements in + ``base`` are the exponents of these monomials. - EXAMPLES:: + - ``var`` -- an object. - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ); - sage: G.an_element() # indirect doctest - GenericGrowthElement(1) - """ - return self.element_class(self, self.base().an_element()) + The string representation of ``var`` acts as a base of the + monomials represented by this group. + - ``category`` -- (default: ``None``) the category of the newly + created growth group. It has to be a subcategory of ``Join of + Category of groups and Category of posets``. This is also the + default category if ``None`` is specified. - def le(self, left, right): - r""" - Return if the growth of ``left`` is at most (less than or - equal to) the growth of ``right``. + EXAMPLES:: - INPUT: + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: P = MonomialGrowthGroup(ZZ, 'x'); P + Growth Group x^ZZ + sage: MonomialGrowthGroup(ZZ, log(SR.var('y'))) + Growth Group log(y)^ZZ - - ``left`` -- an element. + .. SEEALSO:: - - ``right`` -- an element. + :class:`GenericGrowthGroup` - OUTPUT: + TESTS:: - A boolean. + sage: L1 = MonomialGrowthGroup(QQ, log(x)) + sage: L2 = MonomialGrowthGroup(QQ, 'log(x)') + sage: L1 is L2 + True + """ - .. NOTE:: + # enable the category framework for elements + Element = MonomialGrowthElement - This function uses the coercion model to find a common - parent for the two operands. - EXAMPLES:: + # set everything up to determine category + from sage.categories.sets_cat import Sets + from sage.categories.posets import Posets + from sage.categories.magmas import Magmas + from sage.categories.additive_magmas import AdditiveMagmas - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.MonomialGrowthGroup(ZZ, 'x') - sage: x = G.gen() - sage: G.le(x, x^2) - True - sage: G.le(x^2, x) - False - sage: G.le(x^0, 1) - True - """ - return self(left) <= self(right) + _determine_category_subcategory_mapping_ = [ + (Sets(), Sets(), True), + (Posets(), Posets(), False), + (AdditiveMagmas(), Magmas(), False)] + + _determine_category_axiom_mapping_ = [ + ('AdditiveAssociative', 'Associative', False), + ('AdditiveUnital', 'Unital', False), + ('AdditiveInverse', 'Inverse', False), + ('AdditiveCommutative', 'Commutative', False)] - def one(self): + def _repr_short_(self): r""" - Return the neutral element of this growth group. + A short representation string of this monomial growth group. INPUT: @@ -723,258 +2884,333 @@ def one(self): OUTPUT: - An element of this group. + A string. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: e1 = agg.MonomialGrowthGroup(ZZ, 'x').one(); e1 - 1 - sage: e1.is_idempotent() - True + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: MonomialGrowthGroup(ZZ, 'a') # indirect doctest + Growth Group a^ZZ + + + TESTS:: + + sage: MonomialGrowthGroup(ZZ, 'a')._repr_short_() + 'a^ZZ' + sage: MonomialGrowthGroup(QQ, 'a')._repr_short_() + 'a^QQ' + sage: MonomialGrowthGroup(PolynomialRing(QQ, 'x'), 'a')._repr_short_() + 'a^QQ[x]' """ - return self(1) + from misc import parent_to_repr_short, repr_op + return repr_op(self._var_, '^', parent_to_repr_short(self.base())) - def _element_constructor_(self, data, raw_element=None): + def _convert_(self, data): r""" - Convert a given object to this growth group. + Convert ``data`` to something the constructor of the + element class accepts (``raw_element``). INPUT: - - ``data`` -- an object representing the element to be - initialized. - - - ``raw_element`` -- (default: ``None``) if given, then this is - directly passed to the element constructor (i.e., no conversion - is performed). + - ``data`` -- an object. OUTPUT: - An element of this growth group. - - .. NOTE:: - - Either ``data`` or ``raw_element`` has to be given. If - ``raw_element`` is specified, then no positional argument - may be passed. - - This method calls :meth:`_convert_`, which does the actual - conversion from ``data``. + An element of the base ring or ``None`` (when no such element + can be constructed). TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GenericGrowthGroup(ZZ) - sage: z = G_ZZ(raw_element=42); z # indirect doctest - GenericGrowthElement(42) - sage: z is G_ZZ(z) # indirect doctest + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P._convert_('icecream') is None True + sage: P(1) # indirect doctest + 1 + sage: P('x') # indirect doctest + x :: - sage: G_QQ = agg.GenericGrowthGroup(QQ) - sage: q = G_QQ(raw_element=42) # indirect doctest - sage: q is z - False - sage: G_ZZ(q) # indirect doctest - GenericGrowthElement(42) - sage: G_QQ(z) # indirect doctest - GenericGrowthElement(42) - sage: q is G_ZZ(q) # indirect doctest - False + sage: P(x) # indirect doctest + x + sage: P(x^-333) # indirect doctest + x^(-333) + sage: P(log(x)^2) # indirect doctest + Traceback (most recent call last): + ... + ValueError: log(x)^2 is not in Growth Group x^ZZ. :: - sage: G_ZZ() - Traceback (most recent call last): - ... - ValueError: No input specified. Cannot continue. - sage: G_ZZ('blub') # indirect doctest + sage: PR. = ZZ[]; x.parent() + Univariate Polynomial Ring in x over Integer Ring + sage: P(x^2) # indirect doctest + x^2 + + :: + + sage: PSR. = ZZ[[]] + sage: P(x^42) # indirect doctest + x^42 + sage: P(x^12 + O(x^17)) Traceback (most recent call last): ... - ValueError: Cannot convert blub. - sage: G_ZZ('x', raw_element=42) # indirect doctest + ValueError: x^12 + O(x^17) is not in Growth Group x^ZZ. + + :: + + sage: R. = ZZ[] + sage: P(x^4242) # indirect doctest + x^4242 + sage: P(w^4242) # indirect doctest Traceback (most recent call last): ... - ValueError: Input is ambigous: x as well as raw_element=42 are specified. + ValueError: w^4242 is not in Growth Group x^ZZ. :: - sage: G_x = agg.MonomialGrowthGroup(ZZ, 'x') - sage: x = G_x(raw_element=1) # indirect doctest - sage: G_y = agg.MonomialGrowthGroup(ZZ, 'y') - sage: G_y(x) # indirect doctest + sage: PSR. = ZZ[[]] + sage: P(x^7) # indirect doctest + x^7 + sage: P(w^7) # indirect doctest Traceback (most recent call last): ... - ValueError: Cannot convert x. + ValueError: w^7 is not in Growth Group x^ZZ. + + :: + + sage: P('x^7') + x^7 + sage: P('1/x') + x^(-1) + sage: P('x^(-2)') + x^(-2) + sage: P('x^-2') + x^(-2) + + :: + + sage: P('1') + 1 + + :: + + sage: GrowthGroup('x^QQ')(GrowthGroup('x^ZZ')(1)) + 1 """ - if raw_element is None: - if isinstance(data, self.element_class): - if data.parent() == self: - return data - try: - if self._var_ != data.parent()._var_: - raise ValueError('Cannot convert %s.' % (data,)) - except AttributeError: - pass - raw_element = data._raw_element_ - elif isinstance(data, int) and data == 0: - raise ValueError('No input specified. Cannot continue.') - else: - raw_element = self._convert_(data) - if raw_element is None: - raise ValueError('Cannot convert %s.' % (data,)) - elif not isinstance(data, int) or data != 0: - raise ValueError('Input is ambigous: ' - '%s as well as raw_element=%s ' - 'are specified.' % (data, raw_element)) + if data == 1 or data == '1': + return self.base().zero() + var = repr(self._var_) + if str(data) == var: + return self.base().one() - return self.element_class(self, raw_element) + try: + P = data.parent() + except AttributeError: + if var not in str(data): + return # this has to end here + from sage.symbolic.ring import SR + return self._convert_(SR(data)) + + from sage.symbolic.ring import SymbolicRing + from sage.rings.polynomial.polynomial_ring import PolynomialRing_general + from sage.rings.polynomial.multi_polynomial_ring_generic import \ + MPolynomialRing_generic + from sage.rings.power_series_ring import PowerSeriesRing_generic + import operator + if isinstance(P, SymbolicRing): + if data.operator() == operator.pow: + base, exponent = data.operands() + if str(base) == var: + return exponent + elif isinstance(P, (PolynomialRing_general, MPolynomialRing_generic)): + if data.is_monomial() and len(data.variables()) == 1: + if var == str(data.variables()[0]): + return data.degree() + elif isinstance(P, PowerSeriesRing_generic): + if hasattr(data, 'variables') and len(data.variables()) == 1: + from sage.rings.integer_ring import ZZ + if data.is_monomial() and data.precision_absolute() not in ZZ: + if var == str(data.variables()[0]): + return data.degree() + elif len(P.variable_names()) == 1 and \ + var == str(data.variable()[0]): + from sage.rings.integer_ring import ZZ + if data.is_monomial() and data.precision_absolute() not in ZZ: + return data.degree() - def _convert_(self, data): + def gens_monomial(self): r""" - Convert ``data`` to something the constructor of the - element class accepts (``raw_element``). + Return a tuple containing monomial generators of this growth + group. INPUT: - - ``data`` -- an object. + Nothing. OUTPUT: - An element of the base ring or ``None`` (when no such element - can be constructed). + A tuple containing elements of this growth group. .. NOTE:: - This method always returns ``None`` in this abstract base - class, and should be overridden in inherited class. + A generator is called monomial generator if the variable + of the underlying growth group is a valid identifier. For + example, ``x^ZZ`` has ``x`` as a monomial generator, + while ``log(x)^ZZ`` or ``icecream(x)^ZZ`` do not have + monomial generators. TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: G._convert_('icecream') is None - True + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ').gens_monomial() + (x,) + sage: GrowthGroup('log(x)^QQ').gens_monomial() + () """ - pass + if not self._var_.is_monomial(): + return tuple() + return (self(raw_element=self.base().one()),) - def _coerce_map_from_(self, S): + def construction(self): r""" - Return if ``S`` coerces into this growth group. + Return the construction of this growth group. - INPUT: + OUTPUT: - - ``S`` -- a parent. + A pair whose first entry is a + :class:`monomial construction functor ` + and its second entry the base. - OUTPUT: + EXAMPLES:: - A boolean. + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ').construction() + (MonomialGrowthGroup[x], Integer Ring) + """ + return MonomialGrowthGroupFunctor(self._var_), self.base() - .. NOTE:: - Another growth group ``S`` coerces into this growth group - if and only if the base of ``S`` coerces into the base of - this growth group. +class MonomialGrowthGroupFunctor(AbstractGrowthGroupFunctor): + r""" + A :class:`construction functor ` + for :class:`monomial growth groups `. - EXAMPLES:: + INPUT: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.MonomialGrowthGroup(ZZ, 'x') - sage: G_QQ = agg.MonomialGrowthGroup(QQ, 'x') - sage: bool(G_ZZ.has_coerce_map_from(G_QQ)) # indirect doctest - False - sage: bool(G_QQ.has_coerce_map_from(G_ZZ)) # indirect doctest - True - """ - if isinstance(S, GenericGrowthGroup): - if self.base().has_coerce_map_from(S.base()): - return True + - ``var`` -- a string or list of strings (or anything else + :class:`Variable` accepts). + EXAMPLES:: - def gens_monomial(self): - r""" - Return a monomial generator of this growth group, in case - one exists. + sage: from sage.rings.asymptotic.growth_group import GrowthGroup, MonomialGrowthGroupFunctor + sage: GrowthGroup('z^QQ').construction()[0] + MonomialGrowthGroup[z] - INPUT: + .. SEEALSO:: - Nothing. + :doc:`asymptotic_ring`, + :class:`AbstractGrowthGroupFunctor`, + :class:`ExponentialGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.asymptotic_ring.AsymptoticRingFunctor`, + :class:`sage.categories.pushout.ConstructionFunctor`. - OUTPUT: + TESTS:: - An element of this growth group or ``None``. + sage: from sage.rings.asymptotic.growth_group import GrowthGroup, MonomialGrowthGroupFunctor + sage: cm = sage.structure.element.get_coercion_model() + sage: A = GrowthGroup('x^QQ') + sage: B = MonomialGrowthGroupFunctor('x')(ZZ['t']) + sage: cm.common_parent(A, B) + Growth Group x^QQ[t] + """ - .. NOTE:: + _functor_name = 'MonomialGrowthGroup' - A generator is called monomial generator if the variable - of the underlying growth group is a valid identifier. For - example, ``x^ZZ`` has ``x`` as a monomial generator, - while ``log(x)^ZZ`` or ``icecream(x)^ZZ`` do not have - monomial generators. - This method is only implemented for concrete growth - group implementations. + def __init__(self, var): + r""" + See :class:`MonomialGrowthGroupFunctor` for details. TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.GenericGrowthGroup(ZZ).gens_monomial() - Traceback (most recent call last): - ... - NotImplementedError: Only implemented for concrete growth group - implementations. + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroupFunctor + sage: MonomialGrowthGroupFunctor('x') + MonomialGrowthGroup[x] """ - raise NotImplementedError("Only implemented for concrete growth group" - " implementations.") + super(MonomialGrowthGroupFunctor, self).__init__(var, + sage.categories.commutative_additive_monoids.CommutativeAdditiveMonoids()) -class MonomialGrowthElement(GenericGrowthElement): + def _apply_functor(self, base): + r""" + Apply this functor to the given ``base``. + + INPUT: + + - ``base`` - anything :class:`MonomialGrowthGroup` accepts. + + OUTPUT: + + A monomial growth group. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F, R = GrowthGroup('z^QQ').construction() + sage: F(R) # indirect doctest + Growth Group z^QQ + """ + return MonomialGrowthGroup(base, self.var) + + +class ExponentialGrowthElement(GenericGrowthElement): r""" - An implementation of monomial growth elements. + An implementation of exponential growth elements. INPUT: - - ``parent`` -- a :class:`MonomialGrowthGroup`. + - ``parent`` -- an :class:`ExponentialGrowthGroup`. - ``raw_element`` -- an element from the base ring of the parent. - This ``raw_element`` is the exponent of the created monomial + This ``raw_element`` is the base of the created exponential growth element. - A monomial growth element represents a term of the type - `\operatorname{variable}^{\operatorname{exponent}}`. The multiplication - corresponds to the addition of the exponents. + An exponential growth element represents a term of the type + `\operatorname{base}^{\operatorname{variable}}`. The multiplication + corresponds to the multiplication of the bases. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') sage: e1 = P(1); e1 1 sage: e2 = P(raw_element=2); e2 - x^2 + 2^x sage: e1 == e2 False sage: P.le(e1, e2) True - sage: P.le(e1, P.gen()) and P.le(P.gen(), e2) + sage: P.le(e1, P(1)) and P.le(P(1), e2) True """ @property - def exponent(self): + def base(self): r""" - The exponent of this growth element. + The base of this exponential growth element. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P(x^42).exponent + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') + sage: P(42^x).base 42 """ return self._raw_element_ @@ -982,7 +3218,7 @@ def exponent(self): def _repr_(self): r""" - A representation string for this monomial growth element. + A representation string for this exponential growth element. INPUT: @@ -994,72 +3230,75 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(QQ, 'x') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('QQ^x') sage: P(1)._repr_() '1' - sage: P(x^5) # indirect doctest - x^5 - sage: P(x^(1/2)) # indirect doctest - x^(1/2) + sage: P(5^x) # indirect doctest + 5^x + sage: P((1/2)^x) # indirect doctest + (1/2)^x TESTS:: - sage: P(x^-1) # indirect doctest - 1/x - sage: P(x^-42) # indirect doctest - x^(-42) + sage: P((-1)^x) # indirect doctest + (-1)^x + + :: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: G = ExponentialGrowthGroup(ZZ['x'], 'y'); G + Growth Group ZZ[x]^y + sage: G('(1-x)^y') + (-x + 1)^y + sage: G('(1+x)^y') + (x + 1)^y """ from sage.rings.integer_ring import ZZ + from misc import repr_op - if self.exponent == 0: + var = repr(self.parent()._var_) + if self.base.is_one(): return '1' - elif self.exponent == 1: - return self.parent()._var_ - elif self.exponent == -1: - return '1/' + self.parent()._var_ - elif self.exponent in ZZ and self.exponent > 0: - return self.parent()._var_ + '^' + str(self.exponent) - else: - return self.parent()._var_ + '^(' + str(self.exponent) + ')' + return repr_op(str(self.base), '^', var) def _mul_(self, other): r""" - Multiply this monomial growth element with another. + Multiply this exponential growth element with another. INPUT: - - ``other`` -- a :class:`MonomialGrowthElement` + - ``other`` -- a :class:`ExponentialGrowthElement` OUTPUT: - The product as a :class:`MonomialGrowthElement`. + The product as a :class:`ExponentialGrowthElement`. .. NOTE:: - Two monomial growth elements are multiplied by adding - their exponents. + Two exponential growth elements are multiplied by + multiplying their bases. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: a = P(x^2) - sage: b = P(x^3) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') + sage: a = P(2^x) + sage: b = P(3^x) sage: c = a._mul_(b); c - x^5 - sage: c == a * b + 6^x + sage: c == a*b True - sage: a * b * a # indirect doctest - x^7 + sage: a*b*a # indirect doctest + 12^x """ - return self.parent()(raw_element=self.exponent + other.exponent) + return self.parent()(raw_element=self.base * other.base) def __invert__(self): r""" - Return the multiplicative inverse of this monomial growth element. + Return the multiplicative inverse of this exponential growth element. INPUT: @@ -1067,69 +3306,107 @@ def __invert__(self): OUTPUT: - The multiplicative inverse as a :class:`MonomialGrowthElement`. + The multiplicative inverse as a :class:`ExponentialGrowthElement`. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') sage: e1 = P(raw_element=2) sage: e2 = e1.__invert__(); e2 - x^(-2) + (1/2)^x sage: e2 == ~e1 True + sage: e2.parent() + Growth Group QQ^x + + :: + + sage: (~P(raw_element=1)).parent() + Growth Group QQ^x """ - return self.parent()(raw_element=-self.exponent) + return self.parent()._create_element_in_extension_(1 / self.base) - def __pow__(self, power): + def __pow__(self, exponent): r""" - Raises this growth element to the given ``power``. + Calculate the power of this growth element to the given ``exponent``. INPUT: - - ``power`` -- a number. This can be anything that is a - valid right hand side of ``*`` with elements of the + - ``exponent`` -- a number. This can be anything that is valid to be + on the right hand side of ``*`` with an elements of the parent's base. OUTPUT: - The result of this exponentiation, a :class:`MonomialGrowthElement`. + The result of this exponentiation as an :class:`ExponentialGrowthElement`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') + sage: a = P(7^x); a + 7^x + sage: b = a^(1/2); b + sqrt(7)^x + sage: b.parent() + Growth Group SR^x + sage: b^12 + 117649^x + """ + return self.parent()._create_element_in_extension_(self.base ** exponent) + + + def _log_factor_(self, base=None): + r""" + Helper method for calculating the logarithm of the factorization + of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is either a growth + element or something out of which we can construct a growth element + and the second is a multiplicative coefficient. - EXAMPLES:: + TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: x = P.gen() - sage: a = x^7; a - x^7 - sage: a^(1/2) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x') + sage: G('4^x').log_factor(base=2) # indirect doctest Traceback (most recent call last): ... - ValueError: Growth Group x^ZZ disallows taking x^7 to the power of 1/2. - sage: P = agg.MonomialGrowthGroup(QQ, 'x') - sage: b = P.gen()^(7/2); b - x^(7/2) - sage: b^12 - x^42 + ArithmeticError: Cannot build log(4^x, base=2) since x is not in + Growth Group QQ^x. """ - new_exponent = self.exponent * power - P = self.parent() - if new_exponent in P.base(): - return P(raw_element=new_exponent) + if self.is_one(): + return tuple() + b = self.base + if base is None and hasattr(b, 'is_monomial') and b.is_monomial() and \ + b.variable_name() == 'e': + coefficient = b.valuation() + elif base is None and str(b) == 'e': + coefficient = self.parent().base().one() else: - raise ValueError('%s disallows taking %s to the power ' - 'of %s.' % (P, self, power)) + from sage.functions.log import log + coefficient = log(b, base=base) + + return ((str(self.parent()._var_), coefficient),) def _le_(self, other): r""" - Return if this :class:`MonomialGrowthElement` is at most + Return whether this :class:`ExponentialGrowthElement` is at most (less than or equal to) ``other``. INPUT: - - ``other`` -- a :class:`MonomialGrowthElement`. + - ``other`` -- a :class:`ExponentialGrowthElement`. OUTPUT: @@ -1138,39 +3415,78 @@ def _le_(self, other): .. NOTE:: This function compares two instances of - :class:`MonomialGrowthElement`. + :class:`ExponentialGrowthElement`. TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P_ZZ = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P_QQ = agg.MonomialGrowthGroup(QQ, 'x') - sage: P_ZZ.gen() <= P_QQ.gen()^2 # indirect doctest + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_ZZ = GrowthGroup('ZZ^x') + sage: P_SR = GrowthGroup('SR^x') + sage: P_ZZ(2^x) <= P_SR(sqrt(3)^x)^2 # indirect doctest True """ - return self.exponent <= other.exponent + return bool(abs(self.base) <= abs(other.base)) -class MonomialGrowthGroup(GenericGrowthGroup): + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this exponential growth element. + + INPUT: + + - ``rules`` -- a dictionary. + The neutral element of the group is replaced by the value + to key ``'_one_'``. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x') + sage: G((1/2)^x)._substitute_({'x': SR.var('z')}) + (1/2)^z + sage: _.parent() + Symbolic Ring + sage: G((1/2)^x)._substitute_({'x': 2}) + 1/4 + sage: _.parent() + Rational Field + sage: G(1)._substitute_({'_one_': 'one'}) + 'one' + """ + if self.is_one(): + return rules['_one_'] + try: + return self.base ** self.parent()._var_._substitute_(rules) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + +class ExponentialGrowthGroup(GenericGrowthGroup): r""" - A growth group dealing with powers of a fixed object/symbol. + A growth group dealing with expressions involving a fixed + variable/symbol as the exponent. - The elements :class:`MonomialGrowthElement` of this group represent powers - of a fixed base; the group law is the multiplication, which corresponds - to the addition of the exponents of the monomials. + The elements :class:`ExponentialGrowthElement` of this group + represent exponential functions with bases from a fixed base + ring; the group law is the multiplication. INPUT: - ``base`` -- one of SageMath's parents, out of which the elements get their data (``raw_element``). - As monomials are represented by this group, the elements in - ``base`` are the exponents of these monomials. + As exponential expressions are represented by this group, + the elements in ``base`` are the bases of these exponentials. - ``var`` -- an object. - The string representation of ``var`` acts as a base of the - monomials represented by this group. + The string representation of ``var`` acts as an exponent of the + elements represented by this group. - ``category`` -- (default: ``None``) the category of the newly created growth group. It has to be a subcategory of ``Join of @@ -1179,11 +3495,9 @@ class MonomialGrowthGroup(GenericGrowthGroup): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x'); P - Growth Group x^ZZ - sage: agg.MonomialGrowthGroup(ZZ, log(SR.var('y'))) - Growth Group log(y)^ZZ + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: P = ExponentialGrowthGroup(QQ, 'x'); P + Growth Group QQ^x .. SEEALSO:: @@ -1191,83 +3505,32 @@ class MonomialGrowthGroup(GenericGrowthGroup): """ # enable the category framework for elements - Element = MonomialGrowthElement - - - @staticmethod - def __classcall__(cls, base, var, category=None): - r""" - Normalizes the input in order to ensure a unique - representation. - - For more information see :class:`MonomialGrowthGroup`. - - TESTS:: - - sage: import sage.rings.asymptotic.growth_group as agg - sage: P1 = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P2 = agg.MonomialGrowthGroup(ZZ, ZZ['x'].gen()) - sage: P3 = agg.MonomialGrowthGroup(ZZ, SR.var('x')) - sage: P1 is P2 and P2 is P3 - True - sage: P4 = agg.MonomialGrowthGroup(ZZ, buffer('xylophone', 0, 1)) - sage: P1 is P4 - True - sage: P5 = agg.MonomialGrowthGroup(ZZ, 'x ') - sage: P1 is P5 - True - - :: - - sage: L1 = agg.MonomialGrowthGroup(QQ, log(x)) - sage: L2 = agg.MonomialGrowthGroup(QQ, 'log(x)') - sage: L1 is L2 - True - """ - var = str(var).strip() - return super(MonomialGrowthGroup, cls).__classcall__( - cls, base, var, category) - - - @sage.misc.superseded.experimental(trac_number=17601) - def __init__(self, base, var, category): - r""" - For more information see :class:`MonomialGrowthGroup`. - - EXAMPLES:: + Element = ExponentialGrowthElement - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.MonomialGrowthGroup(ZZ, 'x') - Growth Group x^ZZ - sage: agg.MonomialGrowthGroup(QQ, SR.var('n')) - Growth Group n^QQ - sage: agg.MonomialGrowthGroup(ZZ, ZZ['y'].gen()) - Growth Group y^ZZ - sage: agg.MonomialGrowthGroup(QQ, 'log(x)') - Growth Group log(x)^QQ - TESTS:: + # set everything up to determine category + from sage.categories.sets_cat import Sets + from sage.categories.posets import Posets + from sage.categories.magmas import Magmas + from sage.categories.groups import Groups + from sage.categories.division_rings import DivisionRings - sage: agg.MonomialGrowthGroup('x', ZZ) - Traceback (most recent call last): - ... - TypeError: x is not a valid base - """ - if not var: - raise ValueError('Empty var is not allowed.') - if var[0] in '0123456789=+-*/^%': - # This restriction is mainly for optical reasons on the - # representation. Feel free to relax this if needed. - raise ValueError("The variable name '%s' is inappropriate." % - (var,)) - self._var_ = var + _determine_category_subcategory_mapping_ = [ + (Sets(), Sets(), True), + (Posets(), Posets(), False), + (Magmas(), Magmas(), False), + (DivisionRings(), Groups(), False)] - super(MonomialGrowthGroup, self).__init__(category=category, base=base) + _determine_category_axiom_mapping_ = [ + ('Associative', 'Associative', False), + ('Unital', 'Unital', False), + ('Inverse', 'Inverse', False), + ('Commutative', 'Commutative', False)] def _repr_short_(self): r""" - A short representation string of this monomial growth group. + A short representation string of this exponential growth group. INPUT: @@ -1279,48 +3542,25 @@ def _repr_short_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.MonomialGrowthGroup(ZZ, 'a') # indirect doctest - Growth Group a^ZZ + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: ExponentialGrowthGroup(QQ, 'a') # indirect doctest + Growth Group QQ^a TESTS:: - sage: agg.MonomialGrowthGroup(ZZ, 'a')._repr_short_() - 'a^ZZ' - sage: agg.MonomialGrowthGroup(QQ, 'a')._repr_short_() - 'a^QQ' - sage: agg.MonomialGrowthGroup(PolynomialRing(QQ, 'x'), 'a')._repr_short_() - 'a^(Univariate Polynomial Ring in x over Rational Field)' - """ - return '%s^%s' % (self._var_, parent_to_repr_short(self.base())) - - - def __hash__(self): - r""" - Return the hash of this group. - - INPUT: - - Nothing. - - OUTPUT: - - An integer. - - EXAMPLES:: - - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: hash(P) # random - -1234567890123456789 + sage: ExponentialGrowthGroup(QQ, 'a')._repr_short_() + 'QQ^a' + sage: ExponentialGrowthGroup(PolynomialRing(QQ, 'x'), 'a')._repr_short_() + 'QQ[x]^a' """ - return hash((super(MonomialGrowthGroup, self).__hash__(), self._var_)) + from misc import parent_to_repr_short, repr_op + return repr_op(parent_to_repr_short(self.base()), '^', self._var_) def _convert_(self, data): r""" - Convert ``data`` to something the constructor of the + Converts given ``data`` to something the constructor of the element class accepts (``raw_element``). INPUT: @@ -1334,140 +3574,120 @@ def _convert_(self, data): TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('QQ^x') sage: P._convert_('icecream') is None True sage: P(1) # indirect doctest 1 - sage: P('x') # indirect doctest - x + sage: P('2^x') # indirect doctest + 2^x :: - sage: P(x) # indirect doctest - x - sage: P(x^-333) # indirect doctest - x^(-333) - sage: P(log(x)^2) # indirect doctest + sage: P(2^x) # indirect doctest + 2^x + sage: P((-333)^x) # indirect doctest + (-333)^x + sage: P(0) # indirect doctest Traceback (most recent call last): ... - ValueError: Cannot convert log(x)^2. + ValueError: 0 is not in Growth Group QQ^x. :: - sage: PR. = ZZ[]; x.parent() - Univariate Polynomial Ring in x over Integer Ring - sage: P(x^2) # indirect doctest - x^2 - - :: - - sage: PSR. = ZZ[[]] - sage: P(x^42) # indirect doctest - x^42 - sage: P(x^12 + O(x^17)) - Traceback (most recent call last): - ... - ValueError: Cannot convert x^12 + O(x^17). + sage: P('7^x') + 7^x + sage: P('(-2)^x') + (-2)^x :: - sage: R. = ZZ[] - sage: P(x^4242) # indirect doctest - x^4242 - sage: P(w^4242) # indirect doctest - Traceback (most recent call last): - ... - ValueError: Cannot convert w^4242. + sage: P = GrowthGroup('SR^x') + sage: P(sqrt(3)^x) + sqrt(3)^x + sage: P((3^(1/3))^x) + (3^(1/3))^x + sage: P(e^x) + e^x + sage: P(exp(2*x)) + (e^2)^x :: - sage: PSR. = ZZ[[]] - sage: P(x^7) # indirect doctest - x^7 - sage: P(w^7) # indirect doctest - Traceback (most recent call last): - ... - ValueError: Cannot convert w^7. + sage: GrowthGroup('QQ^x')(GrowthGroup('ZZ^x')(1)) + 1 """ - if data == 1: - return self.base().zero() - if str(data) == self._var_: + if data == '1' or isinstance(data, int) and data == 1: return self.base().one() - + var = repr(self._var_) try: P = data.parent() except AttributeError: - return # this has to end here + if data == 1: + return self.base().one() + s = str(data) + if var not in s: + return # this has to end here + + elif s.endswith('^' + var): + return self.base()(s.replace('^' + var, '') + .replace('(', '').replace(')', '')) + else: + return # end of parsing - from sage.symbolic.ring import SR - from sage.rings.polynomial.polynomial_ring import PolynomialRing_general - from sage.rings.polynomial.multi_polynomial_ring_generic import \ - MPolynomialRing_generic - from sage.rings.power_series_ring import PowerSeriesRing_generic + from sage.symbolic.ring import SymbolicRing import operator - if P is SR: - if data.operator() == operator.pow: + from sage.symbolic.operators import mul_vararg + if isinstance(P, SymbolicRing): + op = data.operator() + if op == operator.pow: base, exponent = data.operands() - if str(base) == self._var_: - return exponent - elif isinstance(P, (PolynomialRing_general, MPolynomialRing_generic)): - if data.is_monomial() and len(data.variables()) == 1: - if self._var_ == str(data.variables()[0]): - return data.degree() - elif isinstance(P, PowerSeriesRing_generic): - if hasattr(data, 'variables') and len(data.variables()) == 1: - from sage.rings.integer_ring import ZZ - if data.is_monomial() and data.precision_absolute() not in ZZ: - if self._var_ == str(data.variables()[0]): - return data.degree() - elif len(P.variable_names()) == 1 and \ - self._var_ == str(data.variable()[0]): - from sage.rings.integer_ring import ZZ - if data.is_monomial() and data.precision_absolute() not in ZZ: - return data.degree() + if str(exponent) == var: + return base + elif exponent.operator() == mul_vararg: + return base ** (exponent / P(var)) + elif isinstance(op, sage.functions.log.Function_exp): + from sage.functions.log import exp + base = exp(1) + exponent = data.operands()[0] + if str(exponent) == var: + return base + elif exponent.operator() == mul_vararg: + return base ** (exponent / P(var)) + + elif data == 1: # can be expensive, so let's put it at the end + return self.base().one() - def _coerce_map_from_(self, S): + def some_elements(self): r""" - Return if ``S`` coerces into this growth group. + Return some elements of this exponential growth group. + + See :class:`TestSuite` for a typical use case. INPUT: - - ``S`` -- a parent. + Nothing. OUTPUT: - A boolean. + An iterator. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P_x_ZZ = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P_x_QQ = agg.MonomialGrowthGroup(QQ, 'x') - sage: bool(P_x_ZZ.has_coerce_map_from(P_x_QQ)) # indirect doctest - False - sage: bool(P_x_QQ.has_coerce_map_from(P_x_ZZ)) # indirect doctest - True - sage: P_y_ZZ = agg.MonomialGrowthGroup(ZZ, 'y') - sage: bool(P_y_ZZ.has_coerce_map_from(P_x_ZZ)) # indirect doctest - False - sage: bool(P_x_ZZ.has_coerce_map_from(P_y_ZZ)) # indirect doctest - False - sage: bool(P_y_ZZ.has_coerce_map_from(P_x_QQ)) # indirect doctest - False - sage: bool(P_x_QQ.has_coerce_map_from(P_y_ZZ)) # indirect doctest - False + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: tuple(GrowthGroup('QQ^z').some_elements()) + ((1/2)^z, (-1/2)^z, 2^z, (-2)^z, 1, (-1)^z, + 42^z, (2/3)^z, (-2/3)^z, (3/2)^z, (-3/2)^z, ...) """ - if super(MonomialGrowthGroup, self)._coerce_map_from_(S): - if self._var_ == S._var_: - return True + return iter(self.element_class(self, e) + for e in self.base().some_elements() if e != 0) - def gens_monomial(self): + def gens(self): r""" - Return a tuple containing monomial generators of this growth + Return a tuple of all generators of this exponential growth group. INPUT: @@ -1476,98 +3696,108 @@ def gens_monomial(self): OUTPUT: - A tuple containing elements of this growth group. - - .. NOTE:: - - A generator is called monomial generator if the variable - of the underlying growth group is a valid identifier. For - example, ``x^ZZ`` has ``x`` as a monomial generator, - while ``log(x)^ZZ`` or ``icecream(x)^ZZ`` do not have - monomial generators. + An empty tuple. - TESTS:: + EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.MonomialGrowthGroup(ZZ, 'x').gens_monomial() - (x,) - sage: agg.MonomialGrowthGroup(QQ, 'log(x)').gens_monomial() + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: E = GrowthGroup('ZZ^x') + sage: E.gens() () """ - if self._var_.startswith('log(') and self._var_.endswith(')'): - return () - return (self(raw_element=self.base().one()),) + return tuple() - def gens(self): + def construction(self): r""" - Return a tuple of all generators of this monomial growth - group. - - INPUT: - - Nothing. + Return the construction of this growth group. OUTPUT: - A tuple whose entries are instances of - :class:`MonomialGrowthElement`. + A pair whose first entry is an + :class:`exponential construction functor ` + and its second entry the base. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P.gens() - (x,) - sage: agg.MonomialGrowthGroup(ZZ, 'log(x)').gens() - (log(x),) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('QQ^x').construction() + (ExponentialGrowthGroup[x], Rational Field) """ - return (self(raw_element=self.base().one()),) + return ExponentialGrowthGroupFunctor(self._var_), self.base() - def gen(self, n=0): - r""" - Return the `n`-th generator of this growth group. +class ExponentialGrowthGroupFunctor(AbstractGrowthGroupFunctor): + r""" + A :class:`construction functor ` + for :class:`exponential growth groups `. - INPUT: + INPUT: - - ``n`` -- default: `0`. + - ``var`` -- a string or list of strings (or anything else + :class:`Variable` accepts). - OUTPUT: + EXAMPLES:: - A :class:`MonomialGrowthElement`. + sage: from sage.rings.asymptotic.growth_group import GrowthGroup, ExponentialGrowthGroupFunctor + sage: GrowthGroup('QQ^z').construction()[0] + ExponentialGrowthGroup[z] - EXAMPLES:: + .. SEEALSO:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P.gen() - x + :doc:`asymptotic_ring`, + :class:`AbstractGrowthGroupFunctor`, + :class:`MonomialGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.asymptotic_ring.AsymptoticRingFunctor`, + :class:`sage.categories.pushout.ConstructionFunctor`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup, ExponentialGrowthGroupFunctor + sage: cm = sage.structure.element.get_coercion_model() + sage: A = GrowthGroup('QQ^x') + sage: B = ExponentialGrowthGroupFunctor('x')(ZZ['t']) + sage: cm.common_parent(A, B) + Growth Group QQ[t]^x + """ + + _functor_name = 'ExponentialGrowthGroup' + + + def __init__(self, var): + r""" + See :class:`ExponentialGrowthGroupFunctor` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroupFunctor + sage: ExponentialGrowthGroupFunctor('x') + ExponentialGrowthGroup[x] """ - return self.gens()[n] + super(ExponentialGrowthGroupFunctor, self).__init__(var, + sage.categories.monoids.Monoids()) - def ngens(self): + + def _apply_functor(self, base): r""" - Return the number of generators of this monomial growth group. + Apply this functor to the given ``base``. INPUT: - Nothing. + - ``base`` - anything :class:`ExponentialGrowthGroup` accepts. OUTPUT: - A Python integer. + An exponential growth group. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P.ngens() - 1 - sage: agg.MonomialGrowthGroup(ZZ, 'log(x)').ngens() - 1 + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F, R = GrowthGroup('QQ^z').construction() + sage: F(R) # indirect doctest + Growth Group QQ^z """ - return len(self.gens()) + return ExponentialGrowthGroup(base, self.var) class GrowthGroupFactory(sage.structure.factory.UniqueFactory): @@ -1578,38 +3808,156 @@ class GrowthGroupFactory(sage.structure.factory.UniqueFactory): - ``specification`` -- a string. + - keyword arguments are passed on to the growth group + constructor. + If the keyword ``ignore_variables`` is not specified, then + ``ignore_variables=('e',)`` (to ignore ``e`` as a variable name) + is used. + OUTPUT: An asymptotic growth group. + .. NOTE:: + + An instance of this factory is available as ``GrowthGroup``. + EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.GrowthGroup('x^ZZ') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ') Growth Group x^ZZ - sage: agg.GrowthGroup('log(x)^QQ') + sage: GrowthGroup('log(x)^QQ') Growth Group log(x)^QQ + + This factory can also be used to construct Cartesian products + of growth groups:: + + sage: GrowthGroup('x^ZZ * y^ZZ') + Growth Group x^ZZ * y^ZZ + sage: GrowthGroup('x^ZZ * log(x)^ZZ') + Growth Group x^ZZ * log(x)^ZZ + sage: GrowthGroup('x^ZZ * log(x)^ZZ * y^QQ') + Growth Group x^ZZ * log(x)^ZZ * y^QQ + sage: GrowthGroup('QQ^x * x^ZZ * y^QQ * QQ^z') + Growth Group QQ^x * x^ZZ * y^QQ * QQ^z + sage: GrowthGroup('exp(x)^ZZ * x^ZZ') + Growth Group exp(x)^ZZ * x^ZZ + sage: GrowthGroup('(e^x)^ZZ * x^ZZ') + Growth Group (e^x)^ZZ * x^ZZ + + TESTS:: + + sage: G = GrowthGroup('(e^(n*log(n)))^ZZ') + sage: G, G._var_ + (Growth Group (e^(n*log(n)))^ZZ, e^(n*log(n))) + sage: G = GrowthGroup('(e^n)^ZZ') + sage: G, G._var_ + (Growth Group (e^n)^ZZ, e^n) + sage: G = GrowthGroup('(e^(n*log(n)))^ZZ * (e^n)^ZZ * n^ZZ * log(n)^ZZ') + sage: G, tuple(F._var_ for F in G.cartesian_factors()) + (Growth Group (e^(n*log(n)))^ZZ * (e^n)^ZZ * n^ZZ * log(n)^ZZ, + (e^(n*log(n)), e^n, n, log(n))) + + sage: TestSuite(GrowthGroup('x^ZZ')).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_inverse() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass + + :: + + sage: TestSuite(GrowthGroup('QQ^y')).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_inverse() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass + + :: + + sage: TestSuite(GrowthGroup('x^QQ * log(x)^ZZ')).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_inverse() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass """ def create_key_and_extra_args(self, specification, **kwds): r""" Given the arguments and keyword, create a key that uniquely determines this object. - EXAMPLES:: + TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.GrowthGroup.create_key_and_extra_args('x^ZZ') - (('x^ZZ',), {}) - sage: agg.GrowthGroup.create_key_and_extra_args('asdf') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup.create_key_and_extra_args('asdf') Traceback (most recent call last): ... - ValueError: 'asdf' is not a valid string describing a growth group. + ValueError: 'asdf' is not a valid substring of 'asdf' describing a growth group. """ - factors = tuple(s.strip() for s in specification.split('*')) + from misc import split_str_by_op + factors = split_str_by_op(specification, '*') + factors = tuple(f.replace('**', '^') for f in factors) + for f in factors: if '^' not in f: - raise ValueError("'%s' is not a valid string describing " - "a growth group." % (f,)) + raise ValueError("'%s' is not a valid substring of '%s' describing " + "a growth group." % (f, specification)) + + kwds.setdefault('ignore_variables', ('e',)) return factors, kwds @@ -1620,52 +3968,84 @@ def create_object(self, version, factors, **kwds): TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.GrowthGroup('as^df') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('as^df') # indirect doctest + Traceback (most recent call last): + ... + ValueError: 'as^df' is not a valid substring of as^df + describing a growth group. + > *previous* ValueError: Cannot create a parent out of 'as'. + >> *previous* SyntaxError: unexpected EOF while parsing (, line 1) + > *and* ValueError: Cannot create a parent out of 'df'. + >> *previous* NameError: name 'df' is not defined + sage: GrowthGroup('x^y^z') + Traceback (most recent call last): + ... + ValueError: 'x^y^z' is an ambigous substring of + a growth group description of 'x^y^z'. + Use parentheses to make it unique. + sage: GrowthGroup('(x^y)^z') Traceback (most recent call last): ... - ValueError: 'as^df' is not a valid string describing a growth group. - sage: agg.GrowthGroup('x^y^z') + ValueError: '(x^y)^z' is not a valid substring of (x^y)^z + describing a growth group. + > *previous* ValueError: Cannot create a parent out of 'x^y'. + >> *previous* NameError: name 'x' is not defined + > *and* ValueError: Cannot create a parent out of 'z'. + >> *previous* NameError: name 'z' is not defined + sage: GrowthGroup('x^(y^z)') Traceback (most recent call last): ... - ValueError: Cannot decode x^y^z. + ValueError: 'x^(y^z)' is not a valid substring of x^(y^z) + describing a growth group. + > *previous* ValueError: Cannot create a parent out of 'x'. + >> *previous* NameError: name 'x' is not defined + > *and* ValueError: Cannot create a parent out of 'y^z'. + >> *previous* NameError: name 'y' is not defined """ - if len(factors) > 1: - raise NotImplementedError('Cartesian product of growth groups not ' - 'yet implemented.') - # note: implementation already prepared for cartesian products! - + from misc import repr_short_to_parent, split_str_by_op groups = [] for factor in factors: - b_and_e = factor.split('^') - if len(b_and_e) != 2: - raise ValueError('Cannot decode %s.' % (factor,)) - (b, e) = b_and_e + split = split_str_by_op(factor, '^') + if len(split) != 2: + raise ValueError("'%s' is an ambigous substring of a growth group " + "description of '%s'. Use parentheses to make it " + "unique." % (factor, ' * '.join(factors))) + b, e = split try: - # monomial growth group: 'var^base' - groups.append( - MonomialGrowthGroup(repr_short_to_parent(e), b, **kwds)) - continue - except (TypeError, ValueError): - pass - - raise ValueError("'%s' is not a valid string describing " - "a growth group." % (factor,)) - # todo: once exponential growth groups are implemented, - # move line above to the bottom of this loop - + B = repr_short_to_parent(b) + except ValueError as exc_b: + B = None try: - # exponential growth group: 'base^var' - groups.append( - ExponentialGrowthGroup(repr_short_to_parent(b), e, **kwds)) - continue - except (TypeError, ValueError): - pass + E = repr_short_to_parent(e) + except ValueError as exc_e: + E = None + + if B is None and E is None: + from misc import combine_exceptions + raise combine_exceptions( + ValueError("'%s' is not a valid substring of %s describing " + "a growth group." % (factor, ' * '.join(factors))), + exc_b, exc_e) + elif B is None and E is not None: + groups.append(MonomialGrowthGroup(E, b, **kwds)) + elif B is not None and E is None: + groups.append(ExponentialGrowthGroup(B, e, **kwds)) + else: + raise ValueError("'%s' is an ambigous substring of a growth group " + "description of '%s'." % (factor, ' * '.join(factors))) - # todo: groups --> lists with groups over same variable. - return groups[0] + if len(groups) == 1: + return groups[0] + from sage.categories.cartesian_product import cartesian_product + return cartesian_product(groups) -GrowthGroup = GrowthGroupFactory("GrowthGroup") +GrowthGroup = GrowthGroupFactory("GrowthGroup") +r""" +A factory for growth groups. +This is an instance of :class:`GrowthGroupFactory` whose documentation +provides more details. +""" diff --git a/src/sage/rings/asymptotic/growth_group_cartesian.py b/src/sage/rings/asymptotic/growth_group_cartesian.py new file mode 100644 index 00000000000..05d5f2da9b0 --- /dev/null +++ b/src/sage/rings/asymptotic/growth_group_cartesian.py @@ -0,0 +1,1257 @@ +r""" +Cartesian Products of Growth Groups + +See :doc:`growth_group` for a description. + +AUTHORS: + +- Benjamin Hackl (2015) +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + +.. WARNING:: + + As this code is experimental, warnings are thrown when a growth + group is created for the first time in a session (see + :class:`sage.misc.superseded.experimental`). + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup, GrowthGroup + sage: GenericGrowthGroup(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/17601 for details. + Growth Group Generic(ZZ) + sage: GrowthGroup('x^ZZ * log(x)^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/17601 for details. + Growth Group x^ZZ * log(x)^ZZ + +TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: A = GrowthGroup('QQ^x * x^ZZ'); A + Growth Group QQ^x * x^ZZ + sage: A.construction() + (The cartesian_product functorial construction, + (Growth Group QQ^x, Growth Group x^ZZ)) + sage: A.construction()[1][0].construction() + (ExponentialGrowthGroup[x], Rational Field) + sage: A.construction()[1][1].construction() + (MonomialGrowthGroup[x], Integer Ring) + sage: B = GrowthGroup('x^ZZ * y^ZZ'); B + Growth Group x^ZZ * y^ZZ + sage: B.construction() + (The cartesian_product functorial construction, + (Growth Group x^ZZ, Growth Group y^ZZ)) + sage: C = GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ'); C + Growth Group x^ZZ * log(x)^ZZ * y^ZZ + sage: C.construction() + (The cartesian_product functorial construction, + (Growth Group x^ZZ * log(x)^ZZ, Growth Group y^ZZ)) + sage: C.construction()[1][0].construction() + (The cartesian_product functorial construction, + (Growth Group x^ZZ, Growth Group log(x)^ZZ)) + sage: C.construction()[1][1].construction() + (MonomialGrowthGroup[y], Integer Ring) + +:: + + sage: cm = sage.structure.element.get_coercion_model() + sage: D = GrowthGroup('QQ^x * x^QQ') + sage: cm.common_parent(A, D) + Growth Group QQ^x * x^QQ + sage: E = GrowthGroup('ZZ^x * x^QQ') + sage: cm.record_exceptions() # not tested, see #19411 + sage: cm.common_parent(A, E) + Growth Group QQ^x * x^QQ + sage: for t in cm.exception_stack(): # not tested, see #19411 + ....: print t + +:: + + sage: A.an_element() + (1/2)^x*x + sage: tuple(E.an_element()) + (1, x^(1/2)) + +Classes and Methods +=================== +""" + +#***************************************************************************** +# Copyright (C) 2014--2015 Benjamin Hackl +# 2014--2015 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 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import sage + + +class CartesianProductFactory(sage.structure.factory.UniqueFactory): + r""" + Create various types of cartesian products depending on its input. + + INPUT: + + - ``growth_groups`` -- a tuple (or other iterable) of growth groups. + + - ``order`` -- (default: ``None``) if specified, then this order + is taken for comparing two cartesian product elements. If ``order`` is + ``None`` this is determined automatically. + + .. NOTE:: + + The cartesian product of growth groups is again a growth + group. In particular, the resulting structure is partially + ordered. + + The order on the product is determined as follows: + + - Cartesian factors with respect to the same variable are + ordered lexicographically. This causes + ``GrowthGroup('x^ZZ * log(x)^ZZ')`` and + ``GrowthGroup('log(x)^ZZ * x^ZZ')`` to produce two + different growth groups. + + - Factors over different variables are equipped with the + product order (i.e. the comparison is component-wise). + + Also, note that the sets of variables of the cartesian + factors have to be either equal or disjoint. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: A = GrowthGroup('x^ZZ'); A + Growth Group x^ZZ + sage: B = GrowthGroup('log(x)^ZZ'); B + Growth Group log(x)^ZZ + sage: C = cartesian_product([A, B]); C # indirect doctest + Growth Group x^ZZ * log(x)^ZZ + sage: C._le_ == C.le_lex + True + sage: D = GrowthGroup('y^ZZ'); D + Growth Group y^ZZ + sage: E = cartesian_product([A, D]); E # indirect doctest + Growth Group x^ZZ * y^ZZ + sage: E._le_ == E.le_product + True + sage: F = cartesian_product([C, D]); F # indirect doctest + Growth Group x^ZZ * log(x)^ZZ * y^ZZ + sage: F._le_ == F.le_product + True + sage: cartesian_product([A, E]); G # indirect doctest + Traceback (most recent call last): + ... + ValueError: The growth groups (Growth Group x^ZZ, Growth Group x^ZZ * y^ZZ) + need to have pairwise disjoint or equal variables. + sage: cartesian_product([A, B, D]) # indirect doctest + Growth Group x^ZZ * log(x)^ZZ * y^ZZ + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group_cartesian import CartesianProductFactory + sage: CartesianProductFactory('factory')([A, B], category=Groups() & Posets()) + Growth Group x^ZZ * log(x)^ZZ + sage: CartesianProductFactory('factory')([], category=Sets()) + Traceback (most recent call last): + ... + TypeError: Cannot create cartesian product without factors. + """ + def create_key_and_extra_args(self, growth_groups, category, **kwds): + r""" + Given the arguments and keywords, create a key that uniquely + determines this object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group_cartesian import CartesianProductFactory + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: A = GrowthGroup('x^ZZ') + sage: CartesianProductFactory('factory').create_key_and_extra_args( + ....: [A], category=Sets(), order='blub') + (((Growth Group x^ZZ,), Category of sets), {'order': 'blub'}) + """ + return (tuple(growth_groups), category), kwds + + + def create_object(self, version, args, **kwds): + r""" + Create an object from the given arguments. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: cartesian_product([GrowthGroup('x^ZZ')]) # indirect doctest + Growth Group x^ZZ + """ + growth_groups, category = args + if not growth_groups: + raise TypeError('Cannot create cartesian product without factors.') + order = kwds.pop('order', None) + if order is not None: + return GenericProduct(growth_groups, category, order=order, **kwds) + + vg = tuple((g.variable_names(), g) for g in growth_groups) + + # check if all groups have a variable + if not all(v for v, _ in vg): + raise NotImplementedError('Growth groups %s have no variable.' % + tuple(g for g in growth_groups + if not g.variable_names())) + + # sort by variables + from itertools import groupby, product + vgs = tuple((v, tuple(gs)) for v, gs in + groupby(sorted(vg, key=lambda k: k[0]), key=lambda k: k[0])) + + # check whether variables are pairwise disjoint + for u, w in product(iter(v for v, _ in vgs), repeat=2): + if u != w and not set(u).isdisjoint(set(w)): + raise ValueError('The growth groups %s need to have pairwise ' + 'disjoint or equal variables.' % (growth_groups,)) + + # build cartesian products + u_groups = list() + for _, gs in vgs: + gs = tuple(g for _, g in gs) + if len(gs) > 1: + u_groups.append(UnivariateProduct(gs, category, **kwds)) + else: + u_groups.append(gs[0]) + + if len(u_groups) > 1: + m_group = MultivariateProduct(tuple(u_groups), category, **kwds) + else: + m_group = u_groups[0] + return m_group + + +CartesianProductGrowthGroups = CartesianProductFactory('CartesianProductGrowthGroups') + + +from sage.combinat.posets.cartesian_product import CartesianProductPoset +from growth_group import GenericGrowthGroup +class GenericProduct(CartesianProductPoset, GenericGrowthGroup): + r""" + A cartesian product of growth groups. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^QQ') + sage: L = GrowthGroup('log(x)^ZZ') + sage: C = cartesian_product([P, L], order='lex'); C # indirect doctest + Growth Group x^QQ * log(x)^ZZ + sage: C.an_element() + x^(1/2)*log(x) + + :: + + sage: Px = GrowthGroup('x^QQ') + sage: Lx = GrowthGroup('log(x)^ZZ') + sage: Cx = cartesian_product([Px, Lx], order='lex') # indirect doctest + sage: Py = GrowthGroup('y^QQ') + sage: C = cartesian_product([Cx, Py], order='product'); C # indirect doctest + Growth Group x^QQ * log(x)^ZZ * y^QQ + sage: C.an_element() + x^(1/2)*log(x)*y^(1/2) + + .. SEEALSO:: + + :class:`~sage.sets.cartesian_product.CartesianProduct`, + :class:`~sage.combinat.posets.cartesian_product.CartesianProductPoset`. + """ + + __classcall__ = CartesianProductPoset.__classcall__ + + + def __init__(self, sets, category, **kwds): + r""" + See :class:`GenericProduct` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ * y^ZZ') # indirect doctest + Growth Group x^ZZ * y^ZZ + """ + order = kwds.pop('order') + CartesianProductPoset.__init__(self, sets, category, order, **kwds) + + vars = sum(iter(factor.variable_names() + for factor in self.cartesian_factors()), + tuple()) + from itertools import groupby + from growth_group import Variable + Vars = Variable(tuple(v for v, _ in groupby(vars)), repr=self._repr_short_()) + + GenericGrowthGroup.__init__(self, sets[0], Vars, self.category(), **kwds) + + + __hash__ = CartesianProductPoset.__hash__ + + + def some_elements(self): + r""" + Return some elements of this cartesian product of growth groups. + + See :class:`TestSuite` for a typical use case. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from itertools import islice + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^y * x^QQ * log(x)^ZZ') + sage: tuple(islice(G.some_elements(), 10)) + (x^(1/2)*(1/2)^y, + x^(-1/2)*log(x)*(-1/2)^y, + x^2*log(x)^(-1)*2^y, + x^(-2)*log(x)^2*(-2)^y, + log(x)^(-2), + x*log(x)^3*(-1)^y, + x^(-1)*log(x)^(-3)*42^y, + x^42*log(x)^4*(2/3)^y, + x^(2/3)*log(x)^(-4)*(-2/3)^y, + x^(-2/3)*log(x)^5*(3/2)^y) + """ + from itertools import izip + return iter( + self(c) for c in + izip(*tuple(F.some_elements() for F in self.cartesian_factors()))) + + + def _create_element_in_extension_(self, element): + r""" + Create an element in an extension of this cartesian product of + growth groups which is chosen according to the input ``element``. + + INPUT: + + - ``element`` -- a tuple. + + OUTPUT: + + An element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ * log(z)^ZZ') + sage: z = G('z')[0] + sage: lz = G('log(z)')[1] + sage: G._create_element_in_extension_((z^3, lz)).parent() + Growth Group z^ZZ * log(z)^ZZ + sage: G._create_element_in_extension_((z^(1/2), lz)).parent() + Growth Group z^QQ * log(z)^ZZ + + :: + + sage: G._create_element_in_extension_((3, 3, 3)) + Traceback (most recent call last): + ... + ValueError: Cannot create (3, 3, 3) as a cartesian product like + Growth Group z^ZZ * log(z)^ZZ. + """ + factors = self.cartesian_factors() + if len(element) != len(factors): + raise ValueError('Cannot create %s as a cartesian product like %s.' % + (element, self)) + + if all(n.parent() is f for n, f in zip(element, factors)): + parent = self + else: + from misc import underlying_class + parent = underlying_class(self)(tuple(n.parent() for n in element), + category=self.category()) + return parent(element) + + + def _element_constructor_(self, data): + r""" + Converts the given object to an element of this cartesian + product. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * y^ZZ') + sage: G_log = GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ') + + Conversion from the symbolic ring works:: + + sage: x,y = var('x y') + sage: G(x^-3*y^2) + x^(-3)*y^2 + sage: G(x^4), G(y^2) + (x^4, y^2) + sage: G(1) + 1 + + Even more complex expressions can be parsed:: + + sage: G_log(x^42*log(x)^-42*y^42) + x^42*log(x)^(-42)*y^42 + + TESTS:: + + sage: G = GrowthGroup('x^ZZ * y^ZZ') + sage: G('x'), G('y') + (x, y) + + :: + + sage: G_log(log(x)) + log(x) + + :: + + sage: G(G.cartesian_factors()[0].gen()) + x + + :: + + sage: GrowthGroup('QQ^x * x^QQ')(['x^(1/2)']) + x^(1/2) + sage: l = GrowthGroup('x^ZZ * log(x)^ZZ')(['x', 'log(x)']); l + x*log(x) + sage: type(l) + + sage: GrowthGroup('QQ^x * x^QQ')(['2^log(x)']) + Traceback (most recent call last): + ... + ValueError: ['2^log(x)'] is not in Growth Group QQ^x * x^QQ. + > *previous* ValueError: 2^log(x) is not in any of the factors of + Growth Group QQ^x * x^QQ + sage: GrowthGroup('QQ^x * x^QQ')(['2^log(x)', 'x^55']) + Traceback (most recent call last): + ... + ValueError: ['2^log(x)', 'x^55'] is not in Growth Group QQ^x * x^QQ. + > *previous* ValueError: 2^log(x) is not in any of the factors of + Growth Group QQ^x * x^QQ + + TESTS:: + + sage: n = GrowthGroup('n^ZZ * log(n)^ZZ')('n') + sage: G = GrowthGroup('QQ^n * n^ZZ * log(n)^ZZ') + sage: G(n).value + (1, n, 1) + """ + def convert_factors(data, raw_data): + try: + return self._convert_factors_(data) + except ValueError as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('%s is not in %s.' % (raw_data, self)), e) + + if data == 1: + return self.one() + + elif data is None: + raise ValueError('%s cannot be converted.' % (data,)) + + elif type(data) == self.element_class and data.parent() == self: + return data + + elif isinstance(data, str): + from misc import split_str_by_op + return convert_factors(split_str_by_op(data, '*'), data) + + elif hasattr(data, 'parent'): + P = data.parent() + + if P is self: + return data + + elif P is sage.symbolic.ring.SR: + from sage.symbolic.operators import mul_vararg + if data.operator() == mul_vararg: + return convert_factors(data.operands(), data) + + # room for other parents (e.g. polynomial ring et al.) + + try: + return super(GenericProduct, self)._element_constructor_(data) + except (TypeError, ValueError): + pass + if isinstance(data, (tuple, list, + sage.sets.cartesian_product.CartesianProduct.Element)): + return convert_factors(tuple(data), data) + + return convert_factors((data,), data) + + + _repr_ = GenericGrowthGroup._repr_ + + + def _repr_short_(self): + r""" + A short (shorter than :meth:`._repr_`) representation string + for this cartesian product of growth groups. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^QQ') + sage: L = GrowthGroup('log(x)^ZZ') + sage: cartesian_product([P, L], order='lex')._repr_short_() + 'x^QQ * log(x)^ZZ' + """ + return ' * '.join(S._repr_short_() for S in self.cartesian_factors()) + + + def _convert_factors_(self, factors): + r""" + Helper method. Try to convert some ``factors`` to an + element of one of the cartesian factors and return the product of + all these factors. + + INPUT: + + - ``factors`` -- a tuple or other iterable. + + OUTPUT: + + An element of this cartesian product. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^QQ * y^QQ') + sage: e1 = G._convert_factors_([x^2]) + sage: (e1, e1.parent()) + (x^2, Growth Group x^ZZ * log(x)^QQ * y^QQ) + """ + from sage.misc.misc_c import prod + + def get_factor(data): + for factor in self.cartesian_factors(): + try: + return factor, factor(data) + except (ValueError, TypeError): + pass + raise ValueError('%s is not in any of the factors of %s' % (data, self)) + + return prod(self.cartesian_injection(*get_factor(f)) + for f in factors) + + + def cartesian_injection(self, factor, element): + r""" + Inject the given element into this cartesian product at the given factor. + + INPUT: + + - ``factor`` -- a growth group (a factor of this cartesian product). + + - ``element`` -- an element of ``factor``. + + OUTPUT: + + An element of this cartesian product. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * y^QQ') + sage: G.cartesian_injection(G.cartesian_factors()[1], 'y^7') + y^7 + """ + return self(tuple((f.one() if f != factor else element) + for f in self.cartesian_factors())) + + + def _coerce_map_from_(self, S): + r""" + Return whether ``S`` coerces into this growth group. + + INPUT: + + - ``S`` -- a parent. + + OUTPUT: + + A boolean. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: A = GrowthGroup('QQ^x * x^QQ') + sage: B = GrowthGroup('QQ^x * x^ZZ') + sage: A.has_coerce_map_from(B) # indirect doctest + True + sage: B.has_coerce_map_from(A) # indirect doctest + False + """ + if CartesianProductPoset.has_coerce_map_from(self, S): + return True + + elif isinstance(S, GenericProduct): + factors = S.cartesian_factors() + else: + factors = (S,) + + if all(any(g.has_coerce_map_from(f) for g in self.cartesian_factors()) + for f in factors): + return True + + + def _pushout_(self, other): + r""" + Construct the pushout of this and the other growth group. This is called by + :func:`sage.categories.pushout.pushout`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.categories.pushout import pushout + sage: cm = sage.structure.element.get_coercion_model() + sage: A = GrowthGroup('QQ^x * x^ZZ') + sage: B = GrowthGroup('x^ZZ * log(x)^ZZ') + sage: A._pushout_(B) + Growth Group QQ^x * x^ZZ * log(x)^ZZ + sage: pushout(A, B) + Growth Group QQ^x * x^ZZ * log(x)^ZZ + sage: cm.discover_coercion(A, B) + ((map internal to coercion system -- copy before use) + Conversion map: + From: Growth Group QQ^x * x^ZZ + To: Growth Group QQ^x * x^ZZ * log(x)^ZZ, + (map internal to coercion system -- copy before use) + Conversion map: + From: Growth Group x^ZZ * log(x)^ZZ + To: Growth Group QQ^x * x^ZZ * log(x)^ZZ) + sage: cm.common_parent(A, B) + Growth Group QQ^x * x^ZZ * log(x)^ZZ + + :: + + sage: C = GrowthGroup('QQ^x * x^QQ * y^ZZ') + sage: D = GrowthGroup('x^ZZ * log(x)^QQ * QQ^z') + sage: C._pushout_(D) + Growth Group QQ^x * x^QQ * log(x)^QQ * y^ZZ * QQ^z + sage: cm.common_parent(C, D) + Growth Group QQ^x * x^QQ * log(x)^QQ * y^ZZ * QQ^z + sage: A._pushout_(D) + Growth Group QQ^x * x^ZZ * log(x)^QQ * QQ^z + sage: cm.common_parent(A, D) + Growth Group QQ^x * x^ZZ * log(x)^QQ * QQ^z + sage: cm.common_parent(B, D) + Growth Group x^ZZ * log(x)^QQ * QQ^z + sage: cm.common_parent(A, C) + Growth Group QQ^x * x^QQ * y^ZZ + sage: E = GrowthGroup('log(x)^ZZ * y^ZZ') + sage: cm.common_parent(A, E) + Traceback (most recent call last): + ... + TypeError: no common canonical parent for objects with parents: + 'Growth Group QQ^x * x^ZZ' and 'Growth Group log(x)^ZZ * y^ZZ' + + :: + + sage: F = GrowthGroup('z^QQ') + sage: pushout(C, F) + Growth Group QQ^x * x^QQ * y^ZZ * z^QQ + + :: + + sage: pushout(GrowthGroup('QQ^x * x^ZZ'), GrowthGroup('ZZ^x * x^QQ')) + Growth Group QQ^x * x^QQ + sage: cm.common_parent(GrowthGroup('QQ^x * x^ZZ'), GrowthGroup('ZZ^x * x^QQ')) + Growth Group QQ^x * x^QQ + """ + from growth_group import GenericGrowthGroup, AbstractGrowthGroupFunctor + from misc import merge_overlapping + from misc import underlying_class + + if isinstance(other, GenericProduct): + Ofactors = other.cartesian_factors() + elif isinstance(other, GenericGrowthGroup): + Ofactors = (other,) + elif (other.construction() is not None and + isinstance(other.construction()[0], AbstractGrowthGroupFunctor)): + Ofactors = (other,) + else: + return + + def pushout_univariate_factors(self, other, var, Sfactors, Ofactors): + try: + return merge_overlapping( + Sfactors, Ofactors, + lambda f: (underlying_class(f), f._var_.var_repr)) + except ValueError: + pass + + cm = sage.structure.element.get_coercion_model() + try: + Z = cm.common_parent(*Sfactors+Ofactors) + return (Z,), (Z,) + except TypeError: + pass + + def subfactors(F): + for f in F: + if isinstance(f, GenericProduct): + for g in subfactors(f.cartesian_factors()): + yield g + else: + yield f + + try: + return merge_overlapping( + tuple(subfactors(Sfactors)), tuple(subfactors(Ofactors)), + lambda f: (underlying_class(f), f._var_.var_repr)) + except ValueError: + pass + + from sage.structure.coerce_exceptions import CoercionException + raise CoercionException( + 'Cannot construct the pushout of %s and %s: The factors ' + 'with variables %s are not overlapping, ' + 'no common parent was found, and ' + 'splitting the factors was unsuccessful.' % (self, other, var)) + + + class it: + def __init__(self, it): + self.it = it + self.var = None + self.factors = None + def next(self): + try: + self.var, factors = next(self.it) + self.factors = tuple(factors) + except StopIteration: + self.var = None + self.factors = tuple() + + from itertools import groupby + S = it(groupby(self.cartesian_factors(), key=lambda k: k.variable_names())) + O = it(groupby(Ofactors, key=lambda k: k.variable_names())) + + newS = [] + newO = [] + + S.next() + O.next() + while S.var is not None or O.var is not None: + if S.var is not None and S.var < O.var: + newS.extend(S.factors) + newO.extend(S.factors) + S.next() + elif O.var is not None and S.var > O.var: + newS.extend(O.factors) + newO.extend(O.factors) + O.next() + else: + SL, OL = pushout_univariate_factors(self, other, S.var, + S.factors, O.factors) + newS.extend(SL) + newO.extend(OL) + S.next() + O.next() + + assert(len(newS) == len(newO)) + + if (len(self.cartesian_factors()) == len(newS) and + len(other.cartesian_factors()) == len(newO)): + # We had already all factors in each of the self and + # other, thus splitting it in subproblems (one for + # each factor) is the strategy to use. If a pushout is + # possible :func:`sage.categories.pushout.pushout` + # will manage this by itself. + return + + from sage.categories.pushout import pushout + from sage.categories.cartesian_product import cartesian_product + return pushout(cartesian_product(newS), cartesian_product(newO)) + + + def gens_monomial(self): + r""" + Return a tuple containing monomial generators of this growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A tuple containing elements of this growth group. + + .. NOTE:: + + This method calls the ``gens_monomial()`` method on the + individual factors of this cartesian product and + concatenates the respective outputs. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ * y^QQ * log(z)^ZZ') + sage: G.gens_monomial() + (x, y) + + TESTS:: + + sage: all(g.parent() == G for g in G.gens_monomial()) + True + """ + return sum(iter( + tuple(self.cartesian_injection(factor, g) for g in factor.gens_monomial()) + for factor in self.cartesian_factors()), + tuple()) + + + def variable_names(self): + r""" + Return the names of the variables. + + OUTPUT: + + A tuple of strings. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ * log(x)^ZZ * y^QQ * log(z)^ZZ').variable_names() + ('x', 'y', 'z') + """ + vars = sum(iter(factor.variable_names() + for factor in self.cartesian_factors()), + tuple()) + from itertools import groupby + return tuple(v for v, _ in groupby(vars)) + + + class Element(CartesianProductPoset.Element): + + from growth_group import _is_lt_one_ + is_lt_one = _is_lt_one_ + + + def _repr_(self): + r""" + A representation string for this cartesian product element. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^QQ') + sage: L = GrowthGroup('log(x)^ZZ') + sage: cartesian_product([P, L], order='lex').an_element()._repr_() + 'x^(1/2)*log(x)' + """ + s = '*'.join(repr(v) for v in self.value if not v.is_one()) + if s == '': + return '1' + return s + + + def __pow__(self, exponent): + r""" + Calculate the power of this growth element to the given + ``exponent``. + + INPUT: + + - ``exponent`` -- a number. + + OUTPUT: + + A growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * y^QQ * z^ZZ') + sage: x, y, z = G.gens_monomial() + sage: (x^5 * y * z^5)^(1/5) # indirect doctest + x*y^(1/5)*z + + :: + + sage: G = GrowthGroup('x^QQ * log(x)^QQ'); x = G('x') + sage: (x^(21/5) * log(x)^7)^(1/42) # indirect doctest + x^(1/10)*log(x)^(1/6) + """ + return self.parent()._create_element_in_extension_( + tuple(x ** exponent for x in self.cartesian_factors())) + + + def factors(self): + r""" + Return the atomic factors of this growth element. An atomic factor + cannot be split further and is not the identity (`1`). + + INPUT: + + Nothing. + + OUTPUT: + + A tuple of growth elements. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ') + sage: x, y = G.gens_monomial() + sage: x.factors() + (x,) + sage: f = (x * y).factors(); f + (x, y) + sage: tuple(factor.parent() for factor in f) + (Growth Group x^ZZ, Growth Group y^ZZ) + sage: f = (x * log(x)).factors(); f + (x, log(x)) + sage: tuple(factor.parent() for factor in f) + (Growth Group x^ZZ, Growth Group log(x)^ZZ) + + :: + + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ * log(log(x))^ZZ * y^QQ') + sage: x, y = G.gens_monomial() + sage: f = (x * log(x) * y).factors(); f + (x, log(x), y) + sage: tuple(factor.parent() for factor in f) + (Growth Group x^ZZ, Growth Group log(x)^ZZ, Growth Group y^QQ) + + :: + + sage: G.one().factors() + () + """ + return sum(iter(f.factors() + for f in self.cartesian_factors() + if f != f.parent().one()), + tuple()) + + + from growth_group import _log_factor_, _log_ + log = _log_ + log_factor = _log_factor_ + + + def _log_factor_(self, base=None): + r""" + Helper method for calculating the logarithm of the factorization + of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is either a growth + element or something out of which we can construct a growth element + and the second a multiplicative coefficient. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ') + sage: x, y = G.gens_monomial() + sage: (x * y).log_factor() # indirect doctest + ((log(x), 1), (log(y), 1)) + """ + if self.is_one(): + return tuple() + + def try_create_growth(g): + try: + return self.parent()(g) + except (TypeError, ValueError): + return g + + try: + return sum(iter(tuple((try_create_growth(g), c) + for g, c in factor._log_factor_(base=base)) + for factor in self.cartesian_factors() + if factor != factor.parent().one()), + tuple()) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ArithmeticError('Cannot build log(%s) in %s.' % + (self, self.parent())), e) + + + from growth_group import _rpow_ + rpow = _rpow_ + + + def _rpow_element_(self, base): + r""" + Return an element which is the power of ``base`` to this + element. + + INPUT: + + - ``base`` -- an element. + + OUTPUT: + + A growth element. + + .. NOTE:: + + The parent of the result can be different from the parent + of this element. + + A ``ValueError`` is raised if the calculation is not possible + within this method. (Then the calling method should take care + of the calculation.) + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ') + sage: lx = log(G('x')) + sage: rp = lx._rpow_element_('e'); rp + x + sage: rp.parent() + Growth Group x^ZZ + """ + factors = self.factors() + if len(factors) != 1: + raise ValueError # calling method has to deal with it... + from growth_group import MonomialGrowthGroup + factor = factors[0] + if not isinstance(factor.parent(), MonomialGrowthGroup): + raise ValueError # calling method has to deal with it... + return factor._rpow_element_(base) + + + def exp(self): + r""" + The exponential of this element. + + INPUT: + + Nothing. + + OUTPUT: + + A growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ * log(log(x))^ZZ') + sage: x = G('x') + sage: exp(log(x)) + x + sage: exp(log(log(x))) + log(x) + + :: + + sage: exp(x) + Traceback (most recent call last): + ... + ArithmeticError: Cannot construct e^x in + Growth Group x^ZZ * log(x)^ZZ * log(log(x))^ZZ + > *previous* TypeError: unsupported operand parent(s) for '*': + 'Growth Group x^ZZ * log(x)^ZZ * log(log(x))^ZZ' and + 'Growth Group (e^x)^ZZ' + + TESTS:: + + sage: E = GrowthGroup("(e^y)^QQ * y^QQ * log(y)^QQ") + sage: y = E('y') + sage: log(exp(y)) + y + sage: exp(log(y)) + y + """ + return self.rpow('e') + + + def __invert__(self): + r""" + Return the multiplicative inverse of this cartesian product. + + OUTPUT: + + An growth element. + + .. NOTE:: + + The result may live in a larger parent than we started with. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('ZZ^x * x^ZZ') + sage: g = G('2^x * x^3') + sage: (~g).parent() + Growth Group QQ^x * x^ZZ + """ + return self.parent()._create_element_in_extension_( + tuple(~x for x in self.cartesian_factors())) + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this + cartesian product growth element. + + INPUT: + + - ``rules`` -- a dictionary. + The neutral element of the group is replaced by the value + to key ``'_one_'``. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^QQ * log(x)^QQ') + sage: G(x^3 * log(x)^5)._substitute_({'x': SR.var('z')}) + z^3*log(z)^5 + sage: _.parent() + Symbolic Ring + sage: G(x^3 * log(x)^5)._substitute_({'x': 2.2}) # rel tol 1e-6 + 3.24458458945 + sage: _.parent() + Real Field with 53 bits of precision + sage: G(1 / x)._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in x^(-1) in + Growth Group x^QQ * log(x)^QQ. + > *previous* ZeroDivisionError: Cannot substitute in x^(-1) in + Growth Group x^QQ. + >> *previous* ZeroDivisionError: rational division by zero + sage: G(1)._substitute_({'_one_': 'one'}) + 'one' + """ + if self.is_one(): + return rules['_one_'] + from sage.symbolic.operators import mul_vararg + try: + return mul_vararg( + *tuple(x._substitute_(rules) + for x in self.cartesian_factors())) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + + CartesianProduct = CartesianProductGrowthGroups + + +class UnivariateProduct(GenericProduct): + r""" + A cartesian product of growth groups with the same variables. + + .. NOTE:: + + A univariate product of growth groups is ordered + lexicographically. This is motivated by the assumption + that univariate growth groups can be ordered in a chain + with respect to the growth they model (e.g. + ``x^ZZ * log(x)^ZZ``: polynomial growth dominates + logarithmic growth). + + .. SEEALSO:: + + :class:`MultivariateProduct`, + :class:`GenericProduct`. + """ + + def __init__(self, sets, category, **kwargs): + r""" + See :class:`UnivariateProduct` for details. + + TEST:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: type(GrowthGroup('x^ZZ * log(x)^ZZ')) # indirect doctest + + """ + super(UnivariateProduct, self).__init__( + sets, category, order='lex', **kwargs) + + + CartesianProduct = CartesianProductGrowthGroups + + +class MultivariateProduct(GenericProduct): + r""" + A cartesian product of growth groups with pairwise disjoint + (or equal) variable sets. + + .. NOTE:: + + A multivariate product of growth groups is ordered by + means of the product order, i.e. component-wise. This is + motivated by the assumption that different variables are + considered to be independent (e.g. ``x^ZZ * y^ZZ``). + + .. SEEALSO:: + + :class:`UnivariateProduct`, + :class:`GenericProduct`. + """ + def __init__(self, sets, category, **kwargs): + r""" + + TEST:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: type(GrowthGroup('x^ZZ * y^ZZ')) # indirect doctest + + """ + super(MultivariateProduct, self).__init__( + sets, category, order='product', **kwargs) + + + CartesianProduct = CartesianProductGrowthGroups diff --git a/src/sage/rings/asymptotic/misc.py b/src/sage/rings/asymptotic/misc.py new file mode 100644 index 00000000000..0b142d7698d --- /dev/null +++ b/src/sage/rings/asymptotic/misc.py @@ -0,0 +1,693 @@ +r""" +Asymptotic Expansions --- Miscellaneous + +AUTHORS: + +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + + +Functions, Classes and Methods +============================== +""" + +#***************************************************************************** +# Copyright (C) 2015 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 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import sage + + +def repr_short_to_parent(s): + r""" + Helper method for the growth group factory, which converts a short + representation string to a parent. + + INPUT: + + - ``s`` -- a string, short representation of a parent. + + OUTPUT: + + A parent. + + The possible short representations are shown in the examples below. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import repr_short_to_parent + sage: repr_short_to_parent('ZZ') + Integer Ring + sage: repr_short_to_parent('QQ') + Rational Field + sage: repr_short_to_parent('SR') + Symbolic Ring + sage: repr_short_to_parent('NN') + Non negative integer semiring + + TESTS:: + + sage: repr_short_to_parent('abcdef') + Traceback (most recent call last): + ... + ValueError: Cannot create a parent out of 'abcdef'. + > *previous* NameError: name 'abcdef' is not defined + """ + from sage.misc.sage_eval import sage_eval + try: + P = sage_eval(s) + except Exception as e: + raise combine_exceptions( + ValueError("Cannot create a parent out of '%s'." % (s,)), e) + + from sage.misc.lazy_import import LazyImport + if type(P) is LazyImport: + P = P._get_object() + + from sage.structure.parent import is_Parent + if not is_Parent(P): + raise ValueError("'%s' does not describe a parent." % (s,)) + return P + + +def parent_to_repr_short(P): + r""" + Helper method which generates a short(er) representation string + out of a parent. + + INPUT: + + - ``P`` -- a parent. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import parent_to_repr_short + sage: parent_to_repr_short(ZZ) + 'ZZ' + sage: parent_to_repr_short(QQ) + 'QQ' + sage: parent_to_repr_short(SR) + 'SR' + sage: parent_to_repr_short(ZZ['x']) + 'ZZ[x]' + sage: parent_to_repr_short(QQ['d, k']) + '(QQ[d, k])' + sage: parent_to_repr_short(QQ['e']) + 'QQ[e]' + sage: parent_to_repr_short(SR[['a, r']]) + '(SR[[a, r]])' + sage: parent_to_repr_short(Zmod(3)) + '(Ring of integers modulo 3)' + sage: parent_to_repr_short(Zmod(3)['g']) + '(Univariate Polynomial Ring in g over Ring of integers modulo 3)' + """ + def abbreviate(P): + if P is sage.rings.integer_ring.ZZ: + return 'ZZ' + elif P is sage.rings.rational_field.QQ: + return 'QQ' + elif P is sage.symbolic.ring.SR: + return 'SR' + raise ValueError('Cannot abbreviate %s.' % (P,)) + + poly = sage.rings.polynomial.polynomial_ring.is_PolynomialRing(P) or \ + sage.rings.polynomial.multi_polynomial_ring_generic.is_MPolynomialRing(P) + from sage.rings import multi_power_series_ring + power = sage.rings.power_series_ring.is_PowerSeriesRing(P) or \ + multi_power_series_ring.is_MPowerSeriesRing(P) + + if poly or power: + if poly: + op, cl = ('[', ']') + else: + op, cl = ('[[', ']]') + try: + s = abbreviate(P.base_ring()) + op + ', '.join(P.variable_names()) + cl + except ValueError: + s = str(P) + else: + try: + s = abbreviate(P) + except ValueError: + s = str(P) + + if ' ' in s: + s = '(' + s + ')' + return s + + +def split_str_by_op(string, op, strip_parentheses=True): + r""" + Split the given string into a tuple of substrings arising by + splitting by ``op`` and taking care of parentheses. + + INPUT: + + - ``string`` -- a string. + + - ``op`` -- a string. This is used by + :python:`str.split `. + Thus, if this is ``None``, then any whitespace string is a + separator and empty strings are removed from the result. + + - ``strip_parentheses`` -- (default: ``True``) a boolean. + + OUTPUT: + + A tuple of strings. + + TESTS:: + + sage: from sage.rings.asymptotic.misc import split_str_by_op + sage: split_str_by_op('x^ZZ', '*') + ('x^ZZ',) + sage: split_str_by_op('log(x)^ZZ * y^QQ', '*') + ('log(x)^ZZ', 'y^QQ') + sage: split_str_by_op('log(x)**ZZ * y**QQ', '*') + ('log(x)**ZZ', 'y**QQ') + sage: split_str_by_op('a^b * * c^d', '*') + Traceback (most recent call last): + ... + ValueError: 'a^b * * c^d' is invalid since a '*' follows a '*'. + sage: split_str_by_op('a^b * (c*d^e)', '*') + ('a^b', 'c*d^e') + + :: + + sage: split_str_by_op('(a^b)^c', '^') + ('a^b', 'c') + sage: split_str_by_op('a^(b^c)', '^') + ('a', 'b^c') + + :: + + sage: split_str_by_op('(a) + (b)', op='+', strip_parentheses=True) + ('a', 'b') + sage: split_str_by_op('(a) + (b)', op='+', strip_parentheses=False) + ('(a)', '(b)') + sage: split_str_by_op(' ( t ) ', op='+', strip_parentheses=False) + ('( t )',) + + :: + + sage: split_str_by_op(' ( t ) ', op=None) + ('t',) + sage: split_str_by_op(' ( t )s', op=None) + ('(t)s',) + sage: split_str_by_op(' ( t ) s', op=None) + ('t', 's') + """ + factors = list() + balanced = True + if string and op is not None and string.startswith(op): + raise ValueError("'%s' is invalid since it starts with a '%s'." % + (string, op)) + for s in string.split(op): + if not s: + factors[-1] += op + balanced = False + continue + if not s.strip(): + raise ValueError("'%s' is invalid since a '%s' follows a '%s'." % + (string, op, op)) + if not balanced: + s = factors.pop() + (op if op else '') + s + balanced = s.count('(') == s.count(')') + factors.append(s) + + if not balanced: + raise ValueError("Parentheses in '%s' are not balanced." % (string,)) + + def strip(s): + s = s.strip() + if not s: + return s + if strip_parentheses and s[0] == '(' and s[-1] == ')': + s = s[1:-1] + return s.strip() + + return tuple(strip(f) for f in factors) + + +def repr_op(left, op, right=None): + r""" + Create a string ``left op right`` with + taking care of parentheses in its operands. + + INPUT: + + - ``left`` -- an element. + + - ``op`` -- a string. + + - ``right`` -- an alement. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import repr_op + sage: repr_op('a^b', '^', 'c') + '(a^b)^c' + + TESTS:: + + sage: repr_op('a-b', '^', 'c') + '(a-b)^c' + sage: repr_op('a+b', '^', 'c') + '(a+b)^c' + """ + left = str(left) + right = str(right) if right is not None else '' + + def add_parentheses(s, op): + if op == '^': + signals = ('^', '/', '*', '-', '+', ' ') + else: + return s + if any(sig in s for sig in signals): + return '(%s)' % (s,) + else: + return s + + return add_parentheses(left, op) + op +\ + add_parentheses(right, op) + + +def combine_exceptions(e, *f): + r""" + Helper function which combines the messages of the given exceptions. + + INPUT: + + - ``e`` -- an exception. + + - ``*f`` -- exceptions. + + OUTPUT: + + An exception. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import combine_exceptions + sage: raise combine_exceptions(ValueError('Outer.'), TypeError('Inner.')) + Traceback (most recent call last): + ... + ValueError: Outer. + > *previous* TypeError: Inner. + sage: raise combine_exceptions(ValueError('Outer.'), + ....: TypeError('Inner1.'), TypeError('Inner2.')) + Traceback (most recent call last): + ... + ValueError: Outer. + > *previous* TypeError: Inner1. + > *and* TypeError: Inner2. + sage: raise combine_exceptions(ValueError('Outer.'), + ....: combine_exceptions(TypeError('Middle.'), + ....: TypeError('Inner.'))) + Traceback (most recent call last): + ... + ValueError: Outer. + > *previous* TypeError: Middle. + >> *previous* TypeError: Inner. + """ + import re + msg = ('\n *previous* ' + + '\n *and* '.join("%s: %s" % (ff.__class__.__name__, str(ff)) for ff in f)) + msg = re.sub(r'^([>]* \*previous\*)', r'>\1', msg, flags=re.MULTILINE) + msg = re.sub(r'^([>]* \*and\*)', r'>\1', msg, flags=re.MULTILINE) + msg = str(e.args if len(e.args) > 1 else e.args[0]) + msg + e.args = (msg,) + return e + + +def substitute_raise_exception(element, e): + r""" + Raise an error describing what went wrong with the substitution. + + INPUT: + + - ``element`` -- an element. + + - ``e`` -- an exception which is included in the raised error + message. + + OUTPUT: + + Raise an exception of the same type as ``e``. + + TESTS:: + + sage: from sage.rings.asymptotic.misc import substitute_raise_exception + sage: substitute_raise_exception(x, Exception('blub')) + Traceback (most recent call last): + ... + Exception: Cannot substitute in x in Symbolic Ring. + > *previous* Exception: blub + """ + raise combine_exceptions( + type(e)('Cannot substitute in %s in %s.' % + (element, element.parent())), e) + + +def underlying_class(P): + r""" + Return the underlying class (class without the attached + categories) of the given instance. + + OUTPUT: + + A class. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import underlying_class + sage: type(QQ) + + sage: underlying_class(QQ) + + """ + cls = type(P) + if not hasattr(P, '_is_category_initialized') or not P._is_category_initialized(): + return cls + from sage.structure.misc import is_extension_type + if is_extension_type(cls): + return cls + + from sage.categories.sets_cat import Sets + Sets_parent_class = Sets().parent_class + while issubclass(cls, Sets_parent_class): + cls = cls.__base__ + return cls + + +def merge_overlapping(A, B, key=None): + r""" + Merge the two overlapping tuples/lists. + + INPUT: + + - ``A`` -- a list or tuple (type has to coincide with type of ``B``). + + - ``B`` -- a list or tuple (type has to coincide with type of ``A``). + + - ``key`` -- (default: ``None``) a function. If ``None``, then the + identity is used. This ``key``-function applied on an element + of the list/tuple is used for comparison. Thus elements with the + same key are considered as equal. + + OUTPUT: + + A pair of lists or tuples (depending on the type of ``A`` and ``B``). + + .. NOTE:: + + Suppose we can decompose the list `A=ac` and `B=cb` with + lists `a`, `b`, `c`, where `c` is nonempty. Then + :func:`merge_overlapping` returns the pair `(acb, acb)`. + + Suppose a ``key``-function is specified and `A=ac_A` and + `B=c_Bb`, where the list of keys of the elements of `c_A` + equals the list of keys of the elements of `c_B`. Then + :func:`merge_overlapping` returns the pair `(ac_Ab, ac_Bb)`. + + After unsuccessfully merging `A=ac` and `B=cb`, + a merge of `A=ca` and `B=bc` is tried. + + TESTS:: + + sage: from sage.rings.asymptotic.misc import merge_overlapping + sage: def f(L, s): + ....: return list((ell, s) for ell in L) + sage: key = lambda k: k[0] + sage: merge_overlapping(f([0..3], 'a'), f([5..7], 'b'), key) + Traceback (most recent call last): + ... + ValueError: Input does not have an overlap. + sage: merge_overlapping(f([0..2], 'a'), f([4..7], 'b'), key) + Traceback (most recent call last): + ... + ValueError: Input does not have an overlap. + sage: merge_overlapping(f([4..7], 'a'), f([0..2], 'b'), key) + Traceback (most recent call last): + ... + ValueError: Input does not have an overlap. + sage: merge_overlapping(f([0..3], 'a'), f([3..4], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a'), (4, 'b')], + [(0, 'a'), (1, 'a'), (2, 'a'), (3, 'b'), (4, 'b')]) + sage: merge_overlapping(f([3..4], 'a'), f([0..3], 'b'), key) + ([(0, 'b'), (1, 'b'), (2, 'b'), (3, 'a'), (4, 'a')], + [(0, 'b'), (1, 'b'), (2, 'b'), (3, 'b'), (4, 'a')]) + sage: merge_overlapping(f([0..1], 'a'), f([0..4], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'b'), (3, 'b'), (4, 'b')], + [(0, 'b'), (1, 'b'), (2, 'b'), (3, 'b'), (4, 'b')]) + sage: merge_overlapping(f([0..3], 'a'), f([0..1], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a')], + [(0, 'b'), (1, 'b'), (2, 'a'), (3, 'a')]) + sage: merge_overlapping(f([0..3], 'a'), f([1..3], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a')], + [(0, 'a'), (1, 'b'), (2, 'b'), (3, 'b')]) + sage: merge_overlapping(f([1..3], 'a'), f([0..3], 'b'), key) + ([(0, 'b'), (1, 'a'), (2, 'a'), (3, 'a')], + [(0, 'b'), (1, 'b'), (2, 'b'), (3, 'b')]) + sage: merge_overlapping(f([0..6], 'a'), f([3..4], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a'), (4, 'a'), (5, 'a'), (6, 'a')], + [(0, 'a'), (1, 'a'), (2, 'a'), (3, 'b'), (4, 'b'), (5, 'a'), (6, 'a')]) + sage: merge_overlapping(f([0..3], 'a'), f([1..2], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a')], + [(0, 'a'), (1, 'b'), (2, 'b'), (3, 'a')]) + sage: merge_overlapping(f([1..2], 'a'), f([0..3], 'b'), key) + ([(0, 'b'), (1, 'a'), (2, 'a'), (3, 'b')], + [(0, 'b'), (1, 'b'), (2, 'b'), (3, 'b')]) + sage: merge_overlapping(f([1..3], 'a'), f([1..3], 'b'), key) + ([(1, 'a'), (2, 'a'), (3, 'a')], + [(1, 'b'), (2, 'b'), (3, 'b')]) + """ + if key is None: + Akeys = A + Bkeys = B + else: + Akeys = tuple(key(a) for a in A) + Bkeys = tuple(key(b) for b in B) + + def find_overlapping_index(A, B): + if len(B) > len(A) - 2: + raise StopIteration + matches = iter(i for i in xrange(1, len(A) - len(B)) + if A[i:i+len(B)] == B) + return next(matches) + + def find_mergedoverlapping_index(A, B): + """ + Return in index i where to merge two overlapping tuples/lists ``A`` and ``B``. + + Then ``A + B[i:]`` or ``A[:-i] + B`` are the merged tuples/lists. + + Adapted from http://stackoverflow.com/a/30056066/1052778. + """ + matches = iter(i for i in xrange(min(len(A), len(B)), 0, -1) + if A[-i:] == B[:i]) + return next(matches, 0) + + i = find_mergedoverlapping_index(Akeys, Bkeys) + if i > 0: + return A + B[i:], A[:-i] + B + + i = find_mergedoverlapping_index(Bkeys, Akeys) + if i > 0: + return B[:-i] + A, B + A[i:] + + try: + i = find_overlapping_index(Akeys, Bkeys) + except StopIteration: + pass + else: + return A, A[:i] + B + A[i+len(B):] + + try: + i = find_overlapping_index(Bkeys, Akeys) + except StopIteration: + pass + else: + return B[:i] + A + B[i+len(A):], B + + raise ValueError('Input does not have an overlap.') + + +def log_string(element, base=None): + r""" + Return a representation of the log of the given element to the + given base. + + INPUT: + + - ``element`` -- an object. + + - ``base`` -- an object or ``None``. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import log_string + sage: log_string(3) + 'log(3)' + sage: log_string(3, base=42) + 'log(3, base=42)' + """ + basestr = ', base=' + str(base) if base else '' + return 'log(%s%s)' % (element, basestr) + + +def transform_category(category, + subcategory_mapping, axiom_mapping, + initial_category=None): + r""" + Transform ``category`` to a new category according to the given + mappings. + + INPUT: + + - ``category`` -- a category. + + - ``subcategory_mapping`` -- a list (or other iterable) of triples + ``(from, to, mandatory)``, where + + - ``from`` and ``to`` are categories and + - ``mandatory`` is a boolean. + + - ``axiom_mapping`` -- a list (or other iterable) of triples + ``(from, to, mandatory)``, where + + - ``from`` and ``to`` are strings describing axioms and + - ``mandatory`` is a boolean. + + - ``initial_category`` -- (default: ``None``) a category. When + transforming the given category, this ``initial_category`` is + used as a starting point of the result. This means the resulting + category will be a subcategory of ``initial_category``. + If ``initial_category`` is ``None``, then the + :class:`category of objects ` + is used. + + OUTPUT: + + A category. + + .. NOTE:: + + Consider a subcategory mapping ``(from, to, mandatory)``. If + ``category`` is a subcategory of ``from``, then the + returned category will be a subcategory of ``to``. Otherwise and + if ``mandatory`` is set, then an error is raised. + + Consider an axiom mapping ``(from, to, mandatory)``. If + ``category`` is has axiom ``from``, then the + returned category will have axiom ``to``. Otherwise and + if ``mandatory`` is set, then an error is raised. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import transform_category + sage: from sage.categories.additive_semigroups import AdditiveSemigroups + sage: from sage.categories.additive_monoids import AdditiveMonoids + sage: from sage.categories.additive_groups import AdditiveGroups + sage: S = [ + ....: (Sets(), Sets(), True), + ....: (Posets(), Posets(), False), + ....: (AdditiveMagmas(), Magmas(), False)] + sage: A = [ + ....: ('AdditiveAssociative', 'Associative', False), + ....: ('AdditiveUnital', 'Unital', False), + ....: ('AdditiveInverse', 'Inverse', False), + ....: ('AdditiveCommutative', 'Commutative', False)] + sage: transform_category(Objects(), S, A) + Traceback (most recent call last): + ... + ValueError: Category of objects is not + a subcategory of Category of sets. + sage: transform_category(Sets(), S, A) + Category of sets + sage: transform_category(Posets(), S, A) + Category of posets + sage: transform_category(AdditiveSemigroups(), S, A) + Category of semigroups + sage: transform_category(AdditiveMonoids(), S, A) + Category of monoids + sage: transform_category(AdditiveGroups(), S, A) + Category of groups + sage: transform_category(AdditiveGroups().AdditiveCommutative(), S, A) + Category of commutative groups + + :: + + sage: transform_category(AdditiveGroups().AdditiveCommutative(), S, A, + ....: initial_category=Posets()) + Join of Category of commutative groups + and Category of posets + + :: + + sage: transform_category(ZZ.category(), S, A) + Category of commutative groups + sage: transform_category(QQ.category(), S, A) + Category of commutative groups + sage: transform_category(SR.category(), S, A) + Category of commutative groups + sage: transform_category(Fields(), S, A) + Category of commutative groups + sage: transform_category(ZZ['t'].category(), S, A) + Category of commutative groups + + :: + + sage: A[-1] = ('Commutative', 'AdditiveCommutative', True) + sage: transform_category(Groups(), S, A) + Traceback (most recent call last): + ... + ValueError: Category of groups does not have + axiom Commutative. + """ + if initial_category is None: + from sage.categories.objects import Objects + result = Objects() + else: + result = initial_category + + for A, B, mandatory in subcategory_mapping: + if category.is_subcategory(A): + result &= B + elif mandatory: + raise ValueError('%s is not a subcategory of %s.' % + (category, A)) + + axioms = category.axioms() + for A, B, mandatory in axiom_mapping: + if A in axioms: + result = result._with_axiom(B) + elif mandatory: + raise ValueError('%s does not have axiom %s.' % + (category, A)) + + return result diff --git a/src/sage/rings/asymptotic/term_monoid.py b/src/sage/rings/asymptotic/term_monoid.py index b32f2f69911..0d18e1d900b 100644 --- a/src/sage/rings/asymptotic/term_monoid.py +++ b/src/sage/rings/asymptotic/term_monoid.py @@ -1,14 +1,14 @@ r""" -Asymptotic Term Monoid +(Asymptotic) Term Monoids This module implements asymptotic term monoids. The elements of these monoids are used behind the scenes when performing calculations in an -asymptotic ring (to be implemented). +:doc:`asymptotic ring `. The monoids build upon the (asymptotic) growth groups. While growth elements only model the growth of a function as it tends towards infinity (or tends towards another fixed point; see -:mod:`~sage.rings.asymptotic.growth_group` for more details), an +:doc:`growth_group` for more details), an asymptotic term additionally specifies its "type" and performs the actual arithmetic operations (multiplication and partial addition/absorption of terms). @@ -21,7 +21,15 @@ - :class:`TermWithCoefficient` -- abstract base class for asymptotic terms with coefficients. - :class:`ExactTerm` -- this class represents a growth element - multiplied with some non-zero coefficient from a base ring. + multiplied with some non-zero coefficient from a coefficient ring. + +A characteristic property of asymptotic terms is that some terms are +able to "absorb" other terms (see +:meth:`~sage.rings.asymptotic.term_monoid.GenericTerm.absorb`). For +instance, `O(x^2)` is able to absorb `O(x)` (with result +`O(x^2)`), and `3\cdot x^5` is able to absorb `-2\cdot x^5` (with result +`x^5`). Essentially, absorption can be interpreted as the +addition of "compatible" terms (partial addition). .. WARNING:: @@ -31,14 +39,9 @@ TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.MonomialGrowthGroup(ZZ, 'x') - 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/17601 for details. - sage: T = atm.TermWithCoefficientMonoid(G, ZZ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ') doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. @@ -66,11 +69,11 @@ by which other terms. We start by defining the necessary term monoids and some terms:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: OT = atm.OTermMonoid(growth_group=G) - sage: ET = atm.ExactTermMonoid(growth_group=G, base_ring=QQ) + sage: from sage.rings.asymptotic.term_monoid import OTermMonoid, ExactTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = OTermMonoid(growth_group=G, coefficient_ring=QQ) + sage: ET = ExactTermMonoid(growth_group=G, coefficient_ring=QQ) sage: ot1 = OT(x); ot2 = OT(x^2) sage: et1 = ET(x^2, 2) @@ -172,6 +175,7 @@ Long story short: we consider terms with different coefficients that have equal growth to be incomparable. + Various ======= @@ -182,11 +186,24 @@ AUTHORS: -- Benjamin Hackl (2015-01): initial version -- Benjamin Hackl, Daniel Krenn (2015-05): conception of the asymptotic ring -- Benjamin Hackl (2015-06): refactoring caused by refactoring growth groups -- Daniel Krenn (2015-07): extensive review and patches -- Benjamin Hackl (2015-07): cross-review; short notation +- Benjamin Hackl (2015) +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + Classes and Methods =================== @@ -211,7 +228,7 @@ def absorption(left, right): Let one of the two passed terms absorb the other. Helper function used by - :class:`~sage.rings.asymptotic_ring.AsymptoticExpression`. + :class:`~sage.rings.asymptotic.asymptotic_ring.AsymptoticExpansion`. .. NOTE:: @@ -234,19 +251,18 @@ def absorption(left, right): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: T = atm.TermMonoid('O', G) - sage: atm.absorption(T(x^2), T(x^3)) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (TermMonoid, absorption) + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), ZZ) + sage: absorption(T(x^2), T(x^3)) O(x^3) - sage: atm.absorption(T(x^3), T(x^2)) + sage: absorption(T(x^3), T(x^2)) O(x^3) :: - sage: T = atm.TermMonoid('exact', G, ZZ) - sage: atm.absorption(T(x^2), T(x^3)) + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ'), ZZ) + sage: absorption(T(x^2), T(x^3)) Traceback (most recent call last): ... ArithmeticError: Absorption between x^2 and x^3 is not possible. @@ -266,7 +282,7 @@ def can_absorb(left, right): other. Helper function used by - :class:`~sage.rings.asymptotic_ring.AsymptoticExpression`. + :class:`~sage.rings.asymptotic.asymptotic_ring.AsymptoticExpansion`. INPUT: @@ -285,13 +301,12 @@ def can_absorb(left, right): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: T = atm.TermMonoid('O', G) - sage: atm.can_absorb(T(x^2), T(x^3)) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (TermMonoid, can_absorb) + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), ZZ) + sage: can_absorb(T(x^2), T(x^3)) True - sage: atm.can_absorb(T(x^3), T(x^2)) + sage: can_absorb(T(x^3), T(x^2)) True """ return left.can_absorb(right) or right.can_absorb(left) @@ -310,10 +325,10 @@ class GenericTerm(sage.structure.element.MonoidElement): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) sage: t1 = T(x); t2 = T(x^2); (t1, t2) (Generic Term with growth x, Generic Term with growth x^2) sage: t1 * t2 @@ -334,42 +349,38 @@ def __init__(self, parent, growth): TESTS:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, ZZ) sage: T(x^2) Generic Term with growth x^2 :: - sage: atm.GenericTerm(parent=None, growth=x) + sage: from sage.rings.asymptotic.term_monoid import GenericTerm + sage: GenericTerm(parent=None, growth=x) Traceback (most recent call last): ... ValueError: The parent must be provided - sage: atm.GenericTerm(T, agg.GrowthGroup('y^ZZ').gen()) + sage: GenericTerm(T, GrowthGroup('y^ZZ').gen()) Traceback (most recent call last): ... - ValueError: y is not in the parent's specified growth group + ValueError: y is not in Growth Group x^ZZ """ - from sage.rings.asymptotic.growth_group import GenericGrowthElement - if parent is None: raise ValueError('The parent must be provided') - if growth is None or not isinstance(growth, GenericGrowthElement): - raise ValueError('The growth must be provided and must inherit ' - 'from GenericGrowthElement') - elif growth not in parent.growth_group: - raise ValueError("%s is not in the parent's " - "specified growth group" % growth) - - self.growth = growth + try: + self.growth = parent.growth_group(growth) + except (ValueError, TypeError): + raise ValueError("%s is not in %s" % (growth, parent.growth_group)) + super(GenericTerm, self).__init__(parent=parent) def _mul_(self, other): r""" - Abstract multiplication method for generic terms. + Multiplication of this term by another. INPUT: @@ -377,8 +388,7 @@ def _mul_(self, other): OUTPUT: - A :class:`GenericTerm` representing the product of ``self`` - and ``other``. + A :class:`GenericTerm`. .. NOTE:: @@ -386,12 +396,12 @@ def _mul_(self, other): it can be assumed that this element, as well as ``other`` are from a common parent. - EXAMPLES:: + TESTS:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, ZZ) sage: t1 = T(x); t2 = T(x^2) sage: t1, t2 (Generic Term with growth x, Generic Term with growth x^2) @@ -401,6 +411,238 @@ def _mul_(self, other): return self.parent()(self.growth * other.growth) + def __div__(self, other): + r""" + Division of this term by another. + + INPUT: + + - ``other`` -- an asymptotic term. + + OUTPUT: + + A :class:`GenericTerm`. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + The comparison of two elements with the same parent is done in + :meth:`_div_`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) + sage: t1 = T(x); t2 = T(x^2) + sage: t1 / t2 # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Inversion of Generic Term with growth x^2 + not implemented (in this abstract method). + """ + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._div_(other) + + from sage.structure.element import get_coercion_model + import operator + return get_coercion_model().bin_op(self, other, operator.div) + + + def _div_(self, other): + r""" + Division of this term by another. + + INPUT: + + - ``other`` -- an asymptotic term. + + OUTPUT: + + A :class:`GenericTerm`. + + .. NOTE:: + + This method is called by the coercion framework, thus, + it can be assumed that this element, as well as ``other`` + are from a common parent. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) + sage: t1 = T(x); t2 = T(x^2) + sage: t1 / t2 # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Inversion of Generic Term with growth x^2 + not implemented (in this abstract method). + """ + return self * ~other + + + def __invert__(self): + r""" + Invert this term. + + OUTPUT: + + A :class:`GenericTerm`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) + sage: ~T(x) # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Inversion of Generic Term with growth x + not implemented (in this abstract method). + """ + raise NotImplementedError('Inversion of %s not implemented ' + '(in this abstract method).' % (self,)) + + + def __pow__(self, exponent): + r""" + Calculate the power of this element to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + Raise a :python:`NotImplementedError` + since it is an abstract base class. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: t = GenericTermMonoid(G, ZZ).an_element(); t + Generic Term with growth z + sage: t^3 # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Taking powers of Generic Term with growth z + not implemented (in this abstract method). + """ + raise NotImplementedError('Taking powers of %s not implemented ' + '(in this abstract method).' % (self,)) + + + def _calculate_pow_test_zero_(self, exponent): + r""" + Helper function for :meth:`__pow__` which calculates the power of this + element to the given ``exponent`` only if zero to this exponent is possible. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: t = GenericTermMonoid(G, ZZ).an_element(); t + Generic Term with growth z + sage: t._calculate_pow_test_zero_(3) + Generic Term with growth z^3 + sage: t._calculate_pow_test_zero_(-2) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take Generic Term with growth z to exponent -2. + > *previous* ZeroDivisionError: rational division by zero + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: TermMonoid('O', G, QQ)('z')._calculate_pow_test_zero_(-1) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take O(z) to exponent -1. + > *previous* ZeroDivisionError: rational division by zero + """ + # This assumes `0 = O(g)` for any `g` in the growth group, which + # is valid in the case of a variable going to `\infty`. + # Once non-standard asymptoptics are supported, this has to be + # rewritten. + # See also #19083, comment 64, 27. + + zero = self.parent().coefficient_ring.zero() + try: + zero ** exponent + except (TypeError, ValueError, ZeroDivisionError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ZeroDivisionError('Cannot take %s to exponent %s.' % + (self, exponent)), e) + return self._calculate_pow_(exponent) + + + def _calculate_pow_(self, exponent, new_coefficient=None): + r""" + Helper function for :meth:`__pow__` which calculates the power of this + element to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + - ``new_coefficient`` -- if not ``None`` this is passed on to the + construction of the element (in particular, not taken to any power). + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: t = GenericTermMonoid(G, ZZ).an_element(); t + Generic Term with growth z + sage: t._calculate_pow_(3) + Generic Term with growth z^3 + sage: t._calculate_pow_(3, new_coefficient=2) + Traceback (most recent call last): + ... + ValueError: Coefficient 2 is not 1, but Generic Term Monoid z^ZZ with + (implicit) coefficients in Integer Ring does not support coefficients. + sage: t._calculate_pow_(-2) + Generic Term with growth z^(-2) + sage: t._calculate_pow_(-2, new_coefficient=2) + Traceback (most recent call last): + ... + ValueError: Coefficient 2 is not 1, but Generic Term Monoid z^ZZ with + (implicit) coefficients in Integer Ring does not support coefficients. + """ + try: + g = self.growth ** exponent + except (ValueError, TypeError, ZeroDivisionError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('Cannot take %s to the exponent %s.' % (self, exponent)), e) + + return self.parent()._create_element_in_extension_(g, new_coefficient) + + def can_absorb(self, other): r""" Check whether this asymptotic term is able to absorb @@ -423,10 +665,10 @@ def can_absorb(self, other): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GenericGrowthGroup(ZZ) - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GenericGrowthGroup(ZZ) + sage: T = GenericTermMonoid(G, QQ) sage: g1 = G(raw_element=21); g2 = G(raw_element=42) sage: t1 = T(g1); t2 = T(g2) sage: t1.can_absorb(t2) # indirect doctest @@ -472,11 +714,11 @@ def absorb(self, other, check=True): of this operation. We start by defining some parents and elements:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_QQ = agg.GrowthGroup('x^QQ'); x = G_QQ.gen() - sage: OT = atm.OTermMonoid(G_QQ) - sage: ET = atm.ExactTermMonoid(growth_group=G_QQ, base_ring=QQ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G_QQ = GrowthGroup('x^QQ'); x = G_QQ.gen() + sage: OT = TermMonoid('O', G_QQ, coefficient_ring=ZZ) + sage: ET = TermMonoid('exact', G_QQ, coefficient_ring=QQ) sage: ot1 = OT(x); ot2 = OT(x^2) sage: et1 = ET(x, 100); et2 = ET(x^2, 2) sage: et3 = ET(x^2, 1); et4 = ET(x^2, -2) @@ -559,10 +801,10 @@ def _absorb_(self, other): First, we define some asymptotic terms:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) sage: t1 = T(x); t2 = T(x^2) When it comes to absorption, note that the method @@ -586,6 +828,88 @@ def _absorb_(self, other): raise NotImplementedError('Not implemented in abstract base classes') + def log_term(self, base=None): + r""" + Determine the logarithm of this term. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of terms. + + .. NOTE:: + + This abstract method raises a + :python:`NotImplementedError`. + See :class:`ExactTerm` and :class:`OTerm` for a concrete + implementation. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ'), QQ) + sage: T.an_element().log_term() + Traceback (most recent call last): + ... + NotImplementedError: This method is not implemented in + this abstract base class. + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: T = TermWithCoefficientMonoid(GrowthGroup('x^ZZ'), QQ) + sage: T.an_element().log_term() + Traceback (most recent call last): + ... + NotImplementedError: This method is not implemented in + this abstract base class. + + .. SEEALSO:: + + :meth:`ExactTerm.log_term`, + :meth:`OTerm.log_term`. + """ + raise NotImplementedError('This method is not implemented in this ' + 'abstract base class.') + + + def _log_growth_(self, base=None): + r""" + Helper function to calculate the logarithm of the growth of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of terms. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T(x^2)._log_growth_() + (O(log(x)),) + sage: T(x^1234).log_term() # indirect doctest + (O(log(x)),) + + .. SEEALSO:: + + :meth:`ExactTerm.log_term`, + :meth:`OTerm.log_term`. + """ + return tuple(self.parent()._create_element_in_extension_(g, c) + for g, c in self.growth.log_factor(base=base)) + + def __le__(self, other): r""" Return whether the growth of this term is less than @@ -608,17 +932,17 @@ def __le__(self, other): First, we define some asymptotic terms (and their parents):: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: GT = atm.GenericTermMonoid(G) - sage: OT = atm.OTermMonoid(G) - sage: ET_ZZ = atm.ExactTermMonoid(G, ZZ) - sage: ET_QQ = atm.ExactTermMonoid(G, QQ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: GT = GenericTermMonoid(G, QQ) + sage: OT = TermMonoid('O', G, QQ) + sage: ET_ZZ = TermMonoid('exact', G, ZZ) + sage: ET_QQ = TermMonoid('exact', G, QQ) sage: g1 = GT(x); g2 = GT(x^2); g1, g2 (Generic Term with growth x, Generic Term with growth x^2) sage: o1 = OT(x^-1); o2 = OT(x^3); o1, o2 - (O(1/x), O(x^3)) + (O(x^(-1)), O(x^3)) sage: t1 = ET_ZZ(x^2, 5); t2 = ET_QQ(x^3, 2/7); t1, t2 (5*x^2, 2/7*x^3) @@ -630,7 +954,7 @@ def __le__(self, other): sage: g1 <= g2 True sage: o1, g1 - (O(1/x), Generic Term with growth x) + (O(x^(-1)), Generic Term with growth x) sage: o1 <= g1 False @@ -694,10 +1018,10 @@ def _le_(self, other): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) sage: t1 = T(x^-2); t2 = T(x^5); t1, t2 (Generic Term with growth x^(-2), Generic Term with growth x^5) sage: t1._le_(t2) @@ -730,9 +1054,9 @@ def __eq__(self, other): sage: from sage.rings.asymptotic.growth_group import GrowthGroup sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, ....: ExactTermMonoid, OTermMonoid) - sage: GT = GenericTermMonoid(GrowthGroup('x^ZZ')) + sage: GT = GenericTermMonoid(GrowthGroup('x^ZZ'), QQ) sage: ET = ExactTermMonoid(GrowthGroup('x^ZZ'), ZZ) - sage: OT = OTermMonoid(GrowthGroup('x^ZZ')) + sage: OT = OTermMonoid(GrowthGroup('x^ZZ'), QQ) sage: g = GT.an_element(); e = ET.an_element(); o = OT.an_element() sage: g, e, o (Generic Term with growth x, x, O(x)) @@ -779,7 +1103,7 @@ def _eq_(self, other): sage: from sage.rings.asymptotic.growth_group import GrowthGroup sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid - sage: T = GenericTermMonoid(GrowthGroup('x^ZZ')) + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ'), QQ) sage: t = T.an_element() sage: t == t True @@ -787,7 +1111,7 @@ def _eq_(self, other): :: sage: from sage.rings.asymptotic.term_monoid import OTermMonoid - sage: OT = OTermMonoid(GrowthGroup('x^ZZ')) + sage: OT = OTermMonoid(GrowthGroup('x^ZZ'), QQ) sage: t = OT.an_element(); t O(x) sage: t == OT(x) # indirect doctest @@ -798,9 +1122,9 @@ def _eq_(self, other): return self.growth == other.growth - def _repr_(self): + def is_constant(self): r""" - A representation string for this generic term. + Return whether this term is an (exact) constant. INPUT: @@ -808,40 +1132,178 @@ def _repr_(self): OUTPUT: - A string. + A boolean. + + .. NOTE:: + + Only :class:`ExactTerm` with constant growth (`1`) are + constant. EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(growth_group=G) - sage: T(x)._repr_() - 'Generic Term with growth x' - sage: T(x^7)._repr_() - 'Generic Term with growth x^7' + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, TermMonoid) + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: t = T.an_element(); t + Generic Term with growth x*log(x) + sage: t.is_constant() + False + + :: + + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), QQ) + sage: T('x').is_constant() + False + sage: T(1).is_constant() + False """ - return 'Generic Term with growth ' + repr(self.growth) + return False -class GenericTermMonoid(sage.structure.parent.Parent, - sage.structure.unique_representation.UniqueRepresentation): - r""" - Parent for generic asymptotic terms. + def is_little_o_of_one(self): + r""" + Return whether this generic term is of order `o(1)`. - INPUT: + INPUT: - - ``growth_group`` -- a growth group (i.e. an instance of - :class:`~sage.rings.asymptotic.growth_group.GenericGrowthGroup`). + Nothing. - - ``category`` -- The category of the parent can be specified - in order to broaden the base structure. It has to be a subcategory - of ``Join of Category of Monoids and Category of posets``. This - is also the default category if ``None`` is specified. + OUTPUT: - In this class the base - structure for asymptotic term monoids will be handled. These - monoids are the parents of asymptotic terms (for example, see + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, + ....: TermWithCoefficientMonoid) + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ'), QQ) + sage: T.an_element().is_little_o_of_one() + Traceback (most recent call last): + ... + NotImplementedError: Cannot check whether Generic Term with growth x is o(1) + in the abstract base class + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field. + sage: T = TermWithCoefficientMonoid(GrowthGroup('x^ZZ'), QQ) + sage: T.an_element().is_little_o_of_one() + Traceback (most recent call last): + ... + NotImplementedError: Cannot check whether Term with coefficient 1/2 and growth x + is o(1) in the abstract base class + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field. + """ + raise NotImplementedError('Cannot check whether %s is o(1) in the ' + 'abstract base class %s.' % (self, self.parent())) + + + def rpow(self, base): + r""" + Return the power of ``base`` to this generic term. + + INPUT: + + - ``base`` -- an element or ``'e'``. + + OUTPUT: + + A term. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T.an_element().rpow('e') + Traceback (most recent call last): + ... + NotImplementedError: Cannot take e to the exponent + Generic Term with growth x*log(x) in the abstract base class + Generic Term Monoid x^ZZ * log(x)^ZZ with (implicit) coefficients in Rational Field. + """ + raise NotImplementedError('Cannot take %s to the exponent %s in the ' + 'abstract base class %s.' % (base, self, self.parent())) + + + def _repr_(self): + r""" + A representation string for this generic term. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) + sage: T(x)._repr_() + 'Generic Term with growth x' + sage: T(x^7)._repr_() + 'Generic Term with growth x^7' + """ + return 'Generic Term with growth ' + repr(self.growth) + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this generic term. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + Nothing since a + :python:`TypeError` + is raised. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: t = GenericTermMonoid(GrowthGroup('x^ZZ'), ZZ).an_element() + sage: t._substitute_({}) + Traceback (most recent call last): + ... + TypeError: Cannot substitute in Generic Term with growth x in + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring. + > *previous* TypeError: Cannot substitute in the abstract base class + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring. + """ + from misc import substitute_raise_exception + substitute_raise_exception(self, TypeError( + 'Cannot substitute in the abstract ' + 'base class %s.' % (self.parent(),))) + + +class GenericTermMonoid(sage.structure.unique_representation.UniqueRepresentation, + sage.structure.parent.Parent): + r""" + Parent for generic asymptotic terms. + + INPUT: + + - ``growth_group`` -- a growth group (i.e. an instance of + :class:`~sage.rings.asymptotic.growth_group.GenericGrowthGroup`). + + - ``coefficient_ring`` -- a ring which contains the (maybe implicit) + coefficients of the elements. + + - ``category`` -- The category of the parent can be specified + in order to broaden the base structure. It has to be a subcategory + of ``Join of Category of Monoids and Category of posets``. This + is also the default category if ``None`` is specified. + + In this class the base + structure for asymptotic term monoids will be handled. These + monoids are the parents of asymptotic terms (for example, see :class:`GenericTerm` or :class:`OTerm`). Basically, asymptotic terms consist of a ``growth`` (which is an asymptotic growth group element, for example @@ -851,69 +1313,118 @@ class GenericTermMonoid(sage.structure.parent.Parent, EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_x = agg.GrowthGroup('x^ZZ'); x = G_x.gen() - sage: G_y = agg.GrowthGroup('y^QQ'); y = G_y.gen() - sage: T_x_ZZ = atm.GenericTermMonoid(G_x); T_y_QQ = atm.GenericTermMonoid(G_y) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G_x = GrowthGroup('x^ZZ'); x = G_x.gen() + sage: G_y = GrowthGroup('y^QQ'); y = G_y.gen() + sage: T_x_ZZ = GenericTermMonoid(G_x, QQ) + sage: T_y_QQ = GenericTermMonoid(G_y, QQ) sage: T_x_ZZ - Generic Term Monoid x^ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field sage: T_y_QQ - Generic Term Monoid y^QQ + Generic Term Monoid y^QQ with (implicit) coefficients in Rational Field """ # enable the category framework for elements Element = GenericTerm + + @staticmethod + def __classcall__(cls, growth_group, coefficient_ring, category=None): + r""" + Normalize the input in order to ensure a unique + representation of the parent. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: T = GenericTermMonoid(G, QQ) + sage: from sage.rings.asymptotic.misc import underlying_class + sage: underlying_class(T)(G, QQ) is T + True + + :: + + sage: GenericTermMonoid(None, ZZ) # indirect doctest + Traceback (most recent call last): + ... + ValueError: No growth group specified. + sage: GenericTermMonoid(int, ZZ) # indirect doctest + Traceback (most recent call last): + ... + TypeError: is not a valid growth group. + sage: GenericTermMonoid(G, None) # indirect doctest + Traceback (most recent call last): + ... + ValueError: No coefficient ring specified. + sage: GenericTermMonoid(G, int) # indirect doctest + Traceback (most recent call last): + ... + TypeError: is not a valid coefficient ring. + """ + if growth_group is None: + raise ValueError('No growth group specified.') + if not isinstance(growth_group, sage.structure.parent.Parent): + raise TypeError('%s is not a valid growth group.' % (growth_group,)) + + if coefficient_ring is None: + raise ValueError('No coefficient ring specified.') + if not isinstance(coefficient_ring, sage.structure.parent.Parent): + raise TypeError('%s is not a valid coefficient ring.' % (coefficient_ring,)) + + if category is None: + from sage.categories.monoids import Monoids + from sage.categories.posets import Posets + category = Monoids() & Posets() + + return super(GenericTermMonoid, cls).__classcall__( + cls, growth_group, coefficient_ring, category) + + @sage.misc.superseded.experimental(trac_number=17601) - def __init__(self, growth_group, category=None): + def __init__(self, growth_group, coefficient_ring, category): r""" See :class:`GenericTermMonoid` for more information. EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_x = agg.GrowthGroup('x^ZZ') - sage: T_x = atm.GenericTermMonoid(G_x); T_x - Generic Term Monoid x^ZZ + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid, TermWithCoefficientMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_x = GrowthGroup('x^ZZ') + sage: T_x = GenericTermMonoid(G_x, QQ); T_x + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field sage: T_x.growth_group Growth Group x^ZZ - sage: G_y = agg.GrowthGroup('y^QQ') - sage: T_y = atm.GenericTermMonoid(G_y); T_y - Generic Term Monoid y^QQ + sage: G_y = GrowthGroup('y^QQ') + sage: T_y = GenericTermMonoid(G_y, QQ); T_y + Generic Term Monoid y^QQ with (implicit) coefficients in Rational Field sage: T_x is T_y False :: - sage: atm.GenericTermMonoid(None) + sage: GenericTermMonoid(None, None) Traceback (most recent call last): ... - ValueError: Growth Group has to be specified - """ - from sage.categories.monoids import Monoids - from sage.categories.posets import Posets - from sage.rings.asymptotic.growth_group import GenericGrowthGroup + ValueError: No growth group specified. - if category is None: - category = Monoids() & Posets() - else: - if not isinstance(category, tuple): - category = (category,) - if not any(cat.is_subcategory(Monoids() & Posets()) for cat in - category): - raise ValueError('%s is not a subcategory of %s' - % (category, Monoids() & Posets())) - if growth_group is None: - raise ValueError('Growth Group has to be specified') - else: - if not isinstance(growth_group, GenericGrowthGroup): - raise ValueError('%s does not inherit from %s' - % (growth_group, GenericGrowthGroup())) + :: + + sage: G = GrowthGroup('x^ZZ') + sage: T_ZZ = TermWithCoefficientMonoid(G, ZZ); T_ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring + sage: T_QQ = TermWithCoefficientMonoid(G, QQ); T_QQ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + sage: T_QQ.category() + Join of Category of monoids and Category of posets + """ self._growth_group_ = growth_group + self._coefficient_ring_ = coefficient_ring super(GenericTermMonoid, self).__init__(category=category) + @property def growth_group(self): r""" @@ -921,15 +1432,30 @@ def growth_group(self): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.ExactTermMonoid(G, ZZ).growth_group + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: TermMonoid('exact', GrowthGroup('x^ZZ'), ZZ).growth_group Growth Group x^ZZ """ return self._growth_group_ + @property + def coefficient_ring(self): + r""" + The coefficient ring of this term monoid, i.e. the ring where + the coefficients are from. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: GenericTermMonoid(GrowthGroup('x^ZZ'), ZZ).coefficient_ring + Integer Ring + """ + return self._coefficient_ring_ + + def _repr_(self): r""" A representation string for this generic term monoid. @@ -944,16 +1470,15 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: atm.GenericTermMonoid(G)._repr_() - 'Generic Term Monoid Generic(ZZ)' - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.GenericTermMonoid(growth_group=G)._repr_() - 'Generic Term Monoid x^ZZ' + sage: from sage.rings.asymptotic.growth_group import (GenericGrowthGroup, GrowthGroup) + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: GenericTermMonoid(GenericGrowthGroup(ZZ), QQ)._repr_() + 'Generic Term Monoid Generic(ZZ) with (implicit) coefficients in Rational Field' + sage: GenericTermMonoid(GrowthGroup('x^ZZ'), QQ)._repr_() + 'Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field' """ - return 'Generic Term Monoid %s' % (self.growth_group._repr_short_(), ) + return 'Generic Term Monoid %s with (implicit) coefficients in %s' % \ + (self.growth_group._repr_short_(), self.coefficient_ring) def _coerce_map_from_(self, S): @@ -971,55 +1496,72 @@ def _coerce_map_from_(self, S): .. NOTE:: Another generic term monoid ``S`` coerces into this term - monoid if and only if the growth group of ``S`` coerces - into the growth group of this term monoid. + monoid if and only if both, the growth group of ``S`` coerces + into the growth group of this term monoid and the coefficient + ring of ``S`` coerces into the coefficient ring of this term + monoid. EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GrowthGroup('x^ZZ') - sage: T_ZZ = atm.GenericTermMonoid(growth_group=G_ZZ); T_ZZ - Generic Term Monoid x^ZZ - sage: G_QQ = agg.GrowthGroup('x^QQ') - sage: T_QQ = atm.GenericTermMonoid(growth_group=G_QQ); T_QQ - Generic Term Monoid x^QQ + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: T_ZZ = GenericTermMonoid(G_ZZ, QQ); T_ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + sage: G_QQ = GrowthGroup('x^QQ') + sage: T_QQ = GenericTermMonoid(G_QQ, QQ); T_QQ + Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field sage: T_QQ.has_coerce_map_from(T_ZZ) # indirect doctest True + sage: T_QQ_ZZ = GenericTermMonoid(G_QQ, ZZ); T_QQ_ZZ + Generic Term Monoid x^QQ with (implicit) coefficients in Integer Ring + sage: T_QQ.has_coerce_map_from(T_QQ_ZZ) + True + sage: T_QQ_ZZ.has_coerce_map_from(T_QQ) + False + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: TC_ZZ = TermWithCoefficientMonoid(G_ZZ, ZZ); TC_ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring + sage: TC_QQ = TermWithCoefficientMonoid(G_QQ, QQ); TC_QQ + Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field + sage: TC_QQ.has_coerce_map_from(TC_ZZ) # indirect doctest + True + sage: TC_ZZ.has_coerce_map_from(TC_QQ) # indirect doctest + False """ if isinstance(S, self.__class__): - if self.growth_group.has_coerce_map_from(S.growth_group): + if self.growth_group.has_coerce_map_from(S.growth_group) and \ + self.coefficient_ring.has_coerce_map_from(S.coefficient_ring): return True - def _element_constructor_(self, data): + def _element_constructor_(self, data, coefficient=None): r""" Convert the given object to this term monoid. INPUT: - - ``data`` -- an object representing the element to be - initialized. + - ``data`` -- a growth element or an object representing the + element to be initialized. + + - ``coefficient`` -- (default: ``None``) + an element of the coefficient ring. OUTPUT: An element of this term monoid. - .. NOTE:: - - The object ``data`` is either an asymptotic term that is - to be coerced into this term monoid, or an asymptotic - growth element that is used for creating an element - of this term monoid. - EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GrowthGroup('x^ZZ') - sage: G_QQ = agg.GrowthGroup('x^QQ') - sage: T_ZZ = atm.GenericTermMonoid(growth_group=G_ZZ) - sage: T_QQ = atm.GenericTermMonoid(growth_group=G_QQ) + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: G_QQ = GrowthGroup('x^QQ') + sage: T_ZZ = GenericTermMonoid(G_ZZ, QQ) + sage: T_QQ = GenericTermMonoid(G_QQ, QQ) sage: term1 = T_ZZ(G_ZZ.gen()) sage: term2 = T_QQ(G_QQ.gen()^2) @@ -1027,16 +1569,16 @@ def _element_constructor_(self, data): a common parent has to be found:: sage: term1.parent() - Generic Term Monoid x^ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field sage: term2.parent() - Generic Term Monoid x^QQ + Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field sage: term1 <= term2 True In this case, this works because ``T_ZZ``, the parent of ``term1``, coerces into ``T_QQ``:: - sage: T_QQ.coerce(term1) + sage: T_QQ.coerce(term1) # coercion does not fail Generic Term with growth x The conversion of growth elements also works for the creation @@ -1053,21 +1595,261 @@ def _element_constructor_(self, data): sage: T_ZZ(10 * x^2) Traceback (most recent call last): ... - ValueError: Input is ambiguous: cannot convert 10*x^2 to a generic term. + ValueError: 10*x^2 is not in Generic Term Monoid x^ZZ + with (implicit) coefficients in Rational Field. + > *previous* ValueError: Factor 10*x^2 of 10*x^2 is neither a + coefficient (in Rational Field) nor growth (in Growth Group x^ZZ). + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G = GrowthGroup('x^ZZ') + sage: T = TermWithCoefficientMonoid(G, ZZ) + sage: t1 = T(x^2, 5); t1 # indirect doctest + Term with coefficient 5 and growth x^2 + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: O_ZZ = TermMonoid('O', G_ZZ, QQ) + sage: O_ZZ(x^11) + O(x^11) + + :: + + sage: T(G.gen()^10) + Term with coefficient 1 and growth x^10 + sage: T(G.gen()^10, coefficient=10) + Term with coefficient 10 and growth x^10 + sage: T(x^123) + Term with coefficient 1 and growth x^123 + + :: + + sage: T(x) + Term with coefficient 1 and growth x + + :: + + sage: G_log = GrowthGroup('log(x)^ZZ') + sage: T_log = TermWithCoefficientMonoid(G_log, ZZ) + sage: T_log(log(x)) + Term with coefficient 1 and growth log(x) + """ if isinstance(data, self.element_class) and data.parent() == self: return data + elif isinstance(data, TermWithCoefficient): + return self._create_element_(data.growth, data.coefficient) elif isinstance(data, GenericTerm): - return self.element_class(self, data.growth) + return self._create_element_(data.growth, None) elif isinstance(data, int) and data == 0: - raise ValueError('No input specified. Cannot continue.') - else: + raise ValueError('No input specified. Cannot continue ' + 'creating an element of %s.' % (self,)) + + from misc import combine_exceptions + if coefficient is not None: try: data = self.growth_group(data) - return self.element_class(self, data) - except: - raise ValueError('Input is ambiguous: cannot convert %s to a ' - 'generic term.' % (data,)) + except (ValueError, TypeError) as e: + raise combine_exceptions( + ValueError('Growth %s is not in %s.' % (data, self)), e) + return self._create_element_(data, coefficient) + + try: + growth, coefficient = self._split_growth_and_coefficient_(data) + except ValueError as e: + raise combine_exceptions( + ValueError('%s is not in %s.' % (data, self)), e) + + return self._create_element_(growth, coefficient) + + + def _create_element_(self, growth, coefficient): + r""" + Helper method which creates an element by using the ``element_class``. + + INPUT: + + - ``growth`` -- a growth element. + + - ``coefficient`` -- an element of the coefficient ring. + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: T_ZZ = GenericTermMonoid(G_ZZ, QQ) + sage: T_ZZ(G_ZZ.gen()) # indirect doctest + Generic Term with growth x + """ + if coefficient is not None and coefficient != self.coefficient_ring.one(): + raise ValueError('Coefficient %s is not 1, but %s does not ' + 'support coefficients.' % (coefficient, self)) + return self.element_class(self, growth) + + + def _create_element_in_extension_(self, growth, coefficient): + r""" + Create an element in an extension of this term monoid which + is chosen according to the input. + + INPUT: + + - ``growth`` and ``coefficient`` -- the element data. + + OUTPUT: + + An element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: T = TermMonoid('exact', G, ZZ) + sage: T._create_element_in_extension_(G.an_element(), 3) + 3*z + sage: T._create_element_in_extension_(G.an_element(), 3/2).parent() + Exact Term Monoid z^ZZ with coefficients in Rational Field + """ + if (growth.parent() is self.growth_group) and \ + (coefficient is None or coefficient.parent() is self.coefficient_ring): + parent = self + else: + from misc import underlying_class + parent = underlying_class(self)(growth.parent(), + coefficient.parent() + if coefficient is not None + else self.coefficient_ring, + category=self.category()) + return parent(growth, coefficient) + + + def _split_growth_and_coefficient_(self, data): + r""" + Split given ``data`` into a growth element and a coefficient. + + INPUT: + + ``data`` -- an element. + + OUTPUT: + + A pair ``(growth, coefficient``). + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: T = TermMonoid('exact', G, QQ) + sage: T._split_growth_and_coefficient_('2*x^3') + (x^3, 2) + + :: + + sage: T._split_growth_and_coefficient_('2.7 * x^3') + Traceback (most recent call last): + ... + ValueError: Factor 2.7 of 2.7 * x^3 is neither a coefficient + (in Rational Field) nor growth (in Growth Group x^ZZ). + + :: + + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ') + sage: T = TermMonoid('exact', G, QQ) + sage: T._split_growth_and_coefficient_('3/4 * 2^x * log(x)') + (2^x*log(x), 3/4) + sage: T._split_growth_and_coefficient_('3 * x^2 * 4 * log(x) * x') + (x^3*log(x), 12) + sage: var('x') + x + sage: T._split_growth_and_coefficient_(log(x)^5 * x^2 * 4) + (x^2*log(x)^5, 4) + + :: + + sage: T = TermMonoid('exact', G, SR) + sage: T._split_growth_and_coefficient_(log(x)^5 * x^2 * 4) + (x^2*log(x)^5, 4) + sage: var('y') + y + sage: T._split_growth_and_coefficient_(2^x * y * 4) + (2^x, 4*y) + """ + factors = self._get_factors_(data) + + growth_group = self.growth_group + coefficient_ring = self.coefficient_ring + + coefficients = [] + growths = [] + for f in factors: + try: + growths.append(growth_group(f)) + continue + except (ValueError, TypeError): + pass + + try: + coefficients.append(coefficient_ring(f)) + continue + except (ValueError, TypeError): + pass + + raise ValueError('Factor %s of %s is neither a coefficient (in %s) ' + 'nor growth (in %s).' % + (f, data, coefficient_ring, growth_group)) + + from sage.misc.misc_c import prod + growth = prod(growths) if growths else growth_group.one() + coefficient = prod(coefficients) if coefficients else coefficient_ring.one() + return (growth, coefficient) + + + def _get_factors_(self, data): + r""" + Split given ``data`` into separate factors. + + INPUT: + + - ``data`` -- an object. + + OUTPUT: + + A tuple. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: T_ZZ = TermMonoid('exact', G_ZZ, QQ) + sage: T_ZZ._get_factors_(x^2 * log(x)) + (x^2, log(x)) + """ + if isinstance(data, str): + from misc import split_str_by_op + return split_str_by_op(data, '*') + + try: + P = data.parent() + except AttributeError: + return (data,) + + from sage.symbolic.ring import SymbolicRing + if isinstance(P, SymbolicRing): + from sage.symbolic.operators import mul_vararg + if data.operator() == mul_vararg: + return tuple(data.operands()) + + return (data,) def _an_element_(self): @@ -1084,17 +1866,42 @@ def _an_element_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.OTermMonoid(G).an_element() # indirect doctest + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ') + sage: TermMonoid('O', G, QQ).an_element() # indirect doctest O(x) - sage: atm.GenericTermMonoid(G).an_element() # indirect doctest + sage: GenericTermMonoid(G, QQ).an_element() # indirect doctest Generic Term with growth x """ return self(self.growth_group.an_element()) + def some_elements(self): + r""" + Return some elements of this term monoid. + + See :class:`TestSuite` for a typical use case. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: tuple(TermMonoid('O', G, QQ).some_elements()) + (O(1), O(x), O(x^(-1)), O(x^2), O(x^(-2)), O(x^3), ...) + """ + return iter(self(g) for g in self.growth_group.some_elements()) + + def le(self, left, right): r""" Return whether the term ``left`` is at most (less than or equal @@ -1112,10 +1919,10 @@ def le(self, left, right): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(growth_group=G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) sage: t1 = T(x); t2 = T(x^2) sage: T.le(t1, t2) True @@ -1139,10 +1946,10 @@ class OTerm(GenericTerm): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: OT = atm.OTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import OTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = OTermMonoid(G, QQ) sage: t1 = OT(x^-7); t2 = OT(x^5); t3 = OT(x^42) sage: t1, t2, t3 (O(x^(-7)), O(x^5), O(x^42)) @@ -1160,115 +1967,392 @@ class OTerm(GenericTerm): The conversion of growth elements also works for the creation of `O`-terms:: - sage: x = SR('x'); x.parent() - Symbolic Ring - sage: OT(x^17) - O(x^17) - """ + sage: x = SR('x'); x.parent() + Symbolic Ring + sage: OT(x^17) + O(x^17) + """ + + def _repr_(self): + r""" + A representation string for this `O`-term. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = TermMonoid('O', G, QQ) + sage: t1 = OT(x); t2 = OT(x^2); t3 = OT(x^3) + sage: t1._repr_(), t2._repr_() + ('O(x)', 'O(x^2)') + sage: t3 + O(x^3) + """ + return 'O(%s)' % self.growth + + + def __invert__(self): + r""" + Invert this term. + + OUTPUT: + + A :class:`ZeroDivisionError` since `O`-terms cannot be inverted. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = TermMonoid('O', G, QQ) + sage: ~T(x) # indirect doctest + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot invert O(x). + """ + raise ZeroDivisionError('Cannot invert %s.' % (self,)) + + + def __pow__(self, exponent): + r""" + Calculate the power of this :class:`OTerm` to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + An :class:`OTerm`. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: t = TermMonoid('O', G, ZZ).an_element(); t + O(z) + sage: t^3 # indirect doctest + O(z^3) + sage: t^(1/2) # indirect doctest + O(z^(1/2)) + sage: t^(-1) # indirect doctest + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take O(z) to exponent -1. + > *previous* ZeroDivisionError: rational division by zero + """ + return self._calculate_pow_test_zero_(exponent) + + + def can_absorb(self, other): + r""" + Check whether this `O`-term can absorb ``other``. + + INPUT: + + - ``other`` -- an asymptotic term. + + OUTPUT: + + A boolean. + + .. NOTE:: + + An :class:`OTerm` can absorb any other asymptotic term + with weaker or equal growth. + + See the :ref:`module description ` for a + detailed explanation of absorption. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: OT = TermMonoid('O', GrowthGroup('x^ZZ'), QQ) + sage: t1 = OT(x^21); t2 = OT(x^42) + sage: t1.can_absorb(t2) + False + sage: t2.can_absorb(t1) + True + """ + return other <= self + + + def _absorb_(self, other): + r""" + Let this `O`-term absorb another `O`-term ``other``. + + INPUT: + + - ``other`` -- an asymptotic `O`-term. + + OUTPUT: + + An asymptotic `O`-term. + + .. NOTE:: + + This method is called by the coercion framework, thus, + it can be assumed that this element and ``other`` + have the same parent. + + Also, observe that the result of a "dominant" `O`-term + absorbing another `O`-term always is the "dominant" + `O`-term again. + + See the :ref:`module description ` for a + detailed explanation on absorption. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = TermMonoid('O', G, QQ) + sage: ot1 = OT(x); ot2 = OT(x^2) + sage: ot1.absorb(ot1) + O(x) + sage: ot2.absorb(ot1) + O(x^2) + sage: ot1.absorb(ot2) + Traceback (most recent call last): + ... + ArithmeticError: O(x) cannot absorb O(x^2) + """ + return self + - def _repr_(self): + def log_term(self, base=None): r""" - A representation string for this `O`-term. + Determine the logarithm of this O-term. INPUT: - Nothing. + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. OUTPUT: - A string. + A tuple of terms. + + .. NOTE:: + + This method returns a tuple with the summands that come from + applying the rule `\log(x\cdot y) = \log(x) + \log(y)`. EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: OT = atm.OTermMonoid(G) - sage: t1 = OT(x); t2 = OT(x^2); t3 = OT(x^3) - sage: t1._repr_(), t2._repr_() - ('O(x)', 'O(x^2)') - sage: t3 - O(x^3) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T(x^2).log_term() + (O(log(x)),) + sage: T(x^1234).log_term() + (O(log(x)),) + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ'), QQ) + sage: T('x * y').log_term() + (O(log(x)), O(log(y))) + + .. SEEALSO:: + + :meth:`ExactTerm.log_term`. """ - return 'O(%s)' % self.growth + return self._log_growth_(base=base) - def can_absorb(self, other): + def is_little_o_of_one(self): r""" - Check whether this `O`-term can absorb ``other``. + Return whether this O-term is of order `o(1)`. INPUT: - - ``other`` -- an asymptotic term. + Nothing. OUTPUT: A boolean. - .. NOTE:: + EXAMPLES:: - An :class:`OTerm` can absorb any other asymptotic term - with weaker or equal growth. + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), QQ) + sage: T(x).is_little_o_of_one() + False + sage: T(1).is_little_o_of_one() + False + sage: T(x^(-1)).is_little_o_of_one() + True - See the :ref:`module description ` for a - detailed explanation of absorption. + :: - EXAMPLES:: + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * y^ZZ'), QQ) + sage: T('x * y^(-1)').is_little_o_of_one() + False + sage: T('x^(-1) * y').is_little_o_of_one() + False + sage: T('x^(-2) * y^(-3)').is_little_o_of_one() + True - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: OT = atm.TermMonoid('O', agg.GrowthGroup('x^ZZ')) - sage: t1 = OT(x^21); t2 = OT(x^42) - sage: t1.can_absorb(t2) + :: + + sage: T = TermMonoid('O', GrowthGroup('x^QQ * log(x)^QQ'), QQ) + sage: T('x * log(x)^2').is_little_o_of_one() False - sage: t2.can_absorb(t1) + sage: T('x^2 * log(x)^(-1234)').is_little_o_of_one() + False + sage: T('x^(-1) * log(x)^4242').is_little_o_of_one() + True + sage: T('x^(-1/100) * log(x)^(1000/7)').is_little_o_of_one() True """ - return other <= self + return self.growth.is_lt_one() - def _absorb_(self, other): + def rpow(self, base): r""" - Let this `O`-term absorb another `O`-term ``other``. + Return the power of ``base`` to this O-term. INPUT: - - ``other`` -- an asymptotic `O`-term. + - ``base`` -- an element or ``'e'``. OUTPUT: - An asymptotic `O`-term. + A term. .. NOTE:: - This method is called by the coercion framework, thus, - it can be assumed that this element and ``other`` - have the same parent. + For :class:`OTerm`, the powers can only be + constructed for exponents `O(1)` or if ``base`` is `1`. - Also, observe that the result of a "dominant" `O`-term - absorbing another `O`-term always is the "dominant" - `O`-term again. + EXAMPLES:: - See the :ref:`module description ` for a - detailed explanation on absorption. + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T(1).rpow('e') + O(1) + sage: T(1).rpow(2) + O(1) - EXAMPLES:: + :: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: OT = atm.OTermMonoid(growth_group=G) - sage: ot1 = OT(x); ot2 = OT(x^2) - sage: ot1.absorb(ot1) + sage: T.an_element().rpow(1) + 1 + sage: T('x^2').rpow(1) + 1 + + :: + + sage: T.an_element().rpow('e') + Traceback (most recent call last): + ... + ValueError: Cannot take e to the exponent O(x*log(x)) in + O-Term Monoid x^ZZ * log(x)^ZZ with implicit coefficients in Rational Field + sage: T('log(x)').rpow('e') + Traceback (most recent call last): + ... + ValueError: Cannot take e to the exponent O(log(x)) in + O-Term Monoid x^ZZ * log(x)^ZZ with implicit coefficients in Rational Field + """ + if self.is_one() and base != 0: + return self + if base == 1: + P = self.parent() + return ExactTermMonoid(P.growth_group, P.coefficient_ring).one() + raise ValueError('Cannot take %s to the exponent %s in %s' % + (base, self, self.parent())) + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this O-Term. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), ZZ) + sage: t = T.an_element(); t O(x) - sage: ot2.absorb(ot1) - O(x^2) - sage: ot1.absorb(ot2) + sage: t._substitute_({'x': SR.var('z')}) + Order(z) + sage: t._substitute_({'x': SR.var('z'), 'O': function('Oh')}) + Oh(z) + sage: u = AsymptoticRing('x^ZZ', ZZ)('2*x'); u + 2*x + sage: t._substitute_({'x': u}) + O(x) + sage: T(1/x)._substitute_({'x': 0}) Traceback (most recent call last): ... - ArithmeticError: O(x) cannot absorb O(x^2) + ZeroDivisionError: Cannot substitute in O(x^(-1)) in + O-Term Monoid x^ZZ with implicit coefficients in Integer Ring. + > *previous* ZeroDivisionError: Cannot substitute in x^(-1) in + Growth Group x^ZZ. + >> *previous* ZeroDivisionError: rational division by zero + sage: t._substitute_({'x': 3}) + O(3) + sage: t._substitute_({'x': 'null'}) + Traceback (most recent call last): + ... + ArithmeticError: Cannot substitute in O(x) in + O-Term Monoid x^ZZ with implicit coefficients in Integer Ring. + > *previous* ArithmeticError: O(null) not defined """ - return self + try: + g = self.growth._substitute_(rules) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + try: + return rules['O'](g) + except KeyError: + pass + + try: + P = g.parent() + except AttributeError: + pass + else: + from asymptotic_ring import AsymptoticRing + from sage.symbolic.ring import SymbolicRing + + if isinstance(P, AsymptoticRing): + return g.O() + + elif isinstance(P, SymbolicRing): + return g.Order() + + try: + return sage.rings.big_oh.O(g) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) class OTermMonoid(GenericTermMonoid): @@ -1286,27 +2370,72 @@ class OTermMonoid(GenericTermMonoid): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_x_ZZ = agg.GrowthGroup('x^ZZ') - sage: G_y_QQ = agg.GrowthGroup('y^QQ') - sage: OT_x_ZZ = atm.OTermMonoid(G_x_ZZ); OT_x_ZZ - Asymptotic O-Term Monoid x^ZZ - sage: OT_y_QQ = atm.OTermMonoid(G_y_QQ); OT_y_QQ - Asymptotic O-Term Monoid y^QQ + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import OTermMonoid + sage: G_x_ZZ = GrowthGroup('x^ZZ') + sage: G_y_QQ = GrowthGroup('y^QQ') + sage: OT_x_ZZ = OTermMonoid(G_x_ZZ, QQ); OT_x_ZZ + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + sage: OT_y_QQ = OTermMonoid(G_y_QQ, QQ); OT_y_QQ + O-Term Monoid y^QQ with implicit coefficients in Rational Field `O`-term monoids can also be created by using the :class:`term factory `:: - sage: atm.TermMonoid('O', G_x_ZZ) is OT_x_ZZ + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: TermMonoid('O', G_x_ZZ, QQ) is OT_x_ZZ True - sage: atm.TermMonoid('O', agg.GrowthGroup('x^QQ')) - Asymptotic O-Term Monoid x^QQ + sage: TermMonoid('O', GrowthGroup('x^QQ'), QQ) + O-Term Monoid x^QQ with implicit coefficients in Rational Field """ + # enable the category framework for elements Element = OTerm + def _create_element_(self, growth, coefficient): + r""" + Helper method which creates an element by using the ``element_class``. + + INPUT: + + - ``growth`` -- a growth element. + + - ``coefficient`` -- an element of the coefficient ring (will be + ignored since we create an O-Term). + + OUTPUT: + + An O-term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: T = TermMonoid('O', G, QQ) + sage: T(G.gen()) # indirect doctest + O(x) + sage: T(G.gen(), SR.var('y')) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Cannot create O(x) since given coefficient y + is not valid in O-Term Monoid x^ZZ with implicit coefficients in + Rational Field. + > *previous* TypeError: unable to convert y to a rational + """ + if coefficient is not None: + try: + self.coefficient_ring(coefficient) + except (TypeError, ValueError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('Cannot create O(%s) since given coefficient %s ' + 'is not valid in %s.' % + (growth, coefficient, self)), e) + return self.element_class(self, growth) + + def _coerce_map_from_(self, S): r""" Return whether ``S`` coerces into this term monoid. @@ -1333,13 +2462,13 @@ def _coerce_map_from_(self, S): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() - sage: G_QQ = agg.GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() - sage: OT_ZZ = atm.OTermMonoid(G_ZZ) - sage: OT_QQ = atm.OTermMonoid(G_QQ) - sage: ET = atm.ExactTermMonoid(G_ZZ, ZZ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G_ZZ = GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() + sage: G_QQ = GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() + sage: OT_ZZ = TermMonoid('O', G_ZZ, QQ) + sage: OT_QQ = TermMonoid('O', G_QQ, QQ) + sage: ET = TermMonoid('exact', G_ZZ, ZZ) Now, the :class:`OTermMonoid` whose growth group is over the integer ring has to coerce into the :class:`OTermMonoid` with @@ -1375,13 +2504,14 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: atm.OTermMonoid(G)._repr_() - 'Asymptotic O-Term Monoid x^ZZ' + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: TermMonoid('O', G, QQ)._repr_() + 'O-Term Monoid x^ZZ with implicit coefficients in Rational Field' """ - return 'Asymptotic O-Term Monoid %s' % (self.growth_group._repr_short_(),) + return 'O-Term Monoid %s with implicit coefficients in %s' % \ + (self.growth_group._repr_short_(), self.coefficient_ring) class TermWithCoefficient(GenericTerm): @@ -1396,19 +2526,19 @@ class TermWithCoefficient(GenericTerm): - ``growth`` -- an asymptotic growth element of the parent's growth group. - - ``coefficient`` -- an element of the parent's base ring. + - ``coefficient`` -- an element of the parent's coefficient ring. EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: CT_ZZ = atm.TermWithCoefficientMonoid(G, ZZ) - sage: CT_QQ = atm.TermWithCoefficientMonoid(G, QQ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: CT_ZZ = TermWithCoefficientMonoid(G, ZZ) + sage: CT_QQ = TermWithCoefficientMonoid(G, QQ) sage: CT_ZZ(x^2, 5) - Asymptotic Term with coefficient 5 and growth x^2 + Term with coefficient 5 and growth x^2 sage: CT_QQ(x^3, 3/8) - Asymptotic Term with coefficient 3/8 and growth x^3 + Term with coefficient 3/8 and growth x^3 """ def __init__(self, parent, growth, coefficient): @@ -1419,27 +2549,29 @@ def __init__(self, parent, growth, coefficient): First, we define some monoids:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: CT_ZZ = atm.TermWithCoefficientMonoid(G, ZZ) - sage: CT_QQ = atm.TermWithCoefficientMonoid(G, QQ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: CT_ZZ = TermWithCoefficientMonoid(G, ZZ) + sage: CT_QQ = TermWithCoefficientMonoid(G, QQ) - The coefficients have to be from the given base ring:: + The coefficients have to be from the given coefficient ring:: sage: t = CT_ZZ(x, 1/2) Traceback (most recent call last): ... - ValueError: 1/2 is not in Integer Ring + ValueError: 1/2 is not a coefficient in + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring. sage: t = CT_QQ(x, 1/2); t - Asymptotic Term with coefficient 1/2 and growth x + Term with coefficient 1/2 and growth x For technical reasons, the coefficient 0 is not allowed:: sage: t = CT_ZZ(x^42, 0) Traceback (most recent call last): ... - ValueError: 0 is not a valid coefficient. + ValueError: Zero coefficient 0 is not allowed in + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring. The conversion of growth elements also works for the creation of terms with coefficient:: @@ -1447,15 +2579,18 @@ def __init__(self, parent, growth, coefficient): sage: x = SR('x'); x.parent() Symbolic Ring sage: CT_ZZ(x^42, 42) - Asymptotic Term with coefficient 42 and growth x^42 + Term with coefficient 42 and growth x^42 """ - if coefficient not in parent.base_ring(): - raise ValueError('%s is not in %s' % (coefficient, - parent.base_ring())) - elif coefficient == 0: - raise ValueError('0 is not a valid coefficient') - - self.coefficient = parent.base_ring()(coefficient) + try: + coefficient = parent.coefficient_ring(coefficient) + except (ValueError, TypeError): + raise ValueError('%s is not a coefficient in %s.' % + (coefficient, parent)) + if coefficient == 0: + raise ValueError('Zero coefficient %s is not allowed in %s.' % + (coefficient, parent)) + + self.coefficient = coefficient super(TermWithCoefficient, self).__init__(parent=parent, growth=growth) @@ -1473,14 +2608,14 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.TermWithCoefficientMonoid(G, ZZ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = TermWithCoefficientMonoid(G, ZZ) sage: T(x^2, 5)._repr_() - 'Asymptotic Term with coefficient 5 and growth x^2' + 'Term with coefficient 5 and growth x^2' """ - return 'Asymptotic Term with coefficient %s and growth %s' % \ + return 'Term with coefficient %s and growth %s' % \ (self.coefficient, self.growth) @@ -1505,11 +2640,11 @@ def _mul_(self, other): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: CT = atm.TermWithCoefficientMonoid(G, ZZ) - sage: ET = atm.ExactTermMonoid(G, ZZ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (TermWithCoefficientMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: CT = TermWithCoefficientMonoid(G, ZZ) + sage: ET = TermMonoid('exact', G, ZZ) This method handles the multiplication of abstract terms with coefficient (i.e. :class:`TermWithCoefficient`) and @@ -1518,7 +2653,7 @@ def _mul_(self, other): sage: t1 = CT(x^2, 2); t2 = CT(x^3, 3) sage: t1 * t2 - Asymptotic Term with coefficient 6 and growth x^5 + Term with coefficient 6 and growth x^5 And now, an example for exact terms:: @@ -1530,6 +2665,92 @@ def _mul_(self, other): self.coefficient * other.coefficient) + def _calculate_pow_(self, exponent): + r""" + Helper function for :meth:`~ExactTerm.__pow__` which calculates + the power of this element to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: T = TermWithCoefficientMonoid(G, ZZ) + sage: t = T('2*z'); t + Term with coefficient 2 and growth z + sage: t._calculate_pow_(3) + Term with coefficient 8 and growth z^3 + sage: t._calculate_pow_(-2) + Term with coefficient 1/4 and growth z^(-2) + + :: + + sage: T = TermWithCoefficientMonoid(G, CIF) + sage: T(G.an_element(), coefficient=CIF(RIF(-1,1), RIF(-1,1)))._calculate_pow_(I) + Traceback (most recent call last): + ... + ArithmeticError: Cannot take Term with coefficient 0.? + 0.?*I and + growth z to the exponent I in Generic Term Monoid z^ZZ with + (implicit) coefficients in Complex Interval Field with + 53 bits of precision since its coefficient 0.? + 0.?*I + cannot be taken to this exponent. + > *previous* ValueError: Can't take the argument of + interval strictly containing zero + """ + try: + c = self.coefficient ** exponent + except (TypeError, ValueError, ZeroDivisionError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ArithmeticError('Cannot take %s to the exponent %s in %s since its ' + 'coefficient %s cannot be taken to this exponent.' % + (self, exponent, self.parent(), self.coefficient)), e) + return super(TermWithCoefficient, self)._calculate_pow_(exponent, new_coefficient=c) + + + def _log_coefficient_(self, base=None): + r""" + Helper function to calculate the logarithm of the coefficient of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of terms. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T(3*x^2)._log_coefficient_() + (log(3),) + sage: T(x^1234).log_term() # indirect doctest + (1234*log(x),) + + .. SEEALSO:: + + :meth:`ExactTerm.log_term`, + :meth:`OTerm.log_term`. + """ + if self.coefficient.is_one(): + return tuple() + from sage.functions.log import log + return (self.parent()._create_element_in_extension_( + self.parent().growth_group.one(), log(self.coefficient, base=base)),) + + def _le_(self, other): r""" Return whether this asymptotic term with coefficient grows @@ -1603,7 +2824,7 @@ def _eq_(self, other): sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid sage: T = TermWithCoefficientMonoid(GrowthGroup('x^ZZ'), ZZ) sage: t = T.an_element(); t - Asymptotic Term with coefficient 1 and growth x + Term with coefficient 1 and growth x sage: t == T(x, 1) True sage: t == T(x, 2) @@ -1631,232 +2852,60 @@ class TermWithCoefficientMonoid(GenericTermMonoid): of ``Join of Category of monoids and Category of posets``. This is also the default category if ``None`` is specified. - - ``base_ring`` -- the ring which contains the - coefficients of the elements. - - EXAMPLES:: - - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() - sage: G_QQ = agg.GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() - sage: TC_ZZ = atm.TermWithCoefficientMonoid(G_ZZ, QQ); TC_ZZ - Term Monoid x^ZZ with coefficients from Rational Field - sage: TC_QQ = atm.TermWithCoefficientMonoid(G_QQ, QQ); TC_QQ - Term Monoid x^QQ with coefficients from Rational Field - sage: TC_ZZ == TC_QQ or TC_ZZ is TC_QQ - False - sage: TC_QQ.coerce_map_from(TC_ZZ) - Conversion map: - From: Term Monoid x^ZZ with coefficients from Rational Field - To: Term Monoid x^QQ with coefficients from Rational Field - """ - - # enable the category framework for elements - Element = TermWithCoefficient - - @sage.misc.superseded.experimental(trac_number=17601) - def __init__(self, growth_group, base_ring, category=None): - r""" - For more information see :class:`TermWithCoefficientMonoid`. - - EXAMPLES:: - - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T_ZZ = atm.TermWithCoefficientMonoid(G, ZZ); T_ZZ - Term Monoid x^ZZ with coefficients from Integer Ring - sage: T_QQ = atm.TermWithCoefficientMonoid(G, QQ); T_QQ - Term Monoid x^ZZ with coefficients from Rational Field - sage: T_QQ.category() - Join of Category of monoids and Category of posets - - TESTS:: - - sage: T = atm.TermWithCoefficientMonoid(G, None) - Traceback (most recent call last): - ... - ValueError: None is not a valid base ring. - """ - from sage.categories.rings import Rings - if base_ring not in Rings(): - raise ValueError('%s is not a valid base ring.' % (base_ring,)) - self._base_ring_ = base_ring - super(TermWithCoefficientMonoid, - self).__init__(growth_group=growth_group, category=category) - - def base_ring(self): - r""" - The base ring of this term monoid, i.e. the ring where - the coefficients are from. - - EXAMPLES:: - - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.ExactTermMonoid(G, ZZ).base_ring() - Integer Ring - """ - return self._base_ring_ - - - def _coerce_map_from_(self, S): - r""" - Return whether ``S`` coerces into this term monoid. - - INPUT: - - - ``S`` -- a parent. - - OUTPUT: - - A boolean. - - .. NOTE:: - - Another term monoid ``S`` coerces into this - :class:`TermWithCoefficientMonoid` - if both, the base ring as well as the growth - group underlying ``S`` coerce into the base ring and the - growth group underlying this term monoid, respectively. - - EXAMPLES:: - - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GrowthGroup('x^ZZ') - sage: G_QQ = agg.GrowthGroup('x^QQ') - sage: TC_ZZ = atm.TermWithCoefficientMonoid(G_ZZ, ZZ); TC_ZZ - Term Monoid x^ZZ with coefficients from Integer Ring - sage: TC_QQ = atm.TermWithCoefficientMonoid(G_QQ, QQ); TC_QQ - Term Monoid x^QQ with coefficients from Rational Field - sage: TC_QQ.has_coerce_map_from(TC_ZZ) # indirect doctest - True - sage: TC_ZZ.has_coerce_map_from(TC_QQ) # indirect doctest - False - """ - if isinstance(S, TermWithCoefficientMonoid): - return (super(TermWithCoefficientMonoid, self)._coerce_map_from_(S) and - self.base_ring().has_coerce_map_from(S.base_ring())) - - - def _element_constructor_(self, data, coefficient=None): - r""" - Construct an asymptotic term with coefficient or convert - the given object ``data`` to this term monoid. - - INPUT: - - - ``data`` -- a growth element or an object representing the - element to be initialized. - - - ``coefficient`` -- an element of the base ring. + - ``coefficient_ring`` -- the ring which contains the + coefficients of the elements. - OUTPUT: + EXAMPLES:: - An asymptotic term. + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G_ZZ = GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() + sage: G_QQ = GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() + sage: TC_ZZ = TermWithCoefficientMonoid(G_ZZ, QQ); TC_ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + sage: TC_QQ = TermWithCoefficientMonoid(G_QQ, QQ); TC_QQ + Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field + sage: TC_ZZ == TC_QQ or TC_ZZ is TC_QQ + False + sage: TC_QQ.coerce_map_from(TC_ZZ) + Conversion map: + From: Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + To: Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field + """ - .. NOTE:: + # enable the category framework for elements + Element = TermWithCoefficient - The object ``data`` is either an asymptotic term with - coefficient that is to be coerced into this term monoid, - or an asymptotic growth element that is used together - with ``coefficient`` in order to create an element of - this term monoid. - EXAMPLES:: + def _create_element_(self, growth, coefficient): + r""" + Helper method which creates an element by using the ``element_class``. - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ') - sage: T = atm.TermWithCoefficientMonoid(G, ZZ) - sage: x.parent() == SR - True - sage: t1 = T(x^2, 5); t1 # indirect doctest - Asymptotic Term with coefficient 5 and growth x^2 + INPUT: - TESTS:: + - ``growth`` -- a growth element. - sage: T(5 * x^5) - Asymptotic Term with coefficient 5 and growth x^5 - sage: T(G.gen()^10) - Traceback (most recent call last): - ... - ValueError: Coefficient is not specified. Cannot continue. - sage: T(G.gen()^10, coefficient=10) - Asymptotic Term with coefficient 10 and growth x^10 - sage: T(x^123) - Asymptotic Term with coefficient 1 and growth x^123 + - ``coefficient`` -- an element of the coefficient ring. - :: + OUTPUT: - sage: T(x) - Asymptotic Term with coefficient 1 and growth x + A term. - :: + TESTS:: - sage: G_log = agg.GrowthGroup('log(x)^ZZ') - sage: T_log = atm.TermWithCoefficientMonoid(G_log, ZZ) - sage: T_log(log(x)) - Asymptotic Term with coefficient 1 and growth log(x) + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: T_ZZ = TermMonoid('exact', G_ZZ, QQ) + sage: T_ZZ(G_ZZ.gen(), 4/3) # indirect doctest + 4/3*x """ - if isinstance(data, self.element_class) and data.parent() == self: - return data - elif isinstance(data, TermWithCoefficient): - return self.element_class(self, data.growth, data.coefficient) - elif isinstance(data, int) and data == 0: - raise ValueError('No input specified. Cannot continue.') - - try: - if coefficient is not None: - data = self.growth_group(data) - return self.element_class(self, data, coefficient) - else: - P = data.parent() - from sage.symbolic.ring import SR - import operator - from sage.symbolic.operators import mul_vararg - if P is SR: - op = data.operator() - if op == mul_vararg: - data, coef_tmp = data.operands() - data = self.growth_group(data) - elif op in (operator.pow, None) or\ - isinstance(op, sage.functions.log.Function_log): - coef_tmp = 1 - data = self.growth_group(data) - else: - coeffs = data.coefficients() - if type(coeffs) == list: - # (multivariate) polynomial ring - coef_tmp = coeffs[0] - data = self.growth_group(data / coef_tmp) - elif type(coeffs) == dict: - # power series ring - coef_tmp = coeffs.values()[0] - data = self.growth_group(data / coef_tmp) - - return self.element_class(self, data, coef_tmp) - except (ValueError, AttributeError): - if coefficient is None: - raise ValueError('Coefficient is not specified. ' - 'Cannot continue.') - elif coefficient not in self.base_ring(): - raise ValueError('%s is not in %s' - % (coefficient, self.base_ring())) - elif coefficient == 0: - raise ValueError('0 is not a valid coefficient.') - raise ValueError('Input is ambiguous: cannot convert %s with ' - 'coefficient %s to a term with coefficient.' - % (data, coefficient)) + return self.element_class(self, growth, coefficient) - def _repr_(self): + def _an_element_(self): r""" - A representation string for this monoid of terms with - coefficient. + Return an element of this term with coefficient monoid. INPUT: @@ -1864,23 +2913,29 @@ def _repr_(self): OUTPUT: - A string. + An element of this term monoid. EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.TermWithCoefficientMonoid(G, ZZ)._repr_() - 'Term Monoid x^ZZ with coefficients from Integer Ring' + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (TermWithCoefficientMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ') + sage: TermWithCoefficientMonoid(G, ZZ).an_element() # indirect doctest + Term with coefficient 1 and growth x + sage: TermMonoid('exact', G, ZZ).an_element() # indirect doctest + x + sage: TermMonoid('exact', G, QQ).an_element() # indirect doctest + 1/2*x """ - return 'Term Monoid %s with coefficients from ' \ - '%s' % (self.growth_group._repr_short_(), self.base_ring()) + return self(self.growth_group.an_element(), + self.coefficient_ring.an_element()) - def _an_element_(self): + def some_elements(self): r""" - Return an element of this term with coefficient monoid. + Return some elements of this term with coefficient monoid. + + See :class:`TestSuite` for a typical use case. INPUT: @@ -1888,20 +2943,23 @@ def _an_element_(self): OUTPUT: - An element of this term monoid. + An iterator. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.TermWithCoefficientMonoid(G, ZZ).an_element() # indirect doctest - Asymptotic Term with coefficient 1 and growth x - sage: atm.ExactTermMonoid(G, ZZ).an_element() # indirect doctest - x + sage: from itertools import islice + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^QQ') + sage: T = TermMonoid('exact', G, ZZ) + sage: tuple(islice(T.some_elements(), 10)) + (z^(1/2), z^(-1/2), -z^(1/2), z^2, -z^(-1/2), 2*z^(1/2), + z^(-2), -z^2, 2*z^(-1/2), -2*z^(1/2)) """ - return self(self.growth_group.an_element(), - self.base_ring().an_element()) + from sage.misc.mrange import cantor_product + return iter(self(g, c) for g, c in cantor_product( + self.growth_group.some_elements(), + iter(c for c in self.coefficient_ring.some_elements() if c != 0))) class ExactTerm(TermWithCoefficient): @@ -1917,14 +2975,14 @@ class ExactTerm(TermWithCoefficient): - ``growth`` -- an asymptotic growth element from ``parent.growth_group``. - - ``coefficient`` -- an element from ``parent.base_ring()``. + - ``coefficient`` -- an element from ``parent.coefficient_ring``. EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: ET = atm.ExactTermMonoid(G, QQ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (ExactTermMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: ET = ExactTermMonoid(G, QQ) Asymptotic exact terms may be multiplied (with the usual rules applying):: @@ -1936,7 +2994,7 @@ class ExactTerm(TermWithCoefficient): They may also be multiplied with `O`-terms:: - sage: OT = atm.OTermMonoid(G) + sage: OT = TermMonoid('O', G, QQ) sage: ET(x^2, 42) * OT(x) O(x^3) @@ -1967,14 +3025,6 @@ class ExactTerm(TermWithCoefficient): Symbolic Ring sage: ET(5*x^2) 5*x^2 - sage: x = ZZ['x'].gen(); x.parent() - Univariate Polynomial Ring in x over Integer Ring - sage: ET(5*x^2) - 5*x^2 - sage: x = ZZ[['x']].gen(); x.parent() - Power Series Ring in x over Integer Ring - sage: ET(5*x^2) - 5*x^2 """ def _repr_(self): @@ -1991,10 +3041,10 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: ET = atm.ExactTermMonoid(G, ZZ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: ET = TermMonoid('exact', G, ZZ) sage: et1 = ET(x^2, 2); et1 2*x^2 @@ -2007,15 +3057,72 @@ def _repr_(self): sage: ET(x^0, 42) 42 """ - if self.growth._raw_element_ == 0: - return '%s' % self.coefficient + g = repr(self.growth) + c = repr(self.coefficient) + if g == '1': + return c + elif c == '1': + return '%s' % (g,) + elif c == '-1': + return '-%s' % (g,) else: - if self.coefficient == 1: - return '%s' % self.growth - elif self.coefficient == -1: - return '-%s' % self.growth - else: - return '%s*%s' % (self.coefficient, self.growth) + return '%s*%s' % (c, g) + + + def __invert__(self): + r""" + Invert this term. + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = TermMonoid('exact', G, ZZ) + sage: ~T(x, 2) # indirect doctest + 1/2*x^(-1) + sage: (~T(x, 2)).parent() + Exact Term Monoid x^ZZ with coefficients in Rational Field + """ + try: + c = ~self.coefficient + except ZeroDivisionError: + raise ZeroDivisionError('Cannot invert %s since its coefficient %s ' + 'cannot be inverted.' % (self, self.coefficient)) + g = ~self.growth + return self.parent()._create_element_in_extension_(g, c) + + + def __pow__(self, exponent): + r""" + Calculate the power of this :class:`ExactTerm` to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + An :class:`ExactTerm`. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: T = TermMonoid('exact', G, ZZ) + sage: t = T('2*z'); t + 2*z + sage: t^3 # indirect doctest + 8*z^3 + sage: t^(1/2) # indirect doctest + sqrt(2)*z^(1/2) + """ + return self._calculate_pow_(exponent) def can_absorb(self, other): @@ -2041,9 +3148,9 @@ def can_absorb(self, other): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: ET = atm.TermMonoid('exact', agg.GrowthGroup('x^ZZ'), ZZ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: ET = TermMonoid('exact', GrowthGroup('x^ZZ'), ZZ) sage: t1 = ET(x^21, 1); t2 = ET(x^21, 2); t3 = ET(x^42, 1) sage: t1.can_absorb(t2) True @@ -2078,10 +3185,10 @@ def _absorb_(self, other): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: ET = atm.ExactTermMonoid(G, QQ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: ET = TermMonoid('exact', G, QQ) Asymptotic exact terms can absorb other asymptotic exact terms with the same growth:: @@ -2100,12 +3207,245 @@ def _absorb_(self, other): ArithmeticError: x^5 cannot absorb 2*x^2 """ coeff_new = self.coefficient + other.coefficient - if coeff_new == 0: + if coeff_new.is_zero(): return None else: return self.parent()(self.growth, coeff_new) + def log_term(self, base=None): + r""" + Determine the logarithm of this exact term. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of terms. + + .. NOTE:: + + This method returns a tuple with the summands that come from + applying the rule `\log(x\cdot y) = \log(x) + \log(y)`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * log(x)^ZZ'), SR) + sage: T(3*x^2).log_term() + (log(3), 2*log(x)) + sage: T(x^1234).log_term() + (1234*log(x),) + sage: T(49*x^7).log_term(base=7) + (log(49)/log(7), 7/log(7)*log(x)) + + :: + + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ'), SR) + sage: T('x * y').log_term() + (log(x), log(y)) + sage: T('4 * x * y').log_term(base=2) + (log(4)/log(2), 1/log(2)*log(x), 1/log(2)*log(y)) + + .. SEEALSO:: + + :meth:`OTerm.log_term`. + """ + return self._log_coefficient_(base=base) + self._log_growth_(base=base) + + + def is_constant(self): + r""" + Return whether this term is an (exact) constant. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + .. NOTE:: + + Only :class:`ExactTerm` with constant growth (`1`) are + constant. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T('x * log(x)').is_constant() + False + sage: T('3*x').is_constant() + False + sage: T(1/2).is_constant() + True + sage: T(42).is_constant() + True + """ + return self.growth.is_one() + + + def is_little_o_of_one(self): + r""" + Return whether this exact term is of order `o(1)`. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ'), QQ) + sage: T(x).is_little_o_of_one() + False + sage: T(1).is_little_o_of_one() + False + sage: T(x^(-1)).is_little_o_of_one() + True + + :: + + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * y^ZZ'), QQ) + sage: T('x * y^(-1)').is_little_o_of_one() + False + sage: T('x^(-1) * y').is_little_o_of_one() + False + sage: T('x^(-2) * y^(-3)').is_little_o_of_one() + True + + :: + + sage: T = TermMonoid('exact', GrowthGroup('x^QQ * log(x)^QQ'), QQ) + sage: T('x * log(x)^2').is_little_o_of_one() + False + sage: T('x^2 * log(x)^(-1234)').is_little_o_of_one() + False + sage: T('x^(-1) * log(x)^4242').is_little_o_of_one() + True + sage: T('x^(-1/100) * log(x)^(1000/7)').is_little_o_of_one() + True + """ + return self.growth.is_lt_one() + + + def rpow(self, base): + r""" + Return the power of ``base`` to this exact term. + + INPUT: + + - ``base`` -- an element or ``'e'``. + + OUTPUT: + + A term. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ'), QQ) + sage: T('x').rpow(2) + 2^x + sage: T('log(x)').rpow('e') + x + sage: T('42*log(x)').rpow('e') + x^42 + sage: T('3*x').rpow(2) + 8^x + + :: + + sage: T('3*x^2').rpow(2) + Traceback (most recent call last): + ... + ArithmeticError: Cannot construct 2^(x^2) in + Growth Group QQ^x * x^ZZ * log(x)^ZZ + > *previous* TypeError: unsupported operand parent(s) for '*': + 'Growth Group QQ^x * x^ZZ * log(x)^ZZ' and 'Growth Group ZZ^(x^2)' + """ + P = self.parent() + + if self.is_constant(): + if not hasattr(base, 'parent'): + base = P.coefficient_ring(base) + return P._create_element_in_extension_( + P.growth_group.one(), base ** self.coefficient) + + elem = P._create_element_in_extension_( + self.growth.rpow(base), P.coefficient_ring.one()) + return elem ** self.coefficient + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this exact term. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: E = TermMonoid('exact', GrowthGroup('x^ZZ'), ZZ) + sage: e = E.an_element(); e + x + sage: e._substitute_({'x': SR.var('z')}) + z + sage: E(2/x)._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in 2*x^(-1) in + Exact Term Monoid x^ZZ with coefficients in Integer Ring. + > *previous* ZeroDivisionError: Cannot substitute in x^(-1) in + Growth Group x^ZZ. + >> *previous* ZeroDivisionError: rational division by zero + sage: (e*e)._substitute_({'x': 'something'}) + 'somethingsomething' + sage: E(1/x)._substitute_({'x': 'something'}) + '' + sage: E(1/x)._substitute_({'x': ZZ}) + Traceback (most recent call last): + ... + ValueError: Cannot substitute in x^(-1) in + Exact Term Monoid x^ZZ with coefficients in Integer Ring. + > *previous* ValueError: Cannot substitute in x^(-1) in Growth Group x^ZZ. + >> *previous* ValueError: rank (=-1) must be nonnegative + """ + try: + g = self.growth._substitute_(rules) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + c = self.coefficient + + try: + return c * g + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + class ExactTermMonoid(TermWithCoefficientMonoid): r""" Parent for asymptotic exact terms, implemented in @@ -2120,35 +3460,38 @@ class ExactTermMonoid(TermWithCoefficientMonoid): of ``Join of Category of monoids and Category of posets``. This is also the default category if ``None`` is specified. - - ``base_ring`` -- the ring which contains the coefficients of + - ``coefficient_ring`` -- the ring which contains the coefficients of the elements. EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() - sage: G_QQ = agg.GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() - sage: ET_ZZ = atm.ExactTermMonoid(G_ZZ, ZZ); ET_ZZ - Exact Term Monoid x^ZZ with coefficients from Integer Ring - sage: ET_QQ = atm.ExactTermMonoid(G_QQ, QQ); ET_QQ - Exact Term Monoid x^QQ with coefficients from Rational Field + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import ExactTermMonoid + sage: G_ZZ = GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() + sage: G_QQ = GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() + sage: ET_ZZ = ExactTermMonoid(G_ZZ, ZZ); ET_ZZ + Exact Term Monoid x^ZZ with coefficients in Integer Ring + sage: ET_QQ = ExactTermMonoid(G_QQ, QQ); ET_QQ + Exact Term Monoid x^QQ with coefficients in Rational Field sage: ET_QQ.coerce_map_from(ET_ZZ) Conversion map: - From: Exact Term Monoid x^ZZ with coefficients from Integer Ring - To: Exact Term Monoid x^QQ with coefficients from Rational Field + From: Exact Term Monoid x^ZZ with coefficients in Integer Ring + To: Exact Term Monoid x^QQ with coefficients in Rational Field Exact term monoids can also be created using the :class:`term factory `:: - sage: atm.TermMonoid('exact', G_ZZ, ZZ) is ET_ZZ + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: TermMonoid('exact', G_ZZ, ZZ) is ET_ZZ True - sage: atm.TermMonoid('exact', agg.GrowthGroup('x^ZZ'), QQ) - Exact Term Monoid x^ZZ with coefficients from Rational Field + sage: TermMonoid('exact', GrowthGroup('x^ZZ'), QQ) + Exact Term Monoid x^ZZ with coefficients in Rational Field """ + # enable the category framework for elements Element = ExactTerm + def _repr_(self): r""" A representation string for this exact term monoid. @@ -2163,14 +3506,14 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: atm.ExactTermMonoid(G, QQ)._repr_() - 'Exact Term Monoid x^ZZ with coefficients from Rational Field' + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: TermMonoid('exact', G, QQ)._repr_() + 'Exact Term Monoid x^ZZ with coefficients in Rational Field' """ - return 'Exact Term Monoid %s with coefficients from %s' % \ - (self.growth_group._repr_short_(), self.base_ring()) + return 'Exact Term Monoid %s with coefficients in %s' % \ + (self.growth_group._repr_short_(), self.coefficient_ring) class TermMonoidFactory(sage.structure.factory.UniqueFactory): @@ -2182,14 +3525,22 @@ class TermMonoidFactory(sage.structure.factory.UniqueFactory): - :class:`ExactTermMonoid`. + .. NOTE:: + + An instance of this factory is available as ``TermMonoid``. + INPUT: - - ``term`` -- the kind of term that shall be created. Either - ``'exact'`` or ``'O'`` (capital letter ``O``). + - ``term`` -- the kind of term that shall be created. Either a string + ``'exact'`` or ``'O'`` (capital letter ``O``), + or an existing instance of a term. - ``growth_group`` -- a growth group. - - ``base_ring`` -- the base ring for coefficients. + - ``coefficient_ring`` -- a ring. + + - ``asymptotic_ring`` -- if specified, then ``growth_group`` and + ``coefficient_ring`` are taken from this asymptotic ring. OUTPUT: @@ -2197,15 +3548,85 @@ class TermMonoidFactory(sage.structure.factory.UniqueFactory): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: OT = atm.TermMonoid('O', G); OT - Asymptotic O-Term Monoid x^ZZ - sage: ET = atm.TermMonoid('exact', G, ZZ); ET - Exact Term Monoid x^ZZ with coefficients from Integer Ring + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: TermMonoid('O', G, QQ) + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + sage: TermMonoid('exact', G, ZZ) + Exact Term Monoid x^ZZ with coefficients in Integer Ring + + :: + + sage: R = AsymptoticRing(growth_group=G, coefficient_ring=QQ) + sage: TermMonoid('exact', asymptotic_ring=R) + Exact Term Monoid x^ZZ with coefficients in Rational Field + sage: TermMonoid('O', asymptotic_ring=R) + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + + TESTS:: + + sage: TermMonoid(TermMonoid('O', G, ZZ), asymptotic_ring=R) + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + sage: TermMonoid(TermMonoid('exact', G, ZZ), asymptotic_ring=R) + Exact Term Monoid x^ZZ with coefficients in Rational Field + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: TermMonoid(GenericTermMonoid(G, ZZ), asymptotic_ring=R) + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + + :: + + sage: TestSuite(TermMonoid('exact', GrowthGroup('x^ZZ'), QQ)).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass + + :: + + sage: TestSuite(TermMonoid('O', GrowthGroup('x^QQ'), ZZ)).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass """ - def create_key_and_extra_args(self, term, growth_group, base_ring=None, + def create_key_and_extra_args(self, term, + growth_group=None, coefficient_ring=None, + asymptotic_ring=None, **kwds): r""" Given the arguments and keyword, create a key that uniquely @@ -2213,43 +3634,64 @@ def create_key_and_extra_args(self, term, growth_group, base_ring=None, EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.TermMonoid.create_key_and_extra_args('O', G) - (('O', Growth Group x^ZZ, None), {}) - sage: atm.TermMonoid.create_key_and_extra_args('exact', G, ZZ) - (('exact', Growth Group x^ZZ, Integer Ring), {}) - sage: atm.TermMonoid.create_key_and_extra_args('exact', G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: TermMonoid.create_key_and_extra_args('O', G, QQ) + ((, + Growth Group x^ZZ, Rational Field), {}) + sage: TermMonoid.create_key_and_extra_args('exact', G, ZZ) + ((, + Growth Group x^ZZ, Integer Ring), {}) + sage: TermMonoid.create_key_and_extra_args('exact', G) Traceback (most recent call last): ... - ValueError: A base ring has to be specified + ValueError: A coefficient ring has to be specified + to create a term monoid of type 'exact' TESTS:: - sage: atm.TermMonoid.create_key_and_extra_args('icecream', G) + sage: TermMonoid.create_key_and_extra_args('icecream', G) Traceback (most recent call last): ... - ValueError: icecream has to be either 'exact' or 'O' - sage: atm.TermMonoid.create_key_and_extra_args('O', ZZ) + ValueError: Term specification 'icecream' has to be either + 'exact' or 'O' or an instance of an existing term. + sage: TermMonoid.create_key_and_extra_args('O', ZZ) Traceback (most recent call last): ... ValueError: Integer Ring has to be an asymptotic growth group """ - if term not in ['O', 'exact']: - raise ValueError("%s has to be either 'exact' or 'O'" % term) + if isinstance(term, GenericTermMonoid): + from misc import underlying_class + term_class = underlying_class(term) + elif term == 'O': + term_class = OTermMonoid + elif term == 'exact': + term_class = ExactTermMonoid + else: + raise ValueError("Term specification '%s' has to be either 'exact' or 'O' " + "or an instance of an existing term." % term) + + if asymptotic_ring is not None and \ + (growth_group is not None or coefficient_ring is not None): + raise ValueError("Input ambiguous: asymptotic ring %s as well as " + "growth group %s or coefficient ring %s are given." % + (asymptotic_ring, growth_group, coefficient_ring)) - from sage.rings.asymptotic.growth_group import GenericGrowthGroup + if asymptotic_ring is not None: + growth_group = asymptotic_ring.growth_group + coefficient_ring = asymptotic_ring.coefficient_ring + + from growth_group import GenericGrowthGroup if not isinstance(growth_group, GenericGrowthGroup): raise ValueError("%s has to be an asymptotic growth group" % growth_group) - if term == 'exact' and base_ring is None: - raise ValueError("A base ring has to be specified") - elif term == 'O': - base_ring = None + if coefficient_ring is None: + raise ValueError("A coefficient ring has to be specified to " + "create a term monoid of type '%s'" % (term,)) - return (term, growth_group, base_ring), kwds + return (term_class, growth_group, coefficient_ring), kwds def create_object(self, version, key, **kwds): @@ -2258,20 +3700,21 @@ def create_object(self, version, key, **kwds): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.TermMonoid('O', G) # indirect doctest - Asymptotic O-Term Monoid x^ZZ - sage: atm.TermMonoid('exact', G, ZZ) # indirect doctest - Exact Term Monoid x^ZZ with coefficients from Integer Ring + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: TermMonoid('O', G, QQ) # indirect doctest + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + sage: TermMonoid('exact', G, ZZ) # indirect doctest + Exact Term Monoid x^ZZ with coefficients in Integer Ring """ - - term, growth_group, base_ring = key - if term == 'O': - return OTermMonoid(growth_group, **kwds) - else: - return ExactTermMonoid(growth_group, base_ring, **kwds) + term_class, growth_group, coefficient_ring = key + return term_class(growth_group, coefficient_ring, **kwds) TermMonoid = TermMonoidFactory("TermMonoid") +r""" +A factory for asymptotic term monoids. +This is an instance of :class:`TermMonoidFactory` whose documentation +provides more details. +""" diff --git a/src/sage/rings/big_oh.py b/src/sage/rings/big_oh.py index 95b8f764597..0e98a4a5bec 100644 --- a/src/sage/rings/big_oh.py +++ b/src/sage/rings/big_oh.py @@ -1,5 +1,12 @@ """ Big O for various types (power series, p-adics, etc.) + +.. SEEALSO:: + + - `asymptotic expansions <../../asymptotic_expansions_index.html>`_ + - `p-adic numbers <../../../padics/index.html>`_ + - `power series <../../../power_series/index.html>`_ + - `polynomials <../../../polynomial_rings/index.html>`_ """ import sage.rings.arith as arith @@ -71,6 +78,16 @@ def O(*x, **kwds): sage: K(11^-12, 15) 11^-12 + O(11^15) + We can also work with :doc:`asymptotic expansions + `:: + + sage: A. = AsymptoticRing(growth_group='QQ^n * n^QQ * log(n)^QQ', coefficient_ring=QQ); A + doctest:...: FutureWarning: + This class/method/function is marked as experimental. ... + Asymptotic Ring over Rational Field + sage: O(n) + O(n) + TESTS:: sage: var('x, y') diff --git a/src/sage/rings/cfinite_sequence.py b/src/sage/rings/cfinite_sequence.py index a33e1c75d01..972392426db 100644 --- a/src/sage/rings/cfinite_sequence.py +++ b/src/sage/rings/cfinite_sequence.py @@ -429,6 +429,18 @@ def _repr_(self): else: return 'C-finite sequence, generated by ' + str(self.ogf()) + def __hash__(self): + r""" + Hash value for C finite sequence. + + EXAMPLES:: + + sage: C. = CFiniteSequences(QQ) + sage: hash(C((2-x)/(1-x-x^2))) # random + 42 + """ + return hash(self.parent()) ^ hash(self._ogf) + def _add_(self, other): """ Addition of C-finite sequences. diff --git a/src/sage/rings/complex_ball_acb.pyx b/src/sage/rings/complex_ball_acb.pyx index 6823960c101..0c5b9e6ec29 100644 --- a/src/sage/rings/complex_ball_acb.pyx +++ b/src/sage/rings/complex_ball_acb.pyx @@ -583,7 +583,7 @@ cdef class ComplexBall(Element): """ return acb_is_exact(self.value) - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element right, int op): """ Compare ``left`` and ``right``. @@ -607,19 +607,6 @@ cdef class ComplexBall(Element): False sage: a == b # optional - arb False - """ - return (left)._richcmp(right, op) - - cpdef _richcmp_(left, Element right, int op): - """ - Compare ``left`` and ``right``. - - For more information, see :mod:`sage.rings.complex_ball_acb`. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb sage: a = CBF(1, 2) # optional - arb sage: b = CBF(1, 2) # optional - arb sage: a is b # optional - arb @@ -629,70 +616,70 @@ cdef class ComplexBall(Element): TESTS: - Balls whose intersection consists of one point:: - - sage: a = CBF(RIF(1, 2), RIF(1, 2)) # optional - arb - sage: b = CBF(RIF(2, 4), RIF(2, 4)) # optional - arb - sage: a < b # optional - arb - Traceback (most recent call last): - ... - TypeError: No order is defined for ComplexBalls. - sage: a > b # optional - arb - Traceback (most recent call last): - ... - TypeError: No order is defined for ComplexBalls. - sage: a <= b # optional - arb - Traceback (most recent call last): - ... - TypeError: No order is defined for ComplexBalls. - sage: a >= b # optional - arb - Traceback (most recent call last): - ... - TypeError: No order is defined for ComplexBalls. - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - Balls with non-trivial intersection:: - - sage: a = CBF(RIF(1, 4), RIF(1, 4)) # optional - arb - sage: a = CBF(RIF(2, 5), RIF(2, 5)) # optional - arb - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - One ball contained in another:: - - sage: a = CBF(RIF(1, 4), RIF(1, 4)) # optional - arb - sage: b = CBF(RIF(2, 3), RIF(2, 3)) # optional - arb - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - Disjoint balls:: - - sage: a = CBF(1/3, 1/3) # optional - arb - sage: b = CBF(1/5, 1/5) # optional - arb - sage: a == b # optional - arb - False - sage: a != b # optional - arb - True - - Exact elements:: - - sage: a = CBF(2, 2) # optional - arb - sage: b = CBF(2, 2) # optional - arb - sage: a.is_exact() # optional - arb - True - sage: b.is_exact() # optional - arb - True - sage: a == b # optional - arb - True - sage: a != b # optional - arb - False + Balls whose intersection consists of one point:: + + sage: a = CBF(RIF(1, 2), RIF(1, 2)) # optional - arb + sage: b = CBF(RIF(2, 4), RIF(2, 4)) # optional - arb + sage: a < b # optional - arb + Traceback (most recent call last): + ... + TypeError: No order is defined for ComplexBalls. + sage: a > b # optional - arb + Traceback (most recent call last): + ... + TypeError: No order is defined for ComplexBalls. + sage: a <= b # optional - arb + Traceback (most recent call last): + ... + TypeError: No order is defined for ComplexBalls. + sage: a >= b # optional - arb + Traceback (most recent call last): + ... + TypeError: No order is defined for ComplexBalls. + sage: a == b # optional - arb + False + sage: a != b # optional - arb + False + + Balls with non-trivial intersection:: + + sage: a = CBF(RIF(1, 4), RIF(1, 4)) # optional - arb + sage: a = CBF(RIF(2, 5), RIF(2, 5)) # optional - arb + sage: a == b # optional - arb + False + sage: a != b # optional - arb + False + + One ball contained in another:: + + sage: a = CBF(RIF(1, 4), RIF(1, 4)) # optional - arb + sage: b = CBF(RIF(2, 3), RIF(2, 3)) # optional - arb + sage: a == b # optional - arb + False + sage: a != b # optional - arb + False + + Disjoint balls:: + + sage: a = CBF(1/3, 1/3) # optional - arb + sage: b = CBF(1/5, 1/5) # optional - arb + sage: a == b # optional - arb + False + sage: a != b # optional - arb + True + + Exact elements:: + + sage: a = CBF(2, 2) # optional - arb + sage: b = CBF(2, 2) # optional - arb + sage: a.is_exact() # optional - arb + True + sage: b.is_exact() # optional - arb + True + sage: a == b # optional - arb + True + sage: a != b # optional - arb + False """ cdef ComplexBall lt, rt cdef acb_t difference diff --git a/src/sage/rings/complex_double.pyx b/src/sage/rings/complex_double.pyx index 2dbd9b83612..31c12bd710a 100644 --- a/src/sage/rings/complex_double.pyx +++ b/src/sage/rings/complex_double.pyx @@ -151,7 +151,7 @@ cdef class ComplexDoubleField_class(sage.rings.ring.Field): (-1.0, -1.0 + 1.2246...e-16*I, False) """ from sage.categories.fields import Fields - ParentWithGens.__init__(self, self, ('I',), normalize=False, category = Fields()) + ParentWithGens.__init__(self, self, ('I',), normalize=False, category=Fields().Metric().Complete()) self._populate_coercion_lists_() def __reduce__(self): @@ -795,9 +795,13 @@ cdef class ComplexDoubleElement(FieldElement): """ return hash(complex(self)) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ - Rich comparison between ``left`` and ``right``. + We order the complex numbers in dictionary order by real parts then + imaginary parts. + + This order, of course, does not respect the field structure, though + it agrees with the usual order on the real numbers. EXAMPLES:: @@ -807,18 +811,8 @@ cdef class ComplexDoubleElement(FieldElement): -1 sage: cmp(CDF(1 + i), CDF(-1 - i)) 1 - """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - We order the complex numbers in dictionary order by real parts then - imaginary parts. - This order, of course, does not respect the field structure, though - it agrees with the usual order on the real numbers. - - EXAMPLES:: + :: sage: CDF(2,3) < CDF(3,1) True diff --git a/src/sage/rings/complex_field.py b/src/sage/rings/complex_field.py index 5ac959ccce6..991635b8e6a 100644 --- a/src/sage/rings/complex_field.py +++ b/src/sage/rings/complex_field.py @@ -198,12 +198,12 @@ def __init__(self, prec=53): sage: C = ComplexField(200) sage: C.category() - Category of fields + Join of Category of fields and Category of complete metric spaces sage: TestSuite(C).run() """ self._prec = int(prec) from sage.categories.fields import Fields - ParentWithGens.__init__(self, self._real_field(), ('I',), False, category = Fields()) + ParentWithGens.__init__(self, self._real_field(), ('I',), False, category=Fields().Metric().Complete()) # self._populate_coercion_lists_() self._populate_coercion_lists_(coerce_list=[complex_number.RRtoCC(self._real_field(), self)]) @@ -744,3 +744,4 @@ def _factor_univariate_polynomial(self, f): from sage.structure.factorization import Factorization return Factorization([(R(g).monic(),e) for g,e in zip(*F)], f.leading_coefficient()) + diff --git a/src/sage/rings/complex_interval.pyx b/src/sage/rings/complex_interval.pyx index 2fc79377f4a..936ed82c762 100644 --- a/src/sage/rings/complex_interval.pyx +++ b/src/sage/rings/complex_interval.pyx @@ -40,22 +40,20 @@ heavily modified: #***************************************************************************** -import math -import operator - include "sage/ext/interrupt.pxi" +from sage.libs.gmp.mpz cimport mpz_sgn, mpz_cmpabs_ui +from sage.libs.flint.fmpz cimport * from sage.structure.element cimport FieldElement, RingElement, Element, ModuleElement from complex_number cimport ComplexNumber import complex_interval_field from complex_field import ComplexField -import sage.misc.misc -import integer +from sage.rings.integer cimport Integer import infinity -import real_mpfi -import real_mpfr +cimport real_mpfi cimport real_mpfr +from sage.libs.pari.gen cimport gen as pari_gen cdef double LOG_TEN_TWO_PLUS_EPSILON = 3.321928094887363 # a small overestimate of log(10,2) @@ -121,7 +119,7 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): real, imag = (real).real(), (real).imag() elif isinstance(real, ComplexIntervalFieldElement): real, imag = (real).real(), (real).imag() - elif isinstance(real, sage.libs.pari.all.pari_gen): + elif isinstance(real, pari_gen): real, imag = real.real(), real.imag() elif isinstance(real, list) or isinstance(real, tuple): re, imag = real @@ -476,6 +474,54 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): mpfi_union(x.__im, self.__im, other_intv.__im) return x + def magnitude(self): + """ + The largest absolute value of the elements of the interval, rounded + away from zero. + + OUTPUT: a real number with rounding mode ``RNDU`` + + EXAMPLES:: + + sage: CIF(RIF(-1,1), RIF(-1,1)).magnitude() + 1.41421356237310 + sage: CIF(RIF(1,2), RIF(3,4)).magnitude() + 4.47213595499958 + sage: parent(CIF(1).magnitude()) + Real Field with 53 bits of precision and rounding RNDU + """ + cdef real_mpfi.RealIntervalField_class RIF = self._parent._real_field() + cdef real_mpfr.RealNumber x = RIF.__upper_field._new() + cdef real_mpfr.RealNumber y = RIF.__upper_field._new() + mpfi_mag(x.value, self.__re) + mpfi_mag(y.value, self.__im) + mpfr_hypot(x.value, x.value, y.value, MPFR_RNDA) + return x + + def mignitude(self): + """ + The smallest absolute value of the elements of the interval, rounded + towards zero. + + OUTPUT: a real number with rounding mode ``RNDD`` + + EXAMPLES:: + + sage: CIF(RIF(-1,1), RIF(-1,1)).mignitude() + 0.000000000000000 + sage: CIF(RIF(1,2), RIF(3,4)).mignitude() + 3.16227766016837 + sage: parent(CIF(1).mignitude()) + Real Field with 53 bits of precision and rounding RNDD + """ + cdef real_mpfi.RealIntervalField_class RIF = self._parent._real_field() + cdef real_mpfr.RealNumber x = RIF.__lower_field._new() + cdef real_mpfr.RealNumber y = RIF.__lower_field._new() + mpfi_mig(x.value, self.__re) + mpfi_mig(y.value, self.__im) + mpfr_hypot(x.value, x.value, y.value, MPFR_RNDZ) + return x + def center(self): """ Returns the closest floating-point approximation to the center @@ -733,10 +779,109 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): '[0.99109735947126309 .. 1.1179269966896264] + [1.4042388462787560 .. 1.4984624123369835]*I' sage: CIF(-7, -1) ^ CIF(0.3) 1.117926996689626? - 1.408500714575360?*I - """ - if isinstance(right, (int, long, integer.Integer)): - return RingElement.__pow__(self, right) - return (self.log() * self.parent()(right)).exp() + + Note that ``x^2`` is not the same as ``x*x``:: + + sage: a = CIF(RIF(-1,1)) + sage: print (a^2).str(style="brackets") + [0.00000000000000000 .. 1.0000000000000000] + sage: print (a*a).str(style="brackets") + [-1.0000000000000000 .. 1.0000000000000000] + sage: a = CIF(0, RIF(-1,1)) + sage: print (a^2).str(style="brackets") + [-1.0000000000000000 .. -0.00000000000000000] + sage: print (a*a).str(style="brackets") + [-1.0000000000000000 .. 1.0000000000000000] + sage: a = CIF(RIF(-1,1), RIF(-1,1)) + sage: print (a^2).str(style="brackets") + [-1.0000000000000000 .. 1.0000000000000000] + [-2.0000000000000000 .. 2.0000000000000000]*I + sage: print (a*a).str(style="brackets") + [-2.0000000000000000 .. 2.0000000000000000] + [-2.0000000000000000 .. 2.0000000000000000]*I + + We can take very high powers:: + + sage: RIF = RealIntervalField(27) + sage: CIF = ComplexIntervalField(27) + sage: s = RealField(27, rnd="RNDZ")(1/2)^(1/3) + sage: a = CIF(RIF(-s/2,s/2), RIF(-s, s)) + sage: r = a^(10^10000) + sage: print r.str(style="brackets") + [-2.107553304e1028 .. 2.107553304e1028] + [-2.107553304e1028 .. 2.107553304e1028]*I + + TESTS:: + + sage: CIF = ComplexIntervalField(7) + sage: [CIF(2) ^ RDF(i) for i in range(-5,6)] + [0.03125?, 0.06250?, 0.1250?, 0.2500?, 0.5000?, 1, 2, 4, 8, 16, 32] + sage: pow(CIF(1), CIF(1), CIF(1)) + Traceback (most recent call last): + ... + TypeError: pow() 3rd argument not allowed unless all arguments are integers + """ + if modulus is not None: + raise TypeError("pow() 3rd argument not allowed unless all arguments are integers") + + cdef ComplexIntervalFieldElement z, z2, t = None + z = self + + # Convert right to an integer + if not isinstance(right, Integer): + try: + right = Integer(right) + except TypeError: + # Exponent is really not an integer + return (z.log() * z._parent(right)).exp() + + cdef int s = mpz_sgn((right).value) + if s == 0: + return z._parent.one() + elif s < 0: + z = ~z + if not mpz_cmpabs_ui((right).value, 1): + return z + + # Convert exponent to fmpz_t + cdef fmpz_t e + fmpz_init(e) + fmpz_set_mpz(e, (right).value) + fmpz_abs(e, e) + + # Now we know that e >= 2. + # Use binary powering with special formula for squares. + + # Handle first bit more efficiently: + if fmpz_tstbit(e, 0): + res = z + else: + res = z._parent.one() + fmpz_tdiv_q_2exp(e, e, 1) # e >>= 1 + + # Allocate a temporary ComplexIntervalFieldElement + z2 = z._new() + + while True: + # Compute z2 = z^2 using the formula + # (a + bi)^2 = (a^2 - b^2) + 2abi + mpfi_sqr(z2.__re, z.__re) # a^2 + mpfi_sqr(z2.__im, z.__im) # b^2 + mpfi_sub(z2.__re, z2.__re, z2.__im) # a^2 - b^2 + mpfi_mul(z2.__im, z.__re, z.__im) # ab + mpfi_mul_2ui(z2.__im, z2.__im, 1) # 2ab + z = z2 + if fmpz_tstbit(e, 0): + res *= z + fmpz_tdiv_q_2exp(e, e, 1) # e >>= 1 + if fmpz_is_zero(e): + break + + # Swap temporary elements z2 and t (allocate t first if needed) + if t is not None: + z2 = t + else: + z2 = z2._new() + t = z + fmpz_clear(e) + return res def _magma_init_(self, magma): r""" @@ -927,6 +1072,23 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): return x + def _complex_mpfr_field_(self, field): + """ + Convert to a complex field. + + EXAMPLES:: + + sage: re = RIF("1.2") + sage: im = RIF(2, 3) + sage: a = ComplexIntervalField(30)(re, im) + sage: CC(a) + 1.20000000018626 + 2.50000000000000*I + """ + cdef ComplexNumber x = field(0) + mpfi_mid(x.__re, self.__re) + mpfi_mid(x.__im, self.__im) + return x + def __int__(self): """ Convert ``self`` to an ``int``. @@ -1000,7 +1162,7 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): """ return self.real().__nonzero__() or self.imag().__nonzero__() - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element right, int op): r""" As with the real interval fields this never returns false positives. Thus, `a == b` is ``True`` iff both `a` and `b` represent the same @@ -1037,9 +1199,6 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): sage: CDF(1) >= CDF(1) >= CDF.gen() >= CDF.gen() >= 0 >= -CDF.gen() >= CDF(-1) True """ - return (left)._richcmp(right, op) - - cpdef _richcmp_(left, Element right, int op): cdef ComplexIntervalFieldElement lt, rt lt = left rt = right @@ -1072,7 +1231,7 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): elif op == 5: #>= return real_diff > 0 or (real_diff == 0 and imag_diff >= 0) - def __cmp__(left, right): + cpdef int _cmp_(left, sage.structure.element.Element right) except -2: """ Intervals are compared lexicographically on the 4-tuple: ``(x.real().lower(), x.real().upper(), @@ -1093,27 +1252,18 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): 0 sage: cmp(b, a) 1 - """ - return (left)._cmp(right) - - - cpdef int _cmp_(left, sage.structure.element.Element right) except -2: - """ - Intervals are compared lexicographically on the 4-tuple: - ``(x.real().lower(), x.real().upper(), - x.imag().lower(), x.imag().upper())`` TESTS:: sage: tests = [] sage: for rl in (0, 1): - ... for ru in (rl, rl + 1): - ... for il in (0, 1): - ... for iu in (il, il + 1): - ... tests.append((CIF(RIF(rl, ru), RIF(il, iu)), (rl, ru, il, iu))) + ....: for ru in (rl, rl + 1): + ....: for il in (0, 1): + ....: for iu in (il, il + 1): + ....: tests.append((CIF(RIF(rl, ru), RIF(il, iu)), (rl, ru, il, iu))) sage: for (i1, t1) in tests: - ... for (i2, t2) in tests: - ... assert(cmp(i1, i2) == cmp(t1, t2)) + ....: for (i2, t2) in tests: + ....: assert(cmp(i1, i2) == cmp(t1, t2)) """ cdef int a, b a = mpfi_nan_p(left.__re) diff --git a/src/sage/rings/complex_interval_field.py b/src/sage/rings/complex_interval_field.py index a400f29f278..e147a410be5 100644 --- a/src/sage/rings/complex_interval_field.py +++ b/src/sage/rings/complex_interval_field.py @@ -377,10 +377,15 @@ def __call__(self, x, im=None): 2 + 3*I sage: CIF(pi, e) 3.141592653589794? + 2.718281828459046?*I + sage: ComplexIntervalField(100)(CIF(RIF(2,3))) + 3.? """ if im is None: - if isinstance(x, complex_interval.ComplexIntervalFieldElement) and x.parent() is self: - return x + if isinstance(x, complex_interval.ComplexIntervalFieldElement): + if x.parent() is self: + return x + else: + return complex_interval.ComplexIntervalFieldElement(self, x) elif isinstance(x, complex_double.ComplexDoubleElement): return complex_interval.ComplexIntervalFieldElement(self, x.real(), x.imag()) elif isinstance(x, str): diff --git a/src/sage/rings/complex_number.pyx b/src/sage/rings/complex_number.pyx index 34e941fe73a..8e4de8876a7 100644 --- a/src/sage/rings/complex_number.pyx +++ b/src/sage/rings/complex_number.pyx @@ -1123,11 +1123,10 @@ cdef class ComplexNumber(sage.structure.element.FieldElement): """ return complex(mpfr_get_d(self.__re, rnd), mpfr_get_d(self.__im, rnd)) - # return complex(float(self.__re), float(self.__im)) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, sage.structure.element.Element right) except -2: """ - Rich comparision between ``left`` and ``right``. + Compare ``left`` and ``right``. EXAMPLES:: @@ -1136,9 +1135,6 @@ cdef class ComplexNumber(sage.structure.element.FieldElement): sage: cmp(CC(2, 1), CC(2, 1)) 0 """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, sage.structure.element.Element right) except -2: cdef int a, b a = mpfr_nan_p(left.__re) b = mpfr_nan_p((right).__re) diff --git a/src/sage/rings/continued_fraction.py b/src/sage/rings/continued_fraction.py index 13f17a7c667..fd86a4f6ebb 100644 --- a/src/sage/rings/continued_fraction.py +++ b/src/sage/rings/continued_fraction.py @@ -533,12 +533,8 @@ def __cmp__(self, other): def _mpfr_(self, R): r""" - Return a numerical approximation of ``self`` in the real mpfr ring ``R``. - - The output result is accurate: when the rounding mode of - ``R`` is 'RNDN' then the result is the nearest binary number of ``R`` to - ``self``. The other rounding mode are 'RNDD' (toward +infinity), 'RNDU' - (toward -infinity) and 'RNDZ' (toward zero). + Return a correctly-rounded numerical approximation of ``self`` + in the real mpfr ring ``R``. EXAMPLES:: @@ -589,43 +585,38 @@ def _mpfr_(self, R): sage: cf.n(digits=11) 8.9371541378 - TESTS:: + TESTS: - We check that the rounding works as expected, at least in the rational - case:: + Check that the rounding works as expected (at least in the + rational case):: - sage: for _ in xrange(100): - ....: a = QQ.random_element(num_bound=1<<64) + sage: fields = [] + sage: for prec in [17, 24, 53, 128, 256]: + ....: for rnd in ['RNDN', 'RNDD', 'RNDU', 'RNDZ']: + ....: fields.append(RealField(prec=prec, rnd=rnd)) + sage: for n in range(3000): # long time + ....: a = QQ.random_element(num_bound=2^(n%100)) ....: cf = continued_fraction(a) - ....: for prec in 17,24,53,128,256: - ....: for rnd in 'RNDN','RNDD','RNDU','RNDZ': - ....: R = RealField(prec=prec, rnd=rnd) - ....: assert R(cf) == R(a) + ....: for R in fields: + ....: assert R(cf) == R(a) """ # 1. integer case if self.quotient(1) is Infinity: return R(self.quotient(0)) - # 2. negative numbers - # TODO: it is possible to deal with negative values. The only problem is - # that we need to find the good value for N (which involves - # self.quotient(k) for k=0,1,2) + rnd = R.rounding_mode() + + # 2. negative numbers: reduce to the positive case if self.quotient(0) < 0: - rnd = R.rounding_mode() - if rnd == 'RNDN' or rnd == 'RNDZ': - return -R(-self) - elif rnd == 'RNDD': - r = R(-self) - s,m,e = r.sign_mantissa_exponent() - if e < 0: - return -(R(m+1) >> (-e)) - return -(R(m+1) << e) - else: - r = R(-self) - s,m,e = r.sign_mantissa_exponent() - if e < 0: - return -(R(m-1) >> (-e)) - return -(R(m-1) << e) + sgn = -1 + self = -self + # Adjust rounding for change in sign + if rnd == 'RNDD': + rnd = 'RNDA' + elif rnd == 'RNDU': + rnd = 'RNDZ' + else: + sgn = 1 # 3. positive non integer if self.quotient(0) == 0: # 0 <= self < 1 @@ -655,8 +646,8 @@ def _mpfr_(self, R): assert m_odd.nbits() == R.prec() or m_even.nbits() == R.prec() - if m_even == m_odd: # no need to worry (we have a decimal number) - return R(m_even) >> N + if m_even == m_odd: # no need to worry (we have an exact number) + return R(sgn * m_even) >> N # check ordering # m_even/2^N <= p_even/q_even <= self <= p_odd/q_odd <= m_odd/2^N @@ -665,30 +656,24 @@ def _mpfr_(self, R): assert p_even / q_even <= p_odd / q_odd assert p_odd / q_odd <= m_odd / (ZZ_1 << N) - rnd = R.rounding_mode() if rnd == 'RNDN': # round to the nearest # in order to find the nearest approximation we possibly need to # augment our precision on convergents. while True: assert not(p_odd << (N+1) <= (2*m_odd-1) * q_odd) or not(p_even << (N+1) >= (2*m_even+1) * q_even) if p_odd << (N+1) <= (2*m_odd-1) * q_odd: - return R(m_even) >> N + return R(sgn * m_even) >> N if p_even << (N+1) >= (2*m_even+1) * q_even: - return R(m_odd) >> N + return R(sgn * m_odd) >> N k += 1 p_even = self.numerator(2*k) p_odd = self.numerator(2*k+1) q_even = self.denominator(2*k) q_odd = self.denominator(2*k+1) - elif rnd == 'RNDU': # round up (toward +infinity) - return R(m_odd) >> N - elif rnd == 'RNDD': # round down (toward -infinity) - return R(m_even) >> N - elif rnd == 'RNDZ': # round toward zero - if m_even.sign() == 1: - return R(m_even) >> N - else: - return R(m_odd) >> N + elif rnd == 'RNDU' or rnd == 'RNDA': # round up + return R(sgn * m_odd) >> N + elif rnd == 'RNDD' or rnd == 'RNDZ': # round down + return R(sgn * m_even) >> N else: raise ValueError("%s unknown rounding mode" % rnd) @@ -1471,7 +1456,7 @@ def _rational_(self): return self.numerator(n-1) / self.denominator(n-1) def _latex_(self): - """ + r""" EXAMPLES:: sage: a = continued_fraction(-17/389) diff --git a/src/sage/rings/finite_rings/element_givaro.pyx b/src/sage/rings/finite_rings/element_givaro.pyx index c94ee5a675f..403b99c8fd0 100644 --- a/src/sage/rings/finite_rings/element_givaro.pyx +++ b/src/sage/rings/finite_rings/element_givaro.pyx @@ -1297,19 +1297,6 @@ cdef class FiniteField_givaroElement(FinitePolyExtElement): return make_FiniteField_givaroElement(cache, cache.objectptr.one) return make_FiniteField_givaroElement(cache, r) - def __richcmp__(left, right, int op): - """ - EXAMPLES:: - - sage: k. = GF(9); k - Finite Field in a of size 3^2 - sage: a == k('a') # indirect doctest - True - sage: a == a + 1 - False - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(left, Element right) except -2: """ Comparison of finite field elements is correct or equality diff --git a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx index ba7b6991436..7c890f3ac2f 100644 --- a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx +++ b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx @@ -803,10 +803,16 @@ cdef class FiniteField_ntl_gf2eElement(FinitePolyExtElement): from sage.groups.generic import power return power(self,exp) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ Comparison of finite field elements. + .. NOTE:: + + Finite fields are unordered. However, we adopt the convention that + an element ``e`` is bigger than element ``f`` if its polynomial + representation is bigger. + EXAMPLES:: sage: k. = GF(2^20) @@ -819,13 +825,7 @@ cdef class FiniteField_ntl_gf2eElement(FinitePolyExtElement): sage: e != (e + 1) True - .. NOTE:: - - Finite fields are unordered. However, we adopt the convention that - an element ``e`` is bigger than element ``f`` if its polynomial - representation is bigger. - - EXAMPLES:: + :: sage: K. = GF(2^100) sage: a < a^2 @@ -843,12 +843,6 @@ cdef class FiniteField_ntl_gf2eElement(FinitePolyExtElement): sage: a == a True """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - Comparison of finite field elements. - """ (left._parent._cache).F.restore() c = GF2E_equal((left).x, (right).x) if c == 1: diff --git a/src/sage/rings/finite_rings/element_pari_ffelt.pyx b/src/sage/rings/finite_rings/element_pari_ffelt.pyx index 2ab45e245c5..2401a21b95a 100644 --- a/src/sage/rings/finite_rings/element_pari_ffelt.pyx +++ b/src/sage/rings/finite_rings/element_pari_ffelt.pyx @@ -388,31 +388,13 @@ cdef class FiniteFieldElement_pari_ffelt(FinitePolyExtElement): """ Comparison of finite field elements. - TESTS:: - - sage: k. = FiniteField(3^3, impl='pari_ffelt') - sage: a == 1 - False - sage: a^0 == 1 - True - sage: a == a - True - sage: a < a^2 - True - sage: a > a^2 - False - """ - cdef int r - pari_catch_sig_on() - r = cmp_universal(self.val, (other).val) - pari_catch_sig_off() - return r + .. NOTE:: - def __richcmp__(FiniteFieldElement_pari_ffelt left, object right, int op): - """ - Rich comparison of finite field elements. + Finite fields are unordered. However, for the purpose of + this function, we adopt the lexicographic ordering on the + representing polynomials. - EXAMPLE:: + EXAMPLES:: sage: k. = GF(2^20, impl='pari_ffelt') sage: e = k.random_element() @@ -424,13 +406,7 @@ cdef class FiniteFieldElement_pari_ffelt(FinitePolyExtElement): sage: e != (e + 1) True - .. NOTE:: - - Finite fields are unordered. However, for the purpose of - this function, we adopt the lexicographic ordering on the - representing polynomials. - - EXAMPLE:: + :: sage: K. = GF(2^100, impl='pari_ffelt') sage: a < a^2 @@ -447,8 +423,26 @@ cdef class FiniteFieldElement_pari_ffelt(FinitePolyExtElement): False sage: a == a True + + TESTS:: + + sage: k. = FiniteField(3^3, impl='pari_ffelt') + sage: a == 1 + False + sage: a^0 == 1 + True + sage: a == a + True + sage: a < a^2 + True + sage: a > a^2 + False """ - return (left)._richcmp(right, op) + cdef int r + pari_catch_sig_on() + r = cmp_universal(self.val, (other).val) + pari_catch_sig_off() + return r cpdef ModuleElement _add_(FiniteFieldElement_pari_ffelt self, ModuleElement right): """ diff --git a/src/sage/rings/finite_rings/hom_finite_field.pyx b/src/sage/rings/finite_rings/hom_finite_field.pyx index 04a45c10142..1ca9fd576c1 100644 --- a/src/sage/rings/finite_rings/hom_finite_field.pyx +++ b/src/sage/rings/finite_rings/hom_finite_field.pyx @@ -341,16 +341,10 @@ cdef class FiniteFieldHomomorphism_generic(RingHomomorphism_im_gens): """ return self._section_class(self) - - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - def __hash__(self): return Morphism.__hash__(self) - cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): """ A class implementing Frobenius endomorphisms on finite fields. @@ -670,11 +664,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): """ return self.power() == 0 - - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - def __hash__(self): return Morphism.__hash__(self) diff --git a/src/sage/rings/finite_rings/integer_mod.pyx b/src/sage/rings/finite_rings/integer_mod.pyx index efc5d11d33b..4b041bba56b 100644 --- a/src/sage/rings/finite_rings/integer_mod.pyx +++ b/src/sage/rings/finite_rings/integer_mod.pyx @@ -1838,10 +1838,6 @@ cdef class IntegerMod_gmp(IntegerMod_abstract): else: return 1 - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - cpdef bint is_one(IntegerMod_gmp self): """ Returns ``True`` if this is `1`, otherwise @@ -2252,10 +2248,6 @@ cdef class IntegerMod_int(IntegerMod_abstract): else: return 1 - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - cpdef bint is_one(IntegerMod_int self): """ Returns ``True`` if this is `1`, otherwise @@ -3080,10 +3072,6 @@ cdef class IntegerMod_int64(IntegerMod_abstract): elif self.ivalue < (right).ivalue: return -1 else: return 1 - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - cpdef bint is_one(IntegerMod_int64 self): """ Returns ``True`` if this is `1`, otherwise diff --git a/src/sage/rings/fraction_field_FpT.pyx b/src/sage/rings/fraction_field_FpT.pyx index 4898d3c5760..ee3c10cf786 100644 --- a/src/sage/rings/fraction_field_FpT.pyx +++ b/src/sage/rings/fraction_field_FpT.pyx @@ -341,18 +341,6 @@ cdef class FpTElement(RingElement): else: return "\\frac{%s}{%s}" % (self.numer()._latex_(), self.denom()._latex_()) - def __richcmp__(left, right, int op): - """ - EXAMPLES:: - - sage: K = Frac(GF(5)['t']); t = K.gen() - sage: t == 1 - False - sage: t + 1 < t^2 - True - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element other) except -2: """ Compares this with another element. The ordering is arbitrary, @@ -387,6 +375,14 @@ cdef class FpTElement(RingElement): True sage: b < 1/a False + + :: + + sage: K = Frac(GF(5)['t']); t = K.gen() + sage: t == 1 + False + sage: t + 1 < t^2 + True """ # They are normalized. cdef int j = sage_cmp_nmod_poly_t(self._numer, (other)._numer) diff --git a/src/sage/rings/fraction_field_element.pyx b/src/sage/rings/fraction_field_element.pyx index 4796600bbdf..86d12adfb26 100644 --- a/src/sage/rings/fraction_field_element.pyx +++ b/src/sage/rings/fraction_field_element.pyx @@ -841,18 +841,6 @@ cdef class FractionFieldElement(FieldElement): """ return float(self.__numerator) / float(self.__denominator) - def __richcmp__(left, right, int op): - """ - EXAMPLES:: - - sage: K. = Frac(ZZ['x,y']) - sage: x > y - True - sage: 1 > y - False - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element other) except -2: """ EXAMPLES:: @@ -864,6 +852,14 @@ cdef class FractionFieldElement(FieldElement): True sage: t == t/5 False + + :: + + sage: K. = Frac(ZZ['x,y']) + sage: x > y + True + sage: 1 > y + False """ return cmp(self.__numerator * \ (other).__denominator, diff --git a/src/sage/rings/function_field/function_field_element.pyx b/src/sage/rings/function_field/function_field_element.pyx index c701e7f8e39..2ecaabd469d 100644 --- a/src/sage/rings/function_field/function_field_element.pyx +++ b/src/sage/rings/function_field/function_field_element.pyx @@ -361,6 +361,17 @@ cdef class FunctionFieldElement_polymod(FunctionFieldElement): """ return not not self._x + def __hash__(self): + """ + TESTS:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x*y + 4*x^3) + sage: len({hash(y^i+x^j) for i in [-2..2] for j in [-2..2]}) == 25 + True + """ + return hash(self._x) + cpdef int _cmp_(self, Element other) except -2: """ EXAMPLES:: @@ -563,6 +574,19 @@ cdef class FunctionFieldElement_rational(FunctionFieldElement): """ return not not self._x + def __hash__(self): + """ + TESTS: + + It would be nice if the following would produce a list of + 15 distinct hashes:: + + sage: K. = FunctionField(QQ) + sage: len({hash(t^i+t^j) for i in [-2..2] for j in [i..2]}) + 10 + """ + return hash(self._x) + cpdef int _cmp_(self, Element other) except -2: """ EXAMPLES:: diff --git a/src/sage/rings/ideal.py b/src/sage/rings/ideal.py index 93b9e106461..36932590f97 100644 --- a/src/sage/rings/ideal.py +++ b/src/sage/rings/ideal.py @@ -1229,6 +1229,22 @@ def __contains__(self, x): return x.is_zero() return self.gen().divides(x) + def __hash__(self): + r""" + Very stupid constant hash function! + + TESTS:: + + sage: P. = PolynomialRing(ZZ) + sage: I = P.ideal(x^2) + sage: J = [x, y^2 + x*y]*P + sage: hash(I) + 0 + sage: hash(J) + 0 + """ + return 0 + def __cmp__(self, other): """ Compare the two ideals. diff --git a/src/sage/rings/integer_ring.pyx b/src/sage/rings/integer_ring.pyx index f43931947ba..76adb61c7d6 100644 --- a/src/sage/rings/integer_ring.pyx +++ b/src/sage/rings/integer_ring.pyx @@ -122,7 +122,8 @@ cdef class IntegerRing_class(PrincipalIdealDomain): False sage: Z.category() Join of Category of euclidean domains - and Category of infinite enumerated sets + and Category of infinite enumerated sets + and Category of metric spaces sage: Z(2^(2^5) + 1) 4294967297 @@ -303,7 +304,7 @@ cdef class IntegerRing_class(PrincipalIdealDomain): True """ ParentWithGens.__init__(self, self, ('x',), normalize=False, - category=(EuclideanDomains(), InfiniteEnumeratedSets())) + category=(EuclideanDomains(), InfiniteEnumeratedSets().Metric())) self._populate_coercion_lists_(element_constructor=integer.Integer, init_no_parent=True, convert_method_name='_integer_') @@ -1245,9 +1246,9 @@ cdef class IntegerRing_class(PrincipalIdealDomain): elif n == 2: return sage.rings.integer.Integer(-1) elif n < 1: - raise ValueError, "n must be positive in zeta()" + raise ValueError("n must be positive in zeta()") else: - raise ValueError, "no nth root of unity in integer ring" + raise ValueError("no nth root of unity in integer ring") def parameter(self): r""" diff --git a/src/sage/rings/laurent_series_ring_element.pyx b/src/sage/rings/laurent_series_ring_element.pyx index 391991b4329..77ac755c16e 100644 --- a/src/sage/rings/laurent_series_ring_element.pyx +++ b/src/sage/rings/laurent_series_ring_element.pyx @@ -641,8 +641,11 @@ cdef class LaurentSeries(AlgebraElement): """ if prec == infinity or prec >= self.prec(): return self + P = self._parent + if not self: + return LaurentSeries(P, P.power_series_ring()(0, prec=0), prec) u = self.__u.add_bigoh(prec - self.__n) - return LaurentSeries(self._parent, u, self.__n) + return LaurentSeries(P, u, self.__n) def degree(self): """ @@ -869,9 +872,6 @@ cdef class LaurentSeries(AlgebraElement): return self.prec() return min(self.prec(), f.prec()) - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element right_r) except -2: r""" Comparison of self and right. @@ -1231,10 +1231,22 @@ cdef class LaurentSeries(AlgebraElement): sage: f.power_series() Traceback (most recent call last): ... - ArithmeticError: self is a not a power series + TypeError: self is not a power series + + TESTS: + + Check whether a polynomial over a Laurent series ring is contained in the + polynomial ring over the power series ring (see :trac:`19459`): + + sage: L. = LaurentSeriesRing(GF(2)) + sage: R. = PolynomialRing(L) + sage: O = L.power_series_ring() + sage: S. = PolynomialRing(O) + sage: t**(-1)*x*y in S + False """ if self.__n < 0: - raise ArithmeticError, "self is a not a power series" + raise TypeError("self is not a power series") u = self.__u t = u.parent().gen() return t**(self.__n) * u diff --git a/src/sage/rings/morphism.pyx b/src/sage/rings/morphism.pyx index 502c1ceb976..594cf9ff34e 100644 --- a/src/sage/rings/morphism.pyx +++ b/src/sage/rings/morphism.pyx @@ -1137,20 +1137,6 @@ cdef class RingHomomorphism_im_gens(RingHomomorphism): _slots['__im_gens'] = self.__im_gens return RingHomomorphism._extra_slots(self, _slots) - def __richcmp__(left, right, int op): - """ - Used internally by the cmp method. - - TESTS:: - - sage: R. = QQ[]; f = R.hom([x,x+y]); g = R.hom([y,x]) - sage: cmp(f,g) # indirect doctest - 1 - sage: cmp(g,f) - -1 - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element other) except -2: r""" EXAMPLES: @@ -1175,6 +1161,14 @@ cdef class RingHomomorphism_im_gens(RingHomomorphism): sage: loads(dumps(f2)) == f2 True + :: + + sage: R. = QQ[]; f = R.hom([x,x+y]); g = R.hom([y,x]) + sage: cmp(f,g) # indirect doctest + 1 + sage: cmp(g,f) + -1 + EXAMPLES: A multivariate quotient over a finite field:: @@ -1441,22 +1435,6 @@ cdef class RingHomomorphism_from_base(RingHomomorphism): _slots['__underlying'] = self.__underlying return RingHomomorphism._extra_slots(self, _slots) - def __richcmp__(left, right, int op): - """ - Used internally by the cmp method. - - TESTS:: - - sage: R. = QQ[]; f = R.hom([x,x+y]); g = R.hom([y,x]) - sage: S. = R[] - sage: fS = S.hom(f,S); gS = S.hom(g,S) - sage: cmp(fS,gS) # indirect doctest - 1 - sage: cmp(gS,fS) # indirect doctest - -1 - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element other) except -2: r""" EXAMPLES: @@ -1480,6 +1458,16 @@ cdef class RingHomomorphism_from_base(RingHomomorphism): sage: f1P == loads(dumps(f1P)) True + TESTS:: + + sage: R. = QQ[]; f = R.hom([x,x+y]); g = R.hom([y,x]) + sage: S. = R[] + sage: fS = S.hom(f,S); gS = S.hom(g,S) + sage: cmp(fS,gS) # indirect doctest + 1 + sage: cmp(gS,fS) # indirect doctest + -1 + EXAMPLES: A matrix ring over a multivariate quotient over a finite field:: @@ -2109,9 +2097,6 @@ cdef class FrobeniusEndomorphism_generic(RingHomomorphism): codomain = self.codomain() return hash((domain, codomain, ('Frob', self._power))) - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - cpdef int _cmp_(left, Element right) except -2: if left is right: return 0 domain = left.domain() diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index 1e3d3306f14..030c8d4dda5 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -709,7 +709,7 @@ cdef class NumberFieldElement(FieldElement): raise IndexError, "index must be between 0 and degree minus 1." return self.polynomial()[n] - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, sage.structure.element.Element right) except -2: r""" EXAMPLE:: @@ -719,9 +719,6 @@ cdef class NumberFieldElement(FieldElement): sage: a + 1 < a # indirect doctest False """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, sage.structure.element.Element right) except -2: cdef NumberFieldElement _right = right return not (ZZX_equal(left.__numerator, _right.__numerator) and ZZ_equal(left.__denominator, _right.__denominator)) diff --git a/src/sage/rings/number_field/number_field_element_quadratic.pyx b/src/sage/rings/number_field/number_field_element_quadratic.pyx index 223ed701c1b..1d8ebcf6c2e 100644 --- a/src/sage/rings/number_field/number_field_element_quadratic.pyx +++ b/src/sage/rings/number_field/number_field_element_quadratic.pyx @@ -658,22 +658,15 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): return test return -test - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element _right, int op): r""" - Note: we may implement a more direct way of comparison for integer, - float and quadratic numbers input (ie avoiding coercion). + Rich comparison of elements. TESTS:: sage: K. = QuadraticField(-1) sage: sorted([5*i+1, 2, 3*i+1, 2-i]) [3*i + 1, 5*i + 1, -i + 2, 2] - """ - return (left)._richcmp(right, op) - - cpdef _richcmp_(left, Element _right, int op): - r""" - C implementation of comparison. TESTS: @@ -796,8 +789,8 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): mpz_clear(j) return rich_to_bool_sgn(op, test) - def __cmp__(left, right): - r""" + cpdef int _cmp_(left, Element _right) except -2: + """ Comparisons of elements. When there is a real embedding defined, the comparisons uses comparison @@ -869,12 +862,6 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): sage: map(CDF, l) == sorted(map(CDF, l)) True """ - return (left)._cmp(right) - - cpdef int _cmp_(left, Element _right) except -2: - """ - C implementation of comparison. - """ cdef NumberFieldElement_quadratic right = _right cdef int test diff --git a/src/sage/rings/padics/CA_template.pxi b/src/sage/rings/padics/CA_template.pxi index 9ca4a54a8c5..1f56e90b3af 100644 --- a/src/sage/rings/padics/CA_template.pxi +++ b/src/sage/rings/padics/CA_template.pxi @@ -160,22 +160,6 @@ cdef class CAElement(pAdicTemplateElement): """ return unpickle_cae_v2, (self.__class__, self.parent(), cpickle(self.value, self.prime_pow), self.absprec) - def __richcmp__(self, right, int op): - """ - Compare this element to ``right`` using the comparison operator ``op``. - - TESTS:: - - sage: R = ZpCA(5) - sage: a = R(17) - sage: b = R(21) - sage: a == b - False - sage: a < b - True - """ - return (self)._richcmp(right, op) - cpdef ModuleElement _neg_(self): """ Return the additive inverse of this element. diff --git a/src/sage/rings/padics/CR_template.pxi b/src/sage/rings/padics/CR_template.pxi index 45c70d9ccec..41f63a68b0e 100644 --- a/src/sage/rings/padics/CR_template.pxi +++ b/src/sage/rings/padics/CR_template.pxi @@ -270,22 +270,6 @@ cdef class CRElement(pAdicTemplateElement): """ return unpickle_cre_v2, (self.__class__, self.parent(), cpickle(self.unit, self.prime_pow), self.ordp, self.relprec) - def __richcmp__(self, right, int op): - """ - Compare this element to ``right`` using the comparison operator ``op``. - - TESTS:: - - sage: R = Zp(5) - sage: a = R(17) - sage: b = R(21) - sage: a == b - False - sage: a < b # indirect doctest - True - """ - return (self)._richcmp(right, op) - cpdef ModuleElement _neg_(self): """ Return the additive inverse of this element. diff --git a/src/sage/rings/padics/FM_template.pxi b/src/sage/rings/padics/FM_template.pxi index e9c21608613..f7c446439bb 100644 --- a/src/sage/rings/padics/FM_template.pxi +++ b/src/sage/rings/padics/FM_template.pxi @@ -155,22 +155,6 @@ cdef class FMElement(pAdicTemplateElement): """ return unpickle_fme_v2, (self.__class__, self.parent(), cpickle(self.value, self.prime_pow)) - def __richcmp__(self, right, int op): - """ - Compare this element to ``right`` using the comparison operator ``op``. - - TESTS:: - - sage: R = ZpFM(5) - sage: a = R(17) - sage: b = R(21) - sage: a == b - False - sage: a < b - True - """ - return (self)._richcmp(right, op) - cpdef ModuleElement _neg_(self): r""" Return the additive inverse of this element. diff --git a/src/sage/rings/padics/local_generic.py b/src/sage/rings/padics/local_generic.py index 48bdfb4f64c..8ee98a1b41d 100644 --- a/src/sage/rings/padics/local_generic.py +++ b/src/sage/rings/padics/local_generic.py @@ -55,6 +55,7 @@ def __init__(self, base, prec, names, element_class, category=None): category = CompleteDiscreteValuationFields() else: category = CompleteDiscreteValuationRings() + category = category.Metric().Complete() if default_category is not None: category = check_default_category(default_category, category) Parent.__init__(self, base, names=(names,), normalize=False, category=category, element_constructor=element_class) diff --git a/src/sage/rings/padics/morphism.pyx b/src/sage/rings/padics/morphism.pyx index 49bb5fb5ff1..20c6a31a720 100644 --- a/src/sage/rings/padics/morphism.pyx +++ b/src/sage/rings/padics/morphism.pyx @@ -1,12 +1,16 @@ -"Frobenius endomorphisms on padic fields" +""" +Frobenius endomorphisms on p-adic fields +""" -############################################################################# -# Copyright (C) 2013 Xavier Caruso -# -# Distributed under the terms of the GNU General Public License (GPL) +#***************************************************************************** +# Copyright (C) 2013 Xavier Caruso # +# 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 sage.rings.integer cimport Integer from sage.rings.infinity import Infinity @@ -288,9 +292,6 @@ cdef class FrobeniusEndomorphism_padics(RingHomomorphism): codomain = self.codomain() return hash((domain,codomain,('Frob',self._power))) - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - cpdef int _cmp_(left, Element right) except -2: """ Compare left and right diff --git a/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx b/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx index 377f2f6713b..317b69f56dc 100644 --- a/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx +++ b/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx @@ -445,25 +445,6 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): ans.prime_pow = self.prime_pow return ans - def __richcmp__(left, right, op): - """ - Compares ``left`` and ``right`` under the operation ``op``. - - EXAMPLES:: - - sage: R = ZpFM(5,5) - sage: S. = R[] - sage: f = x^5 + 75*x^3 - 15*x^2 +125*x - 5 - sage: W. = R.ext(f) - sage: w == 1 # indirect doctest - False - sage: y = 1 + w - sage: z = 1 + w + w^27 - sage: y == z - True - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(left, Element right) except -2: """ First compare valuations, then compare the values. diff --git a/src/sage/rings/padics/padic_base_leaves.py b/src/sage/rings/padics/padic_base_leaves.py index a43eb7ef9af..55de7f5c625 100644 --- a/src/sage/rings/padics/padic_base_leaves.py +++ b/src/sage/rings/padics/padic_base_leaves.py @@ -231,7 +231,7 @@ def __init__(self, p, prec, print_mode, names): sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^3)]) sage: R = ZpCR(3, 2) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) + sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) # long time sage: R = ZpCR(next_prime(10^60)) sage: TestSuite(R).run() @@ -320,7 +320,7 @@ def __init__(self, p, prec, print_mode, names): sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^3)]) sage: R = ZpCA(3, 2) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) + sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) # long time sage: R = ZpCA(next_prime(10^60)) sage: TestSuite(R).run() @@ -411,7 +411,7 @@ def __init__(self, p, prec, print_mode, names): sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^3)]) sage: R = ZpFM(3, 2) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) + sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) # long time sage: R = ZpFM(next_prime(10^60)) sage: TestSuite(R).run() @@ -525,10 +525,12 @@ def __init__(self, p, prec, print_mode, names): sage: TestSuite(R).run(elements = [R.random_element() for i in range(2^10)], max_runs = 2^12) # long time sage: R = Qp(3, 1) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) + sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) # long time sage: R = Qp(3, 2) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^9)]) + sage: TestSuite(R).run(elements=[R.random_element() for i in range(3^9)], + ....: skip="_test_metric") # Skip because too long + sage: R._test_metric(elements=[R.random_element() for i in range(3^3)]) sage: R = Qp(next_prime(10^60)) sage: TestSuite(R).run() diff --git a/src/sage/rings/padics/padic_generic.py b/src/sage/rings/padics/padic_generic.py index 1fba8833650..6cccca5d9e8 100644 --- a/src/sage/rings/padics/padic_generic.py +++ b/src/sage/rings/padics/padic_generic.py @@ -24,6 +24,10 @@ # http://www.gnu.org/licenses/ #***************************************************************************** + +from sage.misc.prandom import sample +from sage.misc.misc import some_tuples + from sage.categories.principal_ideal_domains import PrincipalIdealDomains from sage.categories.fields import Fields from sage.rings.infinity import infinity @@ -34,6 +38,7 @@ from sage.rings.padics.precision_error import PrecisionError from sage.misc.cachefunc import cached_method + class pAdicGeneric(PrincipalIdealDomain, LocalGeneric): def __init__(self, base, p, prec, print_mode, names, element_class, category=None): """ @@ -56,6 +61,7 @@ def __init__(self, base, p, prec, print_mode, names, element_class, category=Non category = Fields() else: category = PrincipalIdealDomains() + category = category.Metric().Complete() LocalGeneric.__init__(self, base, prec, names, element_class, category) self._printer = pAdicPrinter(self, print_mode) @@ -518,12 +524,7 @@ def _test_add(self, **options): tester.assertEqual(y.precision_absolute(),x.precision_absolute()) tester.assertEqual(y.precision_relative(),x.precision_relative()) - from sage.combinat.cartesian_product import CartesianProduct - elements = CartesianProduct(elements, elements) - if len(elements) > tester._max_runs: - from random import sample - elements = sample(elements, tester._max_runs) - for x,y in elements: + for x,y in some_tuples(elements, 2, tester._max_runs): z = x + y tester.assertIs(z.parent(), self) tester.assertEqual(z.precision_absolute(), min(x.precision_absolute(), y.precision_absolute())) @@ -552,19 +553,14 @@ def _test_sub(self, **options): """ tester = self._tester(**options) - elements = tester.some_elements() + elements = list(tester.some_elements()) for x in elements: y = x - self.zero() tester.assertEqual(y, x) tester.assertEqual(y.precision_absolute(), x.precision_absolute()) tester.assertEqual(y.precision_relative(), x.precision_relative()) - from sage.combinat.cartesian_product import CartesianProduct - elements = CartesianProduct(elements, elements) - if len(elements) > tester._max_runs: - from random import sample - elements = sample(elements, tester._max_runs) - for x,y in elements: + for x,y in some_tuples(elements, 2, tester._max_runs): z = x - y tester.assertIs(z.parent(), self) tester.assertEqual(z.precision_absolute(), min(x.precision_absolute(), y.precision_absolute())) @@ -628,12 +624,8 @@ def _test_mul(self, **options): """ tester = self._tester(**options) - from sage.combinat.cartesian_product import CartesianProduct - elements = CartesianProduct(tester.some_elements(), tester.some_elements()) - if len(elements) > tester._max_runs: - from random import sample - elements = sample(elements, tester._max_runs) - for x,y in elements: + elements = list(tester.some_elements()) + for x,y in some_tuples(elements, 2, tester._max_runs): z = x * y tester.assertIs(z.parent(), self) tester.assertLessEqual(z.precision_relative(), min(x.precision_relative(), y.precision_relative())) @@ -659,12 +651,8 @@ def _test_div(self, **options): """ tester = self._tester(**options) - from sage.combinat.cartesian_product import CartesianProduct - elements = CartesianProduct(tester.some_elements(), tester.some_elements()) - if len(elements) > tester._max_runs: - from random import sample - elements = sample(elements, tester._max_runs) - for x,y in elements: + elements = list(tester.some_elements()) + for x,y in some_tuples(elements, 2, tester._max_runs): try: z = x / y except (ZeroDivisionError, PrecisionError, ValueError): diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index 7d9fd5dacd4..1a334b28f17 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -76,6 +76,42 @@ cdef class pAdicGenericElement(LocalGenericElement): 3 + O(19^5) sage: a < b True + + :: + + sage: R = Zp(5); a = R(5, 6); b = R(5 + 5^6, 8) + sage: a == b #indirect doctest + True + + :: + + sage: R = Zp(5) + sage: a = R(17) + sage: b = R(21) + sage: a == b + False + sage: a < b + True + + :: + + sage: R = ZpCA(5) + sage: a = R(17) + sage: b = R(21) + sage: a == b + False + sage: a < b + True + + :: + + sage: R = ZpFM(5) + sage: a = R(17) + sage: b = R(21) + sage: a == b + False + sage: a < b + True """ m = min(left.precision_absolute(), right.precision_absolute()) x_ordp = left.valuation() diff --git a/src/sage/rings/polynomial/complex_roots.py b/src/sage/rings/polynomial/complex_roots.py index 5056ea53222..42432118f68 100644 --- a/src/sage/rings/polynomial/complex_roots.py +++ b/src/sage/rings/polynomial/complex_roots.py @@ -24,128 +24,25 @@ [(1.167303978261419?, 1), (-0.764884433600585? - 0.352471546031727?*I, 1), (-0.764884433600585? + 0.352471546031727?*I, 1), (0.181232444469876? - 1.083954101317711?*I, 1), (0.181232444469876? + 1.083954101317711?*I, 1)] """ +#***************************************************************************** +# Copyright (C) 2007 Carl Witty +# +# 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 copy import copy -from sage.rings.real_mpfi import RealIntervalField from sage.rings.complex_field import ComplexField from sage.rings.complex_interval_field import ComplexIntervalField from sage.rings.qqbar import AA, QQbar from sage.rings.arith import sort_complex_numbers_for_display +from sage.rings.polynomial.refine_root import refine_root -def refine_root(ip, ipd, irt, fld): - """ - We are given a polynomial and its derivative (with complex - interval coefficients), an estimated root, and a complex interval - field to use in computations. We use interval arithmetic to - refine the root and prove that we have in fact isolated a unique - root. - - If we succeed, we return the isolated root; if we fail, we return - None. - - EXAMPLES:: - - sage: from sage.rings.polynomial.complex_roots import * - sage: x = polygen(ZZ) - sage: p = x^9 - 1 - sage: ip = CIF['x'](p); ip - x^9 - 1 - sage: ipd = CIF['x'](p.derivative()); ipd - 9*x^8 - sage: irt = CIF(CC(cos(2*pi/9), sin(2*pi/9))); irt - 0.76604444311897802? + 0.64278760968653926?*I - sage: ip(irt) - 0.?e-14 + 0.?e-14*I - sage: ipd(irt) - 6.89439998807080? - 5.78508848717885?*I - sage: refine_root(ip, ipd, irt, CIF) - 0.766044443118978? + 0.642787609686540?*I - """ - - # There has got to be a better way to do this, but I don't know - # what it is... - - # We start with a basic fact: if we do an interval Newton-Raphson - # step, and the refined interval is contained in the original interval, - # then the refined interval contains exactly one root. - - # Unfortunately, our initial estimated root almost certainly does not - # contain the actual root (our initial interval is a point, which - # is exactly equal to whatever floating-point estimate we got from - # the external solver). So we need to do multiple Newton-Raphson - # steps, and check this inclusion property on each step. - - # After a few steps of refinement, if the initial root estimate was - # close to a root, we should have an essentially perfect interval - # bound on the root (since Newton-Raphson has quadratic convergence), - # unless either the real or imaginary component of the root is zero. - # If the real or imaginary component is zero, then we could spend - # a long time computing closer and closer approximations to that - # component. (This doesn't happen for non-zero components, because - # of the imprecision of floating-point numbers combined with the - # outward interval rounding; but close to zero, MPFI provides - # extremely precise numbers.) - - # If the root is actually a real root, but we start with an imaginary - # component, we can bounce back and forth between having a positive - # and negative imaginary component, without ever hitting zero. - # To deal with this, on every other Newton-Raphson step, instead of - # replacing the old interval with the new one, we take the union. - - # If the containment check continues to fail many times in a row, - # we give up and return None; we also return None if we detect - # that the slope in our current interval is not bounded away - # from zero at any step. - - # After every refinement step, we check to see if the real or - # imaginary component of our interval includes zero. If so, we - # try setting it to exactly zero. This gives us a good chance of - # detecting real roots. However, we do this replacement at most - # once per component. - - refinement_steps = 10 - - smashed_real = False - smashed_imag = False - - for i in range(refinement_steps): - slope = ipd(irt) - if slope.contains_zero(): - return None - center = fld(irt.center()) - val = ip(center) - - nirt = center - val / slope - # print irt, nirt, (nirt in irt), nirt.diameter(), irt.diameter(), center, val, slope - if nirt in irt and (nirt.diameter() >= irt.diameter() >> 3 or i >= 8): - # If the new diameter is much less than the original diameter, - # then we have not yet converged. (Perhaps we were asked - # for a particularly high-precision result.) So we don't - # return yet. - return nirt - - if i & 1: - irt = nirt - else: - irt = irt.union(nirt) - # If we don't find a root after a while, try (approximately) - # tripling the size of the region. - if i >= 6: - rD = irt.real().absolute_diameter() - iD = irt.imag().absolute_diameter() - md = max(rD, iD) - md_intv = RealIntervalField(rD.prec())(-md, md) - md_cintv = ComplexIntervalField(rD.prec())(md_intv, md_intv) - irt = irt + md_cintv - - if not smashed_real and irt.real().contains_zero(): - irt = irt.parent()(0, irt.imag()) - smashed_real = True - if not smashed_imag and irt.imag().contains_zero(): - irt = irt.parent()(irt.real(), 0) - smashed_imag = True - - return None def interval_roots(p, rts, prec): """ @@ -335,7 +232,7 @@ def complex_roots(p, skip_squarefree=False, retval='interval', min_prec=0): TESTS: - Verify that trac 12026 is fixed:: + Verify that :trac:`12026` is fixed:: sage: f = matrix(QQ, 8, lambda i, j: 1/(i + j + 1)).charpoly() sage: from sage.rings.polynomial.complex_roots import complex_roots diff --git a/src/sage/rings/polynomial/ideal.py b/src/sage/rings/polynomial/ideal.py index a23f3e8567a..508496c75d4 100644 --- a/src/sage/rings/polynomial/ideal.py +++ b/src/sage/rings/polynomial/ideal.py @@ -64,4 +64,3 @@ def residue_field(self, names=None, check=True): from sage.rings.finite_rings.residue_field import ResidueField return ResidueField(self, names, check=False) - diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 59e873fe00d..081b6d4f340 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -255,16 +255,15 @@ def _repr_(self): """ return repr(self._p) - def _hash_(self): + def __hash__(self): """ TESTS:: sage: X. = InfinitePolynomialRing(QQ) sage: a = x[0] + x[1] sage: hash(a) # indirect doctest - -6172640511012239345 # 64-bit - -957478897 # 32-bit - + 971115012877883067 # 64-bit + -2103273797 # 32-bit """ return hash(self._p) diff --git a/src/sage/rings/polynomial/laurent_polynomial.pyx b/src/sage/rings/polynomial/laurent_polynomial.pyx index 091fc1a2fb0..956c0c09ee5 100644 --- a/src/sage/rings/polynomial/laurent_polynomial.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial.pyx @@ -844,30 +844,6 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial_generic): rl = LaurentPolynomial_univariate(self._parent, r, 0) return (ql, rl) - def __richcmp__(left, right, int op): - """ - Return the rich comparison of ``left`` and ``right`` defined by ``op``. - - EXAMPLES:: - - sage: R. = LaurentPolynomialRing(QQ) - sage: f = x^(-1) + 1 + x - sage: g = x^(-1) + 2 - sage: f == g - False - sage: f != g - True - sage: f < g - True - sage: f <= g - True - sage: f > g - False - sage: f >= g - False - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element right_r) except -2: r""" Comparison of ``self`` and ``right_r``. @@ -886,10 +862,16 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial_generic): sage: g = x^(-1) + 2 sage: f == g False + sage: f != g + True sage: f < g True + sage: f <= g + True sage: f > g False + sage: f >= g + False :: @@ -1332,8 +1314,25 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): sage: R. = LaurentPolynomialRing(QQ) sage: loads(dumps(x1)) == x1 # indirect doctest True + sage: z = x1/x2 + sage: loads(dumps(z)) == z # not tested (bug) + True + """ + # one should also record the monomial self._mon + return self._parent, (self._poly,) # THIS IS WRONG ! + + def __hash__(self): + r""" + TESTS: + + Test that the hash is non-constant (the hash does not need to be + deterministic so we leave some slack for collisions):: + + sage: L. = LaurentPolynomialRing(QQ) + sage: len({hash(w^i*z^j) for i in [-2..2] for j in [-2..2]}) > 20 + True """ - return self._parent, (self._poly,) + return hash(self._poly) ^ hash(self._mon) cdef _new_c(self): """ @@ -1374,7 +1373,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): else: e = e.emin(k) if len(e.nonzero_positions()) > 0: - self._poly = self._poly / self._poly.parent()({e: 1}) + self._poly = self._poly // self._poly.parent()({e: 1}) self._mon = self._mon.eadd(e) else: e = None @@ -1382,7 +1381,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): if e is None or k[i] < e: e = k[i] if e > 0: - self._poly = self._poly / self._poly.parent().gen(i) + self._poly = self._poly // self._poly.parent().gen(i) self._mon = self._mon.eadd_p(e, i) def _dict(self): @@ -2047,8 +2046,17 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): x^2 - x*y^-1 + y^-2 sage: h * (f // h) == f True + + TESTS: + + Check that :trac:`19357` is fixed:: + + sage: x // y + x*y^-1 """ cdef LaurentPolynomial_mpair ans = self._new_c() + self._normalize() + right._normalize() ans._mon = self._mon.esub((right)._mon) ans._poly = self._poly.__floordiv__((right)._poly) return ans diff --git a/src/sage/rings/polynomial/multi_polynomial.pyx b/src/sage/rings/polynomial/multi_polynomial.pyx index 0c56d646477..6abf48db402 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -4,7 +4,7 @@ Base class for elements of multivariate polynomial rings from sage.rings.integer cimport Integer from sage.rings.integer_ring import ZZ - +from sage.structure.element cimport coercion_model from sage.misc.derivative import multi_derivative from sage.rings.infinity import infinity @@ -1207,8 +1207,6 @@ cdef class MPolynomial(CommutativeRingElement): from sage.matrix.constructor import matrix if self.parent() != right.parent(): - from sage.structure.element import get_coercion_model - coercion_model = get_coercion_model() a, b = coercion_model.canonical_coercion(self,right) if variable: variable = a.parent()(variable) diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal.py b/src/sage/rings/polynomial/multi_polynomial_ideal.py index c0e7bce2c1e..460b2ddb4a6 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ideal.py +++ b/src/sage/rings/polynomial/multi_polynomial_ideal.py @@ -2205,7 +2205,6 @@ def variety(self, ring=None): This is due to precision error, which causes the computation of an intermediate Groebner basis to fail. - If the ground field's characteristic is too large for Singular, we resort to a toy implementation:: @@ -2217,6 +2216,22 @@ def variety(self, ring=None): verbose 0 (...: multi_polynomial_ideal.py, variety) Warning: falling back to very slow toy implementation. [{y: 0, x: 0}] + The dictionary expressing the variety will be indexed by generators + of the polynomial ring after changing to the target field. + But the mapping will also accept generators of the original ring, + or even generator names as strings, when provided as keys:: + + sage: K. = QQ[] + sage: I = ideal([x^2+2*y-5,x+y+3]) + sage: v = I.variety(AA)[0]; v + {x: 4.464101615137755?, y: -7.464101615137755?} + sage: v.keys()[0].parent() + Multivariate Polynomial Ring in x, y over Algebraic Real Field + sage: v[x] + 4.464101615137755? + sage: v["y"] + -7.464101615137755? + TESTS:: sage: K. = GF(27) @@ -2246,27 +2261,27 @@ def variety(self, ring=None): x26^2 + x26, x27^2 + x27, x28^2 + x28, x29^2 + x29, x30^2 + x30]) sage: I.basis_is_groebner() True - sage: for V in I.variety(): print V # long time (6s on sage.math, 2011) - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 0, x19: 0, x18: 1, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 0, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 1, x19: 0, x18: 1, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 0, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 0, x19: 0, x18: 1, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 0, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 1, x19: 0, x18: 1, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 0, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} + sage: sorted("".join(str(V[g]) for g in R.gens()) for V in I.variety()) # long time (6s on sage.math, 2011) + ['101000100000000110001000100110', + '101000100000000110001000101110', + '101000100100000101001000100110', + '101000100100000101001000101110', + '101010100000000110001000100110', + '101010100000000110001000101110', + '101010100010000110001000100110', + '101010100010000110001000101110', + '101010100110000110001000100110', + '101010100110000110001000101110', + '101100100000000110001000100110', + '101100100000000110001000101110', + '101100100100000101001000100110', + '101100100100000101001000101110', + '101110100000000110001000100110', + '101110100000000110001000101110', + '101110100010000110001000100110', + '101110100010000110001000101110', + '101110100110000110001000100110', + '101110100110000110001000101110'] Check that the issue at :trac:`7425` is fixed:: @@ -2353,15 +2368,16 @@ def _variety(T, V, v=None): else: raise TypeError("Local/unknown orderings not supported by 'toy_buchberger' implementation.") + from sage.misc.converting_dict import KeyConvertingDict V = [] for t in T: Vbar = _variety([P(f) for f in t], []) #Vbar = _variety(list(t.gens()),[]) for v in Vbar: - V.append(dict([(P(var),val) for var,val in v.iteritems()])) + V.append(KeyConvertingDict(P, v)) V.sort() - return Sequence(V) + return V @require_field def hilbert_polynomial(self): @@ -2692,10 +2708,14 @@ def __init__(self, ring, gens, coerce=True, side = "left"): sage: H.inject_variables() Defining x, y, z sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False) # indirect doctest - sage: I + sage: I #random Left Ideal (y^2, x^2, z^2 - 1) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: H.ideal([y^2, x^2, z^2-H.one()], side="twosided") + sage: sorted(I.gens(),key=str) + [x^2, y^2, z^2 - 1] + sage: H.ideal([y^2, x^2, z^2-H.one()], side="twosided") #random Twosided Ideal (y^2, x^2, z^2 - 1) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(H.ideal([y^2, x^2, z^2-H.one()], side="twosided").gens(),key=str) + [x^2, y^2, z^2 - 1] sage: H.ideal([y^2, x^2, z^2-H.one()], side="right") Traceback (most recent call last): ... @@ -2726,8 +2746,10 @@ def __call_singular(self, cmd, arg = None): sage: H.inject_variables() Defining x, y, z sage: id = H.ideal(x + y, y + z) - sage: id.std() # indirect doctest + sage: id.std() # indirect doctest # random Left Ideal (z, y, x) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(id.std().gens(),key=str) + [x, y, z] """ from sage.libs.singular.function import singular_function fun = singular_function(cmd) @@ -2748,23 +2770,34 @@ def std(self): sage: H.inject_variables() Defining x, y, z sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False) - sage: I.std() + sage: I.std() #random Left Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(I.std().gens(),key=str) + [2*x*y - z - 1, x*z + x, x^2, y*z - y, y^2, z^2 - 1] + If the ideal is a left ideal, then std returns a left Groebner basis. But if it is a two-sided ideal, then the output of std and :meth:`twostd` coincide:: sage: JL = H.ideal([x^3, y^3, z^3 - 4*z]) - sage: JL + sage: JL #random Left Ideal (x^3, y^3, z^3 - 4*z) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: JL.std() + sage: sorted(JL.gens(),key=str) + [x^3, y^3, z^3 - 4*z] + sage: JL.std() #random Left Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, 2*x*y*z - z^2 - 2*z, y^3, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(JL.std().gens(),key=str) + [2*x*y*z - z^2 - 2*z, x*z^2 + 2*x*z, x^3, y*z^2 - 2*y*z, y^3, z^3 - 4*z] sage: JT = H.ideal([x^3, y^3, z^3 - 4*z], side='twosided') - sage: JT + sage: JT #random Twosided Ideal (x^3, y^3, z^3 - 4*z) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: JT.std() + sage: sorted(JT.gens(),key=str) + [x^3, y^3, z^3 - 4*z] + sage: JT.std() #random Twosided Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, y^2*z - 2*y^2, 2*x*y*z - z^2 - 2*z, x^2*z + 2*x^2, y^3, x*y^2 - y*z, x^2*y - x*z - 2*x, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(JT.std().gens(),key=str) + [2*x*y*z - z^2 - 2*z, x*y^2 - y*z, x*z^2 + 2*x*z, x^2*y - x*z - 2*x, x^2*z + 2*x^2, x^3, y*z^2 - 2*y*z, y^2*z - 2*y^2, y^3, z^3 - 4*z] sage: JT.std() == JL.twostd() True @@ -2787,8 +2820,10 @@ def twostd(self): sage: H.inject_variables() Defining x, y, z sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False) - sage: I.twostd() + sage: I.twostd() #random Twosided Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field... + sage: sorted(I.twostd().gens(),key=str) + [2*x*y - z - 1, x*z + x, x^2, y*z - y, y^2, z^2 - 1] ALGORITHM: Uses Singular's twostd command """ @@ -2809,7 +2844,7 @@ def _groebner_strategy(self): sage: A. = FreeAlgebra(QQ, 3) sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False) - sage: I._groebner_strategy() + sage: I._groebner_strategy() #random Groebner Strategy for ideal generated by 6 elements over Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} @@ -2837,7 +2872,7 @@ def reduce(self,p): sage: A. = FreeAlgebra(QQ, 3) sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False, side='twosided') - sage: Q = H.quotient(I); Q + sage: Q = H.quotient(I); Q #random Quotient of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} by the ideal (y^2, x^2, z^2 - 1) @@ -2847,10 +2882,8 @@ def reduce(self,p): Here, we see that the relation that we just found in the quotient is actually a consequence of the given relations:: - sage: I.std() - Twosided Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) - of Noncommutative Multivariate Polynomial Ring in x, y, z over - Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: H.2^2-H.one() in I.std().gens() + True Here is the corresponding direct test:: @@ -2869,10 +2902,10 @@ def _contains_(self,p): sage: A. = FreeAlgebra(QQ, 3) sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: JL = H.ideal([x^3, y^3, z^3 - 4*z]) - sage: JL.std() + sage: JL.std() #random Left Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, 2*x*y*z - z^2 - 2*z, y^3, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} sage: JT = H.ideal([x^3, y^3, z^3 - 4*z], side='twosided') - sage: JT.std() + sage: JT.std() #random Twosided Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, y^2*z - 2*y^2, 2*x*y*z - z^2 - 2*z, x^2*z + 2*x^2, y^3, x*y^2 - y*z, x^2*y - x*z - 2*x, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} Apparently, ``x*y^2-y*z`` should be in the two-sided, but not @@ -2996,6 +3029,18 @@ def __init__(self, ring, gens, coerce=True): Ideal_generic.__init__(self, ring, gens, coerce=coerce) self._gb_by_ordering = dict() + def __hash__(self): + r""" + Stupid constant hash function! + + TESTS:: + + sage: R. = PolynomialRing(IntegerRing(), 2, order='lex') + sage: hash(R.ideal([x, y])) + 0 + """ + return 0 + @cached_method def gens(self): """ @@ -4401,8 +4446,9 @@ def weil_restriction(self): Ring in x0, x1, x2, x3, x4, y0, y1, y2, y3, y4, z0, z1, z2, z3, z4 over Finite Field of size 3 sage: J += sage.rings.ideal.FieldIdeal(J.ring()) # ensure radical ideal - sage: J.variety() - [{y1: 0, y4: 0, x4: 0, y2: 0, y3: 0, y0: 0, x2: 0, z4: 0, z3: 0, z2: 0, x1: 0, z1: 0, z0: 0, x0: 1, x3: 0}] + sage: from sage.doctest.fixtures import reproducible_repr + sage: print(reproducible_repr(J.variety())) + [{x0: 1, x1: 0, x2: 0, x3: 0, x4: 0, y0: 0, y1: 0, y2: 0, y3: 0, y4: 0, z0: 0, z1: 0, z2: 0, z3: 0, z4: 0}] Weil restrictions are often used to study elliptic curves over diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index e50037ea73a..f96796667b1 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -219,7 +219,7 @@ from sage.rings.finite_rings.integer_mod_ring import is_IntegerModRing from sage.rings.number_field.number_field_base cimport NumberField from sage.rings.arith import gcd -from sage.structure.element import coerce_binop, get_coercion_model +from sage.structure.element import coerce_binop from sage.structure.parent cimport Parent from sage.structure.parent_base cimport ParentWithBase @@ -231,6 +231,7 @@ from sage.structure.element cimport RingElement from sage.structure.element cimport ModuleElement from sage.structure.element cimport Element from sage.structure.element cimport CommutativeRingElement +from sage.structure.element cimport coercion_model from sage.structure.factorization import Factorization from sage.structure.sequence import Sequence @@ -2114,7 +2115,7 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn cdef poly *res # ownership will be transferred to us in the next line singular_polynomial_call(&res, self._poly, _ring, coerced_x, MPolynomial_libsingular_get_element) - res_parent = get_coercion_model().common_parent(parent._base, *x) + res_parent = coercion_model.common_parent(parent._base, *x) if res == NULL: return res_parent(0) @@ -2128,10 +2129,6 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn sage_res = res_parent(sage_res) return sage_res - # you may have to replicate this boilerplate code in derived classes if you override - # __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html - # explains how __richcmp__, __hash__, and __cmp__ are tied together. - def __hash__(self): """ This hash incorporates the variable name in an effort to @@ -2155,7 +2152,7 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn """ return self._hash_c() - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ Compare left and right and return -1, 0, and 1 for <,==, and > respectively. @@ -2207,9 +2204,6 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn sage: (66*x^2 + 23) > (66*x^2 + 2) True """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: if left is right: return 0 cdef poly *p = (left)._poly diff --git a/src/sage/rings/polynomial/multi_polynomial_sequence.py b/src/sage/rings/polynomial/multi_polynomial_sequence.py index 8a45334782e..3cdca4a8535 100644 --- a/src/sage/rings/polynomial/multi_polynomial_sequence.py +++ b/src/sage/rings/polynomial/multi_polynomial_sequence.py @@ -157,6 +157,7 @@ from sage.misc.cachefunc import cached_method from types import GeneratorType +from sage.misc.converting_dict import KeyConvertingDict from sage.misc.package import is_package_installed from sage.structure.sequence import Sequence, Sequence_generic @@ -1346,10 +1347,12 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver Without argument, a single arbitrary solution is returned:: + sage: from sage.doctest.fixtures import reproducible_repr sage: R. = BooleanPolynomialRing() sage: S = Sequence([x*y+z, y*z+x, x+y+z+1]) - sage: sol = S.solve(); sol # random - [{y: 1, z: 0, x: 0}] + sage: sol = S.solve() + sage: print(reproducible_repr(sol)) + [{x: 0, y: 1, z: 0}] We check that it is actually a solution:: @@ -1358,7 +1361,8 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver We obtain all solutions:: - sage: sols = S.solve(n=Infinity); sols # random + sage: sols = S.solve(n=Infinity) + sage: print(reproducible_repr(sols)) [{x: 0, y: 1, z: 0}, {x: 1, y: 1, z: 1}] sage: map( lambda x: S.subs(x), sols) [[0, 0, 0], [0, 0, 0]] @@ -1366,15 +1370,17 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver We can force the use of exhaustive search if the optional package ``FES`` is present:: - sage: sol = S.solve(algorithm='exhaustive_search'); sol # random, optional - FES + sage: sol = S.solve(algorithm='exhaustive_search') # optional - FES + sage: print(reproducible_repr(sol)) # optional - FES [{x: 1, y: 1, z: 1}] sage: S.subs( sol[0] ) [0, 0, 0] And we may use SAT-solvers if they are available:: - sage: sol = S.solve(algorithm='sat'); sol # random, optional - cryptominisat - [{y: 1, z: 0, x: 0}] + sage: sol = S.solve(algorithm='sat'); sol # optional - cryptominisat + sage: print(reproducible_repr(sol)) # optional - cryptominisat + [{x: 0, y: 1, z: 0}] sage: S.subs( sol[0] ) [0, 0, 0] @@ -1451,15 +1457,19 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver eliminated_variables = { f.lex_lead() for f in reductors } leftover_variables = { x.lm() for x in R_origin.gens() } - solved_variables - eliminated_variables + key_convert = lambda x: R_origin(x).lm() if leftover_variables != set(): partial_solutions = solutions solutions = [] for sol in partial_solutions: for v in VectorSpace( GF(2), len(leftover_variables) ): - new_solution = sol.copy() + new_solution = KeyConvertingDict(key_convert, sol) for var,val in zip(leftover_variables, v): new_solution[ var ] = val solutions.append( new_solution ) + else: + solutions = [ KeyConvertingDict(key_convert, sol) + for sol in solutions ] for r in reductors: for sol in solutions: diff --git a/src/sage/rings/polynomial/pbori.pyx b/src/sage/rings/polynomial/pbori.pyx index e789f33ce02..ab92d9dd498 100644 --- a/src/sage/rings/polynomial/pbori.pyx +++ b/src/sage/rings/polynomial/pbori.pyx @@ -2200,9 +2200,6 @@ cdef class BooleanMonomial(MonoidElement): self._ring = parent._ring self._pbmonom = PBMonom_Constructor((self._ring)._pbring) - def __dealloc__(self): - pass # destruction by C++ object's destructor - def __reduce__(self): """ Pickling @@ -2219,7 +2216,7 @@ cdef class BooleanMonomial(MonoidElement): gens = self._parent.gens() return self._parent, (tuple([gens.index(x) for x in self.variables()]),) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ Compare BooleanMonomial objects. @@ -2243,10 +2240,6 @@ cdef class BooleanMonomial(MonoidElement): sage: M(x) >= M(x) True """ - # boilerplate code from sage.structure.parent - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: cdef int res res = left._pbmonom.compare((right)._pbmonom) return res @@ -5110,10 +5103,11 @@ class BooleanPolynomialIdeal(MPolynomialIdeal): A Simple example:: + sage: from sage.doctest.fixtures import reproducible_repr sage: R. = BooleanPolynomialRing() sage: I = ideal( [ x*y*z + x*z + y + 1, x+y+z+1 ] ) - sage: I.variety() - [{z: 0, y: 1, x: 0}, {z: 1, y: 1, x: 1}] + sage: print(reproducible_repr(I.variety())) + [{x: 0, y: 1, z: 0}, {x: 1, y: 1, z: 1}] TESTS: @@ -5130,14 +5124,14 @@ class BooleanPolynomialIdeal(MPolynomialIdeal): x1*x2 + x1*x4 + x1*x5 + x1*x6 + x2*x3 + x2*x4 + x2*x5 + x3*x5 + x5*x6 + x5 + x6, \ x1*x2 + x1*x6 + x2*x4 + x2*x5 + x2*x6 + x3*x6 + x4*x6 + x5*x6 + x5] sage: I = R.ideal( polys ) - sage: I.variety() - [{x6: 0, x5: 0, x4: 0, x3: 0, x2: 0, x1: 0}, - {x6: 1, x5: 0, x4: 0, x3: 1, x2: 1, x1: 1}] + sage: print(reproducible_repr(I.variety())) + [{x1: 0, x2: 0, x3: 0, x4: 0, x5: 0, x6: 0}, {x1: 1, x2: 1, x3: 1, x4: 0, x5: 0, x6: 1}] sage: R = PolynomialRing(GF(2), 6, ['x%d'%(i+1) for i in range(6)], order='lex') sage: I = R.ideal( polys ) - sage: (I + sage.rings.ideal.FieldIdeal(R)).variety() - [{x2: 0, x5: 0, x4: 0, x1: 0, x6: 0, x3: 0}, {x2: 1, x5: 0, x4: 0, x1: 1, x6: 1, x3: 1}] + sage: v = (I + sage.rings.ideal.FieldIdeal(R)).variety() + sage: print(reproducible_repr(v)) + [{x1: 0, x2: 0, x3: 0, x4: 0, x5: 0, x6: 0}, {x1: 1, x2: 1, x3: 1, x4: 0, x5: 0, x6: 1}] Check that :trac:`13976` is fixed:: @@ -5148,13 +5142,21 @@ class BooleanPolynomialIdeal(MPolynomialIdeal): sage: sols[0][y] 1 + Make sure the result is a key converting dict, as discussed in + :trac:`9788` and consistent with + :meth:`sage.rings.polynomial.multi_polynomial_ideal.MPolynomialIdeal_singular_repr.variety`:: + + sage: sols[0]["y"] + 1 + """ + from sage.misc.converting_dict import KeyConvertingDict R_bool = self.ring() R = R_bool.cover_ring() I = R.ideal( [ R( f ) for f in self.groebner_basis() ] ) J = FieldIdeal(R) solutions = (I+J).variety(**kwds) - return [ { R_bool(var):val for var,val in s.iteritems() } for s in solutions ] + return [ KeyConvertingDict(R_bool, s) for s in solutions ] def reduce(self, f): diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index 6dc97afb04e..650918aa013 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -161,9 +161,9 @@ class G_AlgFactory(UniqueFactory): TEST:: sage: A. = FreeAlgebra(QQ, 3) - sage: A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) # indirect doctest - Noncommutative Multivariate Polynomial Ring in x, y, z over Rational - Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: H=A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) + sage: sorted(H.relations().iteritems(),key=str) + [(y*x, x*y - z), (z*x, x*z + 2*x), (z*y, y*z - 2*y)] """ # key = (base_ring,names, c,d, order, category) @@ -1385,9 +1385,6 @@ cdef class NCPolynomial_plural(RingElement): """ return unpickle_NCPolynomial_plural, (self._parent, self.dict()) - # you may have to replicate this boilerplate code in derived classes if you override - # __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html - # explains how __richcmp__, __hash__, and __cmp__ are tied together. def __hash__(self): """ This hash incorporates the variable name in an effort to @@ -1411,7 +1408,7 @@ cdef class NCPolynomial_plural(RingElement): """ return self._hash_c() - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ Compare left and right and return -1, 0, and 1 for <,==, and > respectively. @@ -1431,9 +1428,6 @@ cdef class NCPolynomial_plural(RingElement): sage: y^2 > x False -## sage: (2/3*x^2 + 1/2*y + 3) > (2/3*x^2 + 1/4*y + 10) -# True - TESTS:: sage: A. = FreeAlgebra(QQ, 3) @@ -1458,22 +1452,7 @@ cdef class NCPolynomial_plural(RingElement): sage: (x+1) > x True - -# sage: f = 3/4*x^2*y + 1/2*x + 2/7 -# sage: f > f -# False -# sage: f < f -# False -# sage: f == f -# True - -# sage: P. = PolynomialRing(GF(127), order='degrevlex') -# sage: (66*x^2 + 23) > (66*x^2 + 2) -# True """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: if left is right: return 0 cdef poly *p = (left)._poly @@ -1711,10 +1690,13 @@ cdef class NCPolynomial_plural(RingElement): The Groebner basis shows that the result is correct:: - sage: I.std() + sage: I.std() #random Left Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(I.std().gens(),key=str) + [2*x*y - z - 1, x*z + x, x^2, y*z - y, y^2, z^2 - 1] + """ cdef ideal *_I @@ -2954,8 +2936,12 @@ def ExteriorAlgebra(base_ring, names,order='degrevlex'): EXAMPLES:: sage: from sage.rings.polynomial.plural import ExteriorAlgebra - sage: E = ExteriorAlgebra(QQ, ['x', 'y', 'z']) ; E + sage: E = ExteriorAlgebra(QQ, ['x', 'y', 'z']) ; E #random Quotient of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: -x*z, z*y: -y*z, y*x: -x*y} by the ideal (z^2, y^2, x^2) + sage: sorted(E.cover().domain().relations().iteritems(),key=str) + [(y*x, -x*y), (z*x, -x*z), (z*y, -y*z)] + sage: sorted(E.cover().kernel().gens(),key=str) + [x^2, y^2, z^2] sage: E.inject_variables() Defining xbar, ybar, zbar sage: x,y,z = (xbar,ybar,zbar) diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 527a8aa878b..0ec9ccc1560 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -41,7 +41,6 @@ TESTS:: cdef is_FractionField, is_RealField, is_ComplexField -cdef coerce_binop, generic_power, parent cdef ZZ, QQ, RR, CC, RDF, CDF import operator, copy, re @@ -73,8 +72,10 @@ from sage.rings.real_double import is_RealDoubleField, RDF from sage.rings.complex_double import is_ComplexDoubleField, CDF from sage.rings.real_mpfi import is_RealIntervalField -from sage.structure.element import RingElement, generic_power, parent -from sage.structure.element cimport Element, RingElement, ModuleElement, MonoidElement +from sage.structure.element import generic_power +from sage.structure.element cimport parent_c as parent +from sage.structure.element cimport (Element, RingElement, + ModuleElement, MonoidElement, coercion_model) from sage.rings.rational_field import QQ, is_RationalField from sage.rings.integer_ring import ZZ, is_IntegerRing @@ -924,9 +925,6 @@ cdef class Polynomial(CommutativeAlgebraElement): if c: return c return 0 - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - def __nonzero__(self): """ EXAMPLES:: @@ -971,9 +969,6 @@ cdef class Polynomial(CommutativeAlgebraElement): """ return (self.parent(), tuple(self)) - # you may have to replicate this boilerplate code in derived classes if you override - # __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html - # explains how __richcmp__, __hash__, and __cmp__ are tied together. def __hash__(self): return self._hash_c() @@ -3235,7 +3230,7 @@ cdef class Polynomial(CommutativeAlgebraElement): # calling the coercion model bin_op is much more accurate than using the # true division (which is bypassed by polynomials). But it does not work # in all cases!! - cm = sage.structure.element.get_coercion_model() + cm = coercion_model try: S = cm.bin_op(R.one(), ZZ.one(), operator.div).parent() Q = S.base_ring() @@ -4001,6 +3996,72 @@ cdef class Polynomial(CommutativeAlgebraElement): raise NotImplementedError("splitting_field() is only implemented over number fields and finite fields") + def pseudo_quo_rem(self,other): + """ + Compute the pseudo-division of two polynomials. + + INPUT: + + - ``other`` -- a nonzero polynomial + + OUTPUT: + + `Q` and `R` such that `l^{m-n+1} \mathrm{self} = Q \cdot\mathrm{other} + R` + where `m` is the degree of this polynomial, `n` is the degree of + ``other``, `l` is the leading coefficient of ``other``. The result is + such that `\deg(R) < \deg(\mathrm{other})`. + + ALGORITHM: + + Algorithm 3.1.2 in [GTM138]_. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: p = x^4 + 6*x^3 + x^2 - x + 2 + sage: q = 2*x^2 - 3*x - 1 + sage: (quo,rem)=p.pseudo_quo_rem(q); quo,rem + (4*x^2 + 30*x + 51, 175*x + 67) + sage: 2^(4-2+1)*p == quo*q + rem + True + + sage: S. = R[] + sage: p = (-3*x^2 - x)*T^3 - 3*x*T^2 + (x^2 - x)*T + 2*x^2 + 3*x - 2 + sage: q = (-x^2 - 4*x - 5)*T^2 + (6*x^2 + x + 1)*T + 2*x^2 - x + sage: quo,rem=p.pseudo_quo_rem(q); quo,rem + ((3*x^4 + 13*x^3 + 19*x^2 + 5*x)*T + 18*x^4 + 12*x^3 + 16*x^2 + 16*x, + (-113*x^6 - 106*x^5 - 133*x^4 - 101*x^3 - 42*x^2 - 41*x)*T - 34*x^6 + 13*x^5 + 54*x^4 + 126*x^3 + 134*x^2 - 5*x - 50) + sage: (-x^2 - 4*x - 5)^(3-2+1) * p == quo*q + rem + True + + REFERENCES: + + .. [GTM138] Henri Cohen. A Course in Computational Number Theory. + Graduate Texts in Mathematics, vol. 138. Springer, 1993. + """ + if other.is_zero(): + raise ZeroDivisionError("Pseudo-division by zero is not possible") + + # if other is a constant, then R = 0 and Q = self * other^(deg(self)) + if other in self.parent().base_ring(): + return (self * other**(self.degree()), self.parent().zero()) + + R = self + B = other + Q = self.parent().zero() + e = self.degree() - other.degree() + 1 + d = B.leading_coefficient() + + while not R.degree() < B.degree(): + c = R.leading_coefficient() + diffdeg = R.degree() - B.degree() + Q = d*Q + self.parent()(c).shift(diffdeg) + R = d*R - c*B.shift(diffdeg) + e -= 1 + + q = d**e + return (q*Q,q*R) + @coerce_binop def gcd(self, other): """ @@ -4033,23 +4094,23 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: (2*x).gcd(0) x - One can easily add gcd functionality to new rings by providing a - method ``_gcd_univariate_polynomial``:: + One can easily add gcd functionality to new rings by providing a method + ``_gcd_univariate_polynomial``:: - sage: R. = QQ[] - sage: S. = R[] - sage: h1 = y*x - sage: h2 = y^2*x^2 - sage: h1.gcd(h2) + sage: O = ZZ[-sqrt(5)] + sage: R. = O[] + sage: a = O.1 + sage: p = x + a + sage: q = x^2 - 5 + sage: p.gcd(q) Traceback (most recent call last): ... - NotImplementedError: Univariate Polynomial Ring in x over Rational Field does not provide a gcd implementation for univariate polynomials - sage: T. = QQ[] - sage: R._gcd_univariate_polynomial = lambda f,g: S(T(f).gcd(g)) - sage: h1.gcd(h2) - x*y - sage: del R._gcd_univariate_polynomial - + NotImplementedError: Order in Number Field in a with defining polynomial x^2 - 5 does not provide a gcd implementation for univariate polynomials + sage: S. = O.number_field()[] + sage: O._gcd_univariate_polynomial = lambda f,g : R(S(f).gcd(S(g))) + sage: p.gcd(q) + x + a + sage: del O._gcd_univariate_polynomial """ if hasattr(self.base_ring(), '_gcd_univariate_polynomial'): return self.base_ring()._gcd_univariate_polynomial(self, other) @@ -4510,7 +4571,6 @@ cdef class Polynomial(CommutativeAlgebraElement): # sylvester_matrix() in multi_polynomial.pyx. if self.parent() != right.parent(): - coercion_model = sage.structure.element.get_coercion_model() a, b = coercion_model.canonical_coercion(self,right) variable = a.parent()(self.variables()[0]) #We add the variable to cover the case that right is a multivariate @@ -6883,15 +6943,10 @@ cdef class Polynomial(CommutativeAlgebraElement): False sage: R(0).is_squarefree() False - - This can obviously fail if the ring does not implement ``gcd()``:: - sage: S. = QQ[] sage: R. = S[] - sage: (2*x*y).is_squarefree() # R does not provide a gcd implementation - Traceback (most recent call last): - ... - NotImplementedError: Univariate Polynomial Ring in y over Rational Field does not provide a gcd implementation for univariate polynomials + sage: (2*x*y).is_squarefree() + True sage: (2*x*y^2).is_squarefree() False @@ -7871,15 +7926,9 @@ cdef class Polynomial_generic_dense(Polynomial): del x[n] n -= 1 - # you may have to replicate this boilerplate code in derived classes if you override - # __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html - # explains how __richcmp__, __hash__, and __cmp__ are tied together. def __hash__(self): return self._hash_c() - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - def __getitem__(self, n): """ EXAMPLES:: diff --git a/src/sage/rings/polynomial/polynomial_element_generic.py b/src/sage/rings/polynomial/polynomial_element_generic.py index 02d3b9c9fbb..1e11a5e2bbf 100644 --- a/src/sage/rings/polynomial/polynomial_element_generic.py +++ b/src/sage/rings/polynomial/polynomial_element_generic.py @@ -802,6 +802,73 @@ def quo_rem(self, other): rem = rem[:rem.degree()] - c*other[:d].shift(e) return (quo,rem) + def gcd(self,other,algorithm=None): + """ + Return the gcd of this polynomial and ``other`` + + INPUT: + + - ``other`` -- a polynomial defined over the same ring as this + polynomial. + + ALGORITHM: + + Two algorithms are provided: + + - ``generic``: Uses the generic implementation, which depends on the + base ring being a UFD or a field. + - ``dense``: The polynomials are converted to the dense representation, + their gcd is computed and is converted back to the sparse + representation. + + Default is ``dense`` for polynomials over ZZ and ``generic`` in the + other cases. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ,sparse=True) + sage: p = x^6 + 7*x^5 + 8*x^4 + 6*x^3 + 2*x^2 + x + 2 + sage: q = 2*x^4 - x^3 - 2*x^2 - 4*x - 1 + sage: gcd(p,q) + x^2 + x + 1 + sage: gcd(p, q, algorithm = "dense") + x^2 + x + 1 + sage: gcd(p, q, algorithm = "generic") + x^2 + x + 1 + sage: gcd(p, q, algorithm = "foobar") + Traceback (most recent call last): + ... + ValueError: Unknown algorithm 'foobar' + """ + + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + from sage.rings.arith import lcm + + if algorithm is None: + if self.base_ring() == ZZ: + algorithm = "dense" + else: + algorithm = "generic" + if algorithm=="dense": + S = self.parent() + # FLINT is faster but a bug makes the conversion extremely slow, + # so NTL is used in those cases where the conversion is too slow. Cf + # + sd = self.degree() + od = other.degree() + if max(sd,od)<100 or \ + min(len(self.__coeffs)/sd, len(other.__coeffs)/od)>.06: + implementation="FLINT" + else: + implementation="NTL" + D = PolynomialRing(S.base_ring(),'x',implementation=implementation) + g = D(self).gcd(D(other)) + return S(g) + elif algorithm=="generic": + return Polynomial.gcd(self,other) + else: + raise ValueError("Unknown algorithm '%s'" % algorithm) + def reverse(self, degree=None): """ Return this polynomial but with the coefficients reversed. @@ -840,7 +907,6 @@ def truncate(self, n): """ return self[:n] - class Polynomial_generic_domain(Polynomial, IntegralDomainElement): def __init__(self, parent, is_gen=False, construct=False): Polynomial.__init__(self, parent, is_gen=is_gen) diff --git a/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx b/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx index 9e10335ee2c..46c8e1dcbfc 100644 --- a/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx +++ b/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx @@ -68,7 +68,7 @@ cdef class PolynomialRealDense(Polynomial): TESTS: - Check that errors and interrupts are handled properly (see #10100):: + Check that errors and interrupts are handled properly (see :trac:`10100`):: sage: a = var('a') sage: PolynomialRealDense(RR['x'], [1,a]) @@ -83,7 +83,7 @@ cdef class PolynomialRealDense(Polynomial): sage: sig_on_count() 0 - Test that we don't clean up uninitialized coefficients (#9826):: + Test that we don't clean up uninitialized coefficients (:trac:`9826`):: sage: k. = GF(7^3) sage: P. = PolynomialRing(k) @@ -91,6 +91,11 @@ cdef class PolynomialRealDense(Polynomial): Traceback (most recent call last): ... TypeError: Unable to convert x (='a') to real number. + + Check that :trac:`17190` is fixed:: + + sage: RR['x']({}) + 0 """ Polynomial.__init__(self, parent, is_gen=is_gen) self._base_ring = parent._base @@ -108,11 +113,7 @@ cdef class PolynomialRealDense(Polynomial): elif isinstance(x, (int, float, Integer, Rational, RealNumber)): x = [x] elif isinstance(x, dict): - degree = max(x.keys()) - c = [0] * (degree+1) - for i, a in x.items(): - c[i] = a - x = c + x = self._dict_to_list(x,self._base_ring.zero()) elif isinstance(x, pari_gen): x = [self._base_ring(w) for w in x.list()] elif not isinstance(x, list): @@ -665,9 +666,9 @@ cdef class PolynomialRealDense(Polynomial): sage: f = PolynomialRealDense(RR['x']) sage: f(12) 0.000000000000000 - + TESTS:: - + sage: R. = RR[] # :trac:`17311` sage: (x^2+1)(x=5) 26.0000000000000 @@ -676,13 +677,13 @@ cdef class PolynomialRealDense(Polynomial): xx = args[0] else: return Polynomial.__call__(self, *args, **kwds) - + if not isinstance(xx, RealNumber): if self._base_ring.has_coerce_map_from(parent(xx)): xx = self._base_ring(xx) else: return Polynomial.__call__(self, xx) - + cdef Py_ssize_t i cdef mp_rnd_t rnd = self._base_ring.rnd cdef RealNumber x = xx @@ -713,7 +714,7 @@ cdef class PolynomialRealDense(Polynomial): mpfr_mul(res.value, res.value, x.value, rnd) mpfr_add(res.value, res.value, self._coeffs[i], rnd) return res - + def change_ring(self, R): """ EXAMPLES:: diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index e0d2d4f4ff6..9c469c9eb46 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -268,7 +268,7 @@ def __init__(self, base_ring, name=None, sparse=False, element_class=None, categ sage: category(ZZ['x']) Join of Category of unique factorization domains and Category of commutative algebras over - (euclidean domains and infinite enumerated sets) + (euclidean domains and infinite enumerated sets and metric spaces) sage: category(GF(7)['x']) Join of Category of euclidean domains and Category of commutative algebras over (finite fields and diff --git a/src/sage/rings/polynomial/polynomial_template.pxi b/src/sage/rings/polynomial/polynomial_template.pxi index d2aee68f334..57f4550b27d 100644 --- a/src/sage/rings/polynomial/polynomial_template.pxi +++ b/src/sage/rings/polynomial/polynomial_template.pxi @@ -529,7 +529,7 @@ cdef class Polynomial_template(Polynomial): """ return not celement_is_zero(&self.x, (self)._cparent) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ EXAMPLE:: @@ -541,14 +541,6 @@ cdef class Polynomial_template(Polynomial): sage: x > 1 True """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - EXAMPLE:: - - sage: P. = GF(2)[] - """ return celement_cmp(&(left).x, &(right).x, (left)._cparent) def __hash__(self): diff --git a/src/sage/rings/polynomial/refine_root.pyx b/src/sage/rings/polynomial/refine_root.pyx new file mode 100644 index 00000000000..aafbeb442f3 --- /dev/null +++ b/src/sage/rings/polynomial/refine_root.pyx @@ -0,0 +1,141 @@ +""" +Refine polynomial roots using Newton--Raphson + +This is an implementation of the Newton--Raphson algorithm to +approximate roots of complex polynomials. The implementation +is based on interval arithmetic + +AUTHORS: + +- Carl Witty (2007-11-18): initial version +""" + +#***************************************************************************** +# Copyright (C) 2007 Carl Witty +# +# 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 sage.rings.real_mpfi import RealIntervalField +from sage.rings.complex_interval_field import ComplexIntervalField + + +def refine_root(ip, ipd, irt, fld): + """ + We are given a polynomial and its derivative (with complex + interval coefficients), an estimated root, and a complex interval + field to use in computations. We use interval arithmetic to + refine the root and prove that we have in fact isolated a unique + root. + + If we succeed, we return the isolated root; if we fail, we return + None. + + EXAMPLES:: + + sage: from sage.rings.polynomial.refine_root import refine_root + sage: x = polygen(ZZ) + sage: p = x^9 - 1 + sage: ip = CIF['x'](p); ip + x^9 - 1 + sage: ipd = CIF['x'](p.derivative()); ipd + 9*x^8 + sage: irt = CIF(CC(cos(2*pi/9), sin(2*pi/9))); irt + 0.76604444311897802? + 0.64278760968653926?*I + sage: ip(irt) + 0.?e-14 + 0.?e-14*I + sage: ipd(irt) + 6.89439998807080? - 5.78508848717885?*I + sage: refine_root(ip, ipd, irt, CIF) + 0.766044443118978? + 0.642787609686540?*I + """ + + # There has got to be a better way to do this, but I don't know + # what it is... + + # We start with a basic fact: if we do an interval Newton-Raphson + # step, and the refined interval is contained in the original interval, + # then the refined interval contains exactly one root. + + # Unfortunately, our initial estimated root almost certainly does not + # contain the actual root (our initial interval is a point, which + # is exactly equal to whatever floating-point estimate we got from + # the external solver). So we need to do multiple Newton-Raphson + # steps, and check this inclusion property on each step. + + # After a few steps of refinement, if the initial root estimate was + # close to a root, we should have an essentially perfect interval + # bound on the root (since Newton-Raphson has quadratic convergence), + # unless either the real or imaginary component of the root is zero. + # If the real or imaginary component is zero, then we could spend + # a long time computing closer and closer approximations to that + # component. (This doesn't happen for non-zero components, because + # of the imprecision of floating-point numbers combined with the + # outward interval rounding; but close to zero, MPFI provides + # extremely precise numbers.) + + # If the root is actually a real root, but we start with an imaginary + # component, we can bounce back and forth between having a positive + # and negative imaginary component, without ever hitting zero. + # To deal with this, on every other Newton-Raphson step, instead of + # replacing the old interval with the new one, we take the union. + + # If the containment check continues to fail many times in a row, + # we give up and return None; we also return None if we detect + # that the slope in our current interval is not bounded away + # from zero at any step. + + # After every refinement step, we check to see if the real or + # imaginary component of our interval includes zero. If so, we + # try setting it to exactly zero. This gives us a good chance of + # detecting real roots. However, we do this replacement at most + # once per component. + + refinement_steps = 10 + + smashed_real = False + smashed_imag = False + + for i in range(refinement_steps): + slope = ipd(irt) + if slope.contains_zero(): + return None + center = fld(irt.center()) + val = ip(center) + + nirt = center - val / slope + # print irt, nirt, (nirt in irt), nirt.diameter(), irt.diameter(), center, val, slope + if nirt in irt and (nirt.diameter() >= irt.diameter() >> 3 or i >= 8): + # If the new diameter is much less than the original diameter, + # then we have not yet converged. (Perhaps we were asked + # for a particularly high-precision result.) So we don't + # return yet. + return nirt + + if i & 1: + irt = nirt + else: + irt = irt.union(nirt) + # If we don't find a root after a while, try (approximately) + # tripling the size of the region. + if i >= 6: + rD = irt.real().absolute_diameter() + iD = irt.imag().absolute_diameter() + md = max(rD, iD) + md_intv = RealIntervalField(rD.prec())(-md, md) + md_cintv = ComplexIntervalField(rD.prec())(md_intv, md_intv) + irt = irt + md_cintv + + if not smashed_real and irt.real().contains_zero(): + irt = irt.parent()(0, irt.imag()) + smashed_real = True + if not smashed_imag and irt.imag().contains_zero(): + irt = irt.parent()(irt.real(), 0) + smashed_imag = True + + return None diff --git a/src/sage/rings/power_series_poly.pyx b/src/sage/rings/power_series_poly.pyx index e2b3c29cd68..9881351ef45 100644 --- a/src/sage/rings/power_series_poly.pyx +++ b/src/sage/rings/power_series_poly.pyx @@ -89,24 +89,6 @@ cdef class PowerSeries_poly(PowerSeries): """ return self.__class__, (self._parent, self.__f, self._prec, self.__is_gen) - def __richcmp__(left, right, int op): - """ - Used for comparing power series. - - EXAMPLES:: - - sage: R. = ZZ[[]] - sage: f = 1 + t + t^7 - 5*t^10 - sage: g = 1 + t + t^7 - 5*t^10 + O(t^15) - sage: f == f - True - sage: f < g - False - sage: f == g - True - """ - return (left)._richcmp(right, op) - def polynomial(self): """ Return the underlying polynomial of self. diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index e11fdf7f779..3dc1e66c7a7 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -499,6 +499,15 @@ def __init__(self, base_ring, name=None, default_prec=None, sparse=False, sage: R.category() Category of complete discrete valuation rings sage: TestSuite(R).run() + + It is checked that the default precision is non-negative + (see :trac:`19409`):: + + sage: PowerSeriesRing(ZZ, 'x', default_prec=-5) + Traceback (most recent call last): + ... + ValueError: default_prec (= -5) must be non-negative + """ R = PolynomialRing(base_ring, name, sparse=sparse) self.__poly_ring = R @@ -506,6 +515,9 @@ def __init__(self, base_ring, name=None, default_prec=None, sparse=False, if default_prec is None: from sage.misc.defaults import series_precision default_prec = series_precision() + elif default_prec < 0: + raise ValueError("default_prec (= %s) must be non-negative" + % default_prec) self.__params = (base_ring, name, default_prec, sparse) if use_lazy_mpoly_ring and (is_MPolynomialRing(base_ring) or \ @@ -708,9 +720,21 @@ def _element_constructor_(self, f, prec=infinity, check=True): sage: P(1/q) Traceback (most recent call last): ... - ArithmeticError: self is a not a power series + TypeError: self is not a power series + + It is checked that the precision is non-negative + (see :trac:`19409`):: + + sage: PowerSeriesRing(ZZ, 'x')(1, prec=-5) + Traceback (most recent call last): + ... + ValueError: prec (= -5) must be non-negative """ + if prec is not infinity: + prec = integer.Integer(prec) + if prec < 0: + raise ValueError("prec (= %s) must be non-negative" % prec) if isinstance(f, power_series_ring_element.PowerSeries) and f.parent() is self: if prec >= f.prec(): return f diff --git a/src/sage/rings/power_series_ring_element.pyx b/src/sage/rings/power_series_ring_element.pyx index ab3f67a20cf..a991d433559 100644 --- a/src/sage/rings/power_series_ring_element.pyx +++ b/src/sage/rings/power_series_ring_element.pyx @@ -165,8 +165,6 @@ cdef class PowerSeries(AlgebraElement): """ AlgebraElement.__init__(self, parent) self.__is_gen = is_gen - if not (prec is infinity): - prec = int(prec) self._prec = prec def __hash__(self): @@ -315,18 +313,6 @@ cdef class PowerSeries(AlgebraElement): S = self._parent.change_ring(R) return S(self) - def __cmp__(left, right): - """ - Called by comparison operations. - - EXAMPLES:: - - sage: R. = PowerSeriesRing(ZZ) - sage: 1+x^2 < 2-x - True - """ - return (left)._cmp(right) - cpdef int _cmp_(self, Element right) except -2: r""" Comparison of self and ``right``. @@ -358,9 +344,23 @@ cdef class PowerSeries(AlgebraElement): sage: 1 - 2*q + q^2 +O(q^3) == 1 - 2*q^2 + q^2 + O(q^4) False + :: + + sage: R. = ZZ[[]] + sage: 1 + t^2 < 2 - t + True + sage: f = 1 + t + t^7 - 5*t^10 + sage: g = 1 + t + t^7 - 5*t^10 + O(t^15) + sage: f == f + True + sage: f < g + False + sage: f == g + True + TESTS: - Ticket :trac:`9457` is fixed:: + :trac:`9457` is fixed:: sage: A. = PowerSeriesRing(ZZ) sage: g = t + t^3 + t^5 + O(t^6); g @@ -1023,6 +1023,12 @@ cdef class PowerSeries(AlgebraElement): ... ZeroDivisionError: leading coefficient must be a unit + A test for the case where the precision is 0:: + + sage: R. = PowerSeriesRing(ZZ, default_prec=0) + sage: ~(1+x) + O(x^0) + AUTHORS: - David Harvey (2006-09-09): changed to use Newton's method @@ -1048,6 +1054,8 @@ cdef class PowerSeries(AlgebraElement): if prec is infinity: return self._parent(first_coeff, prec=prec) + elif not prec: + return self._parent(0, prec=0) A = self.truncate() R = A.parent() # R is the corresponding polynomial ring diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 87143860d09..cd3a99f14f9 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -362,8 +362,8 @@ AA(2) Just for fun, let's try ``sage_input`` on a very complicated expression. The -output of this example changed with the rewritting of polynomial multiplication -algorithms in #10255:: +output of this example changed with the rewriting of polynomial multiplication +algorithms in :trac:`10255`:: sage: rt2 = sqrt(AA(2)) sage: rt3 = sqrt(QQbar(3)) @@ -449,7 +449,7 @@ ....: def convert_test(v): ....: try: ....: return ty(v) - ....: except ValueError: + ....: except (TypeError, ValueError): ....: return None ....: return [convert_test(_) for _ in all_vals] sage: convert_test_all(float) @@ -3807,7 +3807,8 @@ def degree(self): def interval_fast(self, field): r""" - Given a ``RealIntervalField``, compute the value of this number + Given a :class:`RealIntervalField` or + :class:`ComplexIntervalField`, compute the value of this number using interval arithmetic of at least the precision of the field, and return the value in that field. (More precision may be used in the computation.) The returned interval may be arbitrarily @@ -3827,15 +3828,11 @@ def interval_fast(self, field): sage: x.interval_fast(RIF) Traceback (most recent call last): ... - TypeError: Unable to convert number to real interval. + TypeError: unable to convert 0.7071067811865475244? + 0.7071067811865475244?*I to real interval """ - if field.prec() == self._value.prec(): - return field(self._value) - elif field.prec() > self._value.prec(): + while self._value.prec() < field.prec(): self._more_precision() - return self.interval_fast(field) - else: - return field(self._value) + return field(self._value) def interval_diameter(self, diam): """ @@ -3887,11 +3884,22 @@ def interval(self, field): 0.8412535328311811689? + 0.540640817455597582?*I sage: x.interval(CIF64) 0.8412535328311811689? + 0.5406408174555975822?*I + + The following implicitly use this method:: + + sage: RIF(AA(5).sqrt()) + 2.236067977499790? + sage: AA(-5).sqrt().interval(RIF) + Traceback (most recent call last): + ... + TypeError: unable to convert 2.236067977499789697?*I to real interval """ target = RR(1.0) >> field.prec() val = self.interval_diameter(target) return field(val) + _real_mpfi_ = interval + def radical_expression(self): r""" Attempt to obtain a symbolic expression using radicals. If no @@ -4553,13 +4561,13 @@ def complex_number(self, field): EXAMPLES:: sage: a = QQbar.zeta(5) - sage: a.complex_number(CIF) + sage: a.complex_number(CC) 0.309016994374947 + 0.951056516295154*I - sage: (a + a.conjugate()).complex_number(CIF) + sage: (a + a.conjugate()).complex_number(CC) 0.618033988749895 - 5.42101086242752e-20*I """ v = self.interval(ComplexIntervalField(field.prec())) - return v.center() + return field(v) def complex_exact(self, field): r""" @@ -5204,21 +5212,7 @@ def real_number(self, field): 1.41421356237309 """ v = self.interval(RealIntervalField(field.prec())) - - mode = field.rounding_mode() - if mode == 'RNDN': - return v.center() - if mode == 'RNDD': - return v.lower() - if mode == 'RNDU': - return v.upper() - if mode == 'RNDZ': - if v > 0: - return field(v.lower()) - elif v < 0: - return field(v.upper()) - else: - return field(0) + return field(v) _mpfr_ = real_number diff --git a/src/sage/rings/quotient_ring.py b/src/sage/rings/quotient_ring.py index f268c22cf0b..343b72fe3a9 100644 --- a/src/sage/rings/quotient_ring.py +++ b/src/sage/rings/quotient_ring.py @@ -29,15 +29,16 @@ ``x - I.reduce(x) in I``). Here is a toy example:: sage: from sage.rings.noncommutative_ideals import Ideal_nc + sage: from itertools import product sage: class PowerIdeal(Ideal_nc): - ... def __init__(self, R, n): - ... self._power = n - ... self._power = n - ... Ideal_nc.__init__(self,R,[R.prod(m) for m in CartesianProduct(*[R.gens()]*n)]) - ... def reduce(self,x): - ... R = self.ring() - ... return add([c*R(m) for m,c in x if len(m) = FreeAlgebra(QQ, 3) sage: I3 = PowerIdeal(F,3); I3 Twosided Ideal (x^3, x^2*y, x^2*z, x*y*x, x*y^2, x*y*z, x*z*x, x*z*y, @@ -83,15 +84,16 @@ letterplace wrapper allows to provide the above toy example more easily:: + sage: from itertools import product sage: F. = FreeAlgebra(QQ, implementation='letterplace') - sage: Q3 = F.quo(F*[F.prod(m) for m in CartesianProduct(*[F.gens()]*3)]*F) + sage: Q3 = F.quo(F*[F.prod(m) for m in product(F.gens(), repeat=3)]*F) sage: Q3 Quotient of Free Associative Unital Algebra on 3 generators (x, y, z) over Rational Field by the ideal (x*x*x, x*x*y, x*x*z, x*y*x, x*y*y, x*y*z, x*z*x, x*z*y, x*z*z, y*x*x, y*x*y, y*x*z, y*y*x, y*y*y, y*y*z, y*z*x, y*z*y, y*z*z, z*x*x, z*x*y, z*x*z, z*y*x, z*y*y, z*y*z, z*z*x, z*z*y, z*z*z) sage: Q3.0*Q3.1-Q3.1*Q3.0 xbar*ybar - ybar*xbar sage: Q3.0*(Q3.1*Q3.2)-(Q3.1*Q3.2)*Q3.0 0 - sage: Q2 = F.quo(F*[F.prod(m) for m in CartesianProduct(*[F.gens()]*2)]*F) + sage: Q2 = F.quo(F*[F.prod(m) for m in product(F.gens(), repeat=2)]*F) sage: Q2.is_commutative() True diff --git a/src/sage/rings/quotient_ring_element.py b/src/sage/rings/quotient_ring_element.py index 9f681331d55..853d267a924 100644 --- a/src/sage/rings/quotient_ring_element.py +++ b/src/sage/rings/quotient_ring_element.py @@ -590,6 +590,18 @@ def __float__(self): """ return float(self.lift()) + def __hash__(self): + r""" + TESTS:: + + sage: R. = QQ[] + sage: S. = R.quo(x^2 + y^2) + sage: hash(a) + 15360174650385711 # 64-bit + 1505322287 # 32-bit + """ + return hash(self.__rep) + def __cmp__(self, other): """ EXAMPLES:: diff --git a/src/sage/rings/rational.pyx b/src/sage/rings/rational.pyx index 63fa7fa0225..d1680334abe 100644 --- a/src/sage/rings/rational.pyx +++ b/src/sage/rings/rational.pyx @@ -709,9 +709,9 @@ cdef class Rational(sage.structure.element.FieldElement): l = self.continued_fraction_list() return ContinuedFraction_periodic(l) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, sage.structure.element.Element right) except -2: """ - Rich comparison between two rational numbers. + Compare two rational numbers. INPUT: @@ -730,9 +730,6 @@ cdef class Rational(sage.structure.element.FieldElement): sage: 4/5 < 0.8 False """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, sage.structure.element.Element right) except -2: cdef int i i = mpq_cmp((left).value, (right).value) if i < 0: return -1 diff --git a/src/sage/rings/rational_field.py b/src/sage/rings/rational_field.py index 59a757424eb..4a041a848bb 100644 --- a/src/sage/rings/rational_field.py +++ b/src/sage/rings/rational_field.py @@ -154,7 +154,7 @@ def __init__(self): sage: Q.is_field() True sage: Q.category() - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: Q.zeta() -1 @@ -215,7 +215,7 @@ def __init__(self): ('x',) """ from sage.categories.basic import QuotientFields - ParentWithGens.__init__(self, self, category = QuotientFields()) + ParentWithGens.__init__(self, self, category=QuotientFields().Metric()) self._assign_names(('x',),normalize=False) # ??? self._populate_coercion_lists_(element_constructor=rational.Rational, init_no_parent=True) diff --git a/src/sage/rings/real_arb.pyx b/src/sage/rings/real_arb.pyx index f7ad8c92f67..7c7722dae9e 100644 --- a/src/sage/rings/real_arb.pyx +++ b/src/sage/rings/real_arb.pyx @@ -1392,7 +1392,7 @@ cdef class RealBall(RingElement): """ return arb_is_exact(self.value) - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element right, int op): """ Compare ``left`` and ``right``. @@ -1400,191 +1400,172 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: a = RBF(1) # optional - arb - sage: b = RBF(1) # optional - arb - sage: a is b # optional - arb - False - sage: a == b # optional - arb - True - sage: a = RBF(1/3) # optional - arb - sage: a.is_exact() # optional - arb - False - sage: b = RBF(1/3) # optional - arb - sage: b.is_exact() # optional - arb - False - sage: a == b # optional - arb - False - """ - return (left)._richcmp(right, op) + sage: from sage.rings.real_arb import RealBallField # optional - arb + sage: RBF = RealBallField() # optional - arb + sage: a = RBF(1) # optional - arb + sage: b = RBF(1) # optional - arb + sage: a is b # optional - arb + False + sage: a == b # optional - arb + True + sage: a = RBF(1/3) # optional - arb + sage: a.is_exact() # optional - arb + False + sage: b = RBF(1/3) # optional - arb + sage: b.is_exact() # optional - arb + False + sage: a == b # optional - arb + False - cpdef _richcmp_(left, Element right, int op): - """ - Compare ``left`` and ``right``. + TESTS: - For more information, see :mod:`sage.rings.real_arb`. + Balls whose intersection consists of one point:: - EXAMPLES:: + sage: a = RBF(RIF(1, 2)) # optional - arb + sage: b = RBF(RIF(2, 4)) # optional - arb + sage: a < b # optional - arb + False + sage: a > b # optional - arb + False + sage: a <= b # optional - arb + False + sage: a >= b # optional - arb + False + sage: a == b # optional - arb + False + sage: a != b # optional - arb + False - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: a = RBF(1) # optional - arb - sage: b = RBF(1) # optional - arb - sage: a is b # optional - arb - False - sage: a == b # optional - arb - True + Balls with non-trivial intersection:: - TESTS: + sage: a = RBF(RIF(1, 4)) # optional - arb + sage: a = RBF(RIF(2, 5)) # optional - arb + sage: a < b # optional - arb + False + sage: a <= b # optional - arb + False + sage: a > b # optional - arb + False + sage: a >= b # optional - arb + False + sage: a == b # optional - arb + False + sage: a != b # optional - arb + False - Balls whose intersection consists of one point:: - - sage: a = RBF(RIF(1, 2)) # optional - arb - sage: b = RBF(RIF(2, 4)) # optional - arb - sage: a < b # optional - arb - False - sage: a > b # optional - arb - False - sage: a <= b # optional - arb - False - sage: a >= b # optional - arb - False - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - Balls with non-trivial intersection:: - - sage: a = RBF(RIF(1, 4)) # optional - arb - sage: a = RBF(RIF(2, 5)) # optional - arb - sage: a < b # optional - arb - False - sage: a <= b # optional - arb - False - sage: a > b # optional - arb - False - sage: a >= b # optional - arb - False - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - One ball contained in another:: - - sage: a = RBF(RIF(1, 4)) # optional - arb - sage: b = RBF(RIF(2, 3)) # optional - arb - sage: a < b # optional - arb - False - sage: a <= b # optional - arb - False - sage: a > b # optional - arb - False - sage: a >= b # optional - arb - False - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - Disjoint balls:: - - sage: a = RBF(1/3) # optional - arb - sage: b = RBF(1/2) # optional - arb - sage: a < b # optional - arb - True - sage: a <= b # optional - arb - True - sage: a > b # optional - arb - False - sage: a >= b # optional - arb - False - sage: a == b # optional - arb - False - sage: a != b # optional - arb - True - - Exact elements:: - - sage: a = RBF(2) # optional - arb - sage: b = RBF(2) # optional - arb - sage: a.is_exact() # optional - arb - True - sage: b.is_exact() # optional - arb - True - sage: a < b # optional - arb - False - sage: a <= b # optional - arb - True - sage: a > b # optional - arb - False - sage: a >= b # optional - arb - True - sage: a == b # optional - arb - True - sage: a != b # optional - arb - False - - Special values:: - - sage: inf = RBF(+infinity) # optional - arb - sage: other_inf = RBF(+infinity, 42.r) # optional - arb - sage: neg_inf = RBF(-infinity) # optional - arb - sage: extended_line = 1/RBF(0) # optional - arb - sage: exact_nan = inf - inf # optional - arb - sage: exact_nan.mid(), exact_nan.rad() # optional - arb - (NaN, 0.00000000) - sage: other_exact_nan = inf - inf # optional - arb - - :: - - sage: exact_nan == exact_nan, exact_nan <= exact_nan, exact_nan >= exact_nan # optional - arb - (False, False, False) - sage: exact_nan != exact_nan, exact_nan < exact_nan, exact_nan > exact_nan # optional - arb - (False, False, False) - sage: from operator import eq, ne, le, lt, ge, gt # optional - arb - sage: ops = [eq, ne, le, lt, ge, gt] # optional - arb - sage: any(op(exact_nan, other_exact_nan) for op in ops) # optional - arb - False - sage: any(op(exact_nan, b) for op in ops for b in [RBF(1), extended_line, inf, neg_inf]) # optional - arb - False - - :: - - sage: neg_inf < a < inf and inf > a > neg_inf # optional - arb - True - sage: neg_inf <= b <= inf and inf >= b >= neg_inf # optional - arb - True - sage: neg_inf <= extended_line <= inf and inf >= extended_line >= neg_inf # optional - arb - True - sage: neg_inf < extended_line or extended_line < inf # optional - arb - False - sage: inf > extended_line or extended_line > neg_inf # optional - arb - False - - :: - - sage: all(b <= b == b >= b and not (b < b or b != b or b > b) # optional - arb - ....: for b in [inf, neg_inf, other_inf]) - True - sage: any(b1 == b2 for b1 in [inf, neg_inf, a, extended_line] # optional - arb - ....: for b2 in [inf, neg_inf, a, extended_line] - ....: if not b1 is b2) - False - sage: all(b1 != b2 and not b1 == b2 # optional - arb - ....: for b1 in [inf, neg_inf, a] - ....: for b2 in [inf, neg_inf, a] - ....: if not b1 is b2) - True - sage: neg_inf <= -other_inf == neg_inf == -other_inf < other_inf == inf <= other_inf # optional - arb - True - sage: any(inf < b or b > inf # optional - arb - ....: for b in [inf, other_inf, a, extended_line]) - False - sage: any(inf <= b or b >= inf for b in [a, extended_line]) # optional - arb - False + One ball contained in another:: + + sage: a = RBF(RIF(1, 4)) # optional - arb + sage: b = RBF(RIF(2, 3)) # optional - arb + sage: a < b # optional - arb + False + sage: a <= b # optional - arb + False + sage: a > b # optional - arb + False + sage: a >= b # optional - arb + False + sage: a == b # optional - arb + False + sage: a != b # optional - arb + False + + Disjoint balls:: + + sage: a = RBF(1/3) # optional - arb + sage: b = RBF(1/2) # optional - arb + sage: a < b # optional - arb + True + sage: a <= b # optional - arb + True + sage: a > b # optional - arb + False + sage: a >= b # optional - arb + False + sage: a == b # optional - arb + False + sage: a != b # optional - arb + True + + Exact elements:: + + sage: a = RBF(2) # optional - arb + sage: b = RBF(2) # optional - arb + sage: a.is_exact() # optional - arb + True + sage: b.is_exact() # optional - arb + True + sage: a < b # optional - arb + False + sage: a <= b # optional - arb + True + sage: a > b # optional - arb + False + sage: a >= b # optional - arb + True + sage: a == b # optional - arb + True + sage: a != b # optional - arb + False + + Special values:: + + sage: inf = RBF(+infinity) # optional - arb + sage: other_inf = RBF(+infinity, 42.r) # optional - arb + sage: neg_inf = RBF(-infinity) # optional - arb + sage: extended_line = 1/RBF(0) # optional - arb + sage: exact_nan = inf - inf # optional - arb + sage: exact_nan.mid(), exact_nan.rad() # optional - arb + (NaN, 0.00000000) + sage: other_exact_nan = inf - inf # optional - arb + + :: + + sage: exact_nan == exact_nan, exact_nan <= exact_nan, exact_nan >= exact_nan # optional - arb + (False, False, False) + sage: exact_nan != exact_nan, exact_nan < exact_nan, exact_nan > exact_nan # optional - arb + (False, False, False) + sage: from operator import eq, ne, le, lt, ge, gt # optional - arb + sage: ops = [eq, ne, le, lt, ge, gt] # optional - arb + sage: any(op(exact_nan, other_exact_nan) for op in ops) # optional - arb + False + sage: any(op(exact_nan, b) for op in ops for b in [RBF(1), extended_line, inf, neg_inf]) # optional - arb + False + + :: + + sage: neg_inf < a < inf and inf > a > neg_inf # optional - arb + True + sage: neg_inf <= b <= inf and inf >= b >= neg_inf # optional - arb + True + sage: neg_inf <= extended_line <= inf and inf >= extended_line >= neg_inf # optional - arb + True + sage: neg_inf < extended_line or extended_line < inf # optional - arb + False + sage: inf > extended_line or extended_line > neg_inf # optional - arb + False + + :: + + sage: all(b <= b == b >= b and not (b < b or b != b or b > b) # optional - arb + ....: for b in [inf, neg_inf, other_inf]) + True + sage: any(b1 == b2 for b1 in [inf, neg_inf, a, extended_line] # optional - arb + ....: for b2 in [inf, neg_inf, a, extended_line] + ....: if not b1 is b2) + False + sage: all(b1 != b2 and not b1 == b2 # optional - arb + ....: for b1 in [inf, neg_inf, a] + ....: for b2 in [inf, neg_inf, a] + ....: if not b1 is b2) + True + sage: neg_inf <= -other_inf == neg_inf == -other_inf < other_inf == inf <= other_inf # optional - arb + True + sage: any(inf < b or b > inf # optional - arb + ....: for b in [inf, other_inf, a, extended_line]) + False + sage: any(inf <= b or b >= inf for b in [a, extended_line]) # optional - arb + False """ cdef RealBall lt, rt cdef arb_t difference diff --git a/src/sage/rings/real_double.pyx b/src/sage/rings/real_double.pyx index e6ec3dfc30c..a0a22c4e7d3 100644 --- a/src/sage/rings/real_double.pyx +++ b/src/sage/rings/real_double.pyx @@ -140,7 +140,7 @@ cdef class RealDoubleField_class(Field): sage: TestSuite(R).run() """ from sage.categories.fields import Fields - Field.__init__(self, self, category = Fields()) + Field.__init__(self, self, category=Fields().Metric().Complete()) self._populate_coercion_lists_(element_constructor=RealDoubleElement, init_no_parent=True, convert_method_name='_real_double_') diff --git a/src/sage/rings/real_lazy.pyx b/src/sage/rings/real_lazy.pyx index bfe6fe659f0..fce4acfed0d 100644 --- a/src/sage/rings/real_lazy.pyx +++ b/src/sage/rings/real_lazy.pyx @@ -668,6 +668,26 @@ cdef class LazyFieldElement(FieldElement): True sage: RLF(3) == RLF(4) False + sage: RLF(3) < RLF(5/3) + False + + TESTS:: + + sage: from sage.rings.real_lazy import LazyBinop + sage: RLF(3) < LazyBinop(RLF, 5, 3, operator.div) + False + sage: from sage.rings.real_lazy import LazyWrapper + sage: LazyWrapper(RLF, 3) < LazyWrapper(RLF, 5/3) + False + sage: from sage.rings.real_lazy import LazyUnop + sage: RLF(3) < LazyUnop(RLF, 2, sqrt) + False + sage: from sage.rings.real_lazy import LazyNamedUnop + sage: RLF(3) < LazyNamedUnop(RLF, 0, 'sin') + False + sage: from sage.rings.real_lazy import LazyConstant + sage: RLF(3) < LazyConstant(RLF, 'e') + False """ left = self try: @@ -679,17 +699,6 @@ cdef class LazyFieldElement(FieldElement): left, right = self.approx(), other.approx() return cmp(left, right) - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: RLF(3) < RLF(5/3) - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -993,18 +1002,6 @@ cdef class LazyWrapper(LazyFieldElement): """ return not not self._value - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyWrapper - sage: LazyWrapper(RLF, 3) < LazyWrapper(RLF, 5/3) - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -1176,18 +1173,6 @@ cdef class LazyBinop(LazyFieldElement): # We only do a call here because it is a python call. return self._op(left, right) - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyBinop - sage: RLF(3) < LazyBinop(RLF, 5, 3, operator.div) - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -1280,18 +1265,6 @@ cdef class LazyUnop(LazyFieldElement): return ~arg return self._op(self._arg.eval(R)) - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyUnop - sage: RLF(3) < LazyUnop(RLF, 2, sqrt) - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -1411,18 +1384,6 @@ cdef class LazyNamedUnop(LazyUnop): interval_field = self._parent.interval_field() return self.eval(interval_field._middle_field()) - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyNamedUnop - sage: RLF(3) < LazyNamedUnop(RLF, 0, 'sin') - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -1549,18 +1510,6 @@ cdef class LazyConstant(LazyFieldElement): self._extra_args = args return self - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyConstant - sage: RLF(3) < LazyConstant(RLF, 'e') - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. diff --git a/src/sage/rings/real_mpfi.pxd b/src/sage/rings/real_mpfi.pxd index 5996b03d2f5..865381d25a6 100644 --- a/src/sage/rings/real_mpfi.pxd +++ b/src/sage/rings/real_mpfi.pxd @@ -2,15 +2,13 @@ from sage.libs.mpfi cimport * cimport sage.rings.ring -cimport sage.structure.element from sage.structure.element cimport RingElement -from rational import Rational from rational cimport Rational cimport real_mpfr -cdef class RealIntervalFieldElement(sage.structure.element.RingElement) # forward decl +cdef class RealIntervalFieldElement(RingElement) # forward decl cdef class RealIntervalField_class(sage.rings.ring.Field): cdef int __prec @@ -32,14 +30,17 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): cdef real_mpfr.RealField_class __lower_field cdef real_mpfr.RealField_class __middle_field cdef real_mpfr.RealField_class __upper_field - cdef RealIntervalFieldElement _new(self) + cdef inline RealIntervalFieldElement _new(self): + """Return a new real interval with parent ``self``.""" + return RealIntervalFieldElement.__new__(RealIntervalFieldElement, self) -cdef class RealIntervalFieldElement(sage.structure.element.RingElement): +cdef class RealIntervalFieldElement(RingElement): cdef mpfi_t value - cdef char init - cdef RealIntervalFieldElement _new(self) + cdef inline RealIntervalFieldElement _new(self): + """Return a new real interval with same parent as ``self``.""" + return RealIntervalFieldElement.__new__(RealIntervalFieldElement, self._parent) cdef RealIntervalFieldElement abs(RealIntervalFieldElement self) cdef Rational _simplest_rational_helper(self) cpdef _str_question_style(self, int base, int error_digits, e, bint prefer_sci) diff --git a/src/sage/rings/real_mpfi.pyx b/src/sage/rings/real_mpfi.pyx index 85d3b750787..befdaf3b0df 100644 --- a/src/sage/rings/real_mpfi.pyx +++ b/src/sage/rings/real_mpfi.pyx @@ -239,6 +239,7 @@ Comparisons with numpy types are right (see :trac:`17758` and :trac:`18076`):: import math # for log import sys +import operator include 'sage/ext/interrupt.pxi' include "sage/ext/cdefs.pxi" @@ -246,32 +247,21 @@ from cpython.mem cimport * from cpython.string cimport * cimport sage.rings.ring -import sage.rings.ring - cimport sage.structure.element from sage.structure.element cimport RingElement, Element, ModuleElement -import sage.structure.element cimport real_mpfr -from real_mpfr cimport RealField_class, RealNumber -from real_mpfr import RealField -import real_mpfr +from real_mpfr cimport RealField_class, RealNumber, RealField +from sage.libs.mpfr cimport MPFR_RNDN, MPFR_RNDZ, MPFR_RNDU, MPFR_RNDD, MPFR_RNDA -import operator - -from integer import Integer from integer cimport Integer - -from real_double import RealDoubleElement from real_double cimport RealDoubleElement import sage.rings.complex_field - import sage.rings.infinity from sage.structure.parent_gens cimport ParentWithGens -cdef class RealIntervalFieldElement(sage.structure.element.RingElement) #***************************************************************************** # @@ -574,17 +564,6 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): else: return RealField(self.__prec, self.sci_not, "RNDZ") - cdef RealIntervalFieldElement _new(self): - """ - Return a new real number with parent ``self``. - """ - cdef RealIntervalFieldElement x - x = RealIntervalFieldElement.__new__(RealIntervalFieldElement) - x._parent = self - mpfi_init2(x.value, self.__prec) - x.init = 1 - return x - def _repr_(self): """ Return a string representation of ``self``. @@ -677,7 +656,7 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): sage: R('2', base=2) Traceback (most recent call last): ... - TypeError: Unable to convert number to real interval. + TypeError: unable to convert '2' to real interval sage: a = R('1.1001', base=2); a 1.5625000? sage: a.str(2) @@ -1115,34 +1094,43 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): return self(-1) raise ValueError, "No %sth root of unity in self"%n -R = RealIntervalField() #***************************************************************************** # # RealIntervalFieldElement -- element of Real Field # -# -# #***************************************************************************** -cdef class RealIntervalFieldElement(sage.structure.element.RingElement): +cdef class RealIntervalFieldElement(RingElement): """ A real number interval. """ - cdef RealIntervalFieldElement _new(self): + def __cinit__(self, parent, x=None, base=None): """ - Return a new real interval with same parent as ``self``. + Initialize the parent of this element and allocate memory + + TESTS:: + + sage: from sage.rings.real_mpfi import RealIntervalFieldElement + sage: RealIntervalFieldElement.__new__(RealIntervalFieldElement, None) + Traceback (most recent call last): + ... + TypeError: Cannot convert NoneType to sage.rings.real_mpfi.RealIntervalField_class + sage: RealIntervalFieldElement.__new__(RealIntervalFieldElement, ZZ) + Traceback (most recent call last): + ... + TypeError: Cannot convert sage.rings.integer_ring.IntegerRing_class to sage.rings.real_mpfi.RealIntervalField_class + sage: RealIntervalFieldElement.__new__(RealIntervalFieldElement, RIF) + [.. NaN ..] """ - cdef RealIntervalFieldElement x - x = RealIntervalFieldElement.__new__(RealIntervalFieldElement) - x._parent = self._parent - mpfi_init2(x.value, (self._parent).__prec) - x.init = 1 - return x + cdef RealIntervalField_class p = parent + mpfi_init2(self.value, p.__prec) + self._parent = p - def __init__(self, RealIntervalField_class parent, x=0, int base=10): + def __init__(self, parent, x=0, int base=10): """ - Create a real interval element. Should be called by first creating - a :class:`RealIntervalField`, as illustrated in the examples. + Initialize a real interval element. Should be called by first + creating a :class:`RealIntervalField`, as illustrated in the + examples. EXAMPLES:: @@ -1153,7 +1141,17 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): sage: R('1.2456').str(style='brackets') '[1.0 .. 1.3]' - EXAMPLES: + :: + + sage: RIF = RealIntervalField(53) + sage: RIF(RR.pi()) + 3.1415926535897932? + sage: RIF(RDF.pi()) + 3.1415926535897932? + sage: RIF(math.pi) + 3.1415926535897932? + sage: RIF.pi() + 3.141592653589794? Rounding:: @@ -1170,94 +1168,60 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): Type: ``RealIntervalField?`` for many more examples. """ - import sage.rings.qqbar - - self.init = 0 - if parent is None: - raise TypeError - self._parent = parent - mpfi_init2(self.value, parent.__prec) - self.init = 1 - if x is None: return - cdef RealIntervalFieldElement _x, n, d - cdef RealNumber rn, rn1 - cdef Rational rat, rat1 - cdef Integer integ, integ1 - cdef RealDoubleElement dx, dx1 - cdef int ix, ix1 + if x is None: + return + + cdef RealNumber ra, rb + cdef RealIntervalFieldElement d + if isinstance(x, RealIntervalFieldElement): - _x = x # so we can get at x.value - mpfi_set(self.value, _x.value) + mpfi_set(self.value, (x).value) elif isinstance(x, RealNumber): - rn = x - mpfi_set_fr(self.value, rn.value) + mpfi_set_fr(self.value, (x).value) elif isinstance(x, Rational): - rat = x - mpfi_set_q(self.value, rat.value) + mpfi_set_q(self.value, (x).value) elif isinstance(x, Integer): - integ = x - mpfi_set_z(self.value, integ.value) + mpfi_set_z(self.value, (x).value) + elif isinstance(x, RealDoubleElement): + mpfi_set_d(self.value, (x)._value) elif isinstance(x, int): - ix = x - mpfi_set_si(self.value, ix) + mpfi_set_si(self.value, x) + elif isinstance(x, float): + mpfi_set_d(self.value, x) + elif hasattr(x, '_real_mpfi_'): + d = x._real_mpfi_(self._parent) + mpfi_set(self.value, d.value) elif isinstance(x, tuple): try: a, b = x except ValueError: raise TypeError("tuple defining an interval must have length 2") if isinstance(a, RealNumber) and isinstance(b, RealNumber): - rn = a - rn1 = b - mpfi_interv_fr(self.value, rn.value, rn1.value) + mpfi_interv_fr(self.value, (a).value, (b).value) elif isinstance(a, RealDoubleElement) and isinstance(b, RealDoubleElement): - dx = a - dx1 = b - mpfi_interv_d(self.value, dx._value, dx1._value) + mpfi_interv_d(self.value, (a)._value, (b)._value) elif isinstance(a, Rational) and isinstance(b, Rational): - rat = a - rat1 = b - mpfi_interv_q(self.value, rat.value, rat1.value) + mpfi_interv_q(self.value, (a).value, (b).value) elif isinstance(a, Integer) and isinstance(b, Integer): - integ = a - integ1 = b - mpfi_interv_z(self.value, integ.value, integ1.value) + mpfi_interv_z(self.value, (a).value, (b).value) elif isinstance(a, int) and isinstance(b, int): - ix = a - ix1 = b - mpfi_interv_si(self.value, ix, ix1) + mpfi_interv_si(self.value, a, b) else: # generic fallback - rn = self._parent(a).lower() - rn1 = self._parent(b).upper() - mpfi_interv_fr(self.value, rn.value, rn1.value) - - elif isinstance(x, sage.rings.qqbar.AlgebraicReal): - d = x.interval(self._parent) - mpfi_set(self.value, d.value) - - elif hasattr(x, '_real_mpfi_'): - d = x._real_mpfi_(self._parent) - mpfi_set(self.value, d.value) - + ra = self._parent(a).lower() + rb = self._parent(b).upper() + mpfi_interv_fr(self.value, ra.value, rb.value) + elif isinstance(x, basestring): + s = str(x).replace('..', ',').replace(' ','').replace('+infinity', '@inf@').replace('-infinity','-@inf@') + if mpfi_set_str(self.value, s, base): + raise TypeError("unable to convert {!r} to real interval".format(x)) else: - from sage.symbolic.expression import Expression - - if isinstance(x, Expression): - d = x._real_mpfi_(self._parent) - mpfi_set(self.value, d.value) - - elif isinstance(x, str): - # string - s = str(x).replace('..', ',').replace(' ','').replace('+infinity', '@inf@').replace('-infinity','-@inf@') - if mpfi_set_str(self.value, s, base): - raise TypeError("Unable to convert number to real interval.") - else: - # try coercing to real - try: - rn = self._parent._lower_field()(x) - rn1 = self._parent._upper_field()(x) - except TypeError: - raise TypeError("Unable to convert number to real interval.") - mpfi_interv_fr(self.value, rn.value, rn1.value) + # try coercing to real + try: + ra = self._parent._lower_field()(x) + rb = self._parent._upper_field()(x) + except TypeError: + raise TypeError("unable to convert {!r} to real interval".format(x)) + mpfi_interv_fr(self.value, ra.value, rb.value) def __reduce__(self): """ @@ -1305,7 +1269,7 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): sage: R = RealIntervalField() sage: del R # indirect doctest """ - if self.init: + if self._parent is not None: mpfi_clear(self.value) def __repr__(self): @@ -2446,15 +2410,19 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): """ The largest absolute value of the elements of the interval. + OUTPUT: a real number with rounding mode ``RNDU`` + EXAMPLES:: sage: RIF(-2, 1).magnitude() 2.00000000000000 sage: RIF(-1, 2).magnitude() 2.00000000000000 + sage: parent(RIF(1).magnitude()) + Real Field with 53 bits of precision and rounding RNDU """ cdef RealNumber x - x = (self._parent).__middle_field._new() + x = (self._parent).__upper_field._new() mpfi_mag(x.value, self.value) return x @@ -2462,6 +2430,8 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): """ The smallest absolute value of the elements of the interval. + OUTPUT: a real number with rounding mode ``RNDD`` + EXAMPLES:: sage: RIF(-2, 1).mignitude() @@ -2470,9 +2440,11 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): 1.00000000000000 sage: RIF(3, 4).mignitude() 3.00000000000000 + sage: parent(RIF(1).mignitude()) + Real Field with 53 bits of precision and rounding RNDD """ cdef RealNumber x - x = (self._parent).__middle_field._new() + x = (self._parent).__lower_field._new() mpfi_mig(x.value, self.value) return x @@ -3073,33 +3045,59 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): # Conversions ########################################### -# def __float__(self): -# return mpfr_get_d(self.value, (self._parent).rnd) - -# def __int__(self): -# """ -# Returns integer truncation of this real number. -# """ -# s = self.str(32) -# i = s.find('.') -# return int(s[:i], 32) + def _mpfr_(self, RealField_class field): + """ + Convert to a real field, honoring the rounding mode of the + real field. -# def __long__(self): -# """ -# Returns long integer truncation of this real number. -# """ -# s = self.str(32) -# i = s.find('.') -# return long(s[:i], 32) + EXAMPLES:: -# def __complex__(self): -# return complex(float(self)) + sage: a = RealIntervalField(30)("1.2") + sage: RR(a) + 1.20000000018626 + sage: b = RIF(-1, 3) + sage: RR(b) + 1.00000000000000 -# def _complex_number_(self): -# return sage.rings.complex_field.ComplexField(self.prec())(self) + With different rounding modes:: -# def _pari_(self): -# return sage.libs.pari.all.pari.new_with_bits_prec(str(self), (self._parent).__prec) + sage: RealField(53, rnd="RNDU")(a) + 1.20000000111759 + sage: RealField(53, rnd="RNDD")(a) + 1.19999999925494 + sage: RealField(53, rnd="RNDZ")(a) + 1.19999999925494 + sage: RealField(53, rnd="RNDU")(b) + 3.00000000000000 + sage: RealField(53, rnd="RNDD")(b) + -1.00000000000000 + sage: RealField(53, rnd="RNDZ")(b) + 0.000000000000000 + """ + cdef RealNumber x = field._new() + if field.rnd == MPFR_RNDN: + mpfi_mid(x.value, self.value) + elif field.rnd == MPFR_RNDD: + mpfi_get_left(x.value, self.value) + elif field.rnd == MPFR_RNDU: + mpfi_get_right(x.value, self.value) + elif field.rnd == MPFR_RNDZ: + if mpfi_is_strictly_pos_default(self.value): # interval is > 0 + mpfi_get_left(x.value, self.value) + elif mpfi_is_strictly_neg_default(self.value): # interval is < 0 + mpfi_get_right(x.value, self.value) + else: + mpfr_set_zero(x.value, 1) # interval contains 0 + elif field.rnd == MPFR_RNDA: + # return the endpoint which is furthest from 0 + lo, hi = self.endpoints() + if hi.abs() >= lo.abs(): + mpfi_get_right(x.value, self.value) + else: + mpfi_get_left(x.value, self.value) + else: + raise AssertionError("%s has unknown rounding mode"%field) + return x def unique_sign(self): r""" @@ -3445,11 +3443,10 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): """ return mpfi_nan_p(self.value) - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element right, int op): """ - Rich comparison between ``left`` and ``right``. - - For more information, see :mod:`sage.rings.real_mpfi`. + Implements comparisons between intervals. (See the file header + comment for more information on interval comparison.) EXAMPLES:: @@ -3469,13 +3466,6 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): False sage: RIF(0, 2) > RIF(2, 3) False - """ - return (left)._richcmp(right, op) - - cpdef _richcmp_(left, Element right, int op): - """ - Implements comparisons between intervals. (See the file header - comment for more information on interval comparison.) EXAMPLES:: @@ -3666,7 +3656,7 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): """ return not (mpfr_zero_p(&self.value.left) and mpfr_zero_p(&self.value.right)) - def __cmp__(left, right): + cpdef int _cmp_(left, Element right) except -2: """ Compare two intervals lexicographically. @@ -3694,12 +3684,6 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): sage: cmp(RIF(0, 1), RIF(0, 1/2)) 1 """ - return (left)._cmp(right) - - cpdef int _cmp_(left, Element right) except -2: - """ - Implements the lexicographic total order on intervals. - """ cdef RealIntervalFieldElement lt, rt lt = left @@ -3926,7 +3910,7 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): sage: a.min('x') Traceback (most recent call last): ... - TypeError: Unable to convert number to real interval. + TypeError: unable to convert 'x' to real interval """ cdef RealIntervalFieldElement constructed cdef RealIntervalFieldElement result @@ -4030,7 +4014,7 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): sage: a.max('x') Traceback (most recent call last): ... - TypeError: Unable to convert number to real interval. + TypeError: unable to convert 'x' to real interval """ cdef RealIntervalFieldElement constructed cdef RealIntervalFieldElement result diff --git a/src/sage/rings/real_mpfr.pxd b/src/sage/rings/real_mpfr.pxd index 4b2b1465cf8..cef3184333c 100644 --- a/src/sage/rings/real_mpfr.pxd +++ b/src/sage/rings/real_mpfr.pxd @@ -2,9 +2,7 @@ from sage.libs.mpfr cimport * cimport sage.rings.ring cimport sage.structure.element - -cdef extern from "pari/pari.h": - ctypedef long* GEN +from sage.libs.pari.types cimport GEN cdef class RealNumber(sage.structure.element.RingElement) # forward decl @@ -14,13 +12,15 @@ cdef class RealField_class(sage.rings.ring.Field): cdef bint sci_not cdef mpfr_rnd_t rnd cdef object rnd_str - cdef RealNumber _new(self) - + cdef inline RealNumber _new(self): + """Return a new real number with parent ``self``.""" + return (RealNumber.__new__(RealNumber, self)) cdef class RealNumber(sage.structure.element.RingElement): cdef mpfr_t value - cdef char init - cdef RealNumber _new(self) + cdef inline RealNumber _new(self): + """Return a new real number with same parent as ``self``.""" + return (RealNumber.__new__(RealNumber, self._parent)) cdef _set(self, x, int base) cdef _set_from_GEN_REAL(self, GEN g) cdef RealNumber abs(RealNumber self) diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx index ac9e423689f..c41d68d6e81 100644 --- a/src/sage/rings/real_mpfr.pyx +++ b/src/sage/rings/real_mpfr.pyx @@ -154,8 +154,6 @@ import sage.rings.infinity from sage.structure.parent_gens cimport ParentWithGens -cdef class RealNumber(sage.structure.element.RingElement) - #***************************************************************************** # # Implementation @@ -437,7 +435,6 @@ cdef class RealField_class(sage.rings.ring.Field): Real Field with 17 bits of precision and rounding RNDD """ global MY_MPFR_PREC_MAX - cdef RealNumber rn if prec < MPFR_PREC_MIN or prec > MY_MPFR_PREC_MAX: raise ValueError, "prec (=%s) must be >= %s and <= %s"%( prec, MPFR_PREC_MIN, MY_MPFR_PREC_MAX) @@ -451,36 +448,20 @@ cdef class RealField_class(sage.rings.ring.Field): self.rnd = n self.rnd_str = rnd from sage.categories.fields import Fields - ParentWithGens.__init__(self, self, tuple([]), False, category = Fields()) - - # hack, we cannot call the constructor here - rn = RealNumber.__new__(RealNumber) - rn._parent = self - mpfr_init2(rn.value, self.__prec) - rn.init = 1 - mpfr_set_d(rn.value, 0.0, self.rnd) + ParentWithGens.__init__(self, self, tuple([]), False, category=Fields().Metric().Complete()) + + # Initialize zero and one + cdef RealNumber rn + rn = self._new() + mpfr_set_zero(rn.value, 1) self._zero_element = rn - rn = RealNumber.__new__(RealNumber) - rn._parent = self - mpfr_init2(rn.value, self.__prec) - rn.init = 1 - mpfr_set_d(rn.value, 1.0, self.rnd) + rn = self._new() + mpfr_set_ui(rn.value, 1, MPFR_RNDZ) self._one_element = rn self._populate_coercion_lists_(convert_method_name='_mpfr_') - cdef RealNumber _new(self): - """ - Return a new real number with parent ``self``. - """ - cdef RealNumber x - x = RealNumber.__new__(RealNumber) - x._parent = self - mpfr_init2(x.value, self.__prec) - x.init = 1 - return x - def _repr_(self): """ Return a string representation of ``self``. @@ -969,7 +950,6 @@ cdef class RealField_class(sage.rings.ring.Field): else: return RealField(prec, self.sci_not, _rounding_modes[self.rnd]) - # int mpfr_const_pi (mpfr_t rop, mp_rnd_t rnd) def pi(self): r""" Return `\pi` to the precision of this field. @@ -999,8 +979,6 @@ cdef class RealField_class(sage.rings.ring.Field): if self.__prec > SIG_PREC_THRESHOLD: sig_off() return x - - # int mpfr_const_euler (mpfr_t rop, mp_rnd_t rnd) def euler_constant(self): """ Returns Euler's gamma constant to the precision of this field. @@ -1017,7 +995,6 @@ cdef class RealField_class(sage.rings.ring.Field): sig_off() return x - # int mpfr_const_catalan (mpfr_t rop, mp_rnd_t rnd) def catalan_constant(self): """ Returns Catalan's constant to the precision of this field. @@ -1034,7 +1011,6 @@ cdef class RealField_class(sage.rings.ring.Field): if self.__prec > SIG_PREC_THRESHOLD: sig_off() return x - # int mpfr_const_log2 (mpfr_t rop, mp_rnd_t rnd) def log2(self): r""" Return `\log(2)` (i.e., the natural log of 2) to the precision @@ -1252,8 +1228,6 @@ cdef class RealField_class(sage.rings.ring.Field): # # RealNumber -- element of Real Field # -# -# #***************************************************************************** cdef class RealLiteral(RealNumber) @@ -1270,18 +1244,29 @@ cdef class RealNumber(sage.structure.element.RingElement): internal precision, in order to avoid confusing roundoff issues that occur because numbers are stored internally in binary. """ - cdef RealNumber _new(self): + def __cinit__(self, parent, x=None, base=None): """ - Return a new real number with same parent as self. + Initialize the parent of this element and allocate memory + + TESTS:: + + sage: from sage.rings.real_mpfr import RealNumber + sage: RealNumber.__new__(RealNumber, None) + Traceback (most recent call last): + ... + TypeError: Cannot convert NoneType to sage.rings.real_mpfr.RealField_class + sage: RealNumber.__new__(RealNumber, ZZ) + Traceback (most recent call last): + ... + TypeError: Cannot convert sage.rings.integer_ring.IntegerRing_class to sage.rings.real_mpfr.RealField_class + sage: RealNumber.__new__(RealNumber, RR) + NaN """ - cdef RealNumber x - x = RealNumber.__new__(RealNumber) - x._parent = self._parent - mpfr_init2(x.value, (self._parent).__prec) - x.init = 1 - return x + cdef RealField_class p = parent + mpfr_init2(self.value, p.__prec) + self._parent = p - def __init__(self, RealField_class parent, x=0, int base=10): + def __init__(self, parent, x=0, int base=10): """ Create a real number. Should be called by first creating a RealField, as illustrated in the examples. @@ -1326,16 +1311,8 @@ cdef class RealNumber(sage.structure.element.RingElement): sage: TestSuite(R).run() """ - self.init = 0 - if parent is None: - raise TypeError - self._parent = parent - mpfr_init2(self.value, parent.__prec) - self.init = 1 - if x is None: - return - - self._set(x, base) + if x is not None: + self._set(x, base) def _magma_init_(self, magma): r""" @@ -1493,7 +1470,7 @@ cdef class RealNumber(sage.structure.element.RingElement): return (__create__RealNumber_version0, (self._parent, s, 32)) def __dealloc__(self): - if self.init: + if self._parent is not None: mpfr_clear(self.value) def __repr__(self): @@ -3764,10 +3741,11 @@ cdef class RealNumber(sage.structure.element.RingElement): """ return mpfr_sgn(self.value) != 0 - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ - Return the Cython rich comparison operator (see the Cython - documentation for details). + Return ``-1`` if exactly one of the numbers is ``NaN``. Return ``-1`` + if ``left`` is less than ``right``, ``0`` if ``left`` and ``right`` + are equal, and ``1`` if ``left`` is greater than ``right``. EXAMPLES:: @@ -3797,27 +3775,6 @@ cdef class RealNumber(sage.structure.element.RingElement): False sage: RR('1')>=RR('1') True - """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - Return ``-1`` if exactly one of the numbers is ``NaN``. Return ``-1`` - if ``left`` is less than ``right``, ``0`` if ``left`` and ``right`` - are equal, and ``1`` if ``left`` is greater than ``right``. - - EXAMPLES:: - - sage: RR('1')<=RR('1') - True - sage: RR('1')RR('1') - False - sage: RR('1')>=RR('1') - True - sage: RR('nan')==R('nan') - False sage: RR('inf')==RR('inf') True sage: RR('inf')==RR('-inf') @@ -4374,15 +4331,6 @@ cdef class RealNumber(sage.structure.element.RingElement): sig_off() return x - ########################################################## - # it would be nice to get zero back here: - # sage: R(-1).acos().sin() - # _57 = -0.50165576126683320234e-19 - # i think this could be "fixed" by using MPFI. (put on to-do list.) - # - # this seems to work ok: - # sage: R(-1).acos().cos() - # _58 = -0.10000000000000000000e1 def sin(self): """ Return the sine of ``self``. @@ -4437,9 +4385,6 @@ cdef class RealNumber(sage.structure.element.RingElement): sig_off() return x,y - - # int mpfr_sin_cos (mpfr_t rop, mpfr_t op, mpfr_t, mp_rnd_t rnd) - def arccos(self): """ Return the inverse cosine of ``self``. @@ -4493,10 +4438,6 @@ cdef class RealNumber(sage.structure.element.RingElement): sig_off() return x - #int mpfr_acos _PROTO ((mpfr_ptr, mpfr_srcptr, mp_rnd_t)); - #int mpfr_asin _PROTO ((mpfr_ptr, mpfr_srcptr, mp_rnd_t)); - #int mpfr_atan _PROTO ((mpfr_ptr, mpfr_srcptr, mp_rnd_t)); - def cosh(self): """ Return the hyperbolic cosine of ``self``. @@ -5651,41 +5592,6 @@ def __create__RealNumber_version0(parent, x, base=10): return RealNumber(parent, x, base=base) -cdef inline RealNumber empty_RealNumber(RealField_class parent): - """ - Create and return an empty initialized real number. - - EXAMPLES: - - These are indirect tests of this function:: - - sage: from sage.rings.real_mpfr import RRtoRR - sage: R10 = RealField(10) - sage: R100 = RealField(100) - sage: f = RRtoRR(R100, R10) - sage: a = R100(1.2) - sage: f(a) - 1.2 - sage: g = f.section() - sage: g - Generic map: - From: Real Field with 10 bits of precision - To: Real Field with 100 bits of precision - sage: g(f(a)) # indirect doctest - 1.1992187500000000000000000000 - sage: b = R10(2).sqrt() - sage: f(g(b)) - 1.4 - sage: f(g(b)) == b - True - """ - - cdef RealNumber y = RealNumber.__new__(RealNumber) - y._parent = parent - mpfr_init2(y.value, parent.__prec) - y.init = 1 - return y - cdef class RRtoRR(Map): cpdef Element _call_(self, x): """ @@ -5712,7 +5618,7 @@ cdef class RRtoRR(Map): True """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() if type(x) is RealLiteral: mpfr_set_str(y.value, (x).literal, (x).base, parent.rnd) else: @@ -5745,7 +5651,7 @@ cdef class ZZtoRR(Map): 1.2346e8 """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() mpfr_set_z(y.value, (x).value, parent.rnd) return y @@ -5760,7 +5666,7 @@ cdef class QQtoRR(Map): -0.33333333333333333333333333333333333333333333333333333333333 """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() mpfr_set_q(y.value, (x).value, parent.rnd) return y @@ -5780,7 +5686,7 @@ cdef class double_toRR(Map): 3.1415926535897931159979634685441851615905761718750000000000 """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() mpfr_set_d(y.value, x, parent.rnd) return y @@ -5808,6 +5714,6 @@ cdef class int_toRR(Map): 1.00000000000000 """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() mpfr_set_si(y.value, x, parent.rnd) return y diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index 7d84ca3bab1..b62886b03c9 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -68,7 +68,7 @@ AUTHORS: from sage.misc.cachefunc import cached_method -from sage.structure.element import get_coercion_model +from sage.structure.element cimport coercion_model from sage.structure.parent_gens cimport ParentWithGens from sage.structure.parent cimport Parent from sage.structure.category_object import check_default_category @@ -134,11 +134,14 @@ cdef class Ring(ParentWithGens): Test agaings another bug fixed in :trac:`9944`:: sage: QQ['x'].category() - Join of Category of euclidean domains and Category of commutative algebras over quotient fields + Join of Category of euclidean domains + and Category of commutative algebras over (quotient fields and metric spaces) sage: QQ['x','y'].category() - Join of Category of unique factorization domains and Category of commutative algebras over quotient fields + Join of Category of unique factorization domains + and Category of commutative algebras over (quotient fields and metric spaces) sage: PolynomialRing(MatrixSpace(QQ,2),'x').category() - Category of algebras over (algebras over quotient fields and infinite sets) + Category of algebras over (algebras over + (quotient fields and metric spaces) and infinite sets) sage: PolynomialRing(SteenrodAlgebra(2),'x').category() Category of algebras over graded hopf algebras with basis over Finite Field of size 2 @@ -1308,7 +1311,7 @@ cdef class CommutativeRing(Ring): try: return self.fraction_field() except (NotImplementedError,TypeError): - return get_coercion_model().division_parent(self) + return coercion_model.division_parent(self) def __pow__(self, n, _): """ diff --git a/src/sage/rings/semirings/tropical_semiring.pyx b/src/sage/rings/semirings/tropical_semiring.pyx index cc4d1104ab8..fa06154e1e6 100644 --- a/src/sage/rings/semirings/tropical_semiring.pyx +++ b/src/sage/rings/semirings/tropical_semiring.pyx @@ -134,10 +134,11 @@ cdef class TropicalSemiringElement(RingElement): return hash(self._val) # Comparisons - - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ - Rich comparisons. + Return ``-1`` if ``left`` is less than ``right``, ``0`` if + ``left`` and ``right`` are equal, and ``1`` if ``left`` is + greater than ``right``. EXAMPLES:: @@ -170,30 +171,6 @@ cdef class TropicalSemiringElement(RingElement): False sage: T.infinity() >= T.infinity() True - """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - Return ``-1`` if ``left`` is less than ``right``, ``0`` if - ``left`` and ``right`` are equal, and ``1`` if ``left`` is - greater than ``right``. - - EXAMPLES:: - - sage: T = TropicalSemiring(QQ) - sage: T(2) == T(2) - True - sage: T(2) != T(4) - True - sage: T(2) < T(4) - True - sage: T(2) > T(4) - False - sage: T.infinity() == T.infinity() - True - sage: T(4) <= T.infinity() - True Using the `\max` definition:: diff --git a/src/sage/sandpiles/sandpile.py b/src/sage/sandpiles/sandpile.py index c7410b04368..6d2d2653696 100644 --- a/src/sage/sandpiles/sandpile.py +++ b/src/sage/sandpiles/sandpile.py @@ -490,14 +490,14 @@ def __init__(self, g, sink=None): INPUT: - - ``g`` -- dict for directed multigraph with edges weighted by - nonnegative integers (see NOTE), a Graph or DiGraph. + - ``g`` -- dict for directed multigraph with edges weighted by + nonnegative integers (see NOTE), a Graph or DiGraph. - - ``sink`` -- (optional) A sink vertex. Any outgoing edges from the - designated sink are ignored for the purposes of stabilization. It is - assumed that every vertex has a directed path into the sink. If the - ``sink`` argument is omitted, the first vertex in the list of the - Sandpile's vertices is set as the sink. + - ``sink`` -- (optional) A sink vertex. Any outgoing edges from the + designated sink are ignored for the purposes of stabilization. It is + assumed that every vertex has a directed path into the sink. If the + ``sink`` argument is omitted, the first vertex in the list of the + Sandpile's vertices is set as the sink. OUTPUT: @@ -517,41 +517,37 @@ def __init__(self, g, sink=None): sage: G = Sandpile(g,'d') Here is a square with unweighted edges. In this example, the graph is - also undirected. - - :: + also undirected. :: sage: g = {0:[1,2], 1:[0,3], 2:[0,3], 3:[1,2]} sage: G = Sandpile(g,3) In the following example, multiple edges and loops in the dictionary - become edge weights in the Sandpile. - - :: - - sage: s = Sandpile({0:[1,2,3], 1:[0,1,2,2,2], 2:[1,1,0,2,2,2,2]}) - sage: s.laplacian() - [ 3 -1 -1 -1] - [-1 4 -3 0] - [-1 -2 3 0] - [ 0 0 0 0] - sage: s.dict() - {0: {1: 1, 2: 1, 3: 1}, 1: {0: 1, 1: 1, 2: 3}, 2: {0: 1, 1: 2, 2: 4}} - - Sandpiles can be created from Graphs and DiGraphs. - - sage: g = DiGraph({0:{1:2,2:4}, 1:{1:3,2:1}, 2:{1:7}}, weighted=True) - sage: s = Sandpile(g) - sage: s.dict() - {0: {1: 2, 2: 4}, 1: {0: 0, 1: 3, 2: 1}, 2: {0: 0, 1: 7}} - sage: s.sink() - 0 - sage: s = sandpiles.Cycle(4) - sage: s.laplacian() - [ 2 -1 0 -1] - [-1 2 -1 0] - [ 0 -1 2 -1] - [-1 0 -1 2] + become edge weights in the Sandpile. :: + + sage: s = Sandpile({0:[1,2,3], 1:[0,1,2,2,2], 2:[1,1,0,2,2,2,2]}) + sage: s.laplacian() + [ 3 -1 -1 -1] + [-1 4 -3 0] + [-1 -2 3 0] + [ 0 0 0 0] + sage: s.dict() + {0: {1: 1, 2: 1, 3: 1}, 1: {0: 1, 1: 1, 2: 3}, 2: {0: 1, 1: 2, 2: 4}} + + Sandpiles can be created from Graphs and DiGraphs. :: + + sage: g = DiGraph({0:{1:2,2:4}, 1:{1:3,2:1}, 2:{1:7}}, weighted=True) + sage: s = Sandpile(g) + sage: s.dict() + {0: {1: 2, 2: 4}, 1: {0: 0, 1: 3, 2: 1}, 2: {0: 0, 1: 7}} + sage: s.sink() + 0 + sage: s = sandpiles.Cycle(4) + sage: s.laplacian() + [ 2 -1 0 -1] + [-1 2 -1 0] + [ 0 -1 2 -1] + [-1 0 -1 2] .. NOTE:: diff --git a/src/sage/sat/all.py b/src/sage/sat/all.py new file mode 100644 index 00000000000..d4beeb2a9a1 --- /dev/null +++ b/src/sage/sat/all.py @@ -0,0 +1,2 @@ +from sage.misc.lazy_import import lazy_import +lazy_import('sage.sat.solvers.satsolver', 'SAT') diff --git a/src/sage/sat/converters/polybori.py b/src/sage/sat/converters/polybori.py index bbc3b64ab8c..11b5d295b14 100644 --- a/src/sage/sat/converters/polybori.py +++ b/src/sage/sat/converters/polybori.py @@ -154,7 +154,7 @@ def var(self, m=None, decision=None): INPUT: - ``m`` - something the new variables maps to, usually a monomial - - ``decision`` - is this variable a deicison variable? + - ``decision`` - is this variable a decision variable? EXAMPLE:: diff --git a/src/sage/sat/solvers/sat_lp.py b/src/sage/sat/solvers/sat_lp.py new file mode 100644 index 00000000000..1a74206f162 --- /dev/null +++ b/src/sage/sat/solvers/sat_lp.py @@ -0,0 +1,145 @@ +r""" +Solve SAT problems Integer Linear Programming + +The class defined here is a :class:`~sage.sat.solvers.satsolver.SatSolver` that +solves its instance using :class:`MixedIntegerLinearProgram`. Its performance +can be expected to be slower than when using +:class:`~sage.sat.solvers.cryptominisat.cryptominisat.CryptoMiniSat`. +""" +from satsolver import SatSolver +from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException + +class SatLP(SatSolver): + def __init__(self, solver=None): + r""" + Initializes the instance + + INPUT: + + - ``solver`` -- (default: ``None``) Specify a Linear Program (LP) + solver to be used. If set to ``None``, the default one is used. For + more information on LP solvers and which default solver is used, see + the method + :meth:`solve ` + of the class + :class:`MixedIntegerLinearProgram `. + + EXAMPLE:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + """ + SatSolver.__init__(self) + self._LP = MixedIntegerLinearProgram() + self._vars = self._LP.new_variable(binary=True) + + def var(self): + """ + Return a *new* variable. + + EXAMPLE:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + sage: S.var() + 1 + """ + nvars = n = self._LP.number_of_variables() + while nvars==self._LP.number_of_variables(): + n += 1 + self._vars[n] # creates the variable if needed + return n + + def nvars(self): + """ + Return the number of variables. + + EXAMPLE:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + sage: S.var() + 1 + sage: S.var() + 2 + sage: S.nvars() + 2 + """ + return self._LP.number_of_variables() + + def add_clause(self, lits): + """ + Add a new clause to set of clauses. + + INPUT: + + - ``lits`` - a tuple of integers != 0 + + .. note:: + + If any element ``e`` in ``lits`` has ``abs(e)`` greater + than the number of variables generated so far, then new + variables are created automatically. + + EXAMPLE:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + sage: for u,v in graphs.CycleGraph(6).edges(labels=False): + ....: u,v = u+1,v+1 + ....: S.add_clause((u,v)) + ....: S.add_clause((-u,-v)) + """ + if 0 in lits: + raise ValueError("0 should not appear in the clause: {}".format(lits)) + p = self._LP + p.add_constraint(p.sum(self._vars[x] if x>0 else 1-self._vars[-x] for x in lits) + >=1) + + def __call__(self): + """ + Solve this instance. + + OUTPUT: + + - If this instance is SAT: A tuple of length ``nvars()+1`` + where the ``i``-th entry holds an assignment for the + ``i``-th variables (the ``0``-th entry is always ``None``). + + - If this instance is UNSAT: ``False`` + + EXAMPLE:: + + sage: def is_bipartite_SAT(G): + ....: S=SAT(solver="LP"); S + ....: for u,v in G.edges(labels=False): + ....: u,v = u+1,v+1 + ....: S.add_clause((u,v)) + ....: S.add_clause((-u,-v)) + ....: return S + sage: S = is_bipartite_SAT(graphs.CycleGraph(6)) + sage: S() # random + [None, True, False, True, False, True, False] + sage: True in S() + True + sage: S = is_bipartite_SAT(graphs.CycleGraph(7)) + sage: S() + False + """ + try: + self._LP.solve() + except MIPSolverException: + return False + + b = self._LP.get_values(self._vars) + n = max(b) + return [None]+[bool(b.get(i,0)) for i in range(1,n+1)] + + def __repr__(self): + """ + TESTS:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + """ + return "an ILP-based SAT Solver" diff --git a/src/sage/sat/solvers/satsolver.pyx b/src/sage/sat/solvers/satsolver.pyx index 13e0e892607..734393d4800 100644 --- a/src/sage/sat/solvers/satsolver.pyx +++ b/src/sage/sat/solvers/satsolver.pyx @@ -14,6 +14,7 @@ AUTHORS: - Martin Albrecht (2012): first version """ +from sage.misc.package import PackageNotFoundError cdef class SatSolver: def __cinit__(self, *args, **kwds): @@ -33,7 +34,7 @@ cdef class SatSolver: INPUT: - - ``decision`` - is this variable a deicison variable? + - ``decision`` - is this variable a decision variable? EXAMPLE:: @@ -275,3 +276,60 @@ cdef class SatSolver: """ return ["gens"] +def SAT(solver=None): + r""" + Return a :class:`SatSolver` instance. + + Through this class, one can define and solve `SAT + `__ problems. + + INPUT: + + - ``solver`` (string) -- select a solver. Admissible values are: + + - ``"cryptominisat"`` -- note that the cryptominisat package must be + installed. + + - ``"LP"`` -- use :class:`~sage.sat.solvers.sat_lp.SatLP` to solve the + SAT instance. + + - ``None`` (default) -- use CryptoMiniSat if available, and a LP solver + otherwise. + + EXAMPLE:: + + sage: SAT(solver="LP") + an ILP-based SAT Solver + + TESTS:: + + sage: SAT(solver="Wouhouuuuuu") + Traceback (most recent call last): + ... + ValueError: Solver 'Wouhouuuuuu' is not available + + Forcing CryptoMiniSat:: + + sage: SAT(solver="cryptominisat") # optional - cryptominisat + CryptoMiniSat + #vars: 0, #lits: 0, #clauses: 0, #learnt: 0, #assigns: 0 + + """ + if solver is None: + try: + from sage.sat.solvers.cryptominisat.cryptominisat import CryptoMiniSat + solver = "cryptominisat" + except ImportError: + solver = "LP" + + if solver == 'cryptominisat': + try: + from sage.sat.solvers.cryptominisat.cryptominisat import CryptoMiniSat + except ImportError: + raise PackageNotFoundError("cryptominisat") + return CryptoMiniSat() + elif solver == "LP": + from sat_lp import SatLP + return SatLP() + else: + raise ValueError("Solver '{}' is not available".format(solver)) diff --git a/src/sage/schemes/elliptic_curves/ell_number_field.py b/src/sage/schemes/elliptic_curves/ell_number_field.py index f4577fa0007..9867d3e3144 100644 --- a/src/sage/schemes/elliptic_curves/ell_number_field.py +++ b/src/sage/schemes/elliptic_curves/ell_number_field.py @@ -109,6 +109,8 @@ import gal_reps_number_field +from six import reraise as raise_ + class EllipticCurve_number_field(EllipticCurve_field): r""" Elliptic curve over a number field. @@ -893,7 +895,7 @@ def _reduce_model(self): (a1, a2, a3, a4, a6) = [ZK(a) for a in self.a_invariants()] except TypeError: import sys - raise TypeError, "_reduce_model() requires an integral model.", sys.exc_info()[2] + raise_(TypeError, "_reduce_model() requires an integral model.", sys.exc_info()[2]) # N.B. Must define s, r, t in the right order. if ZK.absolute_degree() == 1: diff --git a/src/sage/schemes/toric/divisor.py b/src/sage/schemes/toric/divisor.py index 1eed28a1390..92bfa8ba654 100644 --- a/src/sage/schemes/toric/divisor.py +++ b/src/sage/schemes/toric/divisor.py @@ -1168,16 +1168,16 @@ def is_ample(self): Example 6.1.3, 6.1.11, 6.1.17 of [CLS]_:: + sage: from itertools import product sage: fan = Fan(cones=[(0,1), (1,2), (2,3), (3,0)], - ... rays=[(-1,2), (0,1), (1,0), (0,-1)]) + ....: rays=[(-1,2), (0,1), (1,0), (0,-1)]) sage: F2 = ToricVariety(fan,'u1, u2, u3, u4') sage: def D(a,b): return a*F2.divisor(2) + b*F2.divisor(3) - ... - sage: [ (a,b) for a,b in CartesianProduct(range(-3,3),range(-3,3)) - ... if D(a,b).is_ample() ] + sage: [ (a,b) for a,b in product(range(-3,3), repeat=2) + ....: if D(a,b).is_ample() ] [(1, 1), (1, 2), (2, 1), (2, 2)] - sage: [ (a,b) for a,b in CartesianProduct(range(-3,3),range(-3,3)) - ... if D(a,b).is_nef() ] + sage: [ (a,b) for a,b in product(range(-3,3), repeat=2) + ....: if D(a,b).is_nef() ] [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] @@ -1246,16 +1246,16 @@ def is_nef(self): Example 6.1.3, 6.1.11, 6.1.17 of [CLS]_:: + sage: from itertools import product sage: fan = Fan(cones=[(0,1), (1,2), (2,3), (3,0)], - ... rays=[(-1,2), (0,1), (1,0), (0,-1)]) + ....: rays=[(-1,2), (0,1), (1,0), (0,-1)]) sage: F2 = ToricVariety(fan,'u1, u2, u3, u4') sage: def D(a,b): return a*F2.divisor(2) + b*F2.divisor(3) - ... - sage: [ (a,b) for a,b in CartesianProduct(range(-3,3),range(-3,3)) - ... if D(a,b).is_ample() ] + sage: [ (a,b) for a,b in product(range(-3,3), repeat=2) + ....: if D(a,b).is_ample() ] [(1, 1), (1, 2), (2, 1), (2, 2)] - sage: [ (a,b) for a,b in CartesianProduct(range(-3,3),range(-3,3)) - ... if D(a,b).is_nef() ] + sage: [ (a,b) for a,b in product(range(-3,3), repeat=2) + ....: if D(a,b).is_nef() ] [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] """ diff --git a/src/sage/schemes/toric/points.py b/src/sage/schemes/toric/points.py index ca902bf97eb..aedb22ac2db 100644 --- a/src/sage/schemes/toric/points.py +++ b/src/sage/schemes/toric/points.py @@ -35,10 +35,9 @@ #***************************************************************************** - +import itertools from copy import copy -from sage.combinat.cartesian_product import CartesianProduct from sage.misc.all import powerset, prod from sage.misc.cachefunc import cached_method from sage.rings.arith import gcd @@ -220,7 +219,7 @@ def _Chow_group_free(self): units = self.units() result = [] ker = self.rays().matrix().integer_kernel().matrix() - for phases in CartesianProduct(*([units] * ker.nrows())): + for phases in itertools.product(units, repeat=ker.nrows()): phases = tuple(prod(mu**exponent for mu, exponent in zip(phases, column)) for column in ker.columns()) result.append(phases) @@ -288,9 +287,10 @@ def rescalings(self): if len(tors) == 0: # optimization for smooth fans return free result = set(free) - for f, t in CartesianProduct(free, tors): - phases = tuple(x*y for x, y in zip(f, t)) - result.add(phases) + for f in free: + for t in tors: + phases = tuple(x*y for x, y in zip(f, t)) + result.add(phases) return tuple(sorted(result)) def orbit(self, point): @@ -390,7 +390,7 @@ def coordinate_iter(self): patch = copy(big_torus) for i in cone.ambient_ray_indices(): patch[i] = [zero] - for p in CartesianProduct(*patch): + for p in itertools.product(*patch): yield tuple(p) def __iter__(self): @@ -869,7 +869,7 @@ def solutions_serial(self, inhomogeneous_equations, log_range): OUTPUT: All solutions (as tuple of log inhomogeneous coordinates) in - the Cartesian product of the ranges. + the cartesian product of the ranges. EXAMPLES:: @@ -881,10 +881,10 @@ def solutions_serial(self, inhomogeneous_equations, log_range): sage: ffe.solutions_serial([s^2-1, s^6-s^2], [range(6)]) sage: list(_) - [[0], [3]] + [(0,), (3,)] """ - from sage.combinat.cartesian_product import CartesianProduct - for log_t in CartesianProduct(*log_range): + from itertools import product + for log_t in product(*log_range): t = self.ambient.exp(log_t) if all(poly(t) == 0 for poly in inhomogeneous_equations): yield log_t @@ -909,14 +909,14 @@ def solutions(self, inhomogeneous_equations, log_range): sage: ffe.solutions([s^2-1, s^6-s^2], [range(6)]) sage: sorted(_) - [[0], [3]] + [(0,), (3,)] """ # Do simple cases in one process (this includes most doctests) if len(log_range) <= 2: for log_t in self.solutions_serial(inhomogeneous_equations, log_range): yield log_t raise StopIteration - # Parallelize the outermost loop of the Cartesian product + # Parallelize the outermost loop of the cartesian product work = [([[r]] + log_range[1:],) for r in log_range[0]] from sage.parallel.decorate import Parallel parallel = Parallel() diff --git a/src/sage/sets/cartesian_product.py b/src/sage/sets/cartesian_product.py index a208f257250..0afc8910e2d 100644 --- a/src/sage/sets/cartesian_product.py +++ b/src/sage/sets/cartesian_product.py @@ -4,7 +4,6 @@ AUTHORS: - Nicolas Thiery (2010-03): initial version - """ #***************************************************************************** # Copyright (C) 2008 Nicolas Thiery , @@ -62,6 +61,8 @@ def __init__(self, sets, category, flatten=False): ``flatten`` is current ignored, and reserved for future use. + No other keyword arguments (``kwargs``) are accepted. + TESTS:: sage: from sage.sets.cartesian_product import CartesianProduct @@ -71,6 +72,10 @@ def __init__(self, sets, category, flatten=False): sage: C.an_element() (1/2, 1, 1) sage: TestSuite(C).run() + sage: cartesian_product([ZZ, ZZ], blub=None) + Traceback (most recent call last): + ... + TypeError: __init__() got an unexpected keyword argument 'blub' """ self._sets = tuple(sets) Parent.__init__(self, category=category) @@ -221,6 +226,45 @@ def _cartesian_product_of_elements(self, elements): assert len(elements) == len(self._sets) return self.element_class(self, elements) + def construction(self): + r""" + Return the construction functor and its arguments for this + cartesian product. + + OUTPUT: + + A pair whose first entry is a cartesian product functor and + its second entry is a list of the cartesian factors. + + EXAMPLES:: + + sage: cartesian_product([ZZ, QQ]).construction() + (The cartesian_product functorial construction, + (Integer Ring, Rational Field)) + """ + from sage.categories.cartesian_product import cartesian_product + return cartesian_product, self.cartesian_factors() + + def _coerce_map_from_(self, S): + r""" + Return ``True`` if ``S`` coerces into this cartesian product. + + TESTS:: + + sage: Z = cartesian_product([ZZ]) + sage: Q = cartesian_product([QQ]) + sage: Z.has_coerce_map_from(Q) # indirect doctest + False + sage: Q.has_coerce_map_from(Z) # indirect doctest + True + """ + if isinstance(S, CartesianProduct): + S_factors = S.cartesian_factors() + R_factors = self.cartesian_factors() + if len(S_factors) == len(R_factors): + if all(r.has_coerce_map_from(s) for r, s in zip(R_factors, S_factors)): + return True + an_element = Sets.CartesianProducts.ParentMethods.an_element class Element(ElementWrapper): @@ -276,3 +320,17 @@ def __iter__(self): 1 """ return iter(self.value) + + def cartesian_factors(self): + r""" + Return the tuple of elements that compose this element. + + EXAMPLES:: + + sage: A = cartesian_product([ZZ, RR]) + sage: A((1, 1.23)).cartesian_factors() + (1, 1.23000000000000) + sage: type(_) + + """ + return self.value diff --git a/src/sage/sets/family.py b/src/sage/sets/family.py index 90f3a4f437b..b76b81f2ddd 100644 --- a/src/sage/sets/family.py +++ b/src/sage/sets/family.py @@ -1009,7 +1009,7 @@ def cardinality(self): Check that :trac:`15195` is fixed:: - sage: C = CartesianProduct(PositiveIntegers(), [1,2,3]) + sage: C = cartesian_product([PositiveIntegers(), [1,2,3]]) sage: C.cardinality() +Infinity sage: F = Family(C, lambda x: x) diff --git a/src/sage/sets/finite_set_maps.py b/src/sage/sets/finite_set_maps.py index daec1e077fd..a79e5e14cdb 100644 --- a/src/sage/sets/finite_set_maps.py +++ b/src/sage/sets/finite_set_maps.py @@ -18,6 +18,8 @@ #***************************************************************************** +import itertools + from sage.structure.parent import Parent from sage.rings.integer import Integer from sage.structure.unique_representation import UniqueRepresentation @@ -25,7 +27,6 @@ from sage.categories.monoids import Monoids from sage.categories.enumerated_sets import EnumeratedSets from sage.sets.finite_enumerated_set import FiniteEnumeratedSet -from sage.combinat.cartesian_product import CartesianProduct from sage.sets.integer_range import IntegerRange from sage.sets.finite_set_map_cy import ( FiniteSetMap_MN, FiniteSetMap_Set, @@ -338,7 +339,7 @@ def __iter__(self): sage: FiniteSetMaps(1,1).list() [[0]] """ - for v in CartesianProduct(*([range(self._n)]*self._m)): + for v in itertools.product(range(self._n), repeat=self._m): yield self._from_list_(v) def _from_list_(self, v): diff --git a/src/sage/sets/set.py b/src/sage/sets/set.py index 83d8bdfd5e0..12ef7a8667e 100644 --- a/src/sage/sets/set.py +++ b/src/sage/sets/set.py @@ -36,14 +36,19 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.structure.element import Element -from sage.structure.parent import Parent, Set_generic + from sage.misc.latex import latex -import sage.rings.infinity +from sage.misc.prandom import choice from sage.misc.misc import is_iterator + +from sage.structure.element import Element +from sage.structure.parent import Parent, Set_generic + from sage.categories.sets_cat import Sets from sage.categories.enumerated_sets import EnumeratedSets +import sage.rings.infinity + def Set(X=frozenset()): r""" Create the underlying set of ``X``. @@ -224,7 +229,12 @@ def __init__(self, X): if isinstance(X, (int,long)) or is_Integer(X): # The coercion model will try to call Set_object(0) raise ValueError('underlying object cannot be an integer') - Parent.__init__(self, category=Sets()) + + category = Sets() + if X in Sets().Finite() or isinstance(X, (tuple,list,set,frozenset)): + category = Sets().Finite() + + Parent.__init__(self, category=category) self.__object = X def __hash__(self): @@ -685,6 +695,21 @@ def __init__(self, X): """ Set_object.__init__(self, X) + def random_element(self): + r""" + Return a random element in this set. + + EXAMPLES:: + + sage: Set([1,2,3]).random_element() # random + 2 + """ + try: + return self.object().random_element() + except AttributeError: + # TODO: this very slow! + return choice(self.list()) + def is_finite(self): r""" Return ``True`` as this is a finite set. diff --git a/src/sage/sets/set_from_iterator.py b/src/sage/sets/set_from_iterator.py index 3ab4303cf7f..3546b11f6a3 100644 --- a/src/sage/sets/set_from_iterator.py +++ b/src/sage/sets/set_from_iterator.py @@ -180,6 +180,24 @@ def __init__(self, f, args=None, kwds=None, name=None, category=None, cache=Fals *getattr(self, '_args', ()), **getattr(self, '_kwds', {})))) + def __hash__(self): + r""" + A simple hash using the first elements of the set. + + EXAMPLES:: + + sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator + sage: E = EnumeratedSetFromIterator(xsrange, (1,200)) + sage: hash(E) + 4600916458883504074 # 64-bit + -2063607862 # 32-bit + """ + try: + return hash(self._cache[:13]) + except AttributeError: + from itertools import islice + return hash(tuple(islice(self, 13))) + def __reduce__(self): r""" Support for pickle. diff --git a/src/sage/structure/category_object.pyx b/src/sage/structure/category_object.pyx index be78efcf49a..405ff0d9d4d 100644 --- a/src/sage/structure/category_object.pyx +++ b/src/sage/structure/category_object.pyx @@ -235,7 +235,8 @@ cdef class CategoryObject(sage_object.SageObject): sage: ZZ.categories() [Join of Category of euclidean domains - and Category of infinite enumerated sets, + and Category of infinite enumerated sets + and Category of metric spaces, Category of euclidean domains, Category of principal ideal domains, Category of unique factorization domains, diff --git a/src/sage/structure/coerce_actions.pyx b/src/sage/structure/coerce_actions.pyx index 30da0d9a117..18f19a13d95 100644 --- a/src/sage/structure/coerce_actions.pyx +++ b/src/sage/structure/coerce_actions.pyx @@ -18,14 +18,13 @@ import operator include "sage/ext/interrupt.pxi" from cpython.int cimport * from cpython.number cimport * -from sage.structure.element cimport parent_c +from sage.structure.element cimport parent_c, coercion_model from sage.categories.action import InverseAction, PrecomposedAction from coerce_exceptions import CoercionException cdef _record_exception(): - from element import get_coercion_model - get_coercion_model()._record_exception() + coercion_model._record_exception() cdef inline an_element(R): if isinstance(R, Parent): diff --git a/src/sage/structure/element.pxd b/src/sage/structure/element.pxd index c2317e745ae..66e6047cb52 100644 --- a/src/sage/structure/element.pxd +++ b/src/sage/structure/element.pxd @@ -148,5 +148,6 @@ cdef class CoercionModel: cpdef canonical_coercion(self, x, y) cpdef bin_op(self, x, y, op) +cdef CoercionModel coercion_model cdef generic_power_c(a, nn, one) diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 5b71a088b05..a76e61926ec 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -128,6 +128,8 @@ underscores). # by any element. Derived class must call __init__ ################################################################## +from libc.limits cimport LONG_MAX, LONG_MIN + include "sage/ext/python.pxi" from sage.ext.stdsage cimport * @@ -524,9 +526,6 @@ cdef class Element(SageObject): pass return res - def __hash__(self): - return hash(str(self)) - def _im_gens_(self, codomain, im_gens): """ Return the image of ``self`` in codomain under the map that sends @@ -962,6 +961,19 @@ cdef class Element(SageObject): return 1 raise + def _cache_key(self): + """ + Provide a hashable key for an element if it is not hashable + + EXAMPLES:: + + sage: a=sage.structure.element.Element(ZZ) + sage: a._cache_key() + (Integer Ring, 'Generic element of a structure') + """ + + return(self.parent(),str(self)) + cdef _richcmp(self, other, int op): """ Compare ``self`` and ``other`` using the coercion framework, @@ -3279,10 +3291,26 @@ cdef class InfinityElement(RingElement): return ZZ(0) cdef class PlusInfinityElement(InfinityElement): - pass + def __hash__(self): + r""" + TESTS:: + + sage: hash(+infinity) + 9223372036854775807 # 64-bit + 2147483647 # 32-bit + """ + return LONG_MAX cdef class MinusInfinityElement(InfinityElement): - pass + def __hash__(self): + r""" + TESTS:: + + sage: hash(-infinity) + -9223372036854775808 # 64-bit + -2147483648 # 32-bit + """ + return LONG_MIN ################################################################################# diff --git a/src/sage/structure/indexed_generators.py b/src/sage/structure/indexed_generators.py index 2f445492466..5a079f3a7e9 100644 --- a/src/sage/structure/indexed_generators.py +++ b/src/sage/structure/indexed_generators.py @@ -74,6 +74,9 @@ class IndexedGenerators(object): - ``generator_cmp`` -- a comparison function (default: ``cmp``), to use for sorting elements in the output of elements + - ``string_quotes`` -- bool (default: ``True``), if ``True`` then + display string indices with quotes + .. NOTE:: These print options may also be accessed and modified using the @@ -130,7 +133,8 @@ def __init__(self, indices, prefix="x", **kwds): 'scalar_mult': "*", 'latex_scalar_mult': None, 'tensor_symbol': None, - 'generator_cmp': cmp} + 'generator_cmp': cmp, + 'string_quotes': True} # 'bracket': its default value here is None, meaning that # the value of self._repr_option_bracket is used; the default # value of that attribute is True -- see immediately before @@ -186,8 +190,9 @@ def print_options(self, **kwds): - ``latex_scalar_mult`` - ``tensor_symbol`` - ``generator_cmp`` + - ``string_quotes`` - See the documentation for :class:`CombinatorialFreeModule` for + See the documentation for :class:`IndexedGenerators` for descriptions of the effects of setting each of these options. OUTPUT: if the user provides any input, set the appropriate @@ -209,7 +214,8 @@ def print_options(self, **kwds): [('bracket', '('), ('generator_cmp', ), ('latex_bracket', False), ('latex_prefix', None), ('latex_scalar_mult', None), ('prefix', 'x'), - ('scalar_mult', '*'), ('tensor_symbol', None)] + ('scalar_mult', '*'), ('string_quotes', True), + ('tensor_symbol', None)] sage: F.print_options(bracket='[') # reset """ # don't just use kwds.get(...) because I want to distinguish @@ -220,7 +226,7 @@ def print_options(self, **kwds): # TODO: make this into a set and put it in a global variable? if option in ['prefix', 'latex_prefix', 'bracket', 'latex_bracket', 'scalar_mult', 'latex_scalar_mult', 'tensor_symbol', - 'generator_cmp' + 'generator_cmp', 'string_quotes' ]: self._print_options[option] = kwds[option] else: @@ -262,6 +268,9 @@ def _repr_generator(self, m): sage: e = F.basis() sage: e['a'] + 2*e['b'] # indirect doctest F['a'] + 2*F['b'] + sage: F.print_options(string_quotes=False) + sage: e['a'] + 2*e['b'] + F[a] + 2*F[b] sage: QS3 = CombinatorialFreeModule(QQ, Permutations(3), prefix="") sage: original_print_options = QS3.print_options() @@ -309,6 +318,9 @@ def _repr_generator(self, m): else: left = bracket right = bracket + quotes = self._print_options.get('string_quotes', True) + if not quotes and isinstance(m, str): + return self.prefix() + left + m + right return self.prefix() + left + repr(m) + right # mind the (m), to accept a tuple for m def _ascii_art_generator(self, m): diff --git a/src/sage/structure/parent.pyx b/src/sage/structure/parent.pyx index 1936d2b69b9..90ff244043c 100644 --- a/src/sage/structure/parent.pyx +++ b/src/sage/structure/parent.pyx @@ -95,7 +95,7 @@ This came up in some subtle bug once:: """ from types import MethodType -from element cimport parent_c +from .element cimport parent_c, coercion_model cimport sage.categories.morphism as morphism cimport sage.categories.map as map from sage.structure.debug_options import debug @@ -118,8 +118,7 @@ dummy_attribute_error = AttributeError(dummy_error_message) cdef _record_exception(): - from element import get_coercion_model - get_coercion_model()._record_exception() + coercion_model._record_exception() cdef object _Integer cdef bint is_Integer(x): @@ -805,6 +804,7 @@ cdef class Parent(category_object.CategoryObject): running ._test_eq() . . . pass running ._test_euclidean_degree() . . . pass running ._test_gcd_vs_xgcd() . . . pass + running ._test_metric() . . . pass running ._test_not_implemented_methods() . . . pass running ._test_one() . . . pass running ._test_pickling() . . . pass @@ -875,6 +875,7 @@ cdef class Parent(category_object.CategoryObject): _test_eq _test_euclidean_degree _test_gcd_vs_xgcd + _test_metric _test_not_implemented_methods _test_one _test_pickling @@ -1117,7 +1118,7 @@ cdef class Parent(category_object.CategoryObject): it is a ring, from the point of view of categories:: sage: MS.category() - Category of infinite algebras over quotient fields + Category of infinite algebras over (quotient fields and metric spaces) sage: MS in Rings() True @@ -1808,7 +1809,6 @@ cdef class Parent(category_object.CategoryObject): if embedding is not None: self.register_embedding(embedding) - def _unset_coercions_used(self): r""" Pretend that this parent has never been interrogated by the coercion @@ -1820,8 +1820,7 @@ cdef class Parent(category_object.CategoryObject): For internal use only! """ self._coercions_used = False - import sage.structure.element - sage.structure.element.get_coercion_model().reset_cache() + coercion_model.reset_cache() def _unset_embedding(self): r""" @@ -2082,10 +2081,10 @@ cdef class Parent(category_object.CategoryObject): [ 0 1] [-272118 0] - sage: a.matrix() * b + sage: a.matrix() * b.matrix() [-272118 0] [ 0 -462] - sage: a * b.matrix() + sage: a.matrix() * b.matrix() [-272118 0] [ 0 -462] """ diff --git a/src/sage/symbolic/function.pyx b/src/sage/symbolic/function.pyx index ba2a3b64a26..08d51cfe37d 100644 --- a/src/sage/symbolic/function.pyx +++ b/src/sage/symbolic/function.pyx @@ -22,7 +22,7 @@ from expression cimport new_Expression_from_GEx, Expression from ring import SR from sage.structure.coerce cimport py_scalar_to_element, is_numpy_type -from sage.structure.element import get_coercion_model +from sage.structure.element cimport coercion_model # we keep a database of symbolic functions initialized in a session # this also makes the .operator() method of symbolic expressions work @@ -255,7 +255,7 @@ cdef class Function(SageObject): evalf = self._evalf_ # catch AttributeError early if any(self._is_numerical(x) for x in args): if not any(isinstance(x, Expression) for x in args): - p = get_coercion_model().common_parent(*args) + p = coercion_model.common_parent(*args) return evalf(*args, parent=p) except Exception: pass diff --git a/src/sage/symbolic/random_tests.py b/src/sage/symbolic/random_tests.py index a2164f69bda..6b6e5944dbe 100644 --- a/src/sage/symbolic/random_tests.py +++ b/src/sage/symbolic/random_tests.py @@ -333,42 +333,43 @@ def assert_strict_weak_order(a,b,c, cmp_func): sage: x = [SR(unsigned_infinity), SR(oo), -SR(oo)] sage: cmp = matrix(3,3) - sage: indices = list(CartesianProduct(range(0,3),range(0,3))) - sage: for i,j in CartesianProduct(range(0,3),range(0,3)): - ... cmp[i,j] = x[i].__cmp__(x[j]) + sage: for i in range(3): + ....: for j in range(3): + ....: cmp[i,j] = x[i].__cmp__(x[j]) sage: cmp [ 0 -1 -1] [ 1 0 -1] [ 1 1 0] """ from sage.matrix.constructor import matrix - from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.permutation import Permutations x = (a,b,c) cmp = matrix(3,3) - indices = list(CartesianProduct(range(0,3),range(0,3))) - for i,j in indices: - cmp[i,j] = (cmp_func(x[i], x[j]) == 1) # or -1, doesn't matter + for i in range(3): + for j in range(3): + cmp[i,j] = (cmp_func(x[i], x[j]) == 1) # or -1, doesn't matter msg = 'The binary relation failed to be a strict weak order on the elements\n' msg += ' a = '+str(a)+'\n' msg += ' b = '+str(b)+'\n' msg += ' c = '+str(c)+'\n' msg += str(cmp) - for i in range(0,3): # irreflexivity + for i in range(3): + # irreflexivity if cmp[i,i]: raise ValueError(msg) - for i,j in indices: # asymmetric - if i==j: continue - #if x[i] == x[j]: continue - if cmp[i,j] and cmp[j,i]: raise ValueError(msg) + # asymmetric + for j in range(i): + if cmp[i,j] and cmp[j,i]: raise ValueError(msg) - for i,j,k in Permutations([0,1,2]): # transitivity + def incomparable(i,j): + return not (cmp[i,j] or cmp[j,i]) + + for i,j,k in Permutations([0,1,2]): + # transitivity if cmp[i,j] and cmp[j,k] and not cmp[i,k]: raise ValueError(msg) - def incomparable(i,j): - return (not cmp[i,j]) and (not cmp[j,i]) - for i,j,k in Permutations([0,1,2]): # transitivity of equivalence + # transitivity of equivalence if incomparable(i,j) and incomparable(j,k) and not incomparable(i,k): raise ValueError(msg) def test_symbolic_expression_order(repetitions=100): diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index bdf639a1995..a7c4bb57f4f 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -251,6 +251,34 @@ cdef class SymbolicRing(CommutativeRing): sage: bool(si == CC.0) True + Polynomial ring element factorizations:: + + sage: R. = QQ[] + sage: SR(factor(5*x^2 - 5)) + 5*(x + 1)*(x - 1) + sage: R. = QQ[] + sage: SR(factor(x^2 - y^2)) + (x + y)*(x - y) + sage: R. = QQ[] + sage: SR(factor(x^2*y^3 + x^2*y^2*z - x*y^3 - x*y^2*z - 2*x*y*z - 2*x*z^2 + 2*y*z + 2*z^2)) + (x*y^2 - 2*z)*(x - 1)*(y + z) + + Asymptotic expansions:: + + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^QQ * log(y)^ZZ', coefficient_ring=ZZ) + doctest:...: FutureWarning: This class/method/function is + marked as experimental. + ... + See http://trac.sagemath.org/17601 for details. + sage: s = SR(3*x^5 * log(y) + 4*y^(3/7) + O(x*log(y))); s + 3*x^5*log(y) + 4*y^(3/7) + Order(x*log(y)) + sage: s.operator(), s.operands() + (, + [3*x^5*log(y), 4*y^(3/7), Order(x*log(y))]) + sage: t = s.operands()[0]; t + 3*x^5*log(y) + sage: t.operator(), t.operands() + (, [x^5, log(y), 3]) """ cdef GEx exp @@ -271,6 +299,7 @@ cdef class SymbolicRing(CommutativeRing): from sage.rings.infinity import (infinity, minus_infinity, unsigned_infinity) + from sage.structure.factorization import Factorization if isinstance(x, (Integer, RealNumber, float, long, complex)): GEx_construct_pyobject(exp, x) @@ -284,6 +313,9 @@ cdef class SymbolicRing(CommutativeRing): return new_Expression_from_GEx(self, g_UnsignedInfinity) elif isinstance(x, (RingElement, Matrix)): GEx_construct_pyobject(exp, x) + elif isinstance(x, Factorization): + from sage.misc.all import prod + return prod([SR(p)**e for p,e in x], SR(x.unit())) else: raise TypeError diff --git a/src/sage/tensor/coordinate_patch.py b/src/sage/tensor/coordinate_patch.py index 88df32a42d7..908e1f9d972 100644 --- a/src/sage/tensor/coordinate_patch.py +++ b/src/sage/tensor/coordinate_patch.py @@ -81,20 +81,18 @@ def __init__(self, coordinates, metric = None): INPUT: - ``coordinates`` -- a set of symbolic variables that serve - as coordinates on this space. + as coordinates on this space. - - ``metric`` (default: None) -- a metric tensor on this - coordinate patch. Providing anything other than ``None`` - is currently not defined. + - ``metric`` (default: ``None``) -- a metric tensor on this + coordinate patch. Providing anything other than ``None`` + is currently not defined. EXAMPLES:: sage: x, y, z = var('x, y, z') sage: S = CoordinatePatch((x, y, z)); S Open subset of R^3 with coordinates x, y, z - """ - from sage.symbolic.ring import is_SymbolicVariable if not all(is_SymbolicVariable(c) for c in coordinates): @@ -107,8 +105,6 @@ def __init__(self, coordinates, metric = None): if metric is not None: raise NotImplementedError("Metric geometry not supported yet.") - - def __eq__(self, other): """ Return equality if and only if other has the same coordinates diff --git a/src/sage/tensor/differential_form_element.py b/src/sage/tensor/differential_form_element.py index e109d133ec4..87abcc47173 100644 --- a/src/sage/tensor/differential_form_element.py +++ b/src/sage/tensor/differential_form_element.py @@ -44,7 +44,7 @@ def sort_subscript(subscript): INPUT: - ``subscript`` -- a subscript, i.e. a range of not necessarily - distinct integers + distinct integers OUTPUT: @@ -396,16 +396,14 @@ def __init__(self, parent, degree, fun = None): if degree == 0 and fun is not None: self.__setitem__([], fun) - def __getitem__(self, subscript): r""" Return a given component of the differential form. INPUT: - - ``subscript``: subscript of the component. Must be an integer - or a list of integers. - + - ``subscript`` -- subscript of the component. Must be an integer + or a list of integers. EXAMPLES:: @@ -426,7 +424,6 @@ def __getitem__(self, subscript): sage: df[2] 0 """ - if isinstance(subscript, (Integer, int)): subscript = (subscript, ) else: @@ -447,14 +444,14 @@ def __getitem__(self, subscript): else: return 0 - def __setitem__(self, subscript, fun): r""" Modify a given component of the differential form. INPUT: - - ``subscript``: subscript of the component. Must be an integer or a list of integers. + - ``subscript`` -- subscript of the component. Must be an integer + or a list of integers. EXAMPLES:: diff --git a/src/sage/tensor/differential_forms.py b/src/sage/tensor/differential_forms.py index 6f367db317b..d4d2332b19e 100644 --- a/src/sage/tensor/differential_forms.py +++ b/src/sage/tensor/differential_forms.py @@ -76,11 +76,13 @@ class DifferentialForms(Algebra): def __init__(self, coordinate_patch = None): """ Construct the algebra of differential forms on a given coordinate patch. + See ``DifferentialForms`` for details. INPUT: - ``coordinate_patch`` -- Coordinate patch where the algebra lives. + If no coordinate patch is given, a default coordinate patch with coordinates (x, y, z) is used. @@ -91,9 +93,7 @@ def __init__(self, coordinate_patch = None): Open subset of R^2 with coordinates p, q sage: F = DifferentialForms(U); F Algebra of differential forms in the variables p, q - """ - from sage.categories.graded_algebras_with_basis \ import GradedAlgebrasWithBasis from sage.structure.parent_gens import ParentWithGens diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 8fbe8419514..15a1fea98e2 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -243,7 +243,8 @@ class :class:`~sage.modules.free_module.FreeModule_generic` sage: M.category() Category of modules over Integer Ring sage: N.category() - Category of modules with basis over (euclidean domains and infinite enumerated sets) + Category of finite dimensional modules with basis over + (euclidean domains and infinite enumerated sets and metric spaces) In other words, the module created by ``FreeModule`` is actually `\ZZ^3`, while, in the absence of any distinguished basis, no *canonical* isomorphism @@ -367,7 +368,8 @@ class :class:`~sage.modules.free_module.FreeModule_generic` sage: V = VectorSpace(QQ,3) ; V Vector space of dimension 3 over Rational Field sage: V.category() - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis + over (quotient fields and metric spaces) sage: V is QQ^3 True sage: V.basis() diff --git a/src/sage/tests/book_schilling_zabrocki_kschur_primer.py b/src/sage/tests/book_schilling_zabrocki_kschur_primer.py index 116e4a37784..15581a2cd6e 100644 --- a/src/sage/tests/book_schilling_zabrocki_kschur_primer.py +++ b/src/sage/tests/book_schilling_zabrocki_kschur_primer.py @@ -475,7 +475,7 @@ Sage example in ./kschurnotes/notes-mike-anne.tex, line 2810:: sage: c = Partition([3,2,1]).to_core(3) - sage: for p in f.support(): + sage: for p in sorted(f.support()): # Sorted for consistant doctest ordering ....: print p, SkewPartition([p.to_core(3).to_partition(),c.to_partition()]) ....: [3, 1, 1, 1, 1] [[5, 2, 1, 1, 1], [5, 2, 1]] diff --git a/src/sage/tests/french_book/domaines_doctest.py b/src/sage/tests/french_book/domaines_doctest.py index f8a15b85660..4628ffd4740 100644 --- a/src/sage/tests/french_book/domaines_doctest.py +++ b/src/sage/tests/french_book/domaines_doctest.py @@ -174,7 +174,7 @@ Sage example in ./domaines.tex, line 415:: sage: QQ.category() - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces Sage example in ./domaines.tex, line 421:: diff --git a/src/sage/tests/french_book/nonlinear_doctest.py b/src/sage/tests/french_book/nonlinear_doctest.py index cb175085117..e51096a073e 100644 --- a/src/sage/tests/french_book/nonlinear_doctest.py +++ b/src/sage/tests/french_book/nonlinear_doctest.py @@ -37,10 +37,11 @@ Sage example in ./nonlinear.tex, line 231:: + sage: from itertools import product sage: def build_complex_roots(degree): ....: R. = PolynomialRing(CDF, 'x') ....: v = [] - ....: for c in CartesianProduct(*[[-1, 1]] * (degree + 1)): + ....: for c in product([-1, 1], repeat=degree+1): ....: v.extend(R(c).roots(multiplicities=False)) ....: return v sage: data = build_complex_roots(12) # long time diff --git a/src/sage/version.py b/src/sage/version.py index da4209e58b1..6de652178fb 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,4 +1,4 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '6.9' -date = '2015-10-10' +version = '6.10.beta2' +date = '2015-10-28' diff --git a/src/sage_setup/autogen/pari/parser.py b/src/sage_setup/autogen/pari/parser.py index f4d8a04d823..639673bd024 100644 --- a/src/sage_setup/autogen/pari/parser.py +++ b/src/sage_setup/autogen/pari/parser.py @@ -47,7 +47,7 @@ def pari_share(): return os.path.join(SAGE_LOCAL, "share", "pari") paren_re = re.compile(r"[(](.*)[)]") -argname_re = re.compile(r"[ {]*([A-Za-z0-9_]+)") +argname_re = re.compile(r"[ {]*([A-Za-z_][A-Za-z0-9_]*)") def read_pari_desc(): """ diff --git a/src/setup.py b/src/setup.py index 45694c063ec..cf0013689f4 100755 --- a/src/setup.py +++ b/src/setup.py @@ -324,6 +324,7 @@ def plural(n,noun): ######################################################################## from distutils.command.build_ext import build_ext +from distutils.command.install import install from distutils.dep_util import newer_group from distutils import log @@ -633,6 +634,22 @@ def run_cythonize(): print('Finished cleaning, time: %.2f seconds.' % (time.time() - t)) +######################################################### +### Install also Jupyter kernel spec +######################################################### + +# We cannot just add the installation of the kernel spec to data_files +# since the file is generated, not copied. +class sage_install(install): + def run(self): + install.run(self) + self.install_kernel_spec() + + def install_kernel_spec(self): + from sage.repl.ipython_kernel.install import SageKernelSpec + SageKernelSpec.update() + + ######################################################### ### Distutils ######################################################### @@ -647,6 +664,5 @@ def run_cythonize(): packages = python_packages, data_files = python_data_files, scripts = [], - cmdclass = { 'build_ext': sage_build_ext }, + cmdclass = dict(build_ext=sage_build_ext, install=sage_install), ext_modules = ext_modules) - From ee778c87c15f75c5243ff5fd0da38efa85129335 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Sun, 1 Nov 2015 16:14:05 -0500 Subject: [PATCH 005/170] Implements a method to compute isometries between positive definite quadratic forms. --- src/sage/quadratic_forms/quadratic_form.py | 2 + .../quadratic_form__automorphisms.py | 2 +- .../quadratic_form__equivalence_testing.py | 238 +++++++++++++++++- 3 files changed, 240 insertions(+), 2 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form.py b/src/sage/quadratic_forms/quadratic_form.py index 1e39e1c6fd1..f3ff37fac10 100644 --- a/src/sage/quadratic_forms/quadratic_form.py +++ b/src/sage/quadratic_forms/quadratic_form.py @@ -375,6 +375,8 @@ class QuadraticForm(SageObject): is_locally_equivalent_to, \ has_equivalent_Jordan_decomposition_at_prime, \ is_rationally_isometric + isometry + def __init__(self, R, n=None, entries=None, unsafe_initialization=False, number_of_automorphisms=None, determinant=None): """ diff --git a/src/sage/quadratic_forms/quadratic_form__automorphisms.py b/src/sage/quadratic_forms/quadratic_form__automorphisms.py index 28999a4a140..b5ac0cb6bdc 100644 --- a/src/sage/quadratic_forms/quadratic_form__automorphisms.py +++ b/src/sage/quadratic_forms/quadratic_form__automorphisms.py @@ -198,7 +198,7 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): len_bound_pari = 2*(len_bound - 1) # Call PARI's qfminim() - parilist = self._pari_().qfminim(len_bound_pari)[2].Vec() + parilist = self._pari_().qfminim(len_bound_pari, flag=2)[2].Vec() # List of lengths parilens = pari(r"(M,v) -> vector(#v, i, (v[i]~ * M * v[i])\2)")(self, parilist) diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index a27837f6b3a..0fa2a89bf0e 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -5,7 +5,6 @@ - Anna Haensch (2014-12-01): added test for rational isometry """ - from sage.rings.arith import hilbert_symbol, prime_divisors, is_prime, valuation, GCD, legendre_symbol from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ @@ -328,6 +327,7 @@ def is_rationally_isometric(self, other): INPUT: +<<<<<<< HEAD a quadratic form OUTPUT: @@ -487,3 +487,239 @@ def is_rationally_isometric(self, other): return False return True + +def isometry(self, other): + """ + Given two equivalent quadratic forms, computes an isometry from one to the other. + Note: Currently only works for positive definite quadratic forms. This is because isometry() relies on + the method short_vector_list_up_to_length() which is only defined for positive definite forms. + + INPUT: + self: a positive definite QuadraticForm + other: a positive definite QuadraticForm + NOTE: This algorithm may not terminate if self is not equivalent to other! + + OUTPUT: + a matrix T representing the isometry transformation, such that if QM is the gram matrix of Q and + FM is the gram matrix of F, then QM == T.transpose() * FM * T yields True. + + EXAMPLES:: + sage: Q = DiagonalQuadraticForm(QQ, [1, 1, 2]) + sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) + sage: T = Q.isometry(F) + sage: T + [ 1/2 1/2 0] + [-1/2 1/2 0] + [ 0 0 1] + + sage: Q.Gram_matrix() == T.transpose() * F.Gram_matrix() * T + True + + sage: T = F.isometry(Q) + sage: T + [ 1 -1 0] + [ 1 1 0] + [ 0 0 1] + + sage: F.Gram_matrix() == T.transpose() * Q.Gram_matrix() * T + True + + sage: A = DiagonalQuadraticForm(QQ, [1,5]) + sage: B = QuadraticForm(QQ, 2, [1, 12, 81]) + sage: T = A.isometry(B) + sage: T + [ 1 -2] + [ 0 1/3] + + sage: A.Gram_matrix() == T.transpose() * B.Gram_matrix() * T + True + + sage: C = DiagonalQuadraticForm(QQ, [1, 5, 9]) + sage: D = DiagonalQuadraticForm(QQ, [6, 30, 1]) + sage: T = D.isometry(C) + sage: T + [ 1 -5 0] + [ 1 1 0] + [ 0 0 1/3] + + sage: D.Gram_matrix() == T.transpose() * C.Gram_matrix() * T + True + + sage: E = DiagonalQuadraticForm(QQ, [1, 1]) + sage: F = QuadraticForm(QQ, 2, [17, 94, 130]) + sage: T = E.isometry(F) + sage: T + [189/17 -43/17] + [ -4 1] + + sage: E.Gram_matrix() == T.transpose() * F.Gram_matrix() * T + True + """ + from sage.matrix.constructor import Matrix + ##Define a method that determines whether or not a matrix is diagonal. + def is_diagonal(matrix): + dim = matrix.dimensions()[0] + for i in range(dim): + for j in range(dim): + if (i != j) and (matrix[i][j] != 0): + return False + return True + + Q = copy.deepcopy(self) + F = copy.deepcopy(other) + + if not Q.is_positive_definite() or not F.is_positive_definite(): + raise TypeError("Both Quadratic Forms must be positive definite.") + + if not is_QuadraticForm(other): + raise TypeError("First argument must be a Quadratic Form.") + + n = Q.dim() + + ##If either form is not diagonal, diagonalize it. + q_diagonal_transform = f_diagonal_transform = Matrix.identity(n) + if not is_diagonal(Q.Gram_matrix()): + Q, q_diagonal_transform = Q.rational_diagonal_form(True) + if not is_diagonal(F.Gram_matrix()): + F, f_diagonal_transform = F.rational_diagonal_form(True) + + #This is the method that does all the work to compute the isometry. + transform = diagonal_isometry(Q,F) + + return f_diagonal_transform * transform * q_diagonal_transform.inverse() + + +##Helper method for isometry. +def diagonal_isometry(V, W): +##Computes an isometry between diagonal equivalent forms V and W. + from sage.functions.other import sqrt + from quadratic_form import DiagonalQuadraticForm + from sage.matrix.constructor import matrix, column_matrix, Matrix + from sage.modules.free_module_element import vector + #----HELPER METHODS-----# + + def diagonal_qf_pull_off_n_terms(gram_matrix, n, ring): + ##Returns the diagonal quadratic form corresponding to the given gram matrix + ##with the first n terms removed. + diagonal = gram_matrix.diagonal(); + return DiagonalQuadraticForm(ring, diagonal[n:]) + + def compute_gram_matrix_from_basis(Q, basis): + ##Computes the gram matrix by treating the columns of basis as basis vectors. + n = Q.dim() + rows = []; + for i in range(n): + rows.append([Q.bilinear_map(basis.column(i), basis.column(j)) for j in range(n)]) + return Matrix(rows) + + def modify_basis(basis, v, i): + ##Modifies basis to include v (a linear combination of the basis vectors) at column i in the basis. + b = copy.deepcopy(basis) + column = vector(QQ, b.dimensions()[0]) + for j in range(len(v)): + column += b.column(i + j) * v[j] + b.set_column(i, column) + return b + + def gram_schmidt(matrix, fixed_vector_index, gram_matrix): + ##Orthogonalizes the columns of matrix against a fixed column vector. + n = matrix.dimensions()[0] + vectors = [0] * n + fixed_vec = matrix.column(fixed_vector_index) + vectors[fixed_vector_index] = fixed_vec + + for i in range(fixed_vector_index): + vectors[i] = matrix.column(i) + for i in range(fixed_vector_index + 1, n): + vectors[i] = matrix.column(i) - (gram_matrix[i, fixed_vector_index] / gram_matrix[fixed_vector_index, fixed_vector_index]) * fixed_vec + + return column_matrix(vectors) + + def vectors_of_common_length(Q, F): + i = max(Q.Gram_matrix()[0][0], F.Gram_matrix()[0][0]) + n = 100 + while True: + q_vecs, f_vecs = Q.short_vector_list_up_to_length(n), F.short_vector_list_up_to_length(n) + while i < n: + ##Find a value represented by both forms + if q_vecs[i] and f_vecs[i]: + return (q_vecs[i][0], f_vecs[i][0]); + i += 1 + n += 100 + + def vectors_of_common_length_dev(Q, F, qb, fb, iteration): + ##Returns a pair of vectors v1 and v2, such that Q(v1) = F(v2), + ##and qb and fb are bases when their columns at index iteration are replaced with + ##v1 and v2 respectively. (i.e. qb and fb contain no all-zero rows) + def zero_row(matrix, col, i): + ##Sets the ith column of matrix to col and returns true if matrix has an all zero row. + m = modify_basis(matrix, col, i) + rows, cols = m.dimensions() + z = [0] * cols + for i in range(rows): + if m.row(i).list() == z: + return True + return False + + i = max(Q.Gram_matrix()[0][0], F.Gram_matrix()[0][0]) + n = 100 + #NOTE: If the given forms are not equivalent, this will be an infinite loop. + while True: + q_vecs, f_vecs = Q.short_vector_list_up_to_length(n), F.short_vector_list_up_to_length(n) + while i < n: + ##Find a value that is represented by both forms + if q_vecs[i] and f_vecs[i]: + ##Find a pair of vectors such that if each becomes a column of qb and fb respectively, + ##neither matrix will have an all-zero row. + for j in range(len(q_vecs[i])): + for k in range(len(f_vecs[i])): + if not zero_row(qb, q_vecs[i][j], iteration) and not zero_row(fb, f_vecs[i][k], iteration): + return (q_vecs[i][j], f_vecs[i][k]) + i += 1 + n += 100 + + #--- END OF HELPER METHODS ---# + + Q, F = copy.deepcopy(V), copy.deepcopy(W) + n = Q.dim() + + q_basis, f_basis = Matrix.identity(n), Matrix.identity(n) + for i in range(n-1): + ##Find vectors v and w such that Q(v) = F(w) + v, w = vectors_of_common_length_dev(Q, F, q_basis, f_basis, i) + #print("Find vectors {0} and {1} such that Q(v) = F(w)".format(v, w)) + + ##Modify the bases to include v and w. + q_basis = modify_basis(q_basis, v, i) + f_basis = modify_basis(f_basis, w, i) + #print("Modified bases:\nQb =\n{0}, \nFb =\n{1}".format(q_basis, f_basis)) + + ##Compute the gram matrices with respect to the modified bases. + QM = compute_gram_matrix_from_basis(V, q_basis) + FM = compute_gram_matrix_from_basis(W, f_basis) + #print("Gram matrices with respect to modified bases:\nQM =\n{0}, \nFM =\n{1}".format(QM,FM)) + + ##Ensure that the bases are orthogonal, so the gram matrices will be diagonal. + q_basis = gram_schmidt(q_basis, i, QM) + f_basis = gram_schmidt(f_basis, i, FM) + #print("Orthogonal bases:\nQb =\n{0}, \nFb =\n{1}".format(q_basis, f_basis)) + + ##Compute the gram matrices with respect to the orthogonal bases. + QM = compute_gram_matrix_from_basis(V, q_basis) + FM = compute_gram_matrix_from_basis(W, f_basis) + #print("Gram matrices with respect to orthogonal bases:\nQM =\n{0}, \nFM =\n{1}".format(QM,FM)) + + ##Pull off the first term and continue + Q = diagonal_qf_pull_off_n_terms(QM, i+1, Q.base_ring()) + F = diagonal_qf_pull_off_n_terms(FM, i+1, F.base_ring()) + #print("Smaller qf's:\nQ =\n{0}, \nF = \n{1}".format(Q, F)) + + ##Compute the final term as a special case: + QM, FM = Q.Gram_matrix(), F.Gram_matrix() + if QM[0][0] != 0: + q_basis.set_col_to_multiple_of_col(n-1, n-1, sqrt(FM[0][0] / QM[0][0])) + elif FM[0][0] != 0: + f_basis.set_col_to_multiple_of_col(n-1, n-1, sqrt(QM[0][0] / FM[0][0])) + + return f_basis * q_basis.inverse() + From 27b21901366cf4dac41e41a247ab930a95d2df7a Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Sun, 1 Nov 2015 16:36:01 -0500 Subject: [PATCH 006/170] Adds a flag parameter to short_vector_list_up_to_length to pass to PARI's qfminim that allows qfminim to work on quadratic forms with non-integral real coefficients. --- .../quadratic_form__automorphisms.py | 17 +++++++++++++---- .../quadratic_form__equivalence_testing.py | 14 +------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form__automorphisms.py b/src/sage/quadratic_forms/quadratic_form__automorphisms.py index b5ac0cb6bdc..3e56828b487 100644 --- a/src/sage/quadratic_forms/quadratic_form__automorphisms.py +++ b/src/sage/quadratic_forms/quadratic_form__automorphisms.py @@ -102,7 +102,9 @@ def basis_of_short_vectors(self, show_lengths=False, safe_flag=None): return basis -def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): + + +def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False, real_entry_flag=False): """ Return a list of lists of short vectors `v`, sorted by length, with Q(`v`) < len_bound. @@ -114,8 +116,14 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): - ``up_to_sign_flag`` -- (default: ``False``) if set to True, then only one of the vectors of the pair `[v, -v]` is listed. - OUTPUT: a list of lists of vectors such that entry ``[i]`` contains - all vectors of length `i`. + - "real_entry_flag" -- (default: "False") if set to True, then qfminim can + operate on forms with non-integral real entries. + + OUTPUT: + + A list of lists of vectors such that entry `[i]` contains all + vectors of length `i`. + EXAMPLES:: @@ -198,7 +206,8 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): len_bound_pari = 2*(len_bound - 1) # Call PARI's qfminim() - parilist = self._pari_().qfminim(len_bound_pari, flag=2)[2].Vec() + real_flag = 2 if real_entry_flag else 0 + parilist = self._pari_().qfminim(len_bound_pari, flag=real_flag)[2].Vec() # List of lengths parilens = pari(r"(M,v) -> vector(#v, i, (v[i]~ * M * v[i])\2)")(self, parilist) diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index 0fa2a89bf0e..22693e603a1 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -635,18 +635,6 @@ def gram_schmidt(matrix, fixed_vector_index, gram_matrix): return column_matrix(vectors) - def vectors_of_common_length(Q, F): - i = max(Q.Gram_matrix()[0][0], F.Gram_matrix()[0][0]) - n = 100 - while True: - q_vecs, f_vecs = Q.short_vector_list_up_to_length(n), F.short_vector_list_up_to_length(n) - while i < n: - ##Find a value represented by both forms - if q_vecs[i] and f_vecs[i]: - return (q_vecs[i][0], f_vecs[i][0]); - i += 1 - n += 100 - def vectors_of_common_length_dev(Q, F, qb, fb, iteration): ##Returns a pair of vectors v1 and v2, such that Q(v1) = F(v2), ##and qb and fb are bases when their columns at index iteration are replaced with @@ -665,7 +653,7 @@ def zero_row(matrix, col, i): n = 100 #NOTE: If the given forms are not equivalent, this will be an infinite loop. while True: - q_vecs, f_vecs = Q.short_vector_list_up_to_length(n), F.short_vector_list_up_to_length(n) + q_vecs, f_vecs = Q.short_vector_list_up_to_length(n, real_entry_flag=True), F.short_vector_list_up_to_length(n, real_entry_flag=True) while i < n: ##Find a value that is represented by both forms if q_vecs[i] and f_vecs[i]: From 92de9efe06c5236871f5faeacabe36a24378114e Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Tue, 3 Nov 2015 18:00:40 -0500 Subject: [PATCH 007/170] Adds isometry method for quadratic forms. --- src/sage/quadratic_forms/quadratic_form.py | 4 ++-- src/sage/quadratic_forms/quadratic_form__automorphisms.py | 3 +-- .../quadratic_forms/quadratic_form__equivalence_testing.py | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form.py b/src/sage/quadratic_forms/quadratic_form.py index f3ff37fac10..d4115aa24d5 100644 --- a/src/sage/quadratic_forms/quadratic_form.py +++ b/src/sage/quadratic_forms/quadratic_form.py @@ -374,8 +374,8 @@ class QuadraticForm(SageObject): is_globally_equivalent_to, \ is_locally_equivalent_to, \ has_equivalent_Jordan_decomposition_at_prime, \ - is_rationally_isometric - isometry + is_rationally_isometric, \ + isometry def __init__(self, R, n=None, entries=None, unsafe_initialization=False, number_of_automorphisms=None, determinant=None): diff --git a/src/sage/quadratic_forms/quadratic_form__automorphisms.py b/src/sage/quadratic_forms/quadratic_form__automorphisms.py index 3e56828b487..67b832034e4 100644 --- a/src/sage/quadratic_forms/quadratic_form__automorphisms.py +++ b/src/sage/quadratic_forms/quadratic_form__automorphisms.py @@ -102,8 +102,6 @@ def basis_of_short_vectors(self, show_lengths=False, safe_flag=None): return basis - - def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False, real_entry_flag=False): """ Return a list of lists of short vectors `v`, sorted by length, with @@ -219,6 +217,7 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False, real_ # In certain trivial cases, PARI can sometimes return longer # vectors than requested. if length < len_bound: + v = parilist[i] sagevec = V(list(parilist[i])) vec_sorted_list[length].append(sagevec) if not up_to_sign_flag : diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index 22693e603a1..17ebfda6f84 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -327,7 +327,6 @@ def is_rationally_isometric(self, other): INPUT: -<<<<<<< HEAD a quadratic form OUTPUT: @@ -555,6 +554,7 @@ def isometry(self, other): sage: E.Gram_matrix() == T.transpose() * F.Gram_matrix() * T True """ + import copy from sage.matrix.constructor import Matrix ##Define a method that determines whether or not a matrix is diagonal. def is_diagonal(matrix): @@ -592,6 +592,7 @@ def is_diagonal(matrix): ##Helper method for isometry. def diagonal_isometry(V, W): ##Computes an isometry between diagonal equivalent forms V and W. + import copy from sage.functions.other import sqrt from quadratic_form import DiagonalQuadraticForm from sage.matrix.constructor import matrix, column_matrix, Matrix @@ -710,4 +711,3 @@ def zero_row(matrix, col, i): f_basis.set_col_to_multiple_of_col(n-1, n-1, sqrt(QM[0][0] / FM[0][0])) return f_basis * q_basis.inverse() - From 2f8ebc70a1b236e1ca765b7cbc982587694f6a77 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Tue, 1 Dec 2015 17:03:28 -0500 Subject: [PATCH 008/170] Bug fixes in 'isometry' method and refactoring to utilize the 'solve' method for quadratic forms --- .../quadratic_form__equivalence_testing.py | 281 +++++++++--------- 1 file changed, 145 insertions(+), 136 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index 17ebfda6f84..78c7a937eda 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -488,75 +488,88 @@ def is_rationally_isometric(self, other): return True def isometry(self, other): - """ - Given two equivalent quadratic forms, computes an isometry from one to the other. - Note: Currently only works for positive definite quadratic forms. This is because isometry() relies on - the method short_vector_list_up_to_length() which is only defined for positive definite forms. + r""" + Given two rationally equivalent quadratic forms, computes a + transition matrix mapping from one to the other. INPUT: - self: a positive definite QuadraticForm - other: a positive definite QuadraticForm - NOTE: This algorithm may not terminate if self is not equivalent to other! + + - ``self`` -- a quadratic form + - ``other`` -- a quadratic form OUTPUT: - a matrix T representing the isometry transformation, such that if QM is the gram matrix of Q and - FM is the gram matrix of F, then QM == T.transpose() * FM * T yields True. + + - A matrix ``T`` representing the isometry transformation, such that if + ``QM`` is the gram matrix of ``self`` and ``FM`` is the gram matrix of + ``other``, then ``QM == T.transpose() * FM * T`` yields ``True``. EXAMPLES:: - sage: Q = DiagonalQuadraticForm(QQ, [1, 1, 2]) - sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) - sage: T = Q.isometry(F) - sage: T - [ 1/2 1/2 0] - [-1/2 1/2 0] - [ 0 0 1] - - sage: Q.Gram_matrix() == T.transpose() * F.Gram_matrix() * T - True - - sage: T = F.isometry(Q) - sage: T - [ 1 -1 0] - [ 1 1 0] - [ 0 0 1] - sage: F.Gram_matrix() == T.transpose() * Q.Gram_matrix() * T - True - - sage: A = DiagonalQuadraticForm(QQ, [1,5]) - sage: B = QuadraticForm(QQ, 2, [1, 12, 81]) - sage: T = A.isometry(B) - sage: T - [ 1 -2] - [ 0 1/3] + sage: Q = DiagonalQuadraticForm(QQ, [1, 1, 2]) + sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) + sage: T = Q.isometry(F); T + [-1/2 -1/2 0] + [ 0 0 -1] + [-1/2 1/2 0] + sage: Q.Gram_matrix() == T.transpose() * F.Gram_matrix() * T + True - sage: A.Gram_matrix() == T.transpose() * B.Gram_matrix() * T - True + sage: T = F.isometry(Q); T + [-1/3 -4/3 -1/3] + [ -1 0 1] + [ 2/3 -1/3 2/3] + sage: F.Gram_matrix() == T.T * Q.Gram_matrix() * T + True + + :: + + sage: L = QuadraticForm(QQ, 3, [2, 2, 0, 2, 2, 5]) + sage: M = QuadraticForm(QQ, 3, [2, 2, 0, 3, 2, 3]) + sage: L.isometry(M) + Traceback (most recent call last): + ... + ArithmeticError: Quadratic form in 3 variables over Rational Field with coefficients: + [ 2 2 0 ] + [ * 2 2 ] + [ * * 5 ] is not rationally isometric to Quadratic form in 3 variables over Rational Field with coefficients: + [ 2 2 0 ] + [ * 3 2 ] + [ * * 3 ] + + :: - sage: C = DiagonalQuadraticForm(QQ, [1, 5, 9]) - sage: D = DiagonalQuadraticForm(QQ, [6, 30, 1]) - sage: T = D.isometry(C) - sage: T - [ 1 -5 0] - [ 1 1 0] - [ 0 0 1/3] + sage: A = DiagonalQuadraticForm(QQ, [1, 5]) + sage: B = QuadraticForm(QQ, 2, [1, 12, 81]) + sage: T = A.isometry(B); T + [ 1 -2] + [ 0 1/3] + sage: A.Gram_matrix() == T.T * B.Gram_matrix() * T + True + + :: - sage: D.Gram_matrix() == T.transpose() * C.Gram_matrix() * T - True + sage: C = DiagonalQuadraticForm(QQ, [1, 5, 9]) + sage: D = DiagonalQuadraticForm(QQ, [6, 30, 1]) + sage: T = C.isometry(D); T + [ 7/18 5/18 0] + [-1/18 7/18 0] + [ 0 0 3] + sage: C.Gram_matrix() == T.T * D.Gram_matrix() * T + True + + :: - sage: E = DiagonalQuadraticForm(QQ, [1, 1]) - sage: F = QuadraticForm(QQ, 2, [17, 94, 130]) - sage: T = E.isometry(F) - sage: T - [189/17 -43/17] - [ -4 1] - - sage: E.Gram_matrix() == T.transpose() * F.Gram_matrix() * T - True + sage: E = DiagonalQuadraticForm(QQ, [1, 1]) + sage: F = QuadraticForm(QQ, 2, [17, 94, 130]) + sage: T = F.isometry(E); T + [ -4 -189/17] + [ -1 -43/17] + sage: F.Gram_matrix() == T.T * E.Gram_matrix() * T + True """ import copy from sage.matrix.constructor import Matrix - ##Define a method that determines whether or not a matrix is diagonal. + # Define a method that determines whether or not a matrix is diagonal. def is_diagonal(matrix): dim = matrix.dimensions()[0] for i in range(dim): @@ -568,45 +581,40 @@ def is_diagonal(matrix): Q = copy.deepcopy(self) F = copy.deepcopy(other) - if not Q.is_positive_definite() or not F.is_positive_definite(): - raise TypeError("Both Quadratic Forms must be positive definite.") - if not is_QuadraticForm(other): raise TypeError("First argument must be a Quadratic Form.") + if not Q.is_rationally_isometric(F): + raise ArithmeticError("{0} is not rationally isometric to {1}".format(Q, F)) + n = Q.dim() - ##If either form is not diagonal, diagonalize it. + # If either form is not diagonal, diagonalize it. q_diagonal_transform = f_diagonal_transform = Matrix.identity(n) if not is_diagonal(Q.Gram_matrix()): Q, q_diagonal_transform = Q.rational_diagonal_form(True) if not is_diagonal(F.Gram_matrix()): F, f_diagonal_transform = F.rational_diagonal_form(True) - #This is the method that does all the work to compute the isometry. + # Call the method that does all the work to compute the isometry. transform = diagonal_isometry(Q,F) return f_diagonal_transform * transform * q_diagonal_transform.inverse() -##Helper method for isometry. +# Helper method for isometry. def diagonal_isometry(V, W): -##Computes an isometry between diagonal equivalent forms V and W. +# Computes an isometry between diagonal equivalent forms V and W. import copy from sage.functions.other import sqrt from quadratic_form import DiagonalQuadraticForm from sage.matrix.constructor import matrix, column_matrix, Matrix from sage.modules.free_module_element import vector + #----HELPER METHODS-----# - def diagonal_qf_pull_off_n_terms(gram_matrix, n, ring): - ##Returns the diagonal quadratic form corresponding to the given gram matrix - ##with the first n terms removed. - diagonal = gram_matrix.diagonal(); - return DiagonalQuadraticForm(ring, diagonal[n:]) - def compute_gram_matrix_from_basis(Q, basis): - ##Computes the gram matrix by treating the columns of basis as basis vectors. + # Computes the gram matrix by treating the columns of basis as basis vectors. n = Q.dim() rows = []; for i in range(n): @@ -614,7 +622,7 @@ def compute_gram_matrix_from_basis(Q, basis): return Matrix(rows) def modify_basis(basis, v, i): - ##Modifies basis to include v (a linear combination of the basis vectors) at column i in the basis. + # Modifies basis to include v (a linear combination of the basis vectors) at column i in the basis. b = copy.deepcopy(basis) column = vector(QQ, b.dimensions()[0]) for j in range(len(v)): @@ -622,92 +630,93 @@ def modify_basis(basis, v, i): b.set_column(i, column) return b - def gram_schmidt(matrix, fixed_vector_index, gram_matrix): - ##Orthogonalizes the columns of matrix against a fixed column vector. + def gram_schmidt(matrix, fixed_vector_index, inner_product): + # Orthogonalizes the columns of matrix, past a fixed vector, with respect to inner_product. n = matrix.dimensions()[0] vectors = [0] * n - fixed_vec = matrix.column(fixed_vector_index) - vectors[fixed_vector_index] = fixed_vec - for i in range(fixed_vector_index): + for i in range(n): vectors[i] = matrix.column(i) - for i in range(fixed_vector_index + 1, n): - vectors[i] = matrix.column(i) - (gram_matrix[i, fixed_vector_index] / gram_matrix[fixed_vector_index, fixed_vector_index]) * fixed_vec + for i in range(fixed_vector_index, n): + for j in range(i+1, n): + vectors[j] = vectors[j] - (inner_product(vectors[j], vectors[i]) / inner_product(vectors[i], vectors[i])) * vectors[i] return column_matrix(vectors) - - def vectors_of_common_length_dev(Q, F, qb, fb, iteration): - ##Returns a pair of vectors v1 and v2, such that Q(v1) = F(v2), - ##and qb and fb are bases when their columns at index iteration are replaced with - ##v1 and v2 respectively. (i.e. qb and fb contain no all-zero rows) - def zero_row(matrix, col, i): - ##Sets the ith column of matrix to col and returns true if matrix has an all zero row. - m = modify_basis(matrix, col, i) - rows, cols = m.dimensions() - z = [0] * cols - for i in range(rows): - if m.row(i).list() == z: - return True - return False - - i = max(Q.Gram_matrix()[0][0], F.Gram_matrix()[0][0]) - n = 100 - #NOTE: If the given forms are not equivalent, this will be an infinite loop. - while True: - q_vecs, f_vecs = Q.short_vector_list_up_to_length(n, real_entry_flag=True), F.short_vector_list_up_to_length(n, real_entry_flag=True) - while i < n: - ##Find a value that is represented by both forms - if q_vecs[i] and f_vecs[i]: - ##Find a pair of vectors such that if each becomes a column of qb and fb respectively, - ##neither matrix will have an all-zero row. - for j in range(len(q_vecs[i])): - for k in range(len(f_vecs[i])): - if not zero_row(qb, q_vecs[i][j], iteration) and not zero_row(fb, f_vecs[i][k], iteration): - return (q_vecs[i][j], f_vecs[i][k]) - i += 1 - n += 100 + def zero_row(matrix, col, i): + ##Sets the ith column of matrix to col and returns true if matrix has an all zero row. + m = modify_basis(matrix, col, i) + rows, cols = m.dimensions() + z = [0] * cols + for i in range(rows): + if m.row(i).list() == z: + return True + return False + #--- END OF HELPER METHODS ---# Q, F = copy.deepcopy(V), copy.deepcopy(W) + QM, FM = Q.Gram_matrix(), F.Gram_matrix() n = Q.dim() - q_basis, f_basis = Matrix.identity(n), Matrix.identity(n) + q_basis, f_basis = Matrix.identity(QQ, n), Matrix.identity(QQ, n) for i in range(n-1): - ##Find vectors v and w such that Q(v) = F(w) - v, w = vectors_of_common_length_dev(Q, F, q_basis, f_basis, i) - #print("Find vectors {0} and {1} such that Q(v) = F(w)".format(v, w)) - - ##Modify the bases to include v and w. - q_basis = modify_basis(q_basis, v, i) - f_basis = modify_basis(f_basis, w, i) - #print("Modified bases:\nQb =\n{0}, \nFb =\n{1}".format(q_basis, f_basis)) + # If first terms are not equal, + if Q.Gram_matrix()[0][0] != F.Gram_matrix()[0][0]: + # Find a vector w such that Q(v) = F(w) where v = [1, ..., 0] + # v, w = vectors_of_common_length_dev(Q, F, q_basis, f_basis, i) + v = vector([0] * (n - i)) + index = 0; + while True: + v[index] = v[index] + 1 + index = (index + 1) % (n - i) + c = Q(v) + try: + w = F.solve(c) + #print("Find vectors {0} and {1} such that Q(v) = F(w)".format(v, w)) + if not zero_row(f_basis, w, i) and not zero_row(q_basis, v, i): + break + except ArithmeticError: + # No solution found, try another vector. + pass + + # Modify the bases to include v and w. + q_basis = modify_basis(q_basis, v, i) + f_basis = modify_basis(f_basis, w, i) + #print("Modified bases:\nQb =\n{0}, \nFb =\n{1}".format(q_basis, f_basis)) - ##Compute the gram matrices with respect to the modified bases. - QM = compute_gram_matrix_from_basis(V, q_basis) - FM = compute_gram_matrix_from_basis(W, f_basis) - #print("Gram matrices with respect to modified bases:\nQM =\n{0}, \nFM =\n{1}".format(QM,FM)) + # Compute the gram matrices with respect to the modified bases. + QM = compute_gram_matrix_from_basis(V, q_basis) + FM = compute_gram_matrix_from_basis(W, f_basis) + #print("Gram matrices with respect to modified bases:\nQM =\n{0}, \nFM =\n{1}".format(QM,FM)) - ##Ensure that the bases are orthogonal, so the gram matrices will be diagonal. - q_basis = gram_schmidt(q_basis, i, QM) - f_basis = gram_schmidt(f_basis, i, FM) - #print("Orthogonal bases:\nQb =\n{0}, \nFb =\n{1}".format(q_basis, f_basis)) - - ##Compute the gram matrices with respect to the orthogonal bases. - QM = compute_gram_matrix_from_basis(V, q_basis) - FM = compute_gram_matrix_from_basis(W, f_basis) - #print("Gram matrices with respect to orthogonal bases:\nQM =\n{0}, \nFM =\n{1}".format(QM,FM)) - - ##Pull off the first term and continue - Q = diagonal_qf_pull_off_n_terms(QM, i+1, Q.base_ring()) - F = diagonal_qf_pull_off_n_terms(FM, i+1, F.base_ring()) + # Ensure that the bases are orthogonal, so the gram matrices will be diagonal. + q_basis = gram_schmidt(q_basis, i, V.bilinear_map) + f_basis = gram_schmidt(f_basis, i, W.bilinear_map) + #print("Orthogonal bases:\nQb =\n{0}, \nFb =\n{1}".format(q_basis, f_basis)) + + # Compute the gram matrices with respect to the orthogonal bases. + QM = compute_gram_matrix_from_basis(V, q_basis) + FM = compute_gram_matrix_from_basis(W, f_basis) + #print("Gram matrices with respect to orthogonal bases:\nQM =\n{0}, \nFM =\n{1}".format(QM,FM)) + + # Pull off the first term and continue + Q = DiagonalQuadraticForm(Q.base_ring(), QM.diagonal()) + Q = Q.extract_variables(range(i+1, Q.dim())) + F = DiagonalQuadraticForm(F.base_ring(), FM.diagonal()) + F = F.extract_variables(range(i+1, F.dim())) #print("Smaller qf's:\nQ =\n{0}, \nF = \n{1}".format(Q, F)) - - ##Compute the final term as a special case: + + # Compute the final term as a special case: QM, FM = Q.Gram_matrix(), F.Gram_matrix() + #print("Penultimate Gram matrices:\nQM = \n{0}, \nFM = \n{1}".format(QM, FM)) if QM[0][0] != 0: q_basis.set_col_to_multiple_of_col(n-1, n-1, sqrt(FM[0][0] / QM[0][0])) elif FM[0][0] != 0: f_basis.set_col_to_multiple_of_col(n-1, n-1, sqrt(QM[0][0] / FM[0][0])) + QM, FM = compute_gram_matrix_from_basis(V, q_basis), compute_gram_matrix_from_basis(W, f_basis) + #print("Final Gram matrices:\nQM =\n{0}, \nFM = \n{1}".format(QM, FM)) + return f_basis * q_basis.inverse() + From d2b3dde11edb22ba199d4666b99ad753865c5322 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Fri, 4 Dec 2015 12:14:36 -0500 Subject: [PATCH 009/170] Removed errant TAB character in quadratic_form.py --- src/sage/quadratic_forms/quadratic_form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/quadratic_forms/quadratic_form.py b/src/sage/quadratic_forms/quadratic_form.py index 747b454ad6d..401a16688a0 100644 --- a/src/sage/quadratic_forms/quadratic_form.py +++ b/src/sage/quadratic_forms/quadratic_form.py @@ -375,7 +375,7 @@ class QuadraticForm(SageObject): is_locally_equivalent_to, \ has_equivalent_Jordan_decomposition_at_prime, \ is_rationally_isometric, \ - isometry + isometry ## Routines for solving equations of the form Q(x) = c. from sage.quadratic_forms.qfsolve import solve From 4b22c5155459c95fe2fac874c49d8bc7843f08c5 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Thu, 10 Dec 2015 12:51:35 -0500 Subject: [PATCH 010/170] Added doctesting to helper methods of isometry --- .../quadratic_form__equivalence_testing.py | 258 ++++++++++++++---- 1 file changed, 210 insertions(+), 48 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index 9f944b113a0..2132b996316 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -602,58 +602,47 @@ def is_diagonal(matrix): return f_diagonal_transform * transform * q_diagonal_transform.inverse() -# Helper method for isometry. def diagonal_isometry(V, W): -# Computes an isometry between diagonal equivalent forms V and W. + r""" + Given two diagonal, rationally equivalent quadratic forms, computes a + transition matrix mapping from one to the other. + + Note: This function is an auxilliary method of ``isometry``, which is the + method that should be used as it performs error-checking that isn't present + in this function. + + INPUT: + + - ``V`` -- a diagonal quadratic form + - ``W`` -- a diagonal quadratic form + + OUTPUT: + + - A matrix ``T`` representing the isometry transformation, such that if + ``VM`` is the gram matrix of ``V`` and ``WM`` is the gram matrix of + ``W``, then ``VM == T.transpose() * WM * T`` yields ``True``. + + EXAMPLES:: + + sage: Q = DiagonalQuadraticForm(QQ, [1, 1, 2]) + sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) + sage: T = Q.isometry(F); T + [-1/2 -1/2 0] + [ 0 0 -1] + [-1/2 1/2 0] + sage: Q.Gram_matrix() == T.transpose() * F.Gram_matrix() * T + True + + sage: T = F.isometry(Q); T + [-1/3 -4/3 -1/3] + [ -1 0 1] + [ 2/3 -1/3 2/3] + sage: F.Gram_matrix() == T.T * Q.Gram_matrix() * T + True + """ import copy from sage.functions.other import sqrt from quadratic_form import DiagonalQuadraticForm - from sage.matrix.constructor import matrix, column_matrix, Matrix - from sage.modules.free_module_element import vector - - #----HELPER METHODS-----# - - def compute_gram_matrix_from_basis(Q, basis): - # Computes the gram matrix by treating the columns of basis as basis vectors. - n = Q.dim() - rows = []; - for i in range(n): - rows.append([Q.bilinear_map(basis.column(i), basis.column(j)) for j in range(n)]) - return Matrix(rows) - - def modify_basis(basis, v, i): - # Modifies basis to include v (a linear combination of the basis vectors) at column i in the basis. - b = copy.deepcopy(basis) - column = vector(QQ, b.dimensions()[0]) - for j in range(len(v)): - column += b.column(i + j) * v[j] - b.set_column(i, column) - return b - - def gram_schmidt(matrix, fixed_vector_index, inner_product): - # Orthogonalizes the columns of matrix, past a fixed vector, with respect to inner_product. - n = matrix.dimensions()[0] - vectors = [0] * n - - for i in range(n): - vectors[i] = matrix.column(i) - for i in range(fixed_vector_index, n): - for j in range(i+1, n): - vectors[j] = vectors[j] - (inner_product(vectors[j], vectors[i]) / inner_product(vectors[i], vectors[i])) * vectors[i] - - return column_matrix(vectors) - - def zero_row(matrix, col, i): - ##Sets the ith column of matrix to col and returns true if matrix has an all zero row. - m = modify_basis(matrix, col, i) - rows, cols = m.dimensions() - z = [0] * cols - for i in range(rows): - if m.row(i).list() == z: - return True - return False - - #--- END OF HELPER METHODS ---# Q, F = copy.deepcopy(V), copy.deepcopy(W) QM, FM = Q.Gram_matrix(), F.Gram_matrix() @@ -719,3 +708,176 @@ def zero_row(matrix, col, i): return f_basis * q_basis.inverse() + +def compute_gram_matrix_from_basis(Q, basis): + r""" + Computes the gram matrix of a quadratic form with respect to the given basis. + + INPUT: + + - ``Q`` -- a quadratic form of rank n + - ``basis`` -- an nxn matrix whose columns are treated as basis vectors + + OUTPUT: + + - A matrix representing the gram matrix of ``Q`` with respect to ``basis`` + + EXAMPLES:: + + sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import * + + sage: Q = QuadraticForm(QQ, 3, [1, 2, 2, 1, 2, 0]) + sage: std_basis = matrix.identity(3) + sage: QM = compute_gram_matrix_from_basis(Q, std_basis) + sage: Q.Gram_matrix() == QM + True + + :: + + sage: Q = DiagonalQuadraticForm(QQ, [1, 2, 2]) + sage: basis = matrix([[1, 1, 0], [2, 1, 0], [0, 0, 1]]) + sage: QM = compute_gram_matrix_from_basis(Q, basis); QM + [9 5 0] + [5 3 0] + [0 0 2] + """ + from sage.matrix.constructor import matrix, Matrix + n = Q.dim() + rows = []; + for i in range(n): + rows.append([Q.bilinear_map(basis.column(i), basis.column(j)) for j in range(n)]) + return Matrix(rows) + + +def modify_basis(basis, v, i): + r""" + Modifies one of the vectors in a basis to be a linear combination of the vectors in the basis. + + Note: This function is not intended for general use. It expects the length of ``v`` to be + the dimension of ``basis`` - ``i``. It was designed as a helper for the ``diagonally_isometry`` method. + + INPUT: + + - ``basis`` -- a square matrix whose columns represent vectors in a basis + - ``v`` -- a vector that represents a linear combination of the vectors in ``basis`` + - ``i`` -- the index of the column in ``basis`` to be modified. + + OUTPUT: + + - A matrix representing the basis with one of its vectors changed to a linear combination of the basis vectors. + + EXAMPLES:: + + sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import * + sage: std_basis = matrix.identity(3) + sage: b1 = modify_basis(std_basis, vector([1, 1, 1]), 0); b1 + [1 0 0] + [1 1 0] + [1 0 1] + sage: b2 = modify_basis(b1, vector([3, 2]), 1); b2 + [1 0 0] + [1 3 0] + [1 2 1] + sage: b3 = modify_basis(b2, vector([2, 2]), 1); b3 + [1 0 0] + [1 6 0] + [1 6 1] + """ + import copy + from sage.modules.free_module_element import vector + + b = copy.deepcopy(basis) + column = vector(QQ, b.dimensions()[0]) + for j in range(len(v)): + column += b.column(i + j) * v[j] + b.set_column(i, column) + return b + +def gram_schmidt(m, fixed_vector_index, inner_product): + r""" + Orthogonalizes a set of vectors, starting at a fixed vector, with respect to a given + inner product. + + INPUT: + + - ``m`` -- a square matrix whose columns represent vectors + - ``fixed_vector_index`` -- any vectors preceding the vector (i.e. to its left) + at this index are not changed. + - ``inner_product`` - a function that takes two vector arguments and returns a scalar, + representing an inner product. + + OUTPUT: + + - A matrix consisting of orthogonal columns with respect to the given inner product + + EXAMPLES:: + + sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import * + sage: Q = QuadraticForm(QQ, 3, [1, 4, 6, 1, 2, 4]); Q + Quadratic form in 3 variables over Rational Field with coefficients: + [ 1 4 6 ] + [ * 1 2 ] + [ * * 4 ] + sage: QM = Q.Gram_matrix(); QM + [1 2 3] + [2 1 1] + [3 1 4] + sage: om = gram_schmidt(QM, 0, Q.bilinear_map); om + [ 1 106/79 -50/51] + [ 2 -25/79 -70/51] + [ 3 -77/79 70/51] + sage: v0 = om.column(0); v1 = om.column(1); v2 = om.column(2) + sage: Q.bilinear_map(v0, v1) == 0 + True + sage: Q.bilinear_map(v0, v2) == 0 + True + sage: Q.bilinear_map(v1, v2) == 0 + True + """ + from sage.matrix.constructor import column_matrix + + n = m.dimensions()[0] + vectors = [0] * n + + for i in range(n): + vectors[i] = m.column(i) + for i in range(fixed_vector_index, n): + for j in range(i+1, n): + vectors[j] = vectors[j] - (inner_product(vectors[j], vectors[i]) / inner_product(vectors[i], vectors[i])) * vectors[i] + + return column_matrix(vectors) + + +def zero_row(matrix, col, i): + r""" + Calls modify_basis(matrix, col, i) and returns true iff the resulting matrix + contains an all-zero row. + + INPUT: + + - ``matrix`` -- a matrix + - ``col`` -- a vector + - ``i`` -- the index of a column in ``matrix`` + + OUTPUT: + + - True if the result of modify_basis(matrix, col, i) contains of an all-zero row, + and False otherwise. + + EXAMPLES:: + + sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import * + sage: m = matrix([[1, 2], [2, 1]]) + sage: zero_row(m, vector([1, 2]), 0) + False + sage: m = matrix([[1, 0], [1, 2]]) + sage: zero_row(m, vector([0, 1]), 0) + True + """ + m = modify_basis(matrix, col, i) + rows, cols = m.dimensions() + z = [0] * cols + for i in range(rows): + if m.row(i).list() == z: + return True + return False From 582ae4500eb3652446c6f829310298953f8dbd02 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Thu, 10 Dec 2015 15:51:54 -0500 Subject: [PATCH 011/170] Minor bugfixes/refactoring on isometry --- .../quadratic_form__equivalence_testing.py | 93 ++++--------------- 1 file changed, 16 insertions(+), 77 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index 2132b996316..95b7e71ebe7 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -567,34 +567,18 @@ def isometry(self, other): sage: F.Gram_matrix() == T.T * E.Gram_matrix() * T True """ - import copy - from sage.matrix.constructor import Matrix - # Define a method that determines whether or not a matrix is diagonal. - def is_diagonal(matrix): - dim = matrix.dimensions()[0] - for i in range(dim): - for j in range(dim): - if (i != j) and (matrix[i][j] != 0): - return False - return True - - Q = copy.deepcopy(self) - F = copy.deepcopy(other) - if not is_QuadraticForm(other): raise TypeError("First argument must be a Quadratic Form.") - if not Q.is_rationally_isometric(F): - raise ArithmeticError("{0} is not rationally isometric to {1}".format(Q, F)) + if not self.is_rationally_isometric(other): + raise ArithmeticError("{0} is not rationally isometric to {1}".format(self, other)) + Q, F = self, other n = Q.dim() # If either form is not diagonal, diagonalize it. - q_diagonal_transform = f_diagonal_transform = Matrix.identity(n) - if not is_diagonal(Q.Gram_matrix()): - Q, q_diagonal_transform = Q.rational_diagonal_form(True) - if not is_diagonal(F.Gram_matrix()): - F, f_diagonal_transform = F.rational_diagonal_form(True) + Q, q_diagonal_transform = self.rational_diagonal_form(True) + F, f_diagonal_transform = other.rational_diagonal_form(True) # Call the method that does all the work to compute the isometry. transform = diagonal_isometry(Q,F) @@ -643,17 +627,20 @@ def diagonal_isometry(V, W): import copy from sage.functions.other import sqrt from quadratic_form import DiagonalQuadraticForm + from sage.matrix.constructor import Matrix + from sage.modules.free_module_element import vector Q, F = copy.deepcopy(V), copy.deepcopy(W) QM, FM = Q.Gram_matrix(), F.Gram_matrix() n = Q.dim() q_basis, f_basis = Matrix.identity(QQ, n), Matrix.identity(QQ, n) - for i in range(n-1): + for i in range(n): # If first terms are not equal, if Q.Gram_matrix()[0][0] != F.Gram_matrix()[0][0]: - # Find a vector w such that Q(v) = F(w) where v = [1, ..., 0] - # v, w = vectors_of_common_length_dev(Q, F, q_basis, f_basis, i) + # Find a vector w such that Q(v) = F(w) where v starts at [1, 0, ..., 0] + # and increments each term by 1 until a vector is found that satisfies the + # conditions below. v = vector([0] * (n - i)) index = 0; while True: @@ -662,8 +649,9 @@ def diagonal_isometry(V, W): c = Q(v) try: w = F.solve(c) - #print("Find vectors {0} and {1} such that Q(v) = F(w)".format(v, w)) - if not zero_row(f_basis, w, i) and not zero_row(q_basis, v, i): + qb = modify_basis(q_basis, v, i) + fb = modify_basis(f_basis, w, i) + if not qb.is_singular() and not fb.is_singular(): break except ArithmeticError: # No solution found, try another vector. @@ -672,39 +660,24 @@ def diagonal_isometry(V, W): # Modify the bases to include v and w. q_basis = modify_basis(q_basis, v, i) f_basis = modify_basis(f_basis, w, i) - #print("Modified bases:\nQb =\n{0}, \nFb =\n{1}".format(q_basis, f_basis)) # Compute the gram matrices with respect to the modified bases. QM = compute_gram_matrix_from_basis(V, q_basis) FM = compute_gram_matrix_from_basis(W, f_basis) - #print("Gram matrices with respect to modified bases:\nQM =\n{0}, \nFM =\n{1}".format(QM,FM)) - + # Ensure that the bases are orthogonal, so the gram matrices will be diagonal. q_basis = gram_schmidt(q_basis, i, V.bilinear_map) f_basis = gram_schmidt(f_basis, i, W.bilinear_map) - #print("Orthogonal bases:\nQb =\n{0}, \nFb =\n{1}".format(q_basis, f_basis)) # Compute the gram matrices with respect to the orthogonal bases. QM = compute_gram_matrix_from_basis(V, q_basis) FM = compute_gram_matrix_from_basis(W, f_basis) - #print("Gram matrices with respect to orthogonal bases:\nQM =\n{0}, \nFM =\n{1}".format(QM,FM)) # Pull off the first term and continue Q = DiagonalQuadraticForm(Q.base_ring(), QM.diagonal()) Q = Q.extract_variables(range(i+1, Q.dim())) F = DiagonalQuadraticForm(F.base_ring(), FM.diagonal()) F = F.extract_variables(range(i+1, F.dim())) - #print("Smaller qf's:\nQ =\n{0}, \nF = \n{1}".format(Q, F)) - - # Compute the final term as a special case: - QM, FM = Q.Gram_matrix(), F.Gram_matrix() - if QM[0][0] != 0: - q_basis.set_col_to_multiple_of_col(n-1, n-1, sqrt(FM[0][0] / QM[0][0])) - elif FM[0][0] != 0: - f_basis.set_col_to_multiple_of_col(n-1, n-1, sqrt(QM[0][0] / FM[0][0])) - - QM, FM = compute_gram_matrix_from_basis(V, q_basis), compute_gram_matrix_from_basis(W, f_basis) - #print("Final Gram matrices:\nQM =\n{0}, \nFM = \n{1}".format(QM, FM)) return f_basis * q_basis.inverse() @@ -742,6 +715,7 @@ def compute_gram_matrix_from_basis(Q, basis): [0 0 2] """ from sage.matrix.constructor import matrix, Matrix + n = Q.dim() rows = []; for i in range(n): @@ -846,38 +820,3 @@ def gram_schmidt(m, fixed_vector_index, inner_product): vectors[j] = vectors[j] - (inner_product(vectors[j], vectors[i]) / inner_product(vectors[i], vectors[i])) * vectors[i] return column_matrix(vectors) - - -def zero_row(matrix, col, i): - r""" - Calls modify_basis(matrix, col, i) and returns true iff the resulting matrix - contains an all-zero row. - - INPUT: - - - ``matrix`` -- a matrix - - ``col`` -- a vector - - ``i`` -- the index of a column in ``matrix`` - - OUTPUT: - - - True if the result of modify_basis(matrix, col, i) contains of an all-zero row, - and False otherwise. - - EXAMPLES:: - - sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import * - sage: m = matrix([[1, 2], [2, 1]]) - sage: zero_row(m, vector([1, 2]), 0) - False - sage: m = matrix([[1, 0], [1, 2]]) - sage: zero_row(m, vector([0, 1]), 0) - True - """ - m = modify_basis(matrix, col, i) - rows, cols = m.dimensions() - z = [0] * cols - for i in range(rows): - if m.row(i).list() == z: - return True - return False From 6d9be4f9e02047514b90a32d0035bf26ffd6d2c2 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Tue, 12 Jan 2016 09:40:46 -0500 Subject: [PATCH 012/170] removed changes to 'short_vector_list_up_to_length()' --- src/sage/quadratic_forms/quadratic_form__automorphisms.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form__automorphisms.py b/src/sage/quadratic_forms/quadratic_form__automorphisms.py index 67b832034e4..07305d99bca 100644 --- a/src/sage/quadratic_forms/quadratic_form__automorphisms.py +++ b/src/sage/quadratic_forms/quadratic_form__automorphisms.py @@ -102,7 +102,7 @@ def basis_of_short_vectors(self, show_lengths=False, safe_flag=None): return basis -def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False, real_entry_flag=False): +def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): """ Return a list of lists of short vectors `v`, sorted by length, with Q(`v`) < len_bound. @@ -114,9 +114,6 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False, real_ - ``up_to_sign_flag`` -- (default: ``False``) if set to True, then only one of the vectors of the pair `[v, -v]` is listed. - - "real_entry_flag" -- (default: "False") if set to True, then qfminim can - operate on forms with non-integral real entries. - OUTPUT: A list of lists of vectors such that entry `[i]` contains all @@ -204,8 +201,7 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False, real_ len_bound_pari = 2*(len_bound - 1) # Call PARI's qfminim() - real_flag = 2 if real_entry_flag else 0 - parilist = self._pari_().qfminim(len_bound_pari, flag=real_flag)[2].Vec() + parilist = self._pari_().qfminim(len_bound_pari)[2].Vec() # List of lengths parilens = pari(r"(M,v) -> vector(#v, i, (v[i]~ * M * v[i])\2)")(self, parilist) From 17d12c6faa5f18a9f822e30239bf201e2fe76c5f Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Tue, 12 Jan 2016 10:00:22 -0500 Subject: [PATCH 013/170] Prefixed helper functions with an underscore and reworded documentation. --- src/sage/quadratic_forms/quadratic_form.py | 2 +- .../quadratic_form__equivalence_testing.py | 79 ++++++++++--------- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form.py b/src/sage/quadratic_forms/quadratic_form.py index 401a16688a0..cfcaa19ea9e 100644 --- a/src/sage/quadratic_forms/quadratic_form.py +++ b/src/sage/quadratic_forms/quadratic_form.py @@ -375,7 +375,7 @@ class QuadraticForm(SageObject): is_locally_equivalent_to, \ has_equivalent_Jordan_decomposition_at_prime, \ is_rationally_isometric, \ - isometry + explicit_isometry ## Routines for solving equations of the form Q(x) = c. from sage.quadratic_forms.qfsolve import solve diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index 95b7e71ebe7..1728542380a 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -487,7 +487,7 @@ def is_rationally_isometric(self, other): return True -def isometry(self, other): +def explicit_isometry(self, other): r""" Given two rationally equivalent quadratic forms, computes a transition matrix mapping from one to the other. @@ -507,14 +507,14 @@ def isometry(self, other): sage: Q = DiagonalQuadraticForm(QQ, [1, 1, 2]) sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) - sage: T = Q.isometry(F); T + sage: T = Q.explicit_isometry(F); T [-1/2 -1/2 0] [ 0 0 -1] [-1/2 1/2 0] sage: Q.Gram_matrix() == T.transpose() * F.Gram_matrix() * T True - sage: T = F.isometry(Q); T + sage: T = F.explicit_isometry(Q); T [-1/3 -4/3 -1/3] [ -1 0 1] [ 2/3 -1/3 2/3] @@ -525,7 +525,7 @@ def isometry(self, other): sage: L = QuadraticForm(QQ, 3, [2, 2, 0, 2, 2, 5]) sage: M = QuadraticForm(QQ, 3, [2, 2, 0, 3, 2, 3]) - sage: L.isometry(M) + sage: L.explicit_isometry(M) Traceback (most recent call last): ... ArithmeticError: Quadratic form in 3 variables over Rational Field with coefficients: @@ -540,7 +540,7 @@ def isometry(self, other): sage: A = DiagonalQuadraticForm(QQ, [1, 5]) sage: B = QuadraticForm(QQ, 2, [1, 12, 81]) - sage: T = A.isometry(B); T + sage: T = A.explicit_isometry(B); T [ 1 -2] [ 0 1/3] sage: A.Gram_matrix() == T.T * B.Gram_matrix() * T @@ -550,7 +550,7 @@ def isometry(self, other): sage: C = DiagonalQuadraticForm(QQ, [1, 5, 9]) sage: D = DiagonalQuadraticForm(QQ, [6, 30, 1]) - sage: T = C.isometry(D); T + sage: T = C.explicit_isometry(D); T [ 7/18 5/18 0] [-1/18 7/18 0] [ 0 0 3] @@ -561,7 +561,7 @@ def isometry(self, other): sage: E = DiagonalQuadraticForm(QQ, [1, 1]) sage: F = QuadraticForm(QQ, 2, [17, 94, 130]) - sage: T = F.isometry(E); T + sage: T = F.explicit_isometry(E); T [ -4 -189/17] [ -1 -43/17] sage: F.Gram_matrix() == T.T * E.Gram_matrix() * T @@ -581,12 +581,12 @@ def isometry(self, other): F, f_diagonal_transform = other.rational_diagonal_form(True) # Call the method that does all the work to compute the isometry. - transform = diagonal_isometry(Q,F) + transform = _diagonal_isometry(Q,F) return f_diagonal_transform * transform * q_diagonal_transform.inverse() -def diagonal_isometry(V, W): +def _diagonal_isometry(V, W): r""" Given two diagonal, rationally equivalent quadratic forms, computes a transition matrix mapping from one to the other. @@ -610,14 +610,14 @@ def diagonal_isometry(V, W): sage: Q = DiagonalQuadraticForm(QQ, [1, 1, 2]) sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) - sage: T = Q.isometry(F); T + sage: T = Q.explicit_isometry(F); T [-1/2 -1/2 0] [ 0 0 -1] [-1/2 1/2 0] sage: Q.Gram_matrix() == T.transpose() * F.Gram_matrix() * T True - sage: T = F.isometry(Q); T + sage: T = F.explicit_isometry(Q); T [-1/3 -4/3 -1/3] [ -1 0 1] [ 2/3 -1/3 2/3] @@ -634,13 +634,15 @@ def diagonal_isometry(V, W): QM, FM = Q.Gram_matrix(), F.Gram_matrix() n = Q.dim() + # These matrices represent a basis for each quadratic form. + # The identity matrix is equivalent to the standard basis. q_basis, f_basis = Matrix.identity(QQ, n), Matrix.identity(QQ, n) for i in range(n): # If first terms are not equal, if Q.Gram_matrix()[0][0] != F.Gram_matrix()[0][0]: # Find a vector w such that Q(v) = F(w) where v starts at [1, 0, ..., 0] # and increments each term by 1 until a vector is found that satisfies the - # conditions below. + # conditions below v = vector([0] * (n - i)) index = 0; while True: @@ -649,29 +651,26 @@ def diagonal_isometry(V, W): c = Q(v) try: w = F.solve(c) - qb = modify_basis(q_basis, v, i) - fb = modify_basis(f_basis, w, i) + # qb and fb are temporary variables to hold the modified bases. + qb = _modify_basis(q_basis, v, i) + fb = _modify_basis(f_basis, w, i) if not qb.is_singular() and not fb.is_singular(): break except ArithmeticError: - # No solution found, try another vector. + #No solution found, try another vector. pass # Modify the bases to include v and w. - q_basis = modify_basis(q_basis, v, i) - f_basis = modify_basis(f_basis, w, i) + q_basis = _modify_basis(q_basis, v, i) + f_basis = _modify_basis(f_basis, w, i) - # Compute the gram matrices with respect to the modified bases. - QM = compute_gram_matrix_from_basis(V, q_basis) - FM = compute_gram_matrix_from_basis(W, f_basis) - # Ensure that the bases are orthogonal, so the gram matrices will be diagonal. - q_basis = gram_schmidt(q_basis, i, V.bilinear_map) - f_basis = gram_schmidt(f_basis, i, W.bilinear_map) + q_basis = _gram_schmidt(q_basis, i, V.bilinear_map) + f_basis = _gram_schmidt(f_basis, i, W.bilinear_map) # Compute the gram matrices with respect to the orthogonal bases. - QM = compute_gram_matrix_from_basis(V, q_basis) - FM = compute_gram_matrix_from_basis(W, f_basis) + QM = _compute_gram_matrix_from_basis(V, q_basis) + FM = _compute_gram_matrix_from_basis(W, f_basis) # Pull off the first term and continue Q = DiagonalQuadraticForm(Q.base_ring(), QM.diagonal()) @@ -682,7 +681,7 @@ def diagonal_isometry(V, W): return f_basis * q_basis.inverse() -def compute_gram_matrix_from_basis(Q, basis): +def _compute_gram_matrix_from_basis(Q, basis): r""" Computes the gram matrix of a quadratic form with respect to the given basis. @@ -697,11 +696,11 @@ def compute_gram_matrix_from_basis(Q, basis): EXAMPLES:: - sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import * + sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import _compute_gram_matrix_from_basis sage: Q = QuadraticForm(QQ, 3, [1, 2, 2, 1, 2, 0]) sage: std_basis = matrix.identity(3) - sage: QM = compute_gram_matrix_from_basis(Q, std_basis) + sage: QM = _compute_gram_matrix_from_basis(Q, std_basis) sage: Q.Gram_matrix() == QM True @@ -709,7 +708,7 @@ def compute_gram_matrix_from_basis(Q, basis): sage: Q = DiagonalQuadraticForm(QQ, [1, 2, 2]) sage: basis = matrix([[1, 1, 0], [2, 1, 0], [0, 0, 1]]) - sage: QM = compute_gram_matrix_from_basis(Q, basis); QM + sage: QM = _compute_gram_matrix_from_basis(Q, basis); QM [9 5 0] [5 3 0] [0 0 2] @@ -723,12 +722,14 @@ def compute_gram_matrix_from_basis(Q, basis): return Matrix(rows) -def modify_basis(basis, v, i): +def _modify_basis(basis, v, i): r""" - Modifies one of the vectors in a basis to be a linear combination of the vectors in the basis. + Given a lattice L with basis matrix M and a vector, v=(v_1,...,v_n) of length n, + this function extends the basis {b_1,...,b_n} of an underlying nxn orthogonal component + of L to contain the vector v_1b_1+...+v_nb_n. Note: This function is not intended for general use. It expects the length of ``v`` to be - the dimension of ``basis`` - ``i``. It was designed as a helper for the ``diagonally_isometry`` method. + the dimension of ``basis`` - ``i``. It was designed as a helper for the ``diagonal_isometry`` method. INPUT: @@ -742,17 +743,17 @@ def modify_basis(basis, v, i): EXAMPLES:: - sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import * + sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import _modify_basis sage: std_basis = matrix.identity(3) - sage: b1 = modify_basis(std_basis, vector([1, 1, 1]), 0); b1 + sage: b1 = _modify_basis(std_basis, vector([1, 1, 1]), 0); b1 [1 0 0] [1 1 0] [1 0 1] - sage: b2 = modify_basis(b1, vector([3, 2]), 1); b2 + sage: b2 = _modify_basis(b1, vector([3, 2]), 1); b2 [1 0 0] [1 3 0] [1 2 1] - sage: b3 = modify_basis(b2, vector([2, 2]), 1); b3 + sage: b3 = _modify_basis(b2, vector([2, 2]), 1); b3 [1 0 0] [1 6 0] [1 6 1] @@ -767,7 +768,7 @@ def modify_basis(basis, v, i): b.set_column(i, column) return b -def gram_schmidt(m, fixed_vector_index, inner_product): +def _gram_schmidt(m, fixed_vector_index, inner_product): r""" Orthogonalizes a set of vectors, starting at a fixed vector, with respect to a given inner product. @@ -786,7 +787,7 @@ def gram_schmidt(m, fixed_vector_index, inner_product): EXAMPLES:: - sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import * + sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import _gram_schmidt sage: Q = QuadraticForm(QQ, 3, [1, 4, 6, 1, 2, 4]); Q Quadratic form in 3 variables over Rational Field with coefficients: [ 1 4 6 ] @@ -796,7 +797,7 @@ def gram_schmidt(m, fixed_vector_index, inner_product): [1 2 3] [2 1 1] [3 1 4] - sage: om = gram_schmidt(QM, 0, Q.bilinear_map); om + sage: om = _gram_schmidt(QM, 0, Q.bilinear_map); om [ 1 106/79 -50/51] [ 2 -25/79 -70/51] [ 3 -77/79 70/51] From 132da46da12b3faa32c8cb22b25f25fb224cd80c Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Thu, 3 Mar 2016 16:11:44 -0500 Subject: [PATCH 014/170] attempt to simplify algorithm --- src/sage/quadratic_forms/quadratic_form.py | 3 +- .../quadratic_form__equivalence_testing.py | 242 ++++++++++++++++++ 2 files changed, 244 insertions(+), 1 deletion(-) diff --git a/src/sage/quadratic_forms/quadratic_form.py b/src/sage/quadratic_forms/quadratic_form.py index 0bb110288bd..f691339e8c2 100644 --- a/src/sage/quadratic_forms/quadratic_form.py +++ b/src/sage/quadratic_forms/quadratic_form.py @@ -373,7 +373,8 @@ class QuadraticForm(SageObject): is_globally_equivalent_to, \ is_locally_equivalent_to, \ has_equivalent_Jordan_decomposition_at_prime, \ - is_rationally_isometric + is_rationally_isometric, \ + isometry ## Routines for solving equations of the form Q(x) = c. from sage.quadratic_forms.qfsolve import solve diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index 69e67d03af9..45995465900 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -487,3 +487,245 @@ def is_rationally_isometric(self, other): return False return True + + + +def isometry(self, other): + r""" + Given two rationally equivalent quadratic forms, computes a + transition matrix mapping from one to the other. + + INPUT: + + - ``self`` -- a quadratic form + - ``other`` -- a quadratic form + + OUTPUT: + + - A matrix ``T`` representing the isometry transformation, such that if + ``QM`` is the gram matrix of ``self`` and ``FM`` is the gram matrix of + ``other``, then ``QM == T.transpose() * FM * T`` yields ``True``. + + EXAMPLES:: + + sage: Q = DiagonalQuadraticForm(QQ, [1, 1, 2]) + sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) + sage: T = Q.isometry(F); T + [-1/2 -1/2 0] + [ 0 0 -1] + [-1/2 1/2 0] + sage: Q.Gram_matrix() == T.transpose() * F.Gram_matrix() * T + True + + sage: T = F.isometry(Q); T + [-1/3 -4/3 -1/3] + [ -1 0 1] + [ 2/3 -1/3 2/3] + sage: F.Gram_matrix() == T.T * Q.Gram_matrix() * T + True + + :: + + sage: L = QuadraticForm(QQ, 3, [2, 2, 0, 2, 2, 5]) + sage: M = QuadraticForm(QQ, 3, [2, 2, 0, 3, 2, 3]) + sage: L.isometry(M) + Traceback (most recent call last): + ... + ArithmeticError: Quadratic form in 3 variables over Rational Field with coefficients: + [ 2 2 0 ] + [ * 2 2 ] + [ * * 5 ] is not rationally isometric to Quadratic form in 3 variables over Rational Field with coefficients: + [ 2 2 0 ] + [ * 3 2 ] + [ * * 3 ] + + :: + + sage: A = DiagonalQuadraticForm(QQ, [1, 5]) + sage: B = QuadraticForm(QQ, 2, [1, 12, 81]) + sage: T = A.isometry(B); T + [ 1 -2] + [ 0 1/3] + sage: A.Gram_matrix() == T.T * B.Gram_matrix() * T + True + + :: + + sage: C = DiagonalQuadraticForm(QQ, [1, 5, 9]) + sage: D = DiagonalQuadraticForm(QQ, [6, 30, 1]) + sage: T = C.isometry(D); T + [ 7/18 5/18 0] + [-1/18 7/18 0] + [ 0 0 3] + sage: C.Gram_matrix() == T.T * D.Gram_matrix() * T + True + + :: + + sage: E = DiagonalQuadraticForm(QQ, [1, 1]) + sage: F = QuadraticForm(QQ, 2, [17, 94, 130]) + sage: T = F.isometry(E); T + [ -4 -189/17] + [ -1 -43/17] + sage: F.Gram_matrix() == T.T * E.Gram_matrix() * T + True + """ + if not is_QuadraticForm(other): + raise TypeError("First argument must be a Quadratic Form.") + + if not self.is_rationally_isometric(other): + raise ArithmeticError("{0} is not rationally isometric to {1}".format(self, other)) + + Q, F = self, other + n = Q.dim() + + # If either form is not diagonal, diagonalize it. + Q, q_diagonal_transform = self.rational_diagonal_form(True) + F, f_diagonal_transform = other.rational_diagonal_form(True) + + # Call the method that does all the work to compute the isometry. + transform = _diagonal_isometry(Q,F) + + return f_diagonal_transform * transform * q_diagonal_transform.inverse() + + + +def _diagonal_isometry(V, W): + import copy + from sage.functions.other import sqrt + from quadratic_form import DiagonalQuadraticForm + from sage.matrix.constructor import Matrix + from sage.modules.free_module_element import vector + + Q, F = copy.deepcopy(V), copy.deepcopy(W) + #QM, FM = Q.Gram_matrix(), F.Gram_matrix() + n = Q.dim() + + change_of_basis_matrix = Matrix.identity(QQ, n) + #column_set? = [false for i in range(n)] + + for i in range(n): + print "i: {0}".format(i) + print "F = {0}".format(F) + print "Q = {0}".format(Q) + + if Q.Gram_matrix()[0][0] != F.Gram_matrix()[0][0]: + + w = F.solve(Q.Gram_matrix()[0][0]) + print "w: {0}".format(w) + change_of_basis_matrix.set_column(i, w) + print "matrix (before ortho):\n{0}".format(change_of_basis_matrix) + + FM = _compute_gram_matrix_from_basis(W, change_of_basis_matrix) + print "gram:\n{0}".format(FM) + + change_of_basis_matrix = _gram_schmidt(change_of_basis_matrix, i, W.bilinear_map) + print "matrix (after ortho):\n{0}".format(change_of_basis_matrix) + FM = _compute_gram_matrix_from_basis(W, change_of_basis_matrix) + print "gram:\n{0}".format(FM) + + F = DiagonalQuadraticForm(F.base_ring(), FM.diagonal()) + F = F.extract_variables(range(i+1, F.dim())) + #Q = DiagonalQuadraticForm(Q.base_ring(), Q.Gram_matrix().diagonal()) + Q = Q.extract_variables(range(1, Q.dim())) + + return change_of_basis_matrix + + + +def _compute_gram_matrix_from_basis(Q, basis): + r""" + Computes the gram matrix of a quadratic form with respect to the given basis. + + INPUT: + + - ``Q`` -- a quadratic form of rank n + - ``basis`` -- an nxn matrix whose columns are treated as basis vectors + + OUTPUT: + + - A matrix representing the gram matrix of ``Q`` with respect to ``basis`` + + EXAMPLES:: + + sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import _compute_gram_matrix_from_basis + + sage: Q = QuadraticForm(QQ, 3, [1, 2, 2, 1, 2, 0]) + sage: std_basis = matrix.identity(3) + sage: QM = _compute_gram_matrix_from_basis(Q, std_basis) + sage: Q.Gram_matrix() == QM + True + + :: + + sage: Q = DiagonalQuadraticForm(QQ, [1, 2, 2]) + sage: basis = matrix([[1, 1, 0], [2, 1, 0], [0, 0, 1]]) + sage: QM = _compute_gram_matrix_from_basis(Q, basis); QM + [9 5 0] + [5 3 0] + [0 0 2] + """ + from sage.matrix.constructor import matrix, Matrix + + n = Q.dim() + rows = []; + for i in range(n): + rows.append([Q.bilinear_map(basis.column(i), basis.column(j)) for j in range(n)]) + return Matrix(rows) + + + +def _gram_schmidt(m, fixed_vector_index, inner_product): + r""" + Orthogonalizes a set of vectors, starting at a fixed vector, with respect to a given + inner product. + + INPUT: + + - ``m`` -- a square matrix whose columns represent vectors + - ``fixed_vector_index`` -- any vectors preceding the vector (i.e. to its left) + at this index are not changed. + - ``inner_product`` - a function that takes two vector arguments and returns a scalar, + representing an inner product. + + OUTPUT: + + - A matrix consisting of orthogonal columns with respect to the given inner product + + EXAMPLES:: + + sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import _gram_schmidt + sage: Q = QuadraticForm(QQ, 3, [1, 4, 6, 1, 2, 4]); Q + Quadratic form in 3 variables over Rational Field with coefficients: + [ 1 4 6 ] + [ * 1 2 ] + [ * * 4 ] + sage: QM = Q.Gram_matrix(); QM + [1 2 3] + [2 1 1] + [3 1 4] + sage: om = _gram_schmidt(QM, 0, Q.bilinear_map); om + [ 1 106/79 -50/51] + [ 2 -25/79 -70/51] + [ 3 -77/79 70/51] + sage: v0 = om.column(0); v1 = om.column(1); v2 = om.column(2) + sage: Q.bilinear_map(v0, v1) == 0 + True + sage: Q.bilinear_map(v0, v2) == 0 + True + sage: Q.bilinear_map(v1, v2) == 0 + True + """ + from sage.matrix.constructor import column_matrix + + n = m.dimensions()[0] + vectors = [0] * n + + for i in range(n): + vectors[i] = m.column(i) + for i in range(fixed_vector_index, n): + for j in range(i+1, n): + vectors[j] = vectors[j] - (inner_product(vectors[j], vectors[i]) / inner_product(vectors[i], vectors[i])) * vectors[i] + + return column_matrix(vectors) + From 24e01fe5dfcbdb6e31947f20e9ddefd3746c20cc Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Wed, 9 Mar 2016 14:35:22 -0500 Subject: [PATCH 015/170] Simplified algorithm and code for 'isometry' and its helpers --- .../quadratic_form__equivalence_testing.py | 157 ++++++++++++++---- 1 file changed, 121 insertions(+), 36 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index 45995465900..d7c55fa73d7 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -508,20 +508,20 @@ def isometry(self, other): EXAMPLES:: - sage: Q = DiagonalQuadraticForm(QQ, [1, 1, 2]) - sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) - sage: T = Q.isometry(F); T + sage: V = DiagonalQuadraticForm(QQ, [1, 1, 2]) + sage: W = DiagonalQuadraticForm(QQ, [2, 2, 2]) + sage: T = V.isometry(W); T + [ 0 0 1] [-1/2 -1/2 0] - [ 0 0 -1] - [-1/2 1/2 0] - sage: Q.Gram_matrix() == T.transpose() * F.Gram_matrix() * T + [ 1/2 -1/2 0] + sage: V.Gram_matrix() == T.transpose() * W.Gram_matrix() * T True - sage: T = F.isometry(Q); T - [-1/3 -4/3 -1/3] - [ -1 0 1] - [ 2/3 -1/3 2/3] - sage: F.Gram_matrix() == T.T * Q.Gram_matrix() * T + sage: T = W.isometry(V); T + [ 0 -1 1] + [ 0 -1 -1] + [ 1 0 0] + sage: W.Gram_matrix() == T.T * V.Gram_matrix() * T True :: @@ -554,9 +554,9 @@ def isometry(self, other): sage: C = DiagonalQuadraticForm(QQ, [1, 5, 9]) sage: D = DiagonalQuadraticForm(QQ, [6, 30, 1]) sage: T = C.isometry(D); T - [ 7/18 5/18 0] - [-1/18 7/18 0] - [ 0 0 3] + [ 0 -5/6 1/2] + [ 0 1/6 1/2] + [ -1 0 0] sage: C.Gram_matrix() == T.T * D.Gram_matrix() * T True @@ -576,14 +576,11 @@ def isometry(self, other): if not self.is_rationally_isometric(other): raise ArithmeticError("{0} is not rationally isometric to {1}".format(self, other)) - Q, F = self, other - n = Q.dim() - - # If either form is not diagonal, diagonalize it. + # Ensure that both quadratic forms are diagonal. Q, q_diagonal_transform = self.rational_diagonal_form(True) F, f_diagonal_transform = other.rational_diagonal_form(True) - # Call the method that does all the work to compute the isometry. + # Call the method that does all the work to compute the transformation. transform = _diagonal_isometry(Q,F) return f_diagonal_transform * transform * q_diagonal_transform.inverse() @@ -591,42 +588,84 @@ def isometry(self, other): def _diagonal_isometry(V, W): + r""" + Given two diagonal, rationally equivalent quadratic forms, computes a + transition matrix mapping from one to the other. + + Note: This function is an auxilliary method of ``isometry``, which is the + method that should be called as it performs error-checking that isn't present + in this function. + + INPUT: + + - ``V`` -- a diagonal quadratic form + - ``W`` -- a diagonal quadratic form + + OUTPUT: + + - A matrix ``T`` representing the isometry transformation, such that if + ``VM`` is the gram matrix of ``V`` and ``WM`` is the gram matrix of + ``W``, then ``VM == T.transpose() * WM * T`` yields ``True``. + + EXAMPLES:: + + sage: Q = DiagonalQuadraticForm(QQ, [1, 2, 4]) + sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) + + sage: T = Q.isometry(F); T + [ 0 1 0] + [-1/2 0 1] + [ 1/2 0 1] + sage: Q.Gram_matrix() == T.T * F.Gram_matrix() * T + True + + sage: T = F.isometry(Q); T + [ 0 -1 -1] + [ 1 0 0] + [ 0 -1/2 1/2] + sage: F.Gram_matrix() == T.T * Q.Gram_matrix() * T + True + """ import copy - from sage.functions.other import sqrt from quadratic_form import DiagonalQuadraticForm from sage.matrix.constructor import Matrix - from sage.modules.free_module_element import vector Q, F = copy.deepcopy(V), copy.deepcopy(W) - #QM, FM = Q.Gram_matrix(), F.Gram_matrix() + FM = F.Gram_matrix() n = Q.dim() change_of_basis_matrix = Matrix.identity(QQ, n) - #column_set? = [false for i in range(n)] for i in range(n): - print "i: {0}".format(i) - print "F = {0}".format(F) - print "Q = {0}".format(Q) - + # If the first terms are not equal... if Q.Gram_matrix()[0][0] != F.Gram_matrix()[0][0]: - + # Find a vector w in F such that F(w) equals the first term of Q. w = F.solve(Q.Gram_matrix()[0][0]) - print "w: {0}".format(w) - change_of_basis_matrix.set_column(i, w) - print "matrix (before ortho):\n{0}".format(change_of_basis_matrix) - FM = _compute_gram_matrix_from_basis(W, change_of_basis_matrix) - print "gram:\n{0}".format(FM) + # Find a non-fixed vector in the current basis to replace by w. + # The new set of vectors must still be linearly independent (i.e. the matrix is non-singular). + j = i + temp_matrix = _modify_basis(change_of_basis_matrix, w, j) + while temp_matrix.is_singular(): + j = j + 1 + temp_matrix = _modify_basis(change_of_basis_matrix, w, j) + change_of_basis_matrix = copy.deepcopy(temp_matrix) + + # We want to fix w to be the basis vector at position i, so swap it with whatever is already there. + col = change_of_basis_matrix.column(i) + change_of_basis_matrix.set_column(i, change_of_basis_matrix.column(j)) + change_of_basis_matrix.set_column(j, col) + + # Orthogonalize the basis. change_of_basis_matrix = _gram_schmidt(change_of_basis_matrix, i, W.bilinear_map) - print "matrix (after ortho):\n{0}".format(change_of_basis_matrix) + + # Obtain the diagonal gram matrix of F. FM = _compute_gram_matrix_from_basis(W, change_of_basis_matrix) - print "gram:\n{0}".format(FM) + # Remove the first term from each quadratic form and continue. F = DiagonalQuadraticForm(F.base_ring(), FM.diagonal()) F = F.extract_variables(range(i+1, F.dim())) - #Q = DiagonalQuadraticForm(Q.base_ring(), Q.Gram_matrix().diagonal()) Q = Q.extract_variables(range(1, Q.dim())) return change_of_basis_matrix @@ -728,4 +767,50 @@ def _gram_schmidt(m, fixed_vector_index, inner_product): vectors[j] = vectors[j] - (inner_product(vectors[j], vectors[i]) / inner_product(vectors[i], vectors[i])) * vectors[i] return column_matrix(vectors) + + +def _modify_basis(basis, v, pos): + r""" + Given a matrix ``basis`` that represents a basis {b_1, ..., b_m} + (where b_i is represented by the ith column in ``basis``) and a vector ``v`` of length n, + this function sets the column at index ``pos`` to be the vector v_1b_{m-n} + ... + v_nb_m. + + INPUT: + + - ``basis`` -- a square matrix whose columns represent vectors in a basis. + - ``v`` -- a vector that represents a linear combination of the vectors in ``basis``. + - ``pos`` -- the index of the column in ``basis`` to be modified. + + OUTPUT: + + - A matrix representing the basis with one of its vectors changed to a linear combination of the basis vectors. + + EXAMPLES:: + + sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import _modify_basis + sage: std_basis = matrix.identity(3) + sage: b1 = _modify_basis(std_basis, vector([1, 1, 1]), 0); b1 + [1 0 0] + [1 1 0] + [1 0 1] + sage: b2 = _modify_basis(b1, vector([3, 2]), 1); b2 + [1 0 0] + [1 3 0] + [1 2 1] + sage: b3 = _modify_basis(b2, vector([2, 2]), 1); b3 + [1 0 0] + [1 6 0] + [1 6 1] + """ + import copy + from sage.modules.free_module_element import vector + + b = copy.deepcopy(basis) + m = b.dimensions()[0] + n = m - len(v) + column = vector(QQ, m) + for j in range(len(v)): + column += b.column(n + j) * v[j] + b.set_column(pos, column) + return b From 642df1071e10e3a771abd7d02ceff017c0a3ef30 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Wed, 9 Mar 2016 14:51:00 -0500 Subject: [PATCH 016/170] Fixed typo in documentation example for _diagonal_isometry --- src/sage/quadratic_forms/quadratic_form__equivalence_testing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index dec57c2bfa4..763c68aed15 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -620,6 +620,7 @@ def _diagonal_isometry(V, W): [ 0 -1 -1] [ 1 0 0] [ 0 -1/2 1/2] + sage: F.Gram_matrix() == T.T * Q.Gram_matrix() * T True """ import copy From 2b8f69416667585a5d5d95bfb0272ffd059d066c Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Fri, 11 Mar 2016 13:16:07 -0500 Subject: [PATCH 017/170] removed old line from quadratic_form__automorphisms --- src/sage/quadratic_forms/quadratic_form__automorphisms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/quadratic_forms/quadratic_form__automorphisms.py b/src/sage/quadratic_forms/quadratic_form__automorphisms.py index 1148366c537..156b520b866 100644 --- a/src/sage/quadratic_forms/quadratic_form__automorphisms.py +++ b/src/sage/quadratic_forms/quadratic_form__automorphisms.py @@ -213,7 +213,6 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): # In certain trivial cases, PARI can sometimes return longer # vectors than requested. if length < len_bound: - v = parilist[i] sagevec = V(list(parilist[i])) vec_sorted_list[length].append(sagevec) if not up_to_sign_flag : From 70ed4293b680ec8859683a8e1c3b7791c1c67e73 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Fri, 11 Mar 2016 14:00:00 -0500 Subject: [PATCH 018/170] Fixed documentation errors and added more comments to _diagonal_isometry --- .../quadratic_form__equivalence_testing.py | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index 763c68aed15..4471960ce29 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -605,18 +605,19 @@ def _diagonal_isometry(V, W): ``W``, then ``VM == T.transpose() * WM * T`` yields ``True``. EXAMPLES:: - + sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import _diagonal_isometry + sage: Q = DiagonalQuadraticForm(QQ, [1, 2, 4]) sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) - sage: T = Q.isometry(F); T + sage: T = _diagonal_isometry(Q, F); T [ 0 1 0] [-1/2 0 1] [ 1/2 0 1] sage: Q.Gram_matrix() == T.T * F.Gram_matrix() * T True - sage: T = F.isometry(Q); T + sage: T = _diagonal_isometry(F, Q); T [ 0 -1 -1] [ 1 0 0] [ 0 -1/2 1/2] @@ -627,22 +628,30 @@ def _diagonal_isometry(V, W): from quadratic_form import DiagonalQuadraticForm from sage.matrix.constructor import Matrix + # We need to modify V and W, so copy them into Q and F respectively. Q, F = copy.deepcopy(V), copy.deepcopy(W) + # Let FM denote the Gram matrix of F. FM = F.Gram_matrix() n = Q.dim() + # This matrix represents a new basis for W, where the columns of the + # matrix are the vectors of the basis. We initialize it to the standard basis. change_of_basis_matrix = Matrix.identity(QQ, n) + # The goal of this loop is to obtain a new basis for W such that the + # Gram matrix of V with respect to the standard basis equals the Gram matrix + # of W with respect to the new basis. for i in range(n): # If the first terms are not equal... if Q.Gram_matrix()[0][0] != F.Gram_matrix()[0][0]: # Find a vector w in F such that F(w) equals the first term of Q. w = F.solve(Q.Gram_matrix()[0][0]) + # We want to extend the basis of W to include the vector w. # Find a non-fixed vector in the current basis to replace by w. - # The new set of vectors must still be linearly independent (i.e. the matrix is non-singular). j = i temp_matrix = _modify_basis(change_of_basis_matrix, w, j) + # The new set of vectors must still be linearly independent (i.e. the matrix is non-singular). while temp_matrix.is_singular(): j = j + 1 temp_matrix = _modify_basis(change_of_basis_matrix, w, j) @@ -660,7 +669,8 @@ def _diagonal_isometry(V, W): # Obtain the diagonal gram matrix of F. FM = _compute_gram_matrix_from_basis(W, change_of_basis_matrix) - # Remove the first term from each quadratic form and continue. + # Now we have that QM[0][0] == FM[0][0] where QM and FM are the Gram matrices + # of Q and F respectively. We remove the first variable from each form and continue. F = DiagonalQuadraticForm(F.base_ring(), FM.diagonal()) F = F.extract_variables(range(i+1, F.dim())) Q = Q.extract_variables(range(1, Q.dim())) @@ -728,26 +738,31 @@ def _gram_schmidt(m, fixed_vector_index, inner_product): EXAMPLES:: - sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import _gram_schmidt - sage: Q = QuadraticForm(QQ, 3, [1, 4, 6, 1, 2, 4]); Q + sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import _gram_schmidt, _compute_gram_matrix_from_basis + sage: Q = QuadraticForm(QQ, 3, [1, 2, 2, 2, 1, 3]); Q Quadratic form in 3 variables over Rational Field with coefficients: - [ 1 4 6 ] - [ * 1 2 ] - [ * * 4 ] + [ 1 2 2 ] + [ * 2 1 ] + [ * * 3 ] sage: QM = Q.Gram_matrix(); QM - [1 2 3] - [2 1 1] - [3 1 4] - sage: om = _gram_schmidt(QM, 0, Q.bilinear_map); om - [ 1 106/79 -50/51] - [ 2 -25/79 -70/51] - [ 3 -77/79 70/51] - sage: v0 = om.column(0); v1 = om.column(1); v2 = om.column(2) - sage: Q.bilinear_map(v0, v1) == 0 + [ 1 1 1] + [ 1 2 1/2] + [ 1 1/2 3] + sage: std_basis = matrix.identity(3) + sage: ortho_basis = _gram_schmidt(std_basis, 0, Q.bilinear_map); ortho_basis + [ 1 -1 -3/2] + [ 0 1 1/2] + [ 0 0 1] + sage: _compute_gram_matrix_from_basis(Q, ortho_basis) + [ 1 0 0] + [ 0 1 0] + [ 0 0 7/4] + sage: v1 = ortho_basis.column(0); v2 = ortho_basis.column(1); v3 = ortho_basis.column(2); + sage: Q.bilinear_map(v1, v2) == 0 True - sage: Q.bilinear_map(v0, v2) == 0 + sage: Q.bilinear_map(v1, v3) == 0 True - sage: Q.bilinear_map(v1, v2) == 0 + sage: Q.bilinear_map(v2, v3) == 0 True """ from sage.matrix.constructor import column_matrix From 0b8841492ca2e5714879cadacb6076f595927bd7 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Sat, 27 Aug 2016 21:51:55 -0400 Subject: [PATCH 019/170] Changed 'from quadratic_form' to 'from sage.quadratic_forms.quadratic_form' --- .../quadratic_forms/quadratic_form__equivalence_testing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index e0c5ece6188..34ece1518f5 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -12,7 +12,7 @@ from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ -from .quadratic_form import is_QuadraticForm +from sage.quadratic_forms.quadratic_form import is_QuadraticForm ################################################################################ @@ -604,7 +604,7 @@ def _diagonal_isometry(V, W): True """ import copy - from quadratic_form import DiagonalQuadraticForm + from sage.quadratic_forms.quadratic_form import DiagonalQuadraticForm from sage.matrix.constructor import Matrix # We need to modify V and W, so copy them into Q and F respectively. From 2b394f41bd18eb22c6558a1441444692b79caec7 Mon Sep 17 00:00:00 2001 From: Tyler Gaona Date: Mon, 29 Aug 2016 08:51:01 -0400 Subject: [PATCH 020/170] Removed trailing whitespace. --- .../quadratic_form__equivalence_testing.py | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index 34ece1518f5..33753142126 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -469,7 +469,7 @@ def is_rationally_isometric(self, other): def isometry(self, other): r""" - Given two rationally equivalent quadratic forms, computes a + Given two rationally equivalent quadratic forms, computes a transition matrix mapping from one to the other. INPUT: @@ -478,13 +478,13 @@ def isometry(self, other): - ``other`` -- a quadratic form OUTPUT: - - - A matrix ``T`` representing the isometry transformation, such that if - ``QM`` is the gram matrix of ``self`` and ``FM`` is the gram matrix of + + - A matrix ``T`` representing the isometry transformation, such that if + ``QM`` is the gram matrix of ``self`` and ``FM`` is the gram matrix of ``other``, then ``QM == T.transpose() * FM * T`` yields ``True``. EXAMPLES:: - + sage: V = DiagonalQuadraticForm(QQ, [1, 1, 2]) sage: W = DiagonalQuadraticForm(QQ, [2, 2, 2]) sage: T = V.isometry(W); T @@ -493,7 +493,7 @@ def isometry(self, other): [ 1/2 -1/2 0] sage: V.Gram_matrix() == T.transpose() * W.Gram_matrix() * T True - + sage: T = W.isometry(V); T [ 0 -1 1] [ 0 -1 -1] @@ -508,16 +508,16 @@ def isometry(self, other): sage: L.isometry(M) Traceback (most recent call last): ... - ArithmeticError: Quadratic form in 3 variables over Rational Field with coefficients: + ArithmeticError: Quadratic form in 3 variables over Rational Field with coefficients: [ 2 2 0 ] [ * 2 2 ] - [ * * 5 ] is not rationally isometric to Quadratic form in 3 variables over Rational Field with coefficients: + [ * * 5 ] is not rationally isometric to Quadratic form in 3 variables over Rational Field with coefficients: [ 2 2 0 ] [ * 3 2 ] [ * * 3 ] :: - + sage: A = DiagonalQuadraticForm(QQ, [1, 5]) sage: B = QuadraticForm(QQ, 2, [1, 12, 81]) sage: T = A.isometry(B); T @@ -527,18 +527,18 @@ def isometry(self, other): True :: - + sage: C = DiagonalQuadraticForm(QQ, [1, 5, 9]) sage: D = DiagonalQuadraticForm(QQ, [6, 30, 1]) sage: T = C.isometry(D); T [ 0 -5/6 1/2] [ 0 1/6 1/2] - [ -1 0 0] + [ -1 0 0] sage: C.Gram_matrix() == T.T * D.Gram_matrix() * T True :: - + sage: E = DiagonalQuadraticForm(QQ, [1, 1]) sage: F = QuadraticForm(QQ, 2, [17, 94, 130]) sage: T = F.isometry(E); T @@ -556,16 +556,16 @@ def isometry(self, other): # Ensure that both quadratic forms are diagonal. Q, q_diagonal_transform = self.rational_diagonal_form(True) F, f_diagonal_transform = other.rational_diagonal_form(True) - + # Call the method that does all the work to compute the transformation. transform = _diagonal_isometry(Q,F) - + return f_diagonal_transform * transform * q_diagonal_transform.inverse() def _diagonal_isometry(V, W): r""" - Given two diagonal, rationally equivalent quadratic forms, computes a + Given two diagonal, rationally equivalent quadratic forms, computes a transition matrix mapping from one to the other. Note: This function is an auxilliary method of ``isometry``, which is the @@ -578,9 +578,9 @@ def _diagonal_isometry(V, W): - ``W`` -- a diagonal quadratic form OUTPUT: - - - A matrix ``T`` representing the isometry transformation, such that if - ``VM`` is the gram matrix of ``V`` and ``WM`` is the gram matrix of + + - A matrix ``T`` representing the isometry transformation, such that if + ``VM`` is the gram matrix of ``V`` and ``WM`` is the gram matrix of ``W``, then ``VM == T.transpose() * WM * T`` yields ``True``. EXAMPLES:: @@ -588,7 +588,7 @@ def _diagonal_isometry(V, W): sage: Q = DiagonalQuadraticForm(QQ, [1, 2, 4]) sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) - + sage: T = _diagonal_isometry(Q, F); T [ 0 1 0] [-1/2 0 1] @@ -606,18 +606,18 @@ def _diagonal_isometry(V, W): import copy from sage.quadratic_forms.quadratic_form import DiagonalQuadraticForm from sage.matrix.constructor import Matrix - + # We need to modify V and W, so copy them into Q and F respectively. Q, F = copy.deepcopy(V), copy.deepcopy(W) # Let FM denote the Gram matrix of F. FM = F.Gram_matrix() n = Q.dim() - # This matrix represents a new basis for W, where the columns of the + # This matrix represents a new basis for W, where the columns of the # matrix are the vectors of the basis. We initialize it to the standard basis. change_of_basis_matrix = Matrix.identity(QQ, n) - # The goal of this loop is to obtain a new basis for W such that the + # The goal of this loop is to obtain a new basis for W such that the # Gram matrix of V with respect to the standard basis equals the Gram matrix # of W with respect to the new basis. for i in range(n): @@ -647,7 +647,7 @@ def _diagonal_isometry(V, W): # Obtain the diagonal gram matrix of F. FM = _compute_gram_matrix_from_basis(W, change_of_basis_matrix) - + # Now we have that QM[0][0] == FM[0][0] where QM and FM are the Gram matrices # of Q and F respectively. We remove the first variable from each form and continue. F = DiagonalQuadraticForm(F.base_ring(), FM.diagonal()) @@ -669,11 +669,11 @@ def _compute_gram_matrix_from_basis(Q, basis): OUTPUT: - A matrix representing the gram matrix of ``Q`` with respect to ``basis`` - + EXAMPLES:: sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import _compute_gram_matrix_from_basis - + sage: Q = QuadraticForm(QQ, 3, [1, 2, 2, 1, 2, 0]) sage: std_basis = matrix.identity(3) sage: QM = _compute_gram_matrix_from_basis(Q, std_basis) @@ -700,13 +700,13 @@ def _compute_gram_matrix_from_basis(Q, basis): def _gram_schmidt(m, fixed_vector_index, inner_product): r""" - Orthogonalizes a set of vectors, starting at a fixed vector, with respect to a given + Orthogonalizes a set of vectors, starting at a fixed vector, with respect to a given inner product. INPUT: - ``m`` -- a square matrix whose columns represent vectors - - ``fixed_vector_index`` -- any vectors preceding the vector (i.e. to its left) + - ``fixed_vector_index`` -- any vectors preceding the vector (i.e. to its left) at this index are not changed. - ``inner_product`` - a function that takes two vector arguments and returns a scalar, representing an inner product. @@ -714,12 +714,12 @@ def _gram_schmidt(m, fixed_vector_index, inner_product): OUTPUT: - A matrix consisting of orthogonal columns with respect to the given inner product - - EXAMPLES:: + + EXAMPLES:: sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import _gram_schmidt, _compute_gram_matrix_from_basis sage: Q = QuadraticForm(QQ, 3, [1, 2, 2, 2, 1, 3]); Q - Quadratic form in 3 variables over Rational Field with coefficients: + Quadratic form in 3 variables over Rational Field with coefficients: [ 1 2 2 ] [ * 2 1 ] [ * * 3 ] @@ -745,10 +745,10 @@ def _gram_schmidt(m, fixed_vector_index, inner_product): True """ from sage.matrix.constructor import column_matrix - + n = m.dimensions()[0] vectors = [0] * n - + for i in range(n): vectors[i] = m.column(i) for i in range(fixed_vector_index, n): @@ -759,8 +759,8 @@ def _gram_schmidt(m, fixed_vector_index, inner_product): def _modify_basis(basis, v, pos): - r""" - Given a matrix ``basis`` that represents a basis {b_1, ..., b_m} + r""" + Given a matrix ``basis`` that represents a basis {b_1, ..., b_m} (where b_i is represented by the ith column in ``basis``) and a vector ``v`` of length n, this function sets the column at index ``pos`` to be the vector v_1b_{m-n} + ... + v_nb_m. @@ -773,11 +773,11 @@ def _modify_basis(basis, v, pos): OUTPUT: - A matrix representing the basis with one of its vectors changed to a linear combination of the basis vectors. - + EXAMPLES:: sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import _modify_basis - sage: std_basis = matrix.identity(3) + sage: std_basis = matrix.identity(3) sage: b1 = _modify_basis(std_basis, vector([1, 1, 1]), 0); b1 [1 0 0] [1 1 0] @@ -796,7 +796,7 @@ def _modify_basis(basis, v, pos): b = copy.deepcopy(basis) m = b.dimensions()[0] - n = m - len(v) + n = m - len(v) column = vector(QQ, m) for j in range(len(v)): column += b.column(n + j) * v[j] From 3308c92d78c4ef22373f2cf7cccaf5d2273f6a79 Mon Sep 17 00:00:00 2001 From: John Doe Date: Tue, 8 Mar 2016 21:53:51 +0100 Subject: [PATCH 021/170] Implement Gosper algorithm for homographic action on continued fractions --- src/sage/rings/continued_fraction.py | 122 +++++++++-- src/sage/rings/continued_fraction_gosper.py | 213 ++++++++++++++++++++ 2 files changed, 321 insertions(+), 14 deletions(-) create mode 100644 src/sage/rings/continued_fraction_gosper.py diff --git a/src/sage/rings/continued_fraction.py b/src/sage/rings/continued_fraction.py index e5464474cf4..21e3b45ff38 100644 --- a/src/sage/rings/continued_fraction.py +++ b/src/sage/rings/continued_fraction.py @@ -174,13 +174,6 @@ .. TODO:: - - Gosper's algorithm to compute the continued fraction of (ax + b)/(cx + d) - knowing the one of x (see Gosper (1972, - http://www.inwap.com/pdp10/hbaker/hakmem/cf.html), Knuth (1998, TAOCP vol - 2, Exercise 4.5.3.15), Fowler (1999). See also Liardet, P. and Stambul, P. - "Algebraic Computation with Continued Fractions." J. Number Th. 73, - 92-121, 1998. - - Improve numerical approximation (the method :meth:`~ContinuedFraction_base._mpfr_` is quite slow compared to the same method for an element of a number field) @@ -206,6 +199,7 @@ from sage.structure.richcmp import richcmp_method, rich_to_bool from .integer import Integer from .infinity import Infinity +from .continued_fraction_gosper import gosper_iterator ZZ_0 = Integer(0) ZZ_1 = Integer(1) @@ -1136,6 +1130,68 @@ def numerical_approx(self, prec=None, digits=None, algorithm=None): n = numerical_approx + def apply_homography(self, a, b, c, d): + """ + Return a new continued fraction (ax + b)/(cx + d). + + This is computed using Gosper's algorithm. + + - Gosper's algorithm to compute the continued fraction of (ax + + b)/(cx + d) knowing the one of x ( + + INPUT: + + - ``a, b, c, d`` -- integer coefficients + + EXAMPLES:: + + sage: a = Integer(randint(-10,10)); b = Integer(randint(-10,10)); + sage: c = Integer(randint(-10,10)); d = Integer(randint(-10,10)); + sage: vals = [pi, sqrt(2), 541/227]; + sage: for val in vals: + ....: x = continued_fraction(val) + ....: y = continued_fraction((a*val+b)/(c*val+d)) + ....: z = x.apply_homography(a,b,c,d) + ....: y == z + ....: + True + True + True + sage: x = continued_fraction(([1,2,3],[4,5])) + sage: val = x.value() + sage: y = continued_fraction((a*val+b)/(c*val+d)) + sage: z = x.apply_homography(a,b,c,d) + sage: y == z + True + + REFERENCES: + + - Gosper (1972, http://www.inwap.com/pdp10/hbaker/hakmem/cf.html), + - Knuth (1998, TAOCP vol 2, Exercise 4.5.3.15), + - Fowler (1999), + - Liardet, P. and Stambul, P. "Algebraic Computation + with Continued Fractions." J. Number Th. 73, 92-121, 1998. + """ + from .rational_field import QQ + from sage.rings.number_field.number_field_element_quadratic import NumberFieldElement_quadratic + + if not all(isinstance(x, Integer) for x in (a, b, c, d)): + raise AttributeError("coefficients a, b, c, d must be integers") + + x = self.value() + z = (a * x + b) / (c * x + d) + _i = iter(gosper_iterator(a, b, c, d, self)) + + if z in QQ or isinstance(z, NumberFieldElement_quadratic): + l = list(_i) + preperiod_length = _i.output_preperiod_length + preperiod = l[:preperiod_length] + period = l[preperiod_length:] + return continued_fraction((preperiod, period), z) + else: + from sage.misc.lazy_list import lazy_list + return continued_fraction(lazy_list(_i), z) + class ContinuedFraction_periodic(ContinuedFraction_base): r""" @@ -1266,6 +1322,36 @@ def length(self): return Infinity return Integer(len(self._x1)) + def preperiod_length(self): + r""" + Returns the number of partial quotients of the preperiodic part of ``self``. + + EXAMPLES:: + + sage: continued_fraction(2/5).preperiod_length() + 3 + sage: cf = continued_fraction([(0,1),(2,)]); cf + [0; 1, (2)*] + sage: cf.preperiod_length() + 2 + """ + return Integer(len(self._x1)) + + def period_length(self): + r""" + Return the number of partial quotients of the preperiodic part of ``self``. + + EXAMPLES:: + + sage: continued_fraction(2/5).period_length() + 1 + sage: cf = continued_fraction([(0,1),(2,)]); cf + [0; 1, (2)*] + sage: cf.period_length() + 1 + """ + return Integer(len(self._x2)) + def __richcmp__(self, other, op): r""" Rich comparison. @@ -1683,12 +1769,6 @@ def __richcmp__(self, other, op): False """ try: - # The following is crazy and prevent us from using cmp(self.value(), - # other.value()). On sage-5.10.beta2: - # sage: cmp(pi, 4) - # -1 - # sage: cmp(pi, pi+4) - # 1 if self.value() == other.value(): return rich_to_bool(op, 0) if self.value() - other.value() > 0: @@ -1942,7 +2022,7 @@ def _repr_(self): sage: # TODO """ - return "[" + str(self._w[0]) + "; " + ", ".join(map(str,self._w[1:20])) + "...]" + return "[" + str(self._w[0]) + "; " + ", ".join(map(str,self._w[1:20])) + ", ...]" def length(self): r""" @@ -2036,8 +2116,22 @@ def value(self): return self._value else: from sage.rings.real_lazy import RLF + if self._w[0] < 0: + return -RLF(-self) return RLF(self) + def __neg__(self): + """ + Return the opposite of ``self``. + """ + from sage.combinat.words.word import Word + _w = self._w + if _w[1] == 1: + _w = Word((-_w[0]-1, _w[2]+1)).concatenate(_w[3:]) + else: + _w = Word((-_w[0]-1, ZZ_1, _w[1]-1)).concatenate(_w[2:]) + return self.__class__(_w) + def check_and_reduce_pair(x1, x2=None): r""" diff --git a/src/sage/rings/continued_fraction_gosper.py b/src/sage/rings/continued_fraction_gosper.py new file mode 100644 index 00000000000..0ed56fa9eed --- /dev/null +++ b/src/sage/rings/continued_fraction_gosper.py @@ -0,0 +1,213 @@ +""" +Gosper iterator + +A class which serves as a stateful iterable for computing the terms of the continued fraction of `(a*x+b)/(c*x+d)`, +where `a, b, c, d` are integers, and `x` is a continued fraction. + +EXAMPLES:: + + sage: from sage.rings.continued_fraction_gosper import gosper_iterator + sage: x = continued_fraction(pi) + sage: it = iter(gosper_iterator(3,2,3,1,x)) + sage: Word(it, length='infinite') + word: 1,10,2,2,1,4,1,1,1,97,4,1,2,1,2,45,6,4,9,1,27,2,6,1,4,2,3,1,3,1,15,2,1,1,2,1,1,2,32,1,... +""" +from sage.rings.infinity import Infinity +from sage.rings.integer import Integer +from sage.rings.real_mpfr import RR + + +class gosper_iterator(object): + + def __init__(self, a, b, c, d, x): + """ + Construct the class. + + INPUT: + + - ``a, b, c, d`` -- Integer coefficients of the transformation. + - ``x`` -- An instance of a continued fraction. + + OUTPUT: + + - The instance of gosper_iterator class. + + TESTS:: + + sage: a = Integer(randint(-10,10)); b = Integer(randint(-10,10)); + sage: c = Integer(randint(-10,10)); d = Integer(randint(-10,10)); + sage: from sage.rings.continued_fraction_gosper import gosper_iterator + sage: x = continued_fraction(([1,2],[3,4])); i = iter(gosper_iterator(a,b,c,d,x)) + sage: l = list(i) + sage: preperiod_length = i.output_preperiod_length + sage: preperiod = l[:preperiod_length] + sage: period = l[preperiod_length:] + sage: continued_fraction((preperiod, period), x.value()) == continued_fraction((a*x.value()+b)/(c*x.value()+d)) + True + """ + from sage.rings.continued_fraction import ContinuedFraction_periodic + self.a = a + self.b = b + self.c = c + self.d = d + + self.x = iter(x) + + self.states = set() + self.states_to_currently_emitted = dict() + + self.currently_emitted = 0 + self.currently_read = 0 + + # Rational or quadratic case + if isinstance(x, ContinuedFraction_periodic): + self.input_preperiod_length = x.preperiod_length() + self.input_period_length = x.period_length() + # Infinite case + else: + self.input_preperiod_length = +Infinity + self.input_period_length = 0 + + self.output_preperiod_length = 0 + + def __iter__(self): + """ + Return the iterable instance of the class. + + Is called upon `iter(gosper_iterator(a,b,c,d,x))`. + + TESTS:: + + sage: a = Integer(randint(-100,100)); b = Integer(randint(-100,100)); + sage: c = Integer(randint(-100,100)); d = Integer(randint(-100,100)); + sage: from sage.rings.continued_fraction_gosper import gosper_iterator + sage: ig = iter(gosper_iterator(a,b,c,d,continued_fraction(pi))); icf = iter(continued_fraction((a*pi+b)/(c*pi+d))); + sage: lg = [next(ig) for _ in range(10)]; lcf = [next(icf) for _ in range(10)]; + sage: lg == lcf + True + """ + return self + + def __next__(self): + """ + Return the next term of the transformation. + + TESTS:: + + sage: a = Integer(randint(-100,100)); b = Integer(randint(-100,100)); + sage: c = Integer(randint(-100,100)); d = Integer(randint(-100,100)); + sage: from sage.rings.continued_fraction_gosper import gosper_iterator + sage: ig = iter(gosper_iterator(a,b,c,d,continued_fraction(pi))); icf = iter(continued_fraction((a*pi+b)/(c*pi+d))); + sage: for i in range(10): + ....: assert next(ig) == next(icf) + """ + limit = 100 + while True: + if self.currently_read >= self.input_preperiod_length: + current_state = ( + ('a', self.a), + ('b', self.b), + ('c', self.c), + ('d', self.d), + ('index', (self.currently_read - self.input_preperiod_length) % self.input_period_length) + ) + # for state in self.states: + # if self.compare_dicts(state, current_state, ['currently_emitted']): + # self.output_preperiod_length = state['currently_emitted'] + # raise StopIteration + if current_state in self.states: + self.output_preperiod_length = self.states_to_currently_emitted[current_state] + raise StopIteration + self.states.add(current_state) + self.states_to_currently_emitted[current_state] = self.currently_emitted + if len(self.states) > 100: + print("ERROR: Stopping iteration, danger of memory overflow.") + raise StopIteration + + if (self.c == 0 and self.d == 0): + raise StopIteration + + ub = self.bound(self.a, self.c) + lb = self.bound(self.a + self.b, self.c + self.d) + s = -self.bound(self.c, self.d) + + if ub == lb and s < 1: + self.emit(ub) + return Integer(ub) + else: + self.ingest() + + limit -= 1 + if limit < 1: + print("ERROR: Next loop iteration ran too many times.") + raise StopIteration + + def emit(self, q): + """ + Change the state of the iterator, emitting the term `q`. + + TESTS:: + + sage: a = Integer(randint(-100,100)); b = Integer(randint(-100,100)); + sage: c = Integer(randint(-100,100)); d = Integer(randint(-100,100)); + sage: from sage.rings.continued_fraction_gosper import gosper_iterator + sage: gi = gosper_iterator(a,b,c,d,continued_fraction(pi)) + sage: for i in range(10): + ....: gi.emit(i) + sage: gi.currently_emitted + 10 + """ + self.currently_emitted += 1 + # This is being computed for the case when no states are being saved (still reading preperiod). + if self.currently_read <= self.input_preperiod_length: + self.output_preperiod_length = self.currently_emitted + a = self.a + b = self.b + self.a = self.c + self.b = self.d + self.c = a - q * self.c + self.d = b - q * self.d + + def ingest(self): + """ + Change the state of the iterator, ingesting another term from the input continued fraction. + + TESTS:: + + sage: a = Integer(randint(-100,100)); b = Integer(randint(-100,100)); + sage: c = Integer(randint(-100,100)); d = Integer(randint(-100,100)); + sage: from sage.rings.continued_fraction_gosper import gosper_iterator + sage: gi = gosper_iterator(a,b,c,d,continued_fraction(pi)) + sage: for i in range(10): + ....: gi.ingest() + sage: gi.currently_read + 10 + """ + try: + p = next(self.x) + self.currently_read += 1 + a = self.a + c = self.c + self.a = a * p + self.b + self.b = a + self.c = c * p + self.d + self.d = c + except StopIteration: + self.b = self.a + self.d = self.c + + @staticmethod + def bound(n, d): + """ + Helper function for division. Return infinity if denominator is zero. + + TESTS:: + + sage: from sage.rings.continued_fraction_gosper import gosper_iterator + sage: gosper_iterator.bound(1,0) + +Infinity + """ + if d == 0: + return +Infinity + else: + return (n / d).floor() From 031088f7ca9b4a18530fb972729472f8e8d71a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 12 May 2019 22:05:36 +0200 Subject: [PATCH 022/170] fix for py2: also define next --- src/sage/rings/continued_fraction_gosper.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/rings/continued_fraction_gosper.py b/src/sage/rings/continued_fraction_gosper.py index 0ed56fa9eed..fce8663951f 100644 --- a/src/sage/rings/continued_fraction_gosper.py +++ b/src/sage/rings/continued_fraction_gosper.py @@ -142,6 +142,8 @@ def __next__(self): print("ERROR: Next loop iteration ran too many times.") raise StopIteration + next = __next__ # for python2 + def emit(self, q): """ Change the state of the iterator, emitting the term `q`. From 5e661aefb675d3895abc9668b43df9a77af83378 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Mon, 7 Oct 2019 10:40:46 -0600 Subject: [PATCH 023/170] made the correction --- src/sage/rings/fast_arith.pyx | 41 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index 91500f8a065..e2e5932fa85 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -126,25 +126,28 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) - Robert Bradshaw (speedup using Pari prime table, py_ints option) """ cdef Integer z - cdef long c_start, c_stop, p, maxpr + cdef long c_start, c_stop, p cdef byteptr pari_prime_ptr - if algorithm == "pari_primes": - if stop is None: - # In this case, "start" is really stop - c_start = 1 - c_stop = start - else: - c_start = start - c_stop = stop - if c_start < 1: - c_start = 1 - if c_stop <= c_start: - return [] - + DEF prime_init_max = 436273290 # hardcoded maximum in definition of prime_init + DEF prime_gap_bound = 1500 # upper bound for gap between primes less than 2^63 + + if stop is None: + # In this case, "start" is really stop + c_start = 1 + c_stop = start + else: + c_start = start + c_stop = stop + if c_start < 1: + c_start = 1 + if c_stop <= c_start: + return [] + + if (algorithm == "pari_primes") and (c_stop + prime_gap_bound <= prime_init_max): if maxprime() < c_stop: - # Adding 1500 should be sufficient to guarantee an + # Adding prime_gap_bound should be sufficient to guarantee an # additional prime, given that c_stop < 2^63. - pari.init_primes(c_stop + 1500) + pari.init_primes(c_stop + prime_gap_bound) assert maxprime() >= c_stop pari_prime_ptr = diffptr @@ -161,7 +164,11 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) res.append(z) NEXT_PRIME_VIADIFF(p, pari_prime_ptr) - elif algorithm == "pari_isprime": + elif (algorithm == "pari_isprime") or (algorithm == "pari_primes"): + if (algorithm == "pari_primes"): + Print(""" + Warning: algorithm ''pari_primes'' cannot find primes greater than {}. + Using ''pari_isprime'' instead.""".format(prime_init_max - prime_gap_bound)) from sage.arith.all import primes res = list(primes(start, stop)) else: From def827e6e0305ca345aa122b925279d95d662219 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Mon, 7 Oct 2019 10:44:41 -0600 Subject: [PATCH 024/170] eliminated tabs --- src/sage/rings/fast_arith.pyx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index e2e5932fa85..ca1e8939923 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -131,17 +131,17 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) DEF prime_init_max = 436273290 # hardcoded maximum in definition of prime_init DEF prime_gap_bound = 1500 # upper bound for gap between primes less than 2^63 - if stop is None: - # In this case, "start" is really stop - c_start = 1 - c_stop = start - else: - c_start = start - c_stop = stop - if c_start < 1: - c_start = 1 - if c_stop <= c_start: - return [] + if stop is None: + # In this case, "start" is really stop + c_start = 1 + c_stop = start + else: + c_start = start + c_stop = stop + if c_start < 1: + c_start = 1 + if c_stop <= c_start: + return [] if (algorithm == "pari_primes") and (c_stop + prime_gap_bound <= prime_init_max): if maxprime() < c_stop: From 0fa29c610e256d02a0a9f64cdc9f439c16d94f2b Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Mon, 7 Oct 2019 10:45:59 -0600 Subject: [PATCH 025/170] print, not Print --- src/sage/rings/fast_arith.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index ca1e8939923..eae0e528cff 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -166,7 +166,7 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) elif (algorithm == "pari_isprime") or (algorithm == "pari_primes"): if (algorithm == "pari_primes"): - Print(""" + print(""" Warning: algorithm ''pari_primes'' cannot find primes greater than {}. Using ''pari_isprime'' instead.""".format(prime_init_max - prime_gap_bound)) from sage.arith.all import primes From 9b6d0dd200b44f2aefdde6ad948a50887d4d7ac9 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Mon, 7 Oct 2019 10:49:19 -0600 Subject: [PATCH 026/170] warning was indented --- src/sage/rings/fast_arith.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index eae0e528cff..417a4e0e2ca 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -167,8 +167,8 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) elif (algorithm == "pari_isprime") or (algorithm == "pari_primes"): if (algorithm == "pari_primes"): print(""" - Warning: algorithm ''pari_primes'' cannot find primes greater than {}. - Using ''pari_isprime'' instead.""".format(prime_init_max - prime_gap_bound)) +Warning: algorithm "pari_primes" cannot find primes greater than {}. +Using "pari_isprime" instead.""".format(prime_init_max - prime_gap_bound)) from sage.arith.all import primes res = list(primes(start, stop)) else: From 4c11b0b04838d3a5108aa10bbb53ebf890ea95a9 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Mon, 7 Oct 2019 11:12:33 -0600 Subject: [PATCH 027/170] docstring and doctest --- src/sage/rings/fast_arith.pyx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index 417a4e0e2ca..25c1605c605 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -56,7 +56,8 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) since in all cases this function makes a table of primes up to stop. If both are large, use algorithm "pari_isprime" instead. - Algorithm "pari_primes" is faster for most input, but crashes for larger input. + Algorithm "pari_primes" is faster but may use a lot of memory and cannot find + primes greater than 436271790. Algorithm "pari_isprime" is slower but will work for much larger input. INPUT: @@ -117,6 +118,14 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) sage: prime_range(4652360, 4652400) [] + + Confirm the fix for trac ticket 28467:: + + sage: prime_range(436271790,436271791) + + Warning: algorithm "pari_primes" cannot find primes greater than 436271790. + Using "pari_isprime" instead. + [] AUTHORS: @@ -169,6 +178,7 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) print(""" Warning: algorithm "pari_primes" cannot find primes greater than {}. Using "pari_isprime" instead.""".format(prime_init_max - prime_gap_bound)) + from sage.arith.all import primes res = list(primes(start, stop)) else: From 8b0a9ffea1914d09dc12d6374db2bfe759ac9eb3 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Mon, 7 Oct 2019 13:39:06 -0600 Subject: [PATCH 028/170] warning was off by 1 --- src/sage/rings/fast_arith.pyx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index 25c1605c605..19479b8a467 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -57,8 +57,8 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) stop. If both are large, use algorithm "pari_isprime" instead. Algorithm "pari_primes" is faster but may use a lot of memory and cannot find - primes greater than 436271790. - Algorithm "pari_isprime" is slower but will work for much larger input. + primes greater than 436271789. Algorithm "pari_isprime" is slower but will work + for much larger input. INPUT: @@ -123,8 +123,8 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) sage: prime_range(436271790,436271791) - Warning: algorithm "pari_primes" cannot find primes greater than 436271790. - Using "pari_isprime" instead. + Warning: algorithm "pari_primes" cannot find primes greater than 436271789. + Using "pari_isprime" instead (which may be slower). [] AUTHORS: @@ -137,7 +137,7 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) cdef Integer z cdef long c_start, c_stop, p cdef byteptr pari_prime_ptr - DEF prime_init_max = 436273290 # hardcoded maximum in definition of prime_init + DEF init_primes_max = 436273290 # hardcoded maximum in definition of pari.init_primes DEF prime_gap_bound = 1500 # upper bound for gap between primes less than 2^63 if stop is None: @@ -152,10 +152,11 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) if c_stop <= c_start: return [] - if (algorithm == "pari_primes") and (c_stop + prime_gap_bound <= prime_init_max): + if (algorithm == "pari_primes") and (c_stop + prime_gap_bound <= init_primes_max): if maxprime() < c_stop: # Adding prime_gap_bound should be sufficient to guarantee an # additional prime, given that c_stop < 2^63. + # Input to pari.init_primes cannot be greater than init_primes_max. pari.init_primes(c_stop + prime_gap_bound) assert maxprime() >= c_stop @@ -177,7 +178,7 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) if (algorithm == "pari_primes"): print(""" Warning: algorithm "pari_primes" cannot find primes greater than {}. -Using "pari_isprime" instead.""".format(prime_init_max - prime_gap_bound)) +Using "pari_isprime" instead (which may be slower).""".format(prime_init_max - prime_gap_bound - 1)) from sage.arith.all import primes res = list(primes(start, stop)) From 936bd7950bfa297a7b96a518865d9286707a9617 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Mon, 7 Oct 2019 13:41:32 -0600 Subject: [PATCH 029/170] wrong name for init_primes_max --- src/sage/rings/fast_arith.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index 19479b8a467..d4a28a3487b 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -178,7 +178,7 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) if (algorithm == "pari_primes"): print(""" Warning: algorithm "pari_primes" cannot find primes greater than {}. -Using "pari_isprime" instead (which may be slower).""".format(prime_init_max - prime_gap_bound - 1)) +Using "pari_isprime" instead (which may be slower).""".format(init_primes_max - prime_gap_bound - 1)) from sage.arith.all import primes res = list(primes(start, stop)) From 45b6d4f5050a9d4ffc54b8bce73fc4cbb71a1128 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Mon, 7 Oct 2019 14:09:54 -0600 Subject: [PATCH 030/170] too long for c integers --- src/sage/rings/fast_arith.pyx | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index d4a28a3487b..0d69aecee79 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -139,20 +139,21 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) cdef byteptr pari_prime_ptr DEF init_primes_max = 436273290 # hardcoded maximum in definition of pari.init_primes DEF prime_gap_bound = 1500 # upper bound for gap between primes less than 2^63 + + if (algorithm == "pari_primes") and (max(start,stop) + prime_gap_bound <= init_primes_max): - if stop is None: - # In this case, "start" is really stop - c_start = 1 - c_stop = start - else: - c_start = start - c_stop = stop - if c_start < 1: + if stop is None: + # In this case, "start" is really stop c_start = 1 - if c_stop <= c_start: - return [] + c_stop = start + else: + c_start = start + c_stop = stop + if c_start < 1: + c_start = 1 + if c_stop <= c_start: + return [] - if (algorithm == "pari_primes") and (c_stop + prime_gap_bound <= init_primes_max): if maxprime() < c_stop: # Adding prime_gap_bound should be sufficient to guarantee an # additional prime, given that c_stop < 2^63. From 587b0bbfc4eec029bbb41f134ad011c2b3550839 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Mon, 7 Oct 2019 14:13:11 -0600 Subject: [PATCH 031/170] failed doctest --- src/sage/rings/fast_arith.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index 0d69aecee79..9a3e48584a1 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -122,7 +122,6 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) Confirm the fix for trac ticket 28467:: sage: prime_range(436271790,436271791) - Warning: algorithm "pari_primes" cannot find primes greater than 436271789. Using "pari_isprime" instead (which may be slower). [] From 3e796ce2420a8124944512ddefd74f042e5510bc Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Sun, 13 Oct 2019 00:21:05 -0600 Subject: [PATCH 032/170] cast to Integer --- src/sage/rings/fast_arith.pyx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index 9a3e48584a1..84ba8bfa80f 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -125,6 +125,8 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) Warning: algorithm "pari_primes" cannot find primes greater than 436271789. Using "pari_isprime" instead (which may be slower). [] + sage: prime_range("90","100") + [97] AUTHORS: @@ -139,6 +141,10 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) DEF init_primes_max = 436273290 # hardcoded maximum in definition of pari.init_primes DEF prime_gap_bound = 1500 # upper bound for gap between primes less than 2^63 + start = Integer(start) + if stop is not None: + stop = Integer(stop) + if (algorithm == "pari_primes") and (max(start,stop) + prime_gap_bound <= init_primes_max): if stop is None: From 8a1fe5a317e180f22392681100bc65ed37ac769b Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Mon, 21 Oct 2019 15:37:18 -0700 Subject: [PATCH 033/170] eliminated warning and replaced 1500 with 250 --- src/sage/rings/fast_arith.pyx | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index 84ba8bfa80f..d4c4c94a35b 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -138,14 +138,16 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) cdef Integer z cdef long c_start, c_stop, p cdef byteptr pari_prime_ptr - DEF init_primes_max = 436273290 # hardcoded maximum in definition of pari.init_primes - DEF prime_gap_bound = 1500 # upper bound for gap between primes less than 2^63 + # input to pari.init_primes cannot be greater than 436273290 (hardcoded bound) + DEF init_primes_max = 436273290 + DEF small_prime_max = 436273009 # a prime < init_primes_max (preferably the largest) + DEF prime_gap_bound = 250 # upper bound for gap between primes <= small_prime_max start = Integer(start) if stop is not None: stop = Integer(stop) - if (algorithm == "pari_primes") and (max(start,stop) + prime_gap_bound <= init_primes_max): + if (algorithm == "pari_primes") and (max(start,stop) <= small_prime_max): if stop is None: # In this case, "start" is really stop @@ -161,9 +163,8 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) if maxprime() < c_stop: # Adding prime_gap_bound should be sufficient to guarantee an - # additional prime, given that c_stop < 2^63. - # Input to pari.init_primes cannot be greater than init_primes_max. - pari.init_primes(c_stop + prime_gap_bound) + # additional prime, given that c_stop <= small_prime_max. + pari.init_primes(min(c_stop + prime_gap_bound, init_primes_max)) assert maxprime() >= c_stop pari_prime_ptr = diffptr @@ -181,11 +182,6 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) NEXT_PRIME_VIADIFF(p, pari_prime_ptr) elif (algorithm == "pari_isprime") or (algorithm == "pari_primes"): - if (algorithm == "pari_primes"): - print(""" -Warning: algorithm "pari_primes" cannot find primes greater than {}. -Using "pari_isprime" instead (which may be slower).""".format(init_primes_max - prime_gap_bound - 1)) - from sage.arith.all import primes res = list(primes(start, stop)) else: From acde22add15084bb0a8efd69b15bf9caf09adfa3 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Mon, 21 Oct 2019 17:54:39 -0700 Subject: [PATCH 034/170] revised docstring --- src/sage/rings/fast_arith.pyx | 47 ++++++++++++++++------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index d4c4c94a35b..4a0b4783dad 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -50,32 +50,31 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) List of all primes between start and stop-1, inclusive. If the second argument is omitted, returns the primes up to the first argument. - - This function is closely related to (and can use) the primes iterator. - Use algorithm "pari_primes" when both start and stop are not too large, - since in all cases this function makes a table of primes up to - stop. If both are large, use algorithm "pari_isprime" instead. - - Algorithm "pari_primes" is faster but may use a lot of memory and cannot find - primes greater than 436271789. Algorithm "pari_isprime" is slower but will work - for much larger input. + + This command is like the Python 2 ``range`` command, except it lists only the + prime numbers that are in the given range. The sage command ``primes`` is an + alternative that uses less memory (but may be slower), because it returns an + iterator, rather than building a list of the primes. INPUT: - - ``start`` -- lower bound + - ``start`` -- integer lower bound (default: 2) - - ``stop`` -- upper bound + - ``stop`` -- integer upper bound - - ``algorithm`` -- string, one of: + - ``algorithm`` -- string (default: "pari_primes"), one of: - - "pari_primes": Uses PARI's primes function. Generates all primes up to stop. - Depends on PARI's primepi function. + - "pari_primes": Uses PARI's primes function to generate all primes from 2 to + stop if stop <= 436273009 (approximately 4.36E8). (Otherwise uses algorithm + "pari_isprime".) This is fast but may crash if there is insufficient memory + (and will not be used when stop > 436273009). - - "pari_isprime": Uses a mod 2 wheel and PARI's isprime function by calling - the primes iterator. - - - ``py_ints`` -- boolean (default False), return Python ints rather than Sage Integers (faster) + - "pari_isprime": Wrapper for ``list(primes(start, stop))``. Each (odd) + integer in the specified range is tested for primality by applying PARI's + isprime function. This is slower but will work for much larger input. + - ``py_ints`` -- boolean (default: ``False``), return Python ints rather than Sage + Integers (faster). Ignored unless algorithm "pari_primes" is being used. EXAMPLES:: @@ -118,13 +117,11 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) sage: prime_range(4652360, 4652400) [] - + Confirm the fix for trac ticket 28467:: - - sage: prime_range(436271790,436271791) - Warning: algorithm "pari_primes" cannot find primes greater than 436271789. - Using "pari_isprime" instead (which may be slower). - [] + + sage: prime_range(436273009,436273010) + [436273009] sage: prime_range("90","100") [97] @@ -148,7 +145,7 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) stop = Integer(stop) if (algorithm == "pari_primes") and (max(start,stop) <= small_prime_max): - + if stop is None: # In this case, "start" is really stop c_start = 1 From 4afb8861e39cf7084cd578344fa2f430c929a8aa Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Fri, 25 Oct 2019 22:09:44 -0600 Subject: [PATCH 035/170] allow reals --- src/sage/rings/fast_arith.pyx | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index 4a0b4783dad..3e9e0e51d6f 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -50,7 +50,7 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) List of all primes between start and stop-1, inclusive. If the second argument is omitted, returns the primes up to the first argument. - + This command is like the Python 2 ``range`` command, except it lists only the prime numbers that are in the given range. The sage command ``primes`` is an alternative that uses less memory (but may be slower), because it returns an @@ -140,9 +140,24 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) DEF small_prime_max = 436273009 # a prime < init_primes_max (preferably the largest) DEF prime_gap_bound = 250 # upper bound for gap between primes <= small_prime_max - start = Integer(start) + # make sure that start and stop are integers + try: + start = Integer(start) + except TypeError as integer_error: + try: + start = Integer(round(start)) + except (ValueError, TypeError) as real_error: + raise TypeError(str(integer_error) + + "\nand argument is also not real: " + str(real_error)) if stop is not None: - stop = Integer(stop) + try: + stop = Integer(stop) + except TypeError as integer_error: + try: + stop = Integer(round(stop)) + except (ValueError, TypeError) as real_error: + raise ValueError(str(integer_error) + + "\nand argument is also not real: " + str(real_error)) if (algorithm == "pari_primes") and (max(start,stop) <= small_prime_max): From 3bc1290838a0909b904a34ba02857045cd42b801 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Sun, 27 Oct 2019 22:01:38 -0600 Subject: [PATCH 036/170] added note to docstring --- src/sage/rings/fast_arith.pyx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index 3e9e0e51d6f..e9adc02e6e8 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -58,7 +58,7 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) INPUT: - - ``start`` -- integer lower bound (default: 2) + - ``start`` -- integer lower bound (default: 1) - ``stop`` -- integer upper bound @@ -103,6 +103,13 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) sage: type(prime_range(8,algorithm="pari_isprime")[0]) + .. NOTE:: + + ``start`` and ``stop`` should be integers, but real numbers will also be accepted + as input. In this case, they will be rounded to nearby integers start\* and + stop\*, so the output will be the primes between start\* and stop\* - 1, which may + not be exactly the same as the primes between ``start`` and ``stop - 1``. + TESTS:: sage: prime_range(-1) From 27603bb1a3bcbe456aabd7b789439dd4c91e1fec Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Sun, 27 Oct 2019 22:14:27 -0600 Subject: [PATCH 037/170] added real number to doctest --- src/sage/rings/fast_arith.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index e9adc02e6e8..6405e8e4477 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -129,7 +129,7 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) sage: prime_range(436273009,436273010) [436273009] - sage: prime_range("90","100") + sage: prime_range(94.3,"100") [97] AUTHORS: From d6ac58c505f329a7682565d4a3b92f2bbcc7dae0 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Fri, 8 Nov 2019 10:21:43 -0700 Subject: [PATCH 038/170] remove input coercion --- src/sage/rings/fast_arith.pyx | 37 +++++++++++------------------------ 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index 6405e8e4477..5c6324a8434 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -105,10 +105,11 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) .. NOTE:: - ``start`` and ``stop`` should be integers, but real numbers will also be accepted - as input. In this case, they will be rounded to nearby integers start\* and - stop\*, so the output will be the primes between start\* and stop\* - 1, which may - not be exactly the same as the primes between ``start`` and ``stop - 1``. + ``start`` and ``stop`` should be integers. Other input may produce unexpected + results. For example, ``prime_range(7, "10", "pari_isprime")`` returns + ``[7]``, but causes an error if the algorithm is changed to "pari_primes", + whereas ``prime_range(7.9, 10, "pari_primes")`` returns ``[7]``, but causes an + error if the algorithm is changed to "pari_isprime". TESTS:: @@ -127,10 +128,13 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) Confirm the fix for trac ticket 28467:: - sage: prime_range(436273009,436273010) + sage: prime_range(436273009, 436273010) [436273009] - sage: prime_range(94.3,"100") - [97] + + To avoid a doctest error in functions/prime_pi.pyx, prime_range must allow real input:: + + sage: prime_range(9.5, 14.3) + [11, 13] AUTHORS: @@ -147,25 +151,6 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) DEF small_prime_max = 436273009 # a prime < init_primes_max (preferably the largest) DEF prime_gap_bound = 250 # upper bound for gap between primes <= small_prime_max - # make sure that start and stop are integers - try: - start = Integer(start) - except TypeError as integer_error: - try: - start = Integer(round(start)) - except (ValueError, TypeError) as real_error: - raise TypeError(str(integer_error) - + "\nand argument is also not real: " + str(real_error)) - if stop is not None: - try: - stop = Integer(stop) - except TypeError as integer_error: - try: - stop = Integer(round(stop)) - except (ValueError, TypeError) as real_error: - raise ValueError(str(integer_error) - + "\nand argument is also not real: " + str(real_error)) - if (algorithm == "pari_primes") and (max(start,stop) <= small_prime_max): if stop is None: From 1754c2f3093d419bcf8d8c0e0e0a021b7d5ff3fa Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Fri, 27 Dec 2019 20:49:08 -0700 Subject: [PATCH 039/170] bug fix: max does not compare integer with None --- src/sage/rings/fast_arith.pyx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index 5c6324a8434..c9cf501ac85 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -151,7 +151,8 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) DEF small_prime_max = 436273009 # a prime < init_primes_max (preferably the largest) DEF prime_gap_bound = 250 # upper bound for gap between primes <= small_prime_max - if (algorithm == "pari_primes") and (max(start,stop) <= small_prime_max): + # if 'stop' is 'None', need to change it to an integer before comparing with 'start' + if (algorithm == "pari_primes") and (max(start, stop or 0) <= small_prime_max): if stop is None: # In this case, "start" is really stop From bcc2c19e52f9c9c4c56e271028cd73feaca48450 Mon Sep 17 00:00:00 2001 From: Peter Bruin Date: Tue, 25 Feb 2020 07:56:56 +0100 Subject: [PATCH 040/170] Trac 29246: improve handling of easy cases in lift_to_sl2z() --- .../modular/arithgroup/congroup_gamma1.py | 8 ++-- .../modular/arithgroup/congroup_gammaH.py | 31 +++++++------- src/sage/modular/cusps.py | 4 +- src/sage/modular/local_comp/liftings.py | 2 +- src/sage/modular/modsym/boundary.py | 4 +- src/sage/modular/modsym/manin_symbol.pyx | 12 ++++-- src/sage/modular/modsym/p1list.pyx | 41 ++++++++++++------- 7 files changed, 58 insertions(+), 44 deletions(-) diff --git a/src/sage/modular/arithgroup/congroup_gamma1.py b/src/sage/modular/arithgroup/congroup_gamma1.py index cb023cf4532..fbaa065f005 100644 --- a/src/sage/modular/arithgroup/congroup_gamma1.py +++ b/src/sage/modular/arithgroup/congroup_gamma1.py @@ -215,11 +215,11 @@ def generators(self, algorithm="farey"): ] sage: Gamma1(3).generators(algorithm="todd-coxeter") [ - [1 1] [-20 9] [ 4 1] [-5 -2] [ 1 -1] [1 0] [1 1] [-5 2] - [0 1], [ 51 -23], [-9 -2], [ 3 1], [ 0 1], [3 1], [0 1], [12 -5], + [1 1] [-2 1] [1 1] [ 1 -1] [1 0] [1 1] [-5 2] [ 1 0] + [0 1], [-3 1], [0 1], [ 0 1], [3 1], [0 1], [12 -5], [-3 1], - [ 1 0] [ 4 -1] [ -5 3] [ 1 -1] [ 7 -3] [ 4 -1] [ -5 3] - [-3 1], [ 9 -2], [-12 7], [ 3 -2], [12 -5], [ 9 -2], [-12 7] + [ 1 -1] [ 1 -1] [ 4 -1] [ -5 3] + [ 3 -2], [ 3 -2], [ 9 -2], [-12 7] ] """ if algorithm=="farey": diff --git a/src/sage/modular/arithgroup/congroup_gammaH.py b/src/sage/modular/arithgroup/congroup_gammaH.py index aa91f91db92..896ee61d5e5 100644 --- a/src/sage/modular/arithgroup/congroup_gammaH.py +++ b/src/sage/modular/arithgroup/congroup_gammaH.py @@ -470,17 +470,14 @@ def generators(self, algorithm="farey"): ] sage: GammaH(7, [2]).generators(algorithm="todd-coxeter") [ - [1 1] [-90 29] [ 15 4] [-10 -3] [ 1 -1] [1 0] [1 1] [-3 -1] - [0 1], [301 -97], [-49 -13], [ 7 2], [ 0 1], [7 1], [0 1], [ 7 2], + [1 1] [-13 4] [ 15 4] [-3 -1] [ 1 -1] [1 0] [1 1] [-3 -1] + [0 1], [ 42 -13], [-49 -13], [ 7 2], [ 0 1], [7 1], [0 1], [ 7 2], - [-13 4] [-5 -1] [-5 -2] [-10 3] [ 1 0] [ 9 -1] [-20 7] - [ 42 -13], [21 4], [28 11], [ 63 -19], [-7 1], [28 -3], [-63 22], + [-13 4] [-5 -1] [-5 -2] [-10 3] [ 1 0] [ 2 -1] [1 0] + [ 42 -13], [21 4], [28 11], [ 63 -19], [-7 1], [ 7 -3], [7 1], - [1 0] [-3 -1] [ 15 -4] [ 2 -1] [ 22 -7] [-5 1] [ 8 -3] - [7 1], [ 7 2], [ 49 -13], [ 7 -3], [ 63 -20], [14 -3], [-21 8], - - [11 5] [-13 -4] - [35 16], [-42 -13] + [-3 -1] [ 15 -4] [ 2 -1] [-5 1] [ 8 -3] [11 5] [-13 -4] + [ 7 2], [ 49 -13], [ 7 -3], [14 -3], [-21 8], [35 16], [-42 -13] ] """ if algorithm=="farey": @@ -969,14 +966,14 @@ def gamma0_coset_reps(self): sage: GammaH(108, [1,-1]).gamma0_coset_reps() [ - [1 0] [-43 -45] [ 31 33] [-49 -54] [ 25 28] [-19 -22] - [0 1], [108 113], [108 115], [108 119], [108 121], [108 125], + [1 0] [-43 -2] [ 31 2] [-49 -5] [ 25 3] [-19 -3] + [0 1], [108 5], [108 7], [108 11], [108 13], [108 17], - [-17 -20] [ 47 57] [ 13 16] [ 41 52] [ 7 9] [-37 -49] - [108 127], [108 131], [108 133], [108 137], [108 139], [108 143], + [-17 -3] [ 47 10] [ 13 3] [ 41 11] [ 7 2] [-37 -12] + [108 19], [108 23], [108 25], [108 29], [108 31], [108 35], - [-35 -47] [ 29 40] [ -5 -7] [ 23 33] [-11 -16] [ 53 79] - [108 145], [108 149], [108 151], [108 155], [108 157], [108 161] + [-35 -12] [ 29 11] [ -5 -2] [ 23 10] [-11 -5] [ 53 26] + [108 37], [108 41], [108 43], [108 47], [108 49], [108 53] ] """ from .all import SL2Z @@ -991,8 +988,8 @@ def coset_reps(self): sage: list(Gamma1(3).coset_reps()) [ - [1 0] [-1 -2] [ 0 -1] [-2 1] [1 0] [-3 -2] [ 0 -1] [-2 -3] - [0 1], [ 3 5], [ 1 0], [ 5 -3], [1 1], [ 8 5], [ 1 2], [ 5 7] + [1 0] [-1 0] [ 0 -1] [ 0 1] [1 0] [-1 0] [ 0 -1] [ 0 1] + [0 1], [ 0 -1], [ 1 0], [-1 0], [1 1], [-1 -1], [ 1 2], [-1 -2] ] sage: len(list(Gamma1(31).coset_reps())) == 31**2 - 1 True diff --git a/src/sage/modular/cusps.py b/src/sage/modular/cusps.py index 47c3f8286d8..ea5a2861de6 100644 --- a/src/sage/modular/cusps.py +++ b/src/sage/modular/cusps.py @@ -751,7 +751,7 @@ def is_gamma_h_equiv(self, other, G): sage: G = GammaH(25,[6]) ; M = G.modular_symbols() ; M Modular Symbols space of dimension 11 for Congruence Subgroup Gamma_H(25) with H generated by [6] of weight 2 with sign 0 and over Rational Field sage: M.cusps() - [33/100, 1/3, 31/125, 1/4, 1/15, -7/15, 7/15, 4/15, 1/20, 3/20, 7/20, 9/20] + [8/25, 1/3, 6/25, 1/4, 1/15, -7/15, 7/15, 4/15, 1/20, 3/20, 7/20, 9/20] sage: len(M.cusps()) 12 @@ -896,7 +896,7 @@ def galois_action(self, t, N): sage: Cusp(oo).galois_action(3, 50) Infinity sage: c=Cusp(0).galois_action(3, 50); c - 50/67 + 50/17 sage: Gamma0(50).reduce_cusp(c) 0 diff --git a/src/sage/modular/local_comp/liftings.py b/src/sage/modular/local_comp/liftings.py index 5d7be71f16e..91bfd2448d3 100644 --- a/src/sage/modular/local_comp/liftings.py +++ b/src/sage/modular/local_comp/liftings.py @@ -171,7 +171,7 @@ def lift_ramified(g, p, u, n): sage: from sage.modular.local_comp.liftings import lift_ramified sage: lift_ramified([2,2,3,2], 3, 1, 1) - [5, 8, 3, 5] + [-1, -1, 3, 2] sage: lift_ramified([8,2,12,2], 3, 2, 23) [323, 110, -133584, -45493] sage: type(lift_ramified([8,2,12,2], 3, 2, 23)[0]) diff --git a/src/sage/modular/modsym/boundary.py b/src/sage/modular/modsym/boundary.py index 55f3b46a823..80fa4da4289 100644 --- a/src/sage/modular/modsym/boundary.py +++ b/src/sage/modular/modsym/boundary.py @@ -508,9 +508,9 @@ def _coerce_in_manin_symbol(self, x): sage: M = ModularSymbols(Gamma1(5), 4) ; B = M.boundary_space() sage: [ B(x) for x in M.basis() ] - [-[2/5], -[-1/5], -[1/3], -[-1/4], -[-1/4], -[-1/4]] + [-[2/5], -[Infinity], -[1/3], -[-1/4], -[-1/4], -[-1/4]] sage: [ B._coerce_in_manin_symbol(x) for x in M.manin_symbols_basis() ] - [-[2/5], -[-1/5], -[1/3], -[-1/4], -[-1/4], -[-1/4]] + [-[2/5], -[Infinity], -[1/3], -[-1/4], -[-1/4], -[-1/4]] """ i = x.i alpha, beta = x.endpoints(self.level()) diff --git a/src/sage/modular/modsym/manin_symbol.pyx b/src/sage/modular/modsym/manin_symbol.pyx index 99b913816d7..0a4944b4ad8 100644 --- a/src/sage/modular/modsym/manin_symbol.pyx +++ b/src/sage/modular/modsym/manin_symbol.pyx @@ -343,6 +343,14 @@ cdef class ManinSymbol(Element): return [ZZ.one(), ZZ.zero(), ZZ.zero(), ZZ.one()] c = Integer(self.u) d = Integer(self.v) + + if c == 0: + if d == 1: + return [ZZ.one(), ZZ.zero(), ZZ.zero(), ZZ.one()] + if d == N - 1: + return [Integer(-1), ZZ.zero(), ZZ.zero(), Integer(-1)] + c = Integer(N) + g, z1, z2 = c.xgcd(d) # We're lucky: z1*c + z2*d = 1. @@ -350,10 +358,6 @@ cdef class ManinSymbol(Element): return [z2, -z1, c, d] # Have to try harder. - if c == 0: - c += N - if d == 0: - d += N m = c # compute prime-to-d part of m. diff --git a/src/sage/modular/modsym/p1list.pyx b/src/sage/modular/modsym/p1list.pyx index 841b920f68e..263a8236bb3 100644 --- a/src/sage/modular/modsym/p1list.pyx +++ b/src/sage/modular/modsym/p1list.pyx @@ -1216,8 +1216,16 @@ def lift_to_sl2z_int(int c, int d, int N): """ cdef int z1, z2, g, m - if c == 0 and d == 0: - raise AttributeError("Element (%s, %s) not in P1." % (c,d)) + if N == 1: + return [1, 0, 0, 1] + + if c == 0: + if d == 1: + return [1, 0, 0, 1] + if d == N - 1: + return [-1, 0, 0, -1] + c = N + g = arith_int.c_xgcd_int(c, d, &z1, &z2) # We're lucky: z1*c + z2*d = 1. @@ -1225,11 +1233,7 @@ def lift_to_sl2z_int(int c, int d, int N): return [z2, -z1, c, d] # Have to try harder. - if c == 0: - c = c + N; - if d == 0: - d = d + N; - m = c; + m = c # compute prime-to-d part of m. while True: @@ -1283,8 +1287,16 @@ def lift_to_sl2z_llong(llong c, llong d, int N): """ cdef llong z1, z2, g, m - if c == 0 and d == 0: - raise AttributeError("Element (%s, %s) not in P1." % (c,d)) + if N == 1: + return [1, 0, 0, 1] + + if c == 0: + if d == 1: + return [1, 0, 0, 1] + if d == N - 1: + return [-1, 0, 0, -1] + c = N + g = arith_llong.c_xgcd_longlong(c, d, &z1, &z2) # We're lucky: z1*c + z2*d = 1. @@ -1292,11 +1304,7 @@ def lift_to_sl2z_llong(llong c, llong d, int N): return [z2, -z1, c, d] # Have to try harder. - if c == 0: - c = c + N; - if d == 0: - d = d + N; - m = c; + m = c # compute prime-to-d part of m. while True: @@ -1351,6 +1359,11 @@ def lift_to_sl2z(c, d, N): Traceback (most recent call last): ... NotImplementedError: N too large + + TESTS:: + + sage: lift_to_sl2z(0, 0, 1) + [1, 0, 0, 1] """ if N <= 46340: return lift_to_sl2z_int(c,d,N) From 392274775a13b653482258179f2d87581385227b Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Sun, 12 Apr 2020 16:00:05 -0600 Subject: [PATCH 041/170] corrections pointed out by reviewer --- src/sage/rings/fast_arith.pyx | 60 ++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index cc5cf43b5ae..69f8316b97d 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -45,15 +45,16 @@ from cypari2.gen cimport Gen as pari_gen from sage.libs.pari.all import pari from sage.rings.integer cimport Integer -cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False): +cpdef prime_range(start, stop=None, algorithm=None, bint py_ints=False): r""" Return a list of all primes between ``start`` and ``stop - 1``, inclusive. If the second argument is omitted, this returns the primes up to the first argument. - The sage command ``primes`` is an alternative that uses less memory (but may be - slower), because it returns an iterator, rather than building a list of the primes. + The sage command :func:`~sage.arith.misc.primes` is an alternative that + uses less memory (but may be slower), because it returns an iterator, + rather than building a list of the primes. INPUT: @@ -61,19 +62,22 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) - ``stop`` -- integer, upper bound - - ``algorithm`` -- optional string (default: "pari_primes"), one of: + - ``algorithm`` -- optional string (default: ``None``), one of: - - "pari_primes": Uses PARI's :pari:`primes` function to generate all primes - from 2 to stop if stop <= 436273009 (approximately 4.36E8). (Otherwise uses - algorithm "pari_isprime".) This is fast but may crash if there is insufficient - memory (and will not be used when stop > 436273009). + - ``None``: Use algorithm ``"pari_primes"`` if ``stop`` <= 436273009 + (approximately 4.36E8). Otherwise use algorithm ``"pari_isprime"``. - - "pari_isprime": Wrapper for ``list(primes(start, stop))``. Each (odd) + - ``"pari_primes"``: Use PARI's :pari:`primes` function to generate all + primes from 2 to stop. This is fast but may crash if there is + insufficient memory. Raises an error if ``stop`` > 436273009. + + - ``"pari_isprime"``: Wrapper for ``list(primes(start, stop))``. Each (odd) integer in the specified range is tested for primality by applying PARI's :pari:`isprime` function. This is slower but will work for much larger input. - ``py_ints`` -- optional boolean (default ``False``), return Python ints rather - than Sage Integers (faster). Ignored unless algorithm "pari_primes" is being used. + than Sage Integers (faster). Ignored unless algorithm ``"pari_primes"`` is being + used. EXAMPLES:: @@ -102,14 +106,6 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) sage: type(prime_range(8,algorithm="pari_isprime")[0]) - .. NOTE:: - - ``start`` and ``stop`` should be integers. Other input may produce unexpected - results. For example, ``prime_range(7, "10", "pari_isprime")`` returns - ``[7]``, but causes an error if the algorithm is changed to "pari_primes", - whereas ``prime_range(7.9, 10, "pari_primes")`` returns ``[7]``, but causes an - error if the algorithm is changed to "pari_isprime". - TESTS:: sage: prime_range(-1) @@ -132,15 +128,25 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) ... ValueError: algorithm must be "pari_primes" or "pari_isprime" - Confirm the fix for trac ticket 28467:: + Confirm the fixes for :trac:`28467`:: sage: prime_range(436273009, 436273010) [436273009] + sage: prime_range(436273009, 436273010, algorithm="pari_primes") + Traceback (most recent call last): + ... + ValueError: algorithm "pari_primes" cannot compute primes larger than 436273008 To avoid a doctest error in functions/prime_pi.pyx, prime_range must allow real input:: sage: prime_range(9.5, 14.3) [11, 13] + sage: prime_range(9.5, 14.3, algorithm="pari_primes") + [11, 13] + sage: prime_range(9.5, 14.3, algorithm="pari_isprime") + Traceback (most recent call last): + ... + TypeError: Attempt to coerce non-integral RealNumber to Integer AUTHORS: @@ -157,8 +163,18 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) DEF small_prime_max = 436273009 # a prime < init_primes_max (preferably the largest) DEF prime_gap_bound = 250 # upper bound for gap between primes <= small_prime_max - # if 'stop' is 'None', need to change it to an integer before comparing with 'start' - if (algorithm == "pari_primes") and (max(start, stop or 0) <= small_prime_max): + if algorithm == None: + # if 'stop' is 'None', need to change it to an integer before comparing with 'start' + if max(start, stop or 0) <= small_prime_max: + algorithm = "pari_primes" + else: + algorithm = "pari_isprime" + + if algorithm == "pari_primes": + + if max(start, stop or 0) > small_prime_max: + raise ValueError('algorithm "pari_primes" cannot compute primes larger than' + + ' {}'.format(small_prime_max - 1)) if stop is None: # In this case, "start" is really stop @@ -192,7 +208,7 @@ cpdef prime_range(start, stop=None, algorithm="pari_primes", bint py_ints=False) res.append(z) NEXT_PRIME_VIADIFF(p, pari_prime_ptr) - elif (algorithm == "pari_isprime") or (algorithm == "pari_primes"): + elif algorithm == "pari_isprime": from sage.arith.all import primes res = list(primes(start, stop)) else: From 252a71269963822838e36856a64c562b3b2a7258 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Wed, 15 Apr 2020 02:06:04 -0600 Subject: [PATCH 042/170] remove redundant doctest --- src/sage/rings/fast_arith.pyx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index 69f8316b97d..fb5a3f8a8d9 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -137,17 +137,6 @@ cpdef prime_range(start, stop=None, algorithm=None, bint py_ints=False): ... ValueError: algorithm "pari_primes" cannot compute primes larger than 436273008 - To avoid a doctest error in functions/prime_pi.pyx, prime_range must allow real input:: - - sage: prime_range(9.5, 14.3) - [11, 13] - sage: prime_range(9.5, 14.3, algorithm="pari_primes") - [11, 13] - sage: prime_range(9.5, 14.3, algorithm="pari_isprime") - Traceback (most recent call last): - ... - TypeError: Attempt to coerce non-integral RealNumber to Integer - AUTHORS: - William Stein (original version) From 57cfbb446eb9f94cce631eabfc958255f6bc5073 Mon Sep 17 00:00:00 2001 From: Alexander Galarraga Date: Mon, 13 Jul 2020 22:44:22 -0400 Subject: [PATCH 043/170] Initial commit with documentation --- .../arithmetic_dynamics/projective_ds.py | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index 878ec1fff89..45d80e1ab7e 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -6908,6 +6908,108 @@ def normal_form(self, return_conjugation=False): return gccc, m * mc * mc2, psi return gccc + def potential_good_reduction(self, prime): + r""" + Return if this dynamical system has potential good reduction at ``prime``. + + A dynamical system has good reduction at ``prime`` if after the coefficients + are reduced modulo ``prime`` the degree remains the same. A dynamical system + `f` has `\textit{potential}` good reduction if there exists + `\phi \in PGL(n,\overline{K})` such that `\phi^{-1} \circ f \circ phi` + has good reduction. + + If this dynamical system has potential good reduction at ``prime``, the map + `\phi` is returned. + + This dynamical system must have as its domain `\mathbb{P}^1(K)`, where + `K` is a number field. + + INPUT: + + - ``prime`` -- a prime ideal of the field of definition of the fixed + points of the map, or a prime number in `\QQ` if the field of definition + of the fixed points is `\QQ`. + + OUTPUT: + + - ``False`` if this dynamical system does not have good reduction + - If this dynamical system `f` has potential good reduction, an element `phi` + of `PGL2` such that `phi^{-1} \circ f \circ \phi` has good reduction + + EXAMPLES:: + + sage: P. = ProjectiveSpace(QQ,1) + sage: system = DynamicalSystem_projective([x^2-y^2,2*x*y]) + sage: prime = system.field_of_definition_periodic(1).prime_above(2) + sage: new_system = potential_good_reduction(system, prime) + sage: new_system + Dynamical System of Projective Space of dimension 1 over Number Field + in a with defining polynomial x^2 + 1 + Defn: Defined on coordinates by sending (x : y) to + ((-1/2*a)*x^2 + (-5/2*a)*y^2 : (-a)*x*y + y^2) + + Note that this map has good reduction at 2:: + + sage: new_system.resultant() + 1 + + :: + + sage: P. = ProjectiveSpace(QQ,1) + sage: system = DynamicalSystem_projective([3^4*x^3+3*x*y^2+y^3,3^6*y^3]) + sage: prime = system.field_of_definition_periodic(1).prime_above(3) + sage: system.potential_good_reduction(prime) + False + """ + if self.domain().base_ring() not in NumberFields: + raise ValueError('dynamical system must be defined over number field') + + field_of_definition_periodic = self.field_of_definition_periodic(1) + + if not (isinstance(prime, NumberFieldFractionalIdeal) or prime in QQ): + raise ValueError('prime must be an ideal of a number field or an element of QQ') + if field_of_definition_periodic is not QQ: + if prime.number_field() != field_of_definition_periodic: + raise ValueError('prime ideal defined over %s ' %prime.number_field() + \ + 'but field of definition of fixed points is %s' %self.domain().base_ring()) + else: + if prime not in QQ: + raise ValueError('field of definition of fixed points is QQ but prime not in QQ') + + system = self.change_ring(field_of_definition_periodic) + fixed_points = system.periodic_points(1) + multipliers = [system.multiplier(i, 1)[0][0] for i in fixed_points] + indifferent_point = None + for mult in multipliers: + if field_of_definition_periodic is not QQ: + valuation = mult.valuation(prime)/(prime.absolute_ramification_index()) + else: + valuation = mult.valuation(prime) + if valuation < 0: + return False + elif valuation == 0: + indifferent_point = fixed_points[multipliers.index(mult)] + if indifferent_point is not None: + preimages = [indifferent_point] + point = indifferent_point + for i in [1,2]: + field_of_definition = system.field_of_definition_preimage(point, 1) + system = system.change_ring(field_of_definition) + preimages_of_point = system.rational_preimages(point, 1) + for preimage in preimages_of_point: + if preimage != point: + preimages.append(preimage) + point = preimage + break + else: + preimages = [fixed_points[0],fixed_points[1],fixed_points[2]] + field_of_definition = field_of_definition_periodic + P = ProjectiveSpace(field_of_definition,1) + preimages = [P(i) for i in preimages] + conjugation = P.point_transformation_matrix(preimages,[P(0),P(1),P([1,0])]) + new_system = system.change_ring(field_of_definition) + return new_system.conjugate(conjugation) + def reduce_base_field(self): """ Return this map defined over the field of definition of the coefficients. From b19f85ca992f1fcd8ae1599ce9afb6804900670b Mon Sep 17 00:00:00 2001 From: Alexander Galarraga Date: Tue, 14 Jul 2020 09:50:41 -0400 Subject: [PATCH 044/170] 30141: fixed typos and added an example --- .../arithmetic_dynamics/projective_ds.py | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index 45d80e1ab7e..29216a5375a 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -6915,11 +6915,12 @@ def potential_good_reduction(self, prime): A dynamical system has good reduction at ``prime`` if after the coefficients are reduced modulo ``prime`` the degree remains the same. A dynamical system `f` has `\textit{potential}` good reduction if there exists - `\phi \in PGL(n,\overline{K})` such that `\phi^{-1} \circ f \circ phi` + `\phi \in PGL(n,\overline{K})` such that `\phi^{-1} \circ f \circ \phi` has good reduction. - If this dynamical system has potential good reduction at ``prime``, the map - `\phi` is returned. + If this dynamical system `f` has potential good reduction at ``prime``, + a dynamical system `g = \phi^{-1} \circ f \circ \phi` which has good + reduction at ``prime`` is returned. This dynamical system must have as its domain `\mathbb{P}^1(K)`, where `K` is a number field. @@ -6933,15 +6934,16 @@ def potential_good_reduction(self, prime): OUTPUT: - ``False`` if this dynamical system does not have good reduction - - If this dynamical system `f` has potential good reduction, an element `phi` - of `PGL2` such that `phi^{-1} \circ f \circ \phi` has good reduction + - If this dynamical system `f` has potential good reduction, a + dynamical system `g` such that `g = \phi^{-1} \circ f \circ \phi` + and `g` has good reduction at ``prime``. EXAMPLES:: sage: P. = ProjectiveSpace(QQ,1) sage: system = DynamicalSystem_projective([x^2-y^2,2*x*y]) sage: prime = system.field_of_definition_periodic(1).prime_above(2) - sage: new_system = potential_good_reduction(system, prime) + sage: new_system = system.potential_good_reduction(prime) sage: new_system Dynamical System of Projective Space of dimension 1 over Number Field in a with defining polynomial x^2 + 1 @@ -6960,6 +6962,14 @@ def potential_good_reduction(self, prime): sage: prime = system.field_of_definition_periodic(1).prime_above(3) sage: system.potential_good_reduction(prime) False + + :: + + sage: P. = ProjectiveSpace(QQ,1) + sage: system = DynamicalSystem_projective([x^5-x*y^4,5*y^5]) + sage: prime = system.field_of_definition_periodic(1).prime_above(5) + sage: system.potential_good_reduction(prime) + False """ if self.domain().base_ring() not in NumberFields: raise ValueError('dynamical system must be defined over number field') @@ -6970,7 +6980,7 @@ def potential_good_reduction(self, prime): raise ValueError('prime must be an ideal of a number field or an element of QQ') if field_of_definition_periodic is not QQ: if prime.number_field() != field_of_definition_periodic: - raise ValueError('prime ideal defined over %s ' %prime.number_field() + \ + raise ValueError('prime ideal of %s ' %prime.number_field() + \ 'but field of definition of fixed points is %s' %self.domain().base_ring()) else: if prime not in QQ: From a460fd1523113904bba4e804edb22b85379209fb Mon Sep 17 00:00:00 2001 From: Alexander Galarraga Date: Mon, 27 Jul 2020 15:04:32 -0400 Subject: [PATCH 045/170] 30141: fixed error messages --- .../dynamics/arithmetic_dynamics/projective_ds.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index 29216a5375a..14b22f7688a 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -6977,14 +6977,15 @@ def potential_good_reduction(self, prime): field_of_definition_periodic = self.field_of_definition_periodic(1) if not (isinstance(prime, NumberFieldFractionalIdeal) or prime in QQ): - raise ValueError('prime must be an ideal of a number field or an element of QQ') - if field_of_definition_periodic is not QQ: + raise TypeError('prime must be an ideal of a number field or an element of QQ') + if prime not in QQ: if prime.number_field() != field_of_definition_periodic: raise ValueError('prime ideal of %s ' %prime.number_field() + \ - 'but field of definition of fixed points is %s' %self.domain().base_ring()) + 'but field of definition of fixed points is %s' %field_of_definition_periodic) else: - if prime not in QQ: - raise ValueError('field of definition of fixed points is QQ but prime not in QQ') + if field_of_definition_periodic is not QQ: + raise ValueError('field of definition of fixed ' + \ + 'points is %s but prime is in QQ. ' %field_of_definition_periodic) system = self.change_ring(field_of_definition_periodic) fixed_points = system.periodic_points(1) @@ -6992,7 +6993,7 @@ def potential_good_reduction(self, prime): indifferent_point = None for mult in multipliers: if field_of_definition_periodic is not QQ: - valuation = mult.valuation(prime)/(prime.absolute_ramification_index()) + valuation = mult.valuation(prime) / prime.absolute_ramification_index() else: valuation = mult.valuation(prime) if valuation < 0: @@ -7012,7 +7013,7 @@ def potential_good_reduction(self, prime): point = preimage break else: - preimages = [fixed_points[0],fixed_points[1],fixed_points[2]] + preimages = [fixed_points[0], fixed_points[1], fixed_points[2]] field_of_definition = field_of_definition_periodic P = ProjectiveSpace(field_of_definition,1) preimages = [P(i) for i in preimages] From ef6c730918afe2d9067238ffb471ca6ef709d88e Mon Sep 17 00:00:00 2001 From: Alexander Galarraga Date: Mon, 27 Jul 2020 16:23:33 -0400 Subject: [PATCH 046/170] 30141: added coersion between abstract polynomial rings --- .../dynamics/arithmetic_dynamics/projective_ds.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index 14b22f7688a..7cf2e00c93c 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -6980,8 +6980,17 @@ def potential_good_reduction(self, prime): raise TypeError('prime must be an ideal of a number field or an element of QQ') if prime not in QQ: if prime.number_field() != field_of_definition_periodic: - raise ValueError('prime ideal of %s ' %prime.number_field() + \ - 'but field of definition of fixed points is %s' %field_of_definition_periodic) + K = prime.number_field() + old_parent = K.defining_polynomial().parent() + new_parent = field_of_definition_periodic.defining_polynomial().parent() + hom = old_parent.hom([new_parent.gens()[0]]) + if hom(K.defining_polynomial()) != \ + field_of_definition_periodic.defining_polynomial(): + raise ValueError('prime ideal of %s ' %K + \ + 'but field of definition of fixed points is %s. ' %L + \ + 'see documentation for examples') + embedding = K.embeddings(field_of_definition_periodic)[0] + prime = embedding(prime) else: if field_of_definition_periodic is not QQ: raise ValueError('field of definition of fixed ' + \ From f8dac42f73c97798c6c4e2b6489c71ef1b3311db Mon Sep 17 00:00:00 2001 From: Alexander Galarraga Date: Mon, 27 Jul 2020 16:31:21 -0400 Subject: [PATCH 047/170] 30141: added test --- .../arithmetic_dynamics/projective_ds.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index 7cf2e00c93c..52184ff9280 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -6940,8 +6940,8 @@ def potential_good_reduction(self, prime): EXAMPLES:: - sage: P. = ProjectiveSpace(QQ,1) - sage: system = DynamicalSystem_projective([x^2-y^2,2*x*y]) + sage: P. = ProjectiveSpace(QQ, 1) + sage: system = DynamicalSystem_projective([x^2-y^2, 2*x*y]) sage: prime = system.field_of_definition_periodic(1).prime_above(2) sage: new_system = system.potential_good_reduction(prime) sage: new_system @@ -6957,19 +6957,32 @@ def potential_good_reduction(self, prime): :: - sage: P. = ProjectiveSpace(QQ,1) - sage: system = DynamicalSystem_projective([3^4*x^3+3*x*y^2+y^3,3^6*y^3]) + sage: P. = ProjectiveSpace(QQ, 1) + sage: system = DynamicalSystem_projective([3^4*x^3+3*x*y^2+y^3, 3^6*y^3]) sage: prime = system.field_of_definition_periodic(1).prime_above(3) sage: system.potential_good_reduction(prime) False :: - sage: P. = ProjectiveSpace(QQ,1) - sage: system = DynamicalSystem_projective([x^5-x*y^4,5*y^5]) + sage: P. = ProjectiveSpace(QQ, 1) + sage: system = DynamicalSystem_projective([x^5-x*y^4, 5*y^5]) sage: prime = system.field_of_definition_periodic(1).prime_above(5) sage: system.potential_good_reduction(prime) False + + TESTS:: + + sage: P. = ProjectiveSpace(QQ, 1) + sage: R. = QQ[] + sage: A. = NumberField(z^2 + 1) + sage: prime = A.prime_above(2) + sage: system = DynamicalSystem_projective([x^2 - y^2, 2*x*y]) + sage: system.potential_good_reduction(prime) + Dynamical System of Projective Space of dimension 1 over Number Field + in a with defining polynomial x^2 + 1 + Defn: Defined on coordinates by sending (x : y) to + ((-1/2*a)*x^2 + (-5/2*a)*y^2 : (-a)*x*y + y^2) """ if self.domain().base_ring() not in NumberFields: raise ValueError('dynamical system must be defined over number field') From 6ab9fe9e9ad650ef8cf6dd0f148d5370f68d5572 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Fri, 14 Aug 2020 22:38:53 +0200 Subject: [PATCH 048/170] Add pyright config --- pyrightconfig.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 pyrightconfig.json diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 00000000000..657b58f008a --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,7 @@ +{ + "include": [ + "src/sage/manifolds" + ], + + "pythonVersion": "3.7" +} From 256a9f5f9ed735045008f0ea99e2d41250ee6755 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Tue, 18 Aug 2020 10:51:27 +0200 Subject: [PATCH 049/170] Extend configuration --- pyrightconfig.json | 13 +++++++++++-- src/sage/manifolds/chart.py | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pyrightconfig.json b/pyrightconfig.json index 657b58f008a..1b7d1fb5868 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -2,6 +2,15 @@ "include": [ "src/sage/manifolds" ], - - "pythonVersion": "3.7" + "executionEnvironments": [ + { + "root": "src" + } + ], + "pythonVersion": "3.7", + + "reportMissingImports": "warning", + "reportGeneralTypeIssues": "warning", + "reportUnboundVariable": "warning", + "reportDuplicateImport": "error" } diff --git a/src/sage/manifolds/chart.py b/src/sage/manifolds/chart.py index 07e3fa94aee..247d2b5e8a4 100644 --- a/src/sage/manifolds/chart.py +++ b/src/sage/manifolds/chart.py @@ -2928,7 +2928,7 @@ def _plot_xx_list(xx_list, rem_coords, ranges, steps, number_values): resu._extra_kwds['axes_labels'] = [r'$'+latex(ac)+r'$' for ac in ambient_coords] else: # 3D graphic - resu.aspect_ratio(1) + resu.set_aspect_ratio(1) if label_axes: labels = [str(ac) for ac in ambient_coords] resu = set_axes_labels(resu, *labels) From dc11c2682fbe174c273391f85de16aca6f96c7a4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 18 Aug 2020 06:46:41 -0700 Subject: [PATCH 050/170] src/doc/en/developer/tools.rst: Add stub --- src/doc/en/developer/index.rst | 8 ++++++++ src/doc/en/developer/tools.rst | 14 ++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/doc/en/developer/tools.rst diff --git a/src/doc/en/developer/index.rst b/src/doc/en/developer/index.rst index dfe9b6024c0..899ebe45a88 100644 --- a/src/doc/en/developer/index.rst +++ b/src/doc/en/developer/index.rst @@ -134,6 +134,14 @@ Testing on multiple platforms portability_testing +Additional development and testing tools +---------------------------------------- + +.. toctree:: + :maxdepth: 3 + + tools + Contributing to Manuals and Tutorials ------------------------------------- diff --git a/src/doc/en/developer/tools.rst b/src/doc/en/developer/tools.rst new file mode 100644 index 00000000000..1da65ee45c9 --- /dev/null +++ b/src/doc/en/developer/tools.rst @@ -0,0 +1,14 @@ +.. nodoctest + +.. highlight:: shell-session + +.. _chapter-tools: + +======================================== +Additional development and testing tools +======================================== + +pyright - a static type checker +=============================== + +...... From a04823f3d6d4119810d99cf32b16b92f5f0558ec Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Tue, 18 Aug 2020 16:59:35 +0200 Subject: [PATCH 051/170] Add documentation --- src/doc/en/developer/coding_basics.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index a58175f9ad0..f2f31930e9e 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -87,7 +87,19 @@ In particular, def SomeIdentityValue(x): return SomeValue(1) - +It is recommended to check your Python code against the style +conventions using the following tools: +- `Pycodestyle ` checks + against the style conventions of PEP8 Python. +- `Pyflakes ` checks for + common coding errors. +- `Pyright ` is a static + type checker. It can be used as a command-line tool and via the + `Pylance ` + extension for Visual Studio Code. + Currently, only the manifolds package is checked. + Further packages can be added in the ``pyrightconfig.json`` file + in the root. .. _chapter-directory-structure: From 86e21f3c7acbcaaaa5009ed9d3282b9b81c4dd3e Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Thu, 20 Aug 2020 20:51:19 +0200 Subject: [PATCH 052/170] Improve documentation --- src/doc/en/developer/coding_basics.rst | 13 ------------- src/doc/en/developer/tools.rst | 26 +++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index f2f31930e9e..5fd9839e0b0 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -87,19 +87,6 @@ In particular, def SomeIdentityValue(x): return SomeValue(1) -It is recommended to check your Python code against the style -conventions using the following tools: -- `Pycodestyle ` checks - against the style conventions of PEP8 Python. -- `Pyflakes ` checks for - common coding errors. -- `Pyright ` is a static - type checker. It can be used as a command-line tool and via the - `Pylance ` - extension for Visual Studio Code. - Currently, only the manifolds package is checked. - Further packages can be added in the ``pyrightconfig.json`` file - in the root. .. _chapter-directory-structure: diff --git a/src/doc/en/developer/tools.rst b/src/doc/en/developer/tools.rst index 1da65ee45c9..3aa765b293d 100644 --- a/src/doc/en/developer/tools.rst +++ b/src/doc/en/developer/tools.rst @@ -8,7 +8,31 @@ Additional development and testing tools ======================================== -pyright - a static type checker +Pyright =============================== +`Pyright ` is static type checker. + +*Installation:* ``npm install -g pyright``, see `documentation ` for details. +*Usage:* + - Manual: Run ``pyright path/to/the/file.py`` + - VS Code: Install the `Pylance ` extension. +*Configuration:* ``pyrightconfig.json`` in the root +*Note*: Currently, only the manifolds package is checked. Further packages can be added in the ``pyrightconfig.json`` file. +*Documentation:* https://github.com/microsoft/pyright#documentation + +Pycodestyle +=============================== +`Pycodestyle ` checks against the style conventions of PEP8 Python. + +*Installation:* ``pip install -U pycodestyle --user`` +*Usage:* + - Manual: Run ``pycodestyle path/to/the/file.py`` + - VS Code: Activate by adding the setting ``"python.linting.pycodestyleEnabled": true``, see `official VS Code documentation ` for details. +*Configuration:* ``[pycodestyle]`` block in ``src/tox.ini`` +*Documentation:* https://pycodestyle.pycqa.org/en/latest/index.html + +Pyflakes +=============================== +`Pyflakes ` checks for common coding errors. ...... From 88f24d00e8d95183a1d60ed7887b16e0fd7c37b1 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Fri, 21 Aug 2020 19:44:06 +0200 Subject: [PATCH 053/170] Remove dots --- src/doc/en/developer/tools.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/doc/en/developer/tools.rst b/src/doc/en/developer/tools.rst index 3aa765b293d..03ed977ba23 100644 --- a/src/doc/en/developer/tools.rst +++ b/src/doc/en/developer/tools.rst @@ -34,5 +34,3 @@ Pycodestyle Pyflakes =============================== `Pyflakes ` checks for common coding errors. - -...... From 3a20ec6c21a43363e9827db316dc46ebdd478ab7 Mon Sep 17 00:00:00 2001 From: Alexander Galarraga Date: Mon, 24 Aug 2020 15:32:06 -0400 Subject: [PATCH 054/170] 30141: fixed field of definition issue --- src/sage/dynamics/arithmetic_dynamics/projective_ds.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index 52184ff9280..0dd509f49d3 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -7023,11 +7023,12 @@ def potential_good_reduction(self, prime): elif valuation == 0: indifferent_point = fixed_points[multipliers.index(mult)] if indifferent_point is not None: - preimages = [indifferent_point] point = indifferent_point + field_of_definition = system.field_of_definition_preimage(point, 2) + system = system.change_ring(field_of_definition) + point = indifferent_point.change_ring(field_of_definition) + preimages = [point] for i in [1,2]: - field_of_definition = system.field_of_definition_preimage(point, 1) - system = system.change_ring(field_of_definition) preimages_of_point = system.rational_preimages(point, 1) for preimage in preimages_of_point: if preimage != point: From 524a2983ce89d74e7111aac1d57e8fbfc67caa81 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Mon, 24 Aug 2020 21:35:08 +0200 Subject: [PATCH 055/170] Revert changes in chart class --- src/sage/manifolds/chart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/manifolds/chart.py b/src/sage/manifolds/chart.py index 247d2b5e8a4..07e3fa94aee 100644 --- a/src/sage/manifolds/chart.py +++ b/src/sage/manifolds/chart.py @@ -2928,7 +2928,7 @@ def _plot_xx_list(xx_list, rem_coords, ranges, steps, number_values): resu._extra_kwds['axes_labels'] = [r'$'+latex(ac)+r'$' for ac in ambient_coords] else: # 3D graphic - resu.set_aspect_ratio(1) + resu.aspect_ratio(1) if label_axes: labels = [str(ac) for ac in ambient_coords] resu = set_axes_labels(resu, *labels) From 4159c3aa530e4bdc4ff5faefb449761d952770a2 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Tue, 25 Aug 2020 14:38:35 +0200 Subject: [PATCH 056/170] Fix indent --- src/doc/en/developer/tools.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/doc/en/developer/tools.rst b/src/doc/en/developer/tools.rst index 03ed977ba23..2bab6a52aca 100644 --- a/src/doc/en/developer/tools.rst +++ b/src/doc/en/developer/tools.rst @@ -14,8 +14,8 @@ Pyright *Installation:* ``npm install -g pyright``, see `documentation ` for details. *Usage:* - - Manual: Run ``pyright path/to/the/file.py`` - - VS Code: Install the `Pylance ` extension. +- Manual: Run ``pyright path/to/the/file.py`` +- VS Code: Install the `Pylance ` extension. *Configuration:* ``pyrightconfig.json`` in the root *Note*: Currently, only the manifolds package is checked. Further packages can be added in the ``pyrightconfig.json`` file. *Documentation:* https://github.com/microsoft/pyright#documentation @@ -26,8 +26,8 @@ Pycodestyle *Installation:* ``pip install -U pycodestyle --user`` *Usage:* - - Manual: Run ``pycodestyle path/to/the/file.py`` - - VS Code: Activate by adding the setting ``"python.linting.pycodestyleEnabled": true``, see `official VS Code documentation ` for details. +- Manual: Run ``pycodestyle path/to/the/file.py`` +- VS Code: Activate by adding the setting ``"python.linting.pycodestyleEnabled": true``, see `official VS Code documentation ` for details. *Configuration:* ``[pycodestyle]`` block in ``src/tox.ini`` *Documentation:* https://pycodestyle.pycqa.org/en/latest/index.html From 9906e830f9911f6c67a5cb90e5393eaec37d6a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20R=C3=BCth?= Date: Fri, 28 Aug 2020 01:11:58 +0200 Subject: [PATCH 057/170] Use list instead of polynomial for relative elements --- .../rings/number_field/number_field_element.pyx | 12 ++++++++++++ src/sage/rings/valuation/augmented_valuation.py | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index 6881da43608..820f8996689 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -3036,6 +3036,18 @@ cdef class NumberFieldElement(FieldElement): sage: g is f.polynomial() False + + Note that in relative number fields, this produces the polynomial of + the internal representation of this element which might not be what + you are looking for:: + + sage: R. = K[] + sage: L. = K.extension(y^2 - a) + sage: b.polynomial() + -1/2*x^19 - 1/2*x^17 - 1/2*x^15 - 1/2*x^13 - x^11 - 1/2*x^9 - 1/2*x^7 - 1/2*x^5 - x^3 - 1/2*x + sage: R(list(b)) + y + """ from sage.rings.polynomial.polynomial_ring_constructor import _single_variate as Pol return Pol(QQ, var)(self._coefficients()) diff --git a/src/sage/rings/valuation/augmented_valuation.py b/src/sage/rings/valuation/augmented_valuation.py index dda486bb13e..cda072edd89 100644 --- a/src/sage/rings/valuation/augmented_valuation.py +++ b/src/sage/rings/valuation/augmented_valuation.py @@ -1083,6 +1083,18 @@ def lift(self, F): sage: w.lift(w.residue_ring().gen()) (1 + O(2^10))*x + TESTS: + + Verify that :trac:`30305` has been resolved:: + + sage: R. = QQ[] + sage: K. = NumberField(T^2 + T + 1) + sage: R. = K[] + sage: v0 = GaussValuation(R, valuations.TrivialValuation(K)) + sage: v = v0.augmentation(x^2 + x + 2, 1) + sage: v.lift(v.reduce(x)) == x + True + """ F = self.residue_ring().coerce(F) @@ -1096,10 +1108,14 @@ def lift(self, F): if self.psi().degree() > 1: from sage.rings.polynomial.polynomial_quotient_ring_element import PolynomialQuotientRingElement from sage.rings.function_field.element import FunctionFieldElement_polymod + from sage.rings.number_field.number_field_element import NumberFieldElement_relative + from sage.all import PolynomialRing if isinstance(F, PolynomialQuotientRingElement): G = F.lift() elif isinstance(F, FunctionFieldElement_polymod): G = F.element() + elif isinstance(F, NumberFieldElement_relative): + G = PolynomialRing(F.base_ring(), 'x')(list(F)) else: G = F.polynomial() assert(G(self._residue_field_generator()) == F) From 7d629a2f0572aab41778a5e6f7c5eff9278ec39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 28 Aug 2020 16:00:00 +0200 Subject: [PATCH 058/170] fix typos in occurrence, occurred, occurring (all take two r) --- src/sage/categories/functor.pyx | 2 +- src/sage/combinat/finite_state_machine_generators.py | 2 +- src/sage/combinat/free_module.py | 2 +- src/sage/combinat/ncsf_qsym/qsym.py | 2 +- src/sage/combinat/set_partition.py | 2 +- src/sage/combinat/similarity_class_type.py | 2 +- src/sage/combinat/words/word_generators.py | 2 +- src/sage/databases/cunningham_tables.py | 2 +- src/sage/doctest/util.py | 2 +- src/sage/functions/exp_integral.py | 6 +++--- .../combinatorial_polyhedron/combinatorial_face.pyx | 2 +- src/sage/graphs/base/boost_graph.pyx | 2 +- src/sage/graphs/digraph.py | 8 ++++---- src/sage/graphs/dot2tex_utils.py | 2 +- src/sage/graphs/generic_graph.py | 2 +- src/sage/interfaces/expect.py | 4 ++-- src/sage/interfaces/gap.py | 2 +- src/sage/interfaces/polymake.py | 2 +- src/sage/interfaces/singular.py | 2 +- src/sage/manifolds/differentiable/tensorfield.py | 2 +- src/sage/manifolds/section.py | 2 +- src/sage/matrix/args.pyx | 2 +- src/sage/misc/sage_unittest.py | 2 +- src/sage/misc/sageinspect.py | 2 +- src/sage/misc/temporary_file.py | 2 +- src/sage/modular/abvar/homspace.py | 2 +- src/sage/modular/arithgroup/farey.cpp | 4 ++-- src/sage/modular/modform_hecketriangle/abstract_space.py | 4 ++-- src/sage/modular/modform_hecketriangle/analytic_type.py | 2 +- src/sage/modular/modform_hecketriangle/space.py | 2 +- src/sage/quivers/paths.pyx | 2 +- src/sage/rings/polynomial/laurent_polynomial.pyx | 2 +- src/sage/rings/polynomial/multi_polynomial_sequence.py | 2 +- src/sage/rings/polynomial/polynomial_element_generic.py | 2 +- src/sage/rings/polynomial/symmetric_ideal.py | 2 +- src/sage/stats/basic_stats.py | 4 ++-- src/sage/symbolic/expression_conversions.py | 2 +- src/sage/symbolic/todo.txt | 4 ++-- 38 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/sage/categories/functor.pyx b/src/sage/categories/functor.pyx index fee6d9de4a1..66873a37ac8 100644 --- a/src/sage/categories/functor.pyx +++ b/src/sage/categories/functor.pyx @@ -500,7 +500,7 @@ class ForgetfulFunctor_generic(Functor): sage: F1 = ForgetfulFunctor(FiniteFields(),Fields()) - This is to test against a bug occuring in a previous version + This is to test against a bug occurring in a previous version (see :trac:`8800`):: sage: F1 == QQ #indirect doctest diff --git a/src/sage/combinat/finite_state_machine_generators.py b/src/sage/combinat/finite_state_machine_generators.py index ba7d86b0d49..78bef233f6f 100644 --- a/src/sage/combinat/finite_state_machine_generators.py +++ b/src/sage/combinat/finite_state_machine_generators.py @@ -1401,7 +1401,7 @@ def Recursion(self, recursions, base, function=None, var=None, - ``base`` -- base of the digit expansion. - - ``function`` -- symbolic function ``f`` occuring in the + - ``function`` -- symbolic function ``f`` occurring in the recursions. - ``var`` -- symbolic variable. diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index 41730a2a063..f49965f27db 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -391,7 +391,7 @@ def __init__(self, R, basis_keys=None, element_class=None, category=None, Regression test for :trac:`10127`: ``self._indices`` needs to be set early enough, in case the initialization of the categories - use ``self.basis().keys()``. This occured on several occasions + use ``self.basis().keys()``. This occurred on several occasions in non trivial constructions. In the following example, :class:`AlgebrasWithBasis` constructs ``Homset(self,self)`` to extend by bilinearity method ``product_on_basis``, which in diff --git a/src/sage/combinat/ncsf_qsym/qsym.py b/src/sage/combinat/ncsf_qsym/qsym.py index 5f8e2d64e5c..44aae642397 100644 --- a/src/sage/combinat/ncsf_qsym/qsym.py +++ b/src/sage/combinat/ncsf_qsym/qsym.py @@ -113,7 +113,7 @@ class QuasiSymmetricFunctions(UniqueRepresentation, Parent): `R`-subalgebra of the ring of power series in countably many variables `R[[x_1, x_2, x_3, \ldots]]`. It consists of those formal power series `p` which are degree-bounded (i. e., the degrees - of all monomials occuring with nonzero coefficient in `p` are bounded + of all monomials occurring with nonzero coefficient in `p` are bounded from above, although the bound can depend on `p`) and satisfy the following condition: For every tuple `(a_1, a_2, \ldots, a_m)` of positive integers, the coefficient of the monomial diff --git a/src/sage/combinat/set_partition.py b/src/sage/combinat/set_partition.py index 7a237ec15cf..ee6ed60126a 100644 --- a/src/sage/combinat/set_partition.py +++ b/src/sage/combinat/set_partition.py @@ -2223,7 +2223,7 @@ def from_rook_placement(self, rooks, bijection="arcs", n=None): If ``n`` is not given, it is first checked whether it can be determined from the parent, otherwise it is the maximal - occuring integer in the set of rooks. + occurring integer in the set of rooks. INPUT: diff --git a/src/sage/combinat/similarity_class_type.py b/src/sage/combinat/similarity_class_type.py index ec3676a710c..7ee14fbcc4d 100644 --- a/src/sage/combinat/similarity_class_type.py +++ b/src/sage/combinat/similarity_class_type.py @@ -791,7 +791,7 @@ def centralizer_group_card(self, q = None): def as_partition_dictionary(self): r""" - Return a dictionary whose keys are the partitions of types occuring in + Return a dictionary whose keys are the partitions of types occurring in ``self`` and the value at the key `\lambda` is the partition formed by sorting the degrees of primary types with partition `\lambda`. diff --git a/src/sage/combinat/words/word_generators.py b/src/sage/combinat/words/word_generators.py index 03b046efab0..abbd7ee95a4 100644 --- a/src/sage/combinat/words/word_generators.py +++ b/src/sage/combinat/words/word_generators.py @@ -1062,7 +1062,7 @@ def _KolakoskiWord_iterator(self, a=1, b=2): - ``a`` - positive integer (default: 1), the first letter occurring in the returned Kolakoski word. - ``b`` - positive integer (default: 2), the second and last letter - occuring in the returned Kolakoski word. + occurring in the returned Kolakoski word. OUTPUT: diff --git a/src/sage/databases/cunningham_tables.py b/src/sage/databases/cunningham_tables.py index d25ceb082a6..0ffadf2ed02 100644 --- a/src/sage/databases/cunningham_tables.py +++ b/src/sage/databases/cunningham_tables.py @@ -12,7 +12,7 @@ @cached_function def cunningham_prime_factors(): r""" - List of all the prime numbers occuring in the so called Cunningham table. + List of all the prime numbers occurring in the so called Cunningham table. They occur in the factorization of numbers of type $b^n+1$ or $b^n-1$ with $b \in \{2,3,5,6,7,10,11,12\}$. diff --git a/src/sage/doctest/util.py b/src/sage/doctest/util.py index cd1706e011d..4e735e6fe98 100644 --- a/src/sage/doctest/util.py +++ b/src/sage/doctest/util.py @@ -60,7 +60,7 @@ def count_noun(number, noun, plural=None, pad_number=False, pad_noun=False): def dict_difference(self, other): """ - Return a dict with all key-value pairs occuring in ``self`` but not + Return a dict with all key-value pairs occurring in ``self`` but not in ``other``. EXAMPLES:: diff --git a/src/sage/functions/exp_integral.py b/src/sage/functions/exp_integral.py index 7f4ced54aa1..28b9d007c81 100644 --- a/src/sage/functions/exp_integral.py +++ b/src/sage/functions/exp_integral.py @@ -1312,10 +1312,10 @@ def _derivative_(self, z, diff_param=None): ################################################################### -## Code below here was moved from sage/functions/transcendental.py -## This occured as part of Trac #11143. +# Code below here was moved from sage/functions/transcendental.py +# This occurred as part of Trac #11143. ################################################################### -# + # This class has a name which is not specific enough # see Function_exp_integral_e above, for example, which # is the "generalized" exponential integral function. We diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx index 32fc72b1171..fc1602ea844 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx @@ -268,7 +268,7 @@ cdef class CombinatorialFace(SageObject): Return an index for the face. If the face was constructed from a :class:`sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator`, - then this is the index of the occurence in the iterator. + then this is the index of the occurrence in the iterator. If the face was constructed from :meth:`sage:geometry.polyhedron.combinatorial_polyhedronn.base.CombinatorialPolyhedron.face_by_face_lattice_index`, diff --git a/src/sage/graphs/base/boost_graph.pyx b/src/sage/graphs/base/boost_graph.pyx index 075aa13706b..be7f4407ae9 100644 --- a/src/sage/graphs/base/boost_graph.pyx +++ b/src/sage/graphs/base/boost_graph.pyx @@ -1593,7 +1593,7 @@ cpdef min_cycle_basis(g_sage, weight_function=None, by_weight=False): v_int = min_path.predecessors[v_int] min_path_nodes.append(v_int if v_int < n else v_int - n) - # removal of edges occuring even number of times + # removal of edges occurring even number of times edges = set() for edge in zip(min_path_nodes[:-1], min_path_nodes[1:]): edges ^= {edge} diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index b65b9aa31fd..aa7fefb24e6 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -2683,7 +2683,7 @@ def _all_cycles_iterator_vertex(self, vertex, starting_vertices=None, simple=Fal - ``simple`` -- boolean (default: ``False``); if set to ``True``, then only simple cycles are considered. A cycle is simple if the only - vertex occuring twice in it is the starting and ending one. + vertex occurring twice in it is the starting and ending one. - ``rooted`` -- boolean (default: ``False``); if set to False, then cycles differing only by their starting vertex are considered the same @@ -2803,7 +2803,7 @@ def all_cycles_iterator(self, starting_vertices=None, simple=False, - ``simple`` -- boolean (default: ``False``); if set to ``True``, then only simple cycles are considered. A cycle is simple if the only - vertex occuring twice in it is the starting and ending one. + vertex occurring twice in it is the starting and ending one. - ``rooted`` -- boolean (default: ``False``); if set to False, then cycles differing only by their starting vertex are considered the same @@ -2882,7 +2882,7 @@ def all_cycles_iterator(self, starting_vertices=None, simple=False, ['a', 'a', 'a', 'a']] One may prefer to enumerate simple cycles, i.e. cycles such that the only - vertex occuring twice in it is the starting and ending one (see also + vertex occurring twice in it is the starting and ending one (see also :meth:`all_simple_cycles`):: sage: it = g.all_cycles_iterator(simple=True) @@ -3347,7 +3347,7 @@ def level_sets(self): the levels `l[j]` for `j < i`, and at least one in level `l[i-1]` (unless `i = 0`). - The level decomposition contains exactly the vertices not occuring in + The level decomposition contains exactly the vertices not occurring in any cycle of the graph. In particular, the graph is acyclic if and only if the decomposition forms a set partition of its vertices, and we recover the usual level set decomposition of the corresponding poset. diff --git a/src/sage/graphs/dot2tex_utils.py b/src/sage/graphs/dot2tex_utils.py index d8c29fd0373..63b5b496738 100644 --- a/src/sage/graphs/dot2tex_utils.py +++ b/src/sage/graphs/dot2tex_utils.py @@ -52,7 +52,7 @@ def assert_have_dot2tex(): For support, please contact . """ import_error_string = """ -An error occured when importing dot2tex. +An error occurred when importing dot2tex. Please see :meth:`sage.graphs.generic_graph.GenericGraph.layout_graphviz` for installation instructions. diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 3fbc28d75dd..99d8fa90145 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -23513,7 +23513,7 @@ def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_lab for u, v in edge_iter: if G.has_edge(u, v): continue - # We count the number of occurence of each distinct label + # We count the number of occurrence of each distinct label if ignore_edge_labels: G.add_edge(u, v, [[None, len(g.edge_label(u, v))]]) else: diff --git a/src/sage/interfaces/expect.py b/src/sage/interfaces/expect.py index cce13898753..cbe3a939cd6 100644 --- a/src/sage/interfaces/expect.py +++ b/src/sage/interfaces/expect.py @@ -780,7 +780,7 @@ def _eval_line_using_file(self, line, restart_if_needed=True): - ``restart_if_needed`` - (optional bool, default ``True``) -- If it is ``True``, the command evaluation is evaluated a second time after restarting the interface, if an - ``EOFError`` occured. + ``EOFError`` occurred. TESTS:: @@ -882,7 +882,7 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if - ``restart_if_needed`` (optional bool, default ``True``) -- If it is ``True``, the command evaluation is evaluated a second time after restarting the interface, if an - ``EOFError`` occured. + ``EOFError`` occurred. TESTS:: diff --git a/src/sage/interfaces/gap.py b/src/sage/interfaces/gap.py index 5f24b102da6..fcaf6e430cf 100644 --- a/src/sage/interfaces/gap.py +++ b/src/sage/interfaces/gap.py @@ -704,7 +704,7 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if - ``restart_if_needed`` (optional bool, default ``True``) -- If it is ``True``, the command evaluation is evaluated a second time after restarting the interface, if an - ``EOFError`` occured. + ``EOFError`` occurred. TESTS:: diff --git a/src/sage/interfaces/polymake.py b/src/sage/interfaces/polymake.py index b4f1a4760b1..a8d7a50b035 100644 --- a/src/sage/interfaces/polymake.py +++ b/src/sage/interfaces/polymake.py @@ -1983,7 +1983,7 @@ def _synchronize(self): elif i > 0: raise RuntimeError("Polymake unexpectedly {}".format(_available_polymake_answers[i])) except pexpect.TIMEOUT: - warnings.warn("A timeout has occured when synchronising {}.".format(self), RuntimeWarning) + warnings.warn("A timeout has occurred when synchronising {}.".format(self), RuntimeWarning) self._interrupt() except pexpect.EOF: self._crash_msg() diff --git a/src/sage/interfaces/singular.py b/src/sage/interfaces/singular.py index 88a33b0349b..e0faf1409ee 100644 --- a/src/sage/interfaces/singular.py +++ b/src/sage/interfaces/singular.py @@ -631,7 +631,7 @@ def eval(self, x, allow_semicolon=True, strip=True, **kwds): # singular.set(). Moreover, it is not done by calling a separate _eval_line. # In that way, the time spent by waiting for the singular prompt is reduced. - # Before #10296, it was possible that garbage collection occured inside + # Before #10296, it was possible that garbage collection occurred inside # of _eval_line. But collection of the garbage would launch another call # to _eval_line. The result would have been a dead lock, that could only # be avoided by synchronisation. Since garbage collection is now done diff --git a/src/sage/manifolds/differentiable/tensorfield.py b/src/sage/manifolds/differentiable/tensorfield.py index 142ad21557f..03909bb4baa 100644 --- a/src/sage/manifolds/differentiable/tensorfield.py +++ b/src/sage/manifolds/differentiable/tensorfield.py @@ -2288,7 +2288,7 @@ def __eq__(self, other): except ValueError: break else: - # If this point is reached, no exception has occured; hence + # If this point is reached, no exception has occurred; hence # the result is valid and can be returned: return resu # If this point is reached, the comparison has not been possible diff --git a/src/sage/manifolds/section.py b/src/sage/manifolds/section.py index 0d6f93272cc..b37dff1944a 100644 --- a/src/sage/manifolds/section.py +++ b/src/sage/manifolds/section.py @@ -1887,7 +1887,7 @@ def __eq__(self, other): except ValueError: break else: - # If this point is reached, no exception has occured; hence + # If this point is reached, no exception has occurred; hence # the result is valid and can be returned: return resu # If this point is reached, the comparison has not been possible diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index bf4692ad726..5cbeb0eaa7f 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -987,7 +987,7 @@ cdef class MatrixArgs: """ cdef list seqsparse = [] cdef list values = [] - cdef long maxrow = -1, maxcol = -1 # Maximum occuring value for indices + cdef long maxrow = -1, maxcol = -1 # Maximum occurring value for indices cdef long i, j for (i0, j0), x in self.entries.items(): sig_check() diff --git a/src/sage/misc/sage_unittest.py b/src/sage/misc/sage_unittest.py index 8fc8ba71131..e587a6d8c0d 100644 --- a/src/sage/misc/sage_unittest.py +++ b/src/sage/misc/sage_unittest.py @@ -298,7 +298,7 @@ def _test_b(self, tester): tester.fail() except catch_exception as e: failed.append(method_name) if isinstance(e, TestSuiteFailure): - # The failure occured in a nested testsuite + # The failure occurred in a nested testsuite # which has already reported the details of # that failure if not tester._verbose: diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index 8e73143fe89..6602962ae1a 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -2312,7 +2312,7 @@ class Element(object): sage: sage_getsourcelines(HC) ([' class Homsets(HomsetsCategory):\n', ...], ...) - Testing against a bug that has occured during work on :trac:`11768`:: + Testing against a bug that has occurred during work on :trac:`11768`:: sage: P. = QQ[] sage: I = P*[x,y] diff --git a/src/sage/misc/temporary_file.py b/src/sage/misc/temporary_file.py index 91bd88642b5..e50726b0225 100644 --- a/src/sage/misc/temporary_file.py +++ b/src/sage/misc/temporary_file.py @@ -165,7 +165,7 @@ class atomic_write(object): This is to be used in a ``with`` statement, where a temporary file is created when entering the ``with`` and is moved in place of the - target file when exiting the ``with`` (if no exceptions occured). + target file when exiting the ``with`` (if no exceptions occurred). INPUT: diff --git a/src/sage/modular/abvar/homspace.py b/src/sage/modular/abvar/homspace.py index 522d094d791..9414cd8a977 100644 --- a/src/sage/modular/abvar/homspace.py +++ b/src/sage/modular/abvar/homspace.py @@ -776,7 +776,7 @@ def __init__(self, A, gens=None, category=None): TESTS: The following tests against a problem on 32 bit machines that - occured while working on :trac:`9944`:: + occurred while working on :trac:`9944`:: sage: sage.modular.abvar.homspace.EndomorphismSubring(J1(12345)) Endomorphism ring of Abelian variety J1(12345) of dimension 5405473 diff --git a/src/sage/modular/arithgroup/farey.cpp b/src/sage/modular/arithgroup/farey.cpp index 5f7bcc3434d..a98ade699d0 100644 --- a/src/sage/modular/arithgroup/farey.cpp +++ b/src/sage/modular/arithgroup/farey.cpp @@ -863,7 +863,7 @@ FareySymbol::LLT_algorithm(const SL2Z& M, vector& p, SL2Z& beta) const { p.push_back((int)k + 1); } else { //Based on Lemma 4 of the article by Kurth/Long, - //this case can not occure. + //this case can not happen. throw string("Mathematical complications in ") + __FUNCTION__; return; @@ -891,7 +891,7 @@ bool FareySymbol::is_element(const SL2Z& M) const { // Case (3) of the article if( beta == SL2Z::S or beta == SL2Z::U ) { - // If 0 and infty are adjacent vertices, they only can occure + // If 0 and infty are adjacent vertices, they only can appear // at the beginning or the end. if( x[0] == 0 and pairing[0] == EVEN ) return true; if( x.back() == 0 and pairing.back() == EVEN ) return true; diff --git a/src/sage/modular/modform_hecketriangle/abstract_space.py b/src/sage/modular/modform_hecketriangle/abstract_space.py index 0e429a25268..092fa8c7c39 100644 --- a/src/sage/modular/modform_hecketriangle/abstract_space.py +++ b/src/sage/modular/modform_hecketriangle/abstract_space.py @@ -704,7 +704,7 @@ def weight_parameters(self): l2 = num % n l1 = ((num-l2)/n).numerator() else: - raise ValueError("Invalid or non-occuring weight k={}, ep={}!".format(k,ep)) + raise ValueError("Invalid or non-occurring weight k={}, ep={}!".format(k,ep)) return (l1, l2) # TODO: this only makes sense for modular forms, @@ -2219,7 +2219,7 @@ def rationalize_series(self, laurent_series, coeff_bound = 1e-10, denom_factor = - ``denom_factor`` -- An integer (default: 1) whose factor might occur in the denominator of the given Laurent coefficients - (in addition to naturally occuring factors). + (in addition to naturally occurring factors). OUTPUT: diff --git a/src/sage/modular/modform_hecketriangle/analytic_type.py b/src/sage/modular/modform_hecketriangle/analytic_type.py index 121fbada96b..b56fc11ec00 100644 --- a/src/sage/modular/modform_hecketriangle/analytic_type.py +++ b/src/sage/modular/modform_hecketriangle/analytic_type.py @@ -288,7 +288,7 @@ class AnalyticType(FiniteLatticePoset): Container for all possible analytic types of forms and/or spaces. The ``analytic type`` of forms spaces or rings describes all possible - occuring basic ``analytic properties`` of elements in the space/ring + occurring basic ``analytic properties`` of elements in the space/ring (or more). For ambient spaces/rings this means that all elements with those properties diff --git a/src/sage/modular/modform_hecketriangle/space.py b/src/sage/modular/modform_hecketriangle/space.py index 0727e6c54ff..a3e9065ad19 100644 --- a/src/sage/modular/modform_hecketriangle/space.py +++ b/src/sage/modular/modform_hecketriangle/space.py @@ -72,7 +72,7 @@ def canonical_parameters(group, base_ring, k, ep, n=None): try: num = ZZ(num) except TypeError: - raise ValueError("Invalid or non-occuring weight k={}, ep={}!".format(k,ep)) + raise ValueError("Invalid or non-occurring weight k={}, ep={}!".format(k,ep)) return (group, base_ring, k, ep, n) diff --git a/src/sage/quivers/paths.pyx b/src/sage/quivers/paths.pyx index 5d36de7e308..3416e334c84 100644 --- a/src/sage/quivers/paths.pyx +++ b/src/sage/quivers/paths.pyx @@ -135,7 +135,7 @@ cdef class QuiverPath(MonoidElement): - ``start``, integer, the label of the initial vertex. - ``end``, integer, the label of the terminal vertex. - ``path``, list of integers, providing the list of arrows - occuring in the path, labelled according to the position in + occurring in the path, labelled according to the position in the list of all arrows (resp. the list of outgoing arrows at each vertex). diff --git a/src/sage/rings/polynomial/laurent_polynomial.pyx b/src/sage/rings/polynomial/laurent_polynomial.pyx index afabe4e9b98..d6d021bcfcb 100644 --- a/src/sage/rings/polynomial/laurent_polynomial.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial.pyx @@ -1490,7 +1490,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): def variables(self): """ - Return the tuple of variables occuring in this Laurent polynomial. + Return the tuple of variables occurring in this Laurent polynomial. EXAMPLES:: diff --git a/src/sage/rings/polynomial/multi_polynomial_sequence.py b/src/sage/rings/polynomial/multi_polynomial_sequence.py index 5681663d134..31c262fbbf4 100644 --- a/src/sage/rings/polynomial/multi_polynomial_sequence.py +++ b/src/sage/rings/polynomial/multi_polynomial_sequence.py @@ -1422,7 +1422,7 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver TESTS: - Make sure that variables not occuring in the equations are no problem:: + Make sure that variables not occurring in the equations are no problem:: sage: R. = BooleanPolynomialRing() sage: S = Sequence([x*y+z, y*z+x, x+y+z+1]) diff --git a/src/sage/rings/polynomial/polynomial_element_generic.py b/src/sage/rings/polynomial/polynomial_element_generic.py index 8bc566884fb..7e3215be7a2 100644 --- a/src/sage/rings/polynomial/polynomial_element_generic.py +++ b/src/sage/rings/polynomial/polynomial_element_generic.py @@ -101,7 +101,7 @@ def __init__(self, parent, x=None, check=True, is_gen=False, construct=False): for n, c in x.dict().items(): w[n] = R(c) # The following line has been added in trac ticket #9944. - # Apparently, the "else" case has never occured before. + # Apparently, the "else" case has never occurred before. x = w elif isinstance(x, list): x = dict((i, c) for (i, c) in enumerate(x) if c) diff --git a/src/sage/rings/polynomial/symmetric_ideal.py b/src/sage/rings/polynomial/symmetric_ideal.py index 989bc6cb4a2..fb27c6cd691 100644 --- a/src/sage/rings/polynomial/symmetric_ideal.py +++ b/src/sage/rings/polynomial/symmetric_ideal.py @@ -142,7 +142,7 @@ class SymmetricIdeal( Ideal_generic ): However, any element of ``J`` has symmetric reduction zero even after applying a permutation. This even holds when the permutations involve higher variable indices than the ones - occuring in ``J``:: + occurring in ``J``:: sage: [[(p^P).reduce(J) for p in J] for P in Permutations(3)] [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] diff --git a/src/sage/stats/basic_stats.py b/src/sage/stats/basic_stats.py index 2c0c2df4277..0450b9d21b7 100644 --- a/src/sage/stats/basic_stats.py +++ b/src/sage/stats/basic_stats.py @@ -10,7 +10,7 @@ of a list, divided by the number of members). Further revisions may include the geometric and harmonic mean. The ``median`` function returns the number separating the higher half of a sample from the lower half. The ``mode`` -returns the most common occuring member of a sample, plus the number of times +returns the most common occurring member of a sample, plus the number of times it occurs. If entries occur equally common, the smallest of a list of the most common entries is returned. The ``moving_average`` is a finite impulse response filter, creating a series of averages using a user-defined number of @@ -90,7 +90,7 @@ def mode(v): """ Return the mode of `v`. - The mode is the list of the most frequently occuring + The mode is the list of the most frequently occurring elements in `v`. If `n` is the most times that any element occurs in `v`, then the mode is the list of elements of `v` that occur `n` times. The list is sorted if possible. diff --git a/src/sage/symbolic/expression_conversions.py b/src/sage/symbolic/expression_conversions.py index 040d448bbc4..dcf20f57b4e 100644 --- a/src/sage/symbolic/expression_conversions.py +++ b/src/sage/symbolic/expression_conversions.py @@ -858,7 +858,7 @@ def derivative(self, ex, operator): sage: diff(f(x, t), x)._sympy_(), diff(f(x, t), t)._sympy_() (Derivative(f(x, t), x), Derivative(f(x, t), t)) - Check differentiating by variables with multiple occurences + Check differentiating by variables with multiple occurrences (:trac:`28964`):: sage: f = function('f') diff --git a/src/sage/symbolic/todo.txt b/src/sage/symbolic/todo.txt index e4f39125793..476eb715075 100644 --- a/src/sage/symbolic/todo.txt +++ b/src/sage/symbolic/todo.txt @@ -100,7 +100,7 @@ Wall time: 0.05 s sage: time z = expand((u + v + a + b + c)^100) ^C^C^C ------------------------------------------------------------ -Unhandled SIGBUS: A bus error occured in SAGE. +Unhandled SIGBUS: A bus error occurred in SAGE. done * pretty serious todo -- all predefined constants in utils.cpp get made wrong: // TODO: BECAUSE of Floor division, this gives the wrong answer!! @@ -143,7 +143,7 @@ For PHASE II: LATER (Hopefully Burcin) done * CRASH: sage: f = (Mod(2,7)*x^2 + Mod(2,7))^7 ------------------------------------------------------------ -Unhandled SIGBUS: A bus error occured in SAGE. +Unhandled SIGBUS: A bus error occurred in SAGE. It works fine with leading coefficient Mod(1,7) From c4ee43b00a3ffcd18d204f793052dac838b2ef46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 28 Aug 2020 21:22:43 +0200 Subject: [PATCH 059/170] some pyflakes details in modified files --- src/sage/combinat/similarity_class_type.py | 7 +++---- src/sage/interfaces/polymake.py | 9 ++++++--- src/sage/rings/polynomial/symmetric_ideal.py | 19 +++++++++---------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/sage/combinat/similarity_class_type.py b/src/sage/combinat/similarity_class_type.py index 7ee14fbcc4d..3ff23ebd9f4 100644 --- a/src/sage/combinat/similarity_class_type.py +++ b/src/sage/combinat/similarity_class_type.py @@ -195,7 +195,7 @@ class type, it is also possible to compute the number of classes of that type @cached_function -def fq(n, q = None): +def fq(n, q=None): r""" Return `(1-q^{-1}) (1-q^{-2}) \cdots (1-q^{-n})`. @@ -399,7 +399,7 @@ def __repr__(self): sage: PrimarySimilarityClassType(2, [3, 2, 1]) [2, [3, 2, 1]] """ - return "%s"%([self._deg, self._par]) + return "%s" % ([self._deg, self._par],) def __hash__(self): r""" @@ -513,7 +513,7 @@ def centralizer_algebra_dim(self): return self.degree()*centralizer_algebra_dim(self.partition()) @cached_in_parent_method - def statistic(self, func, q = None): + def statistic(self, func, q=None): r""" Return `n_{\lambda}(q^d)` where `n_{\lambda}` is the value returned by ``func`` upon input `\lambda`, if ``self`` is `(d, \lambda)`. @@ -1598,4 +1598,3 @@ def matrix_centralizer_cardinalities_length_two(n, q = None, selftranspose = Fal for tau in SimilarityClassTypes(n): for pair in ext_orbit_centralizers(tau, q = q, selftranspose = selftranspose): yield (q**tau.centralizer_algebra_dim()*pair[0], tau.number_of_classes(invertible = invertible, q = q)*pair[1]) - diff --git a/src/sage/interfaces/polymake.py b/src/sage/interfaces/polymake.py index a8d7a50b035..542b04e4375 100644 --- a/src/sage/interfaces/polymake.py +++ b/src/sage/interfaces/polymake.py @@ -316,6 +316,7 @@ def _coerce_impl(self, x, use_special=True): A = [] z = dict() cls = self._object_class() + def convert(y): if isinstance(y, cls): return y @@ -1363,7 +1364,7 @@ def __getitem__(self, key): P = self._check_valid() if isinstance(key, slice): indices = key.indices(len(self)) - return [ self[i] for i in range(*indices) ] + return [self[i] for i in range(*indices)] _, T = self.typeof() if self._name.startswith('@'): return P('${}[{}]'.format(self._name[1:], key)) @@ -2177,7 +2178,7 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if if self._terminal_echo and first: i = out.find("\n") j = out.rfind("\r") - out = out[i+1:j].replace('\r\n', '\n') + out = out[i + 1:j].replace('\r\n', '\n') else: out = out.strip().replace('\r\n', '\n') first = False @@ -2367,7 +2368,7 @@ def application(self, app): self._application = app patterns = ["{} > ".format(app), # 0: normal prompt r"{} \([0-9]+\)> ".format(app), # 1: continuation prompt - "Please choose ".format(app), # 2: user input expected when requesting "help" + "Please choose {}".format(app), # 2: user input expected when requesting "help" "killed by signal", # 3: what we are looking for when interrupting a computation "polymake: +ERROR: +", # 4: error "polymake: +WARNING: +", # 5: warning @@ -2380,8 +2381,10 @@ def application(self, app): if pat: raise RuntimeError("When changing the application, polymake unexpectedly {}".format(_available_polymake_answers[pat])) + Polymake = PolymakeExpect + class PolymakeJuPyMake(PolymakeAbstract): r""" diff --git a/src/sage/rings/polynomial/symmetric_ideal.py b/src/sage/rings/polynomial/symmetric_ideal.py index fb27c6cd691..71b5937c518 100644 --- a/src/sage/rings/polynomial/symmetric_ideal.py +++ b/src/sage/rings/polynomial/symmetric_ideal.py @@ -40,7 +40,7 @@ True """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2009 Simon King # # Distributed under the terms of the GNU General Public License (GPL) @@ -52,17 +52,16 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** -from __future__ import print_function - +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.rings.ideal import Ideal_generic from sage.rings.integer import Integer from sage.structure.sequence import Sequence from sage.misc.cachefunc import cached_method import sys -class SymmetricIdeal( Ideal_generic ): + +class SymmetricIdeal(Ideal_generic): r""" Ideal in an Infinite Polynomial Ring, invariant under permutation of variable indices @@ -486,7 +485,6 @@ def interreduction(self, tailreduce=True, sorted=False, report=None, RStrat=None """ DONE = [] - j = 0 TODO = [] PARENT = self.ring() for P in self.gens(): @@ -515,9 +513,8 @@ def interreduction(self, tailreduce=True, sorted=False, report=None, RStrat=None return SymmetricIdeal(PARENT,[0]) VarList.sort(key=PARENT.varname_key, reverse=True) from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - CommonR = PolynomialRing(self.base_ring(), VarList, order=self.ring()._order) - ## Now, the symmetric interreduction starts + # Now, the symmetric interreduction starts if not (report is None): print('Symmetric interreduction') from sage.rings.polynomial.symmetric_reduction import SymmetricReductionStrategy @@ -962,7 +959,9 @@ def groebner_basis(self, tailreduce=False, reduced=True, algorithm=None, report= print("->", len(newOUT.gens()), 'generators') # Symmetrise out to the next index: N += 1 - newOUT = newOUT.symmetrisation(N=N,tailreduce=tailreduce,report=report,use_full_group=use_full_group) + newOUT = newOUT.symmetrisation(N=N, tailreduce=tailreduce, + report=report, + use_full_group=use_full_group) if [X.lm() for X in OUT.gens()] == [X.lm() for X in newOUT.gens()]: if reduced: if tailreduce: From 55127f74e98a67f566294d4aa5a47b311dc5db3e Mon Sep 17 00:00:00 2001 From: Joshua Campbell Date: Fri, 28 Aug 2020 15:00:08 -0700 Subject: [PATCH 060/170] Add dark theme to Three.js viewer --- src/sage/ext_data/threejs/animation.css | 37 +++++++++++++++++++ .../ext_data/threejs/threejs_template.html | 33 +++++++++++++---- src/sage/plot/plot3d/base.pyx | 10 ++++- 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/src/sage/ext_data/threejs/animation.css b/src/sage/ext_data/threejs/animation.css index 2f7bdc41eec..c8281b95704 100644 --- a/src/sage/ext_data/threejs/animation.css +++ b/src/sage/ext_data/threejs/animation.css @@ -36,11 +36,19 @@ border-radius: 28px; cursor: pointer; } +.dark-theme #animation-ui button { + padding: 1px; + border: 1px dotted black; +} #animation-ui button:focus { background: #b8b9ff; outline: none; } +.dark-theme #animation-ui button:focus { + background: none; + border-color: white; +} /* Setting the outline to none isn't enough for Firefox. */ #animation-ui button::-moz-focus-inner { @@ -50,6 +58,10 @@ #animation-ui button:active { background: #efefff; } +.dark-theme #animation-ui button:active { + background: #303030; + border-style: solid; +} #animation-ui.playing .play, #animation-ui.paused .pause, @@ -156,3 +168,28 @@ input[type=range].slider:focus::-ms-fill-lower { input[type=range].slider:focus::-ms-fill-upper { background: #b8b9f6; } + +.dark-theme input[type=range].slider { + background: black; +} +.dark-theme input[type=range].slider::-webkit-slider-runnable-track { + background: #303030; +} +.dark-theme input[type=range].slider:focus::-webkit-slider-thumb { + border: 1px dotted white; +} +.dark-theme input[type=range].slider::-moz-range-track { + background: #303030; +} +.dark-theme input[type=range].slider:focus::-moz-range-thumb { + border: 1px dotted white; +} +.dark-theme input[type=range].slider::-ms-fill-lower { + background: #303030; +} +.dark-theme input[type=range].slider::-ms-fill-upper { + background: #303030; +} +.dark-theme input[type=range].slider:focus::-ms-thumb { + border: 1px dotted white; +} diff --git a/src/sage/ext_data/threejs/threejs_template.html b/src/sage/ext_data/threejs/threejs_template.html index 62dc0c44a93..b0f3a9ff235 100644 --- a/src/sage/ext_data/threejs/threejs_template.html +++ b/src/sage/ext_data/threejs/threejs_template.html @@ -19,7 +19,17 @@ #menu-content div { border-top: 1px solid black; padding: 10px; white-space: nowrap; } - #menu-content div:hover { background-color: #FEFEFE;; } + #menu-content div:hover { background-color: #FEFEFE; } + + .dark-theme #menu-container { color: white; } + + .dark-theme #menu-message { background-color: #181818; } + + .dark-theme #menu-content { background-color: #181818; border-color: white; } + + .dark-theme #menu-content div { border-color: white; } + + .dark-theme #menu-content div:hover { background-color: #303030; } SAGE_STYLES @@ -29,17 +39,20 @@ SAGE_SCRIPTS