From bb9c543f9f913634311e3a665d7c4495b5047248 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Mon, 18 Jun 2018 11:40:38 +0530 Subject: [PATCH 001/264] Added the basic class structure of the Triconnectivity module. --- src/doc/en/reference/references/index.rst | 7 ++ src/sage/graphs/connectivity.pyx | 98 +++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 9a34807ebcb..62d6585d989 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -1257,6 +1257,10 @@ REFERENCES: .. [Gu] GUAVA manual, http://www.gap-system.org/Packages/guava.html +.. [Gut2001] Carsten Gutwenger and Petra Mutzel. *A Linear Time Implementation + of SPQR-Trees*, International Symposium on Graph Drawing, + (2001) 77-90 + .. [GW1999] Frederick M. Goodman and Hans Wenzl. *Crystal bases of quantum affine algebras and affine Kazhdan-Lusztig polyonmials*. Int. Math. Res. Notices **5** (1999), 251-275. @@ -1332,6 +1336,9 @@ REFERENCES: .. [Hoc] Winfried Hochstaettler, "About the Tic-Tac-Toe Matroid", preprint. +.. [Hopcroft1973] J. E. Hopcroft and R. E. Tarjan. *Dividing a Graph into + Triconnected Components*, SIAM J. Comput., 2(3), 135–158 + .. [Hopkins2017] Sam Hopkins, *RSK via local transformations*, http://web.mit.edu/~shopkins/docs/rsk.pdf diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 919ecdc6366..0177f9efb2e 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1852,3 +1852,101 @@ def bridges(G, labels=True): my_bridges.append(tuple(b)) return my_bridges + + +from sage.graphs.base.sparse_graph cimport SparseGraph + + +class Triconnectivity: + """ + This module is not yet complete, it has work to be done. + + This module implements the algorithm for finding the triconnected + components of a biconnected graph. + Refer to [Gut2001]_ and [Hopcroft1973]_ for the algorithm. + + EXAMPLES:: + + An example to show how the triconnected components can be accessed: + + sage: from sage.graphs.connectivity import Triconnectivity + sage: g = Graph([['a','b',1],['a','c',1],['b','c',10]], weighted=True) + sage: tric = Triconnectivity(g) + sage: tric.components_list + [] + """ + class Component: + edge_list = [] + component_type = 0 #bond = 0, polygon = 1, triconnected = 2 + def __init__(self, edges, type_c=0): + self.edge_list = edges + component_type = type_c + def add_edge(self, e): + self.edge_list.append(e) + + graph_copy = None #type SparseGraph + vertex_to_int = {} # mapping of vertices to integers in c_graph + start_vertex = 0 + components_list = [] #list of components + num_components = 0 + edge_status = {} #status of each edge, unseen=0, tree=1, frond=2, removed=3 + Estack = [] + Tstack = [] + number = [] # DFS number of vertex i + vertex_at = [] # vertex with DFS number of i$ + lowpt1 = [] # lowpt1 number of vertex i + lowpt2 = [] # lowpt2 number of vertex i + nd = [] # number of descendants of vertex i + edge_phi = {} # (key, value) = (edge, corresponding phi value) + adj = [] # i^th value contains a list of incident edges of vertex i + in_adj = {} # this variable is used in the PathSearch() function + newnum = [] # new DFS number of vertex i + startsPath = {} # dict of (edge, True/False) to denote if a path starts at edge + highpt = [] # List of fronds entering vertex i in the order they are visited + old_to_new = [] # New DFS number of the vertex with i as old DFS number + degree = [] # Degree of vertex i + parent = [] # Parent vertex of vertex i in the palm tree + tree_arc = [] # Tree arc entering the vertex i + first_child = [] + high = [] # One of the elements in highpt[i] + dfs_counter = 0 + n = 0 # number of vertices + m = 0 # number of edges + + + def __init__(self, G): + self.graph_copy = G.copy(implementation='c_graph') + self.vertex_to_int = self.graph_copy.relabel(inplace=True, return_map=True) + self.n = self.graph_copy.order() + self.m = self.graph_copy.num_edges() + self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) + self.number = [0]*self.n + self.lowpt1 = [None]*self.n + self.lowpt2 = [None]*self.n + self.nd = [None]*self.n + self.parent = [None]*self.n + self.degree = [None]*self.n + self.tree_arc = [None]*self.n + self.vertex_at = [1]*self.n + self.dfs_counter = 0 + # We call the function split_multi_egdes() next. + + def new_component(self, edges, type_c=0): + c = self.Component(edges, type_c) + self.components_list.append(c) + # Remove the edges from graph + for e in edges: + self.edge_status[e] = 3 + + def add_edge_to_component(self, comp, e): + comp.add_edge(e) + self.edge_status[e] = 3 + + + + + + + + + From c85b5e808212dbf1cedf09cec4e9be783b3bbb4d Mon Sep 17 00:00:00 2001 From: saiharsh Date: Tue, 19 Jun 2018 23:19:00 +0530 Subject: [PATCH 002/264] Split_multiple_edges function is added. --- src/sage/graphs/connectivity.pyx | 67 ++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 0177f9efb2e..23365ea3bfa 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1887,7 +1887,6 @@ class Triconnectivity: graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph start_vertex = 0 - components_list = [] #list of components num_components = 0 edge_status = {} #status of each edge, unseen=0, tree=1, frond=2, removed=3 Estack = [] @@ -1918,7 +1917,7 @@ class Triconnectivity: self.graph_copy = G.copy(implementation='c_graph') self.vertex_to_int = self.graph_copy.relabel(inplace=True, return_map=True) self.n = self.graph_copy.order() - self.m = self.graph_copy.num_edges() + self.m = self.graph_copy.size() self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) self.number = [0]*self.n self.lowpt1 = [None]*self.n @@ -1929,7 +1928,8 @@ class Triconnectivity: self.tree_arc = [None]*self.n self.vertex_at = [1]*self.n self.dfs_counter = 0 - # We call the function split_multi_egdes() next. + self.components_list = [] #list of components + self.split_multi_egdes() def new_component(self, edges, type_c=0): c = self.Component(edges, type_c) @@ -1937,16 +1937,61 @@ class Triconnectivity: # Remove the edges from graph for e in edges: self.edge_status[e] = 3 + self.num_components += 1 def add_edge_to_component(self, comp, e): comp.add_edge(e) self.edge_status[e] = 3 - - - - - - - - + def split_multi_egdes(self): + """ + This function will remove all the multiple edges present in + graph_copy and append the multiple edges in component list. + + If there are `k` multiple edges between `u` and `v` then `k+1` + edges will be added to a component. + + It won't return anything but update the components_list and + graph_copy, which will become simple graph. + + Example:: + + An example to list the components build after removal of multiple edges + + sage: G = Graph() + sage: G.add_cycle(vertices=[0,1,2,3,4]) + sage: G.allow_multiple_edges(True) + sage: G.add_edges(G.edges()) + sage: G.add_edges([[0,1],[3, 4]]) + sage: from sage.graphs.connectivity import Triconnectivity + sage: t = Triconnectivity(G) + sage: for l in t.components_list: + ....: print l.edge_list + [(0, 1), (0, 1), (0, 1), (0, 1)] + [(0, 4), (0, 4), (0, 4)] + [(1, 2), (1, 2), (1, 2)] + [(2, 3), (2, 3), (2, 3)] + [(3, 4), (3, 4)] + sage: t.num_components + 5 + """ + + comp = [] + if self.graph_copy.has_multiple_edges(): + sorted_edges = sorted(self.graph_copy.multiple_edges(labels=False)) + for i in range(len(sorted_edges) - 1): + + # It will add k - 1 multiple edges to comp + if sorted_edges[i] == sorted_edges[i + 1]: + self.graph_copy.delete_edge(sorted_edges[i]) + comp.append(sorted_edges[i]) + else: + if len(comp): + comp.append(sorted_edges[i-1]) + comp.append(sorted_edges[i-1]) + self.new_component(comp) + comp = [] + if len(comp): + comp.append(sorted_edges[i-1]) + comp.append(sorted_edges[i-1]) + self.new_component(comp) \ No newline at end of file From f1cfce2eb8f0a3e7991f425ef804ecf1f6a732d3 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Fri, 22 Jun 2018 01:24:28 +0530 Subject: [PATCH 003/264] Added the function dfs1() which builds the palm tree. --- src/doc/en/reference/references/index.rst | 2 +- src/sage/graphs/connectivity.pyx | 117 +++++++++++++++++++--- 2 files changed, 106 insertions(+), 13 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 62d6585d989..a419a82cfb4 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -1258,7 +1258,7 @@ REFERENCES: .. [Gu] GUAVA manual, http://www.gap-system.org/Packages/guava.html .. [Gut2001] Carsten Gutwenger and Petra Mutzel. *A Linear Time Implementation - of SPQR-Trees*, International Symposium on Graph Drawing, + of SPQR-Trees*, International Symposium on Graph Drawing, (2001) 77-90 .. [GW1999] Frederick M. Goodman and Hans Wenzl. *Crystal bases of quantum diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 23365ea3bfa..bd689413f02 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1855,14 +1855,14 @@ def bridges(G, labels=True): from sage.graphs.base.sparse_graph cimport SparseGraph - + class Triconnectivity: """ This module is not yet complete, it has work to be done. This module implements the algorithm for finding the triconnected - components of a biconnected graph. + components of a biconnected graph. Refer to [Gut2001]_ and [Hopcroft1973]_ for the algorithm. EXAMPLES:: @@ -1883,7 +1883,7 @@ class Triconnectivity: component_type = type_c def add_edge(self, e): self.edge_list.append(e) - + graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph start_vertex = 0 @@ -1891,7 +1891,7 @@ class Triconnectivity: edge_status = {} #status of each edge, unseen=0, tree=1, frond=2, removed=3 Estack = [] Tstack = [] - number = [] # DFS number of vertex i + dfs_number = [] # DFS number of vertex i vertex_at = [] # vertex with DFS number of i$ lowpt1 = [] # lowpt1 number of vertex i lowpt2 = [] # lowpt2 number of vertex i @@ -1911,7 +1911,7 @@ class Triconnectivity: dfs_counter = 0 n = 0 # number of vertices m = 0 # number of edges - + def __init__(self, G): self.graph_copy = G.copy(implementation='c_graph') @@ -1919,7 +1919,7 @@ class Triconnectivity: self.n = self.graph_copy.order() self.m = self.graph_copy.size() self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) - self.number = [0]*self.n + self.dfs_number = [0]*self.n self.lowpt1 = [None]*self.n self.lowpt2 = [None]*self.n self.nd = [None]*self.n @@ -1930,7 +1930,10 @@ class Triconnectivity: self.dfs_counter = 0 self.components_list = [] #list of components self.split_multi_egdes() - + self.dfs_counter = 0 # Initialisation for dfs1() + self.start_vertex = 0 # Initialisation for dfs1() + self.dfs1(self.start_vertex) + def new_component(self, edges, type_c=0): c = self.Component(edges, type_c) self.components_list.append(c) @@ -1938,11 +1941,11 @@ class Triconnectivity: for e in edges: self.edge_status[e] = 3 self.num_components += 1 - + def add_edge_to_component(self, comp, e): comp.add_edge(e) self.edge_status[e] = 3 - + def split_multi_egdes(self): """ This function will remove all the multiple edges present in @@ -1966,12 +1969,12 @@ class Triconnectivity: sage: from sage.graphs.connectivity import Triconnectivity sage: t = Triconnectivity(G) sage: for l in t.components_list: - ....: print l.edge_list + ....: print(l.edge_list) [(0, 1), (0, 1), (0, 1), (0, 1)] [(0, 4), (0, 4), (0, 4)] [(1, 2), (1, 2), (1, 2)] [(2, 3), (2, 3), (2, 3)] - [(3, 4), (3, 4)] + [(3, 4), (3, 4), (3, 4), (3, 4)] sage: t.num_components 5 """ @@ -1994,4 +1997,94 @@ class Triconnectivity: if len(comp): comp.append(sorted_edges[i-1]) comp.append(sorted_edges[i-1]) - self.new_component(comp) \ No newline at end of file + self.new_component(comp) + + def dfs1(self, v, u=None): + """ + This function builds the palm tree of the graph. + Also populates the lists lowpt1, lowpt2, nd, parent, and dfs_number. + It updates the dict edge_status to reflect palm tree arcs and fronds. + + Input:: + + - ``v`` -- The start vertex for DFS. + + - ``u`` -- The parent vertex of ``v`` in the palm tree. + + Example:: + + An example to list the components build after removal of multiple edges + + sage: from sage.graphs.connectivity import Triconnectivity + sage: G = Graph() + sage: G.add_edges([(1,2),(1,13),(1,12),(1,8),(1,4),(2,13),(2,3),(3,13)]) + sage: G.add_edges([(3,4),(4,5),(4,7),(5,6),(5,7),(5,8),(6,7),(8,9),(8,12)]) + sage: G.add_edges([(8,11),(9,10),(9,12),(9,11),(10,11),(10,12)]) + sage: tric = Triconnectivity(G) + sage: tric.edge_status + {(0, 1, None): 1, + (0, 3, None): 2, + (0, 7, None): 2, + (0, 11, None): 2, + (0, 12, None): 2, + (1, 2, None): 1, + (1, 12, None): 2, + (2, 3, None): 1, + (2, 12, None): 1, + (3, 4, None): 1, + (3, 6, None): 2, + (4, 5, None): 1, + (4, 6, None): 2, + (4, 7, None): 1, + (5, 6, None): 1, + (7, 8, None): 1, + (7, 10, None): 2, + (7, 11, None): 2, + (8, 9, None): 1, + (8, 10, None): 2, + (8, 11, None): 2, + (9, 10, None): 1, + (9, 11, None): 1} + sage: tric.lowpt1 + [1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 8, 1, 1] + sage: tric.lowpt2 + [1, 2, 2, 4, 4, 5, 5, 8, 8, 8, 9, 8, 2] + sage: tric.parent + [None, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 9, 2] + """ + + self.dfs_counter += 1 + self.dfs_number[v] = self.dfs_counter + self.parent[v] = u + self.lowpt1[v] = self.lowpt2[v] = self.dfs_number[v] + self.nd[v] = 1 + for e in self.graph_copy.edges_incident([v]): + if self.edge_status[e] != 0 : + continue + + w = e[0] if e[0] != v else e[1] # Other vertex of edge e + if (self.dfs_number[w] == 0): + self.edge_status[e] = 1 # tree edge + self.tree_arc[w] = e + self.dfs1(w, v) + + if (self.lowpt1[w] < self.lowpt1[v]): + self.lowpt2[v] = min(self.lowpt1[v], self.lowpt2[w]) + self.lowpt1[v] = self.lowpt1[w] + + elif (self.lowpt1[w] == self.lowpt1[v]): + self.lowpt2[v] = min(self.lowpt2[v], self.lowpt2[w]) + + else: + self.lowpt2[v] = min(self.lowpt2[v], self.lowpt1[w]) + + self.nd[v] += self.nd[w] + + else: + self.edge_status[e] = 2 #frond + if (self.dfs_number[w] < self.lowpt1[v]): + self.lowpt2[v] = self.lowpt1[v] + self.lowpt1[v] = self.dfs_number[w] + elif (self.dfs_number[w] > self.lowpt1[v]): + self.lowpt2[v] = min(self.lowpt2[v], self.dfs_number[w]) + From bcc5aee054d20c62d67c94119205a3f38b9f238e Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Fri, 22 Jun 2018 01:28:41 +0530 Subject: [PATCH 004/264] Fixed a small error in split_multi_edges() --- src/sage/graphs/connectivity.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index bd689413f02..b17ad66b598 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1986,7 +1986,7 @@ class Triconnectivity: # It will add k - 1 multiple edges to comp if sorted_edges[i] == sorted_edges[i + 1]: - self.graph_copy.delete_edge(sorted_edges[i]) + self.edge_status[sorted_edges[i]] = 3 # edge removed comp.append(sorted_edges[i]) else: if len(comp): From 078067e2c0ca8676e651319e49d627d7e4de29d8 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Fri, 22 Jun 2018 08:45:44 +0530 Subject: [PATCH 005/264] Modified dfs1() to check biconnectivity --- src/sage/graphs/connectivity.pyx | 70 ++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index b17ad66b598..9447eeb8f72 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1886,6 +1886,7 @@ class Triconnectivity: graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph + int_to_vertex = {} # mapping of integers to original vertices start_vertex = 0 num_components = 0 edge_status = {} #status of each edge, unseen=0, tree=1, frond=2, removed=3 @@ -1900,7 +1901,7 @@ class Triconnectivity: adj = [] # i^th value contains a list of incident edges of vertex i in_adj = {} # this variable is used in the PathSearch() function newnum = [] # new DFS number of vertex i - startsPath = {} # dict of (edge, True/False) to denote if a path starts at edge + startsPath = {} # dict of (edge, T/F) to denote if a path starts at edge highpt = [] # List of fronds entering vertex i in the order they are visited old_to_new = [] # New DFS number of the vertex with i as old DFS number degree = [] # Degree of vertex i @@ -1911,11 +1912,16 @@ class Triconnectivity: dfs_counter = 0 n = 0 # number of vertices m = 0 # number of edges + check = True # If the graph needs to be tested for biconnectivity + is_biconnected = True # Boolean to store if the graph is biconnected or not + cut_vertex = None # If graph is not biconnected - def __init__(self, G): + def __init__(self, G, check=True): self.graph_copy = G.copy(implementation='c_graph') + self.check = check self.vertex_to_int = self.graph_copy.relabel(inplace=True, return_map=True) + self.int_to_vertex = dict([(v,k) for k,v in self.vertex_to_int.items()]) self.n = self.graph_copy.order() self.m = self.graph_copy.size() self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) @@ -1932,7 +1938,20 @@ class Triconnectivity: self.split_multi_egdes() self.dfs_counter = 0 # Initialisation for dfs1() self.start_vertex = 0 # Initialisation for dfs1() - self.dfs1(self.start_vertex) + self.cut_vertex = self.dfs1(self.start_vertex, check=check) + + if (check == True): + # graph is disconnected + if (self.dfs_number < self.n): + self.is_biconnected = False + return + + # graph has a cut vertex + if (self.cut_vertex != None): + self.cut_vertex = self.int_to_vertex[self.cut_vertex] + self.is_biconnected = False + return + def new_component(self, edges, type_c=0): c = self.Component(edges, type_c) @@ -1999,7 +2018,8 @@ class Triconnectivity: comp.append(sorted_edges[i-1]) self.new_component(comp) - def dfs1(self, v, u=None): + + def dfs1(self, v, u=None, check=True): """ This function builds the palm tree of the graph. Also populates the lists lowpt1, lowpt2, nd, parent, and dfs_number. @@ -2011,16 +2031,26 @@ class Triconnectivity: - ``u`` -- The parent vertex of ``v`` in the palm tree. + - ``check`` -- if True, the graph is tested for biconnectivity. If the + graph has a cut vertex, the cut vertex is returned. If set to False, + the graph is assumed to be biconnected, function returns None. + + Output:: + + - If ``check`` is set to True, and a cut vertex is found, the cut vertex + is returned. If no cut vertex is found, return value if None. + If ``check`` is set to False, ``None`` is returned. + Example:: - An example to list the components build after removal of multiple edges + An example to build the palm tree: sage: from sage.graphs.connectivity import Triconnectivity sage: G = Graph() sage: G.add_edges([(1,2),(1,13),(1,12),(1,8),(1,4),(2,13),(2,3),(3,13)]) sage: G.add_edges([(3,4),(4,5),(4,7),(5,6),(5,7),(5,8),(6,7),(8,9),(8,12)]) sage: G.add_edges([(8,11),(9,10),(9,12),(9,11),(10,11),(10,12)]) - sage: tric = Triconnectivity(G) + sage: tric = Triconnectivity(G, check=False) sage: tric.edge_status {(0, 1, None): 1, (0, 3, None): 2, @@ -2051,11 +2081,24 @@ class Triconnectivity: [1, 2, 2, 4, 4, 5, 5, 8, 8, 8, 9, 8, 2] sage: tric.parent [None, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 9, 2] + + An example of a disconnected graph: + + sage: G = Graph() + sage: G.add_edges([(1,2),(1,3),(2,3),(3,4),(3,5),(4,5)]) + sage: tric = Triconnectivity(G, check=True) + sage: tric.is_biconnected + False + sage: tric.cut_vertex + 3 """ + first_son = None # For testing biconnectivity + s1 = None # Storing the cut vertex, if there is one self.dfs_counter += 1 self.dfs_number[v] = self.dfs_counter self.parent[v] = u + self.degree[v] = self.graph_copy.degree(v) self.lowpt1[v] = self.lowpt2[v] = self.dfs_number[v] self.nd[v] = 1 for e in self.graph_copy.edges_incident([v]): @@ -2065,12 +2108,19 @@ class Triconnectivity: w = e[0] if e[0] != v else e[1] # Other vertex of edge e if (self.dfs_number[w] == 0): self.edge_status[e] = 1 # tree edge + if (first_son == None): + first_son = w self.tree_arc[w] = e - self.dfs1(w, v) + s1 = self.dfs1(w, v, check) + + if (check == True): + # check for cut vertex + if ((self.lowpt1[w] >= self.dfs_number[v]) and (w != first_son or u != None)): + s1 = v if (self.lowpt1[w] < self.lowpt1[v]): - self.lowpt2[v] = min(self.lowpt1[v], self.lowpt2[w]) - self.lowpt1[v] = self.lowpt1[w] + self.lowpt2[v] = min(self.lowpt1[v], self.lowpt2[w]) + self.lowpt1[v] = self.lowpt1[w] elif (self.lowpt1[w] == self.lowpt1[v]): self.lowpt2[v] = min(self.lowpt2[v], self.lowpt2[w]) @@ -2088,3 +2138,5 @@ class Triconnectivity: elif (self.dfs_number[w] > self.lowpt1[v]): self.lowpt2[v] = min(self.lowpt2[v], self.dfs_number[w]) + return s1 + From 9875d507ef991628946a1c6be7aa479dfdd2410b Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Fri, 22 Jun 2018 23:48:01 +0530 Subject: [PATCH 006/264] Added the function build_acceptable_adjacency_structure() --- src/sage/graphs/connectivity.pyx | 90 +++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 13 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 9447eeb8f72..63a488248b5 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1889,7 +1889,7 @@ class Triconnectivity: int_to_vertex = {} # mapping of integers to original vertices start_vertex = 0 num_components = 0 - edge_status = {} #status of each edge, unseen=0, tree=1, frond=2, removed=3 + edge_status = {} # status of each edge, unseen=0, tree=1, frond=2, removed=3 Estack = [] Tstack = [] dfs_number = [] # DFS number of vertex i @@ -1912,27 +1912,31 @@ class Triconnectivity: dfs_counter = 0 n = 0 # number of vertices m = 0 # number of edges - check = True # If the graph needs to be tested for biconnectivity is_biconnected = True # Boolean to store if the graph is biconnected or not cut_vertex = None # If graph is not biconnected + # T/F to denote if the source and target of the palm tree edge are + # opposite to that of the graph + edge_reverse = {} + def __init__(self, G, check=True): self.graph_copy = G.copy(implementation='c_graph') - self.check = check self.vertex_to_int = self.graph_copy.relabel(inplace=True, return_map=True) self.int_to_vertex = dict([(v,k) for k,v in self.vertex_to_int.items()]) self.n = self.graph_copy.order() self.m = self.graph_copy.size() self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) - self.dfs_number = [0]*self.n - self.lowpt1 = [None]*self.n - self.lowpt2 = [None]*self.n - self.nd = [None]*self.n - self.parent = [None]*self.n - self.degree = [None]*self.n - self.tree_arc = [None]*self.n - self.vertex_at = [1]*self.n + self.edge_reverse = dict((e,False) for e in self.graph_copy.edges()) + self.dfs_number = [0 for i in xrange(self.n)] + self.lowpt1 = [None for i in xrange(self.n)] + self.lowpt2 = [None for i in xrange(self.n)] + self.adj = [[] for i in xrange(self.n)] + self.nd = [None for i in xrange(self.n)] + self.parent = [None for i in xrange(self.n)] + self.degree = [None for i in xrange(self.n)] + self.tree_arc = [None for i in xrange(self.n)] + self.vertex_at = [1 for i in xrange(self.n)] self.dfs_counter = 0 self.components_list = [] #list of components self.split_multi_egdes() @@ -1951,7 +1955,20 @@ class Triconnectivity: self.cut_vertex = self.int_to_vertex[self.cut_vertex] self.is_biconnected = False return - + + # Reversing the edges to reflect the palm tree arcs and fronds + # Is there a better way to do it? + for e in self.graph_copy.edges(): + up = (self.dfs_number[e[1]] - self.dfs_number[e[0]]) > 0 + if ((up and self.edge_status[e]==2) or (not up and self.edge_status[e]==1)): + # Reverse the edge + # self.graph_copy.delete_edge(e) + # self.graph_copy.add_edge(e[1],e[0]) + # self.edge_status[(e[1],e[0],e[2])] = self.edge_status.pop(e) + self.edge_reverse[e] = True + + self.build_acceptable_adj_struct() + def new_component(self, edges, type_c=0): c = self.Component(edges, type_c) @@ -2139,4 +2156,51 @@ class Triconnectivity: self.lowpt2[v] = min(self.lowpt2[v], self.dfs_number[w]) return s1 - + + + def build_acceptable_adj_struct(self): + """ + Builds the adjacency lists for each vertex with certain properties + of the ordering, using the ``lowpt1`` and ``lowpt2`` values. + + The list ``adj`` and the dictionary ``in_adj`` are populated. + """ + max = 3*self.n + 2 + bucket = [[] for i in range(max+1)] + + for e in self.graph_copy.edges(): + edge_type = self.edge_status[e] + if (edge_type == 3): + continue + + # compute phi value + # the if condition for edge_reverse can be avoided if we find + # a different way to implement reversed edges + if (self.edge_reverse[e] == True): + if (edge_type==1): # tree arc + if (self.lowpt2[e[0]] < self.dfs_number[e[1]]): + phi = 3*self.lowpt1[e[0]] + elif (self.lowpt2[e[0]] >= self.dfs_number[e[1]]): + phi = 3*self.lowpt1[e[0]] + 2 + else: # tree frond + phi = 3*self.dfs_number[e[0]]+1 + else: + if (edge_type==1): # tree arc + if (self.lowpt2[e[1]] < self.dfs_number[e[0]]): + phi = 3*self.lowpt1[e[1]] + elif (self.lowpt2[e[1]] >= self.dfs_number[e[0]]): + phi = 3*self.lowpt1[e[1]] + 2 + else: # tree frond + phi = 3*self.dfs_number[e[1]]+1 + + bucket[phi].append(e) + + for i in xrange(1,max+1): + for e in bucket[i]: + if (self.edge_reverse[e] == True): + self.adj[e[1]].append(e) + self.in_adj[e] = self.adj[e[1]] + else: + self.adj[e[0]].append(e) + self.in_adj[e] = self.adj[e[0]] + From 49293bf6914d240a70fb4dcb33c057c766938c6e Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Sun, 24 Jun 2018 10:12:08 +0530 Subject: [PATCH 007/264] Added adjacency list to make adjacency queries linear time. --- src/sage/graphs/connectivity.pyx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 63a488248b5..8eb1fd67066 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1914,6 +1914,7 @@ class Triconnectivity: m = 0 # number of edges is_biconnected = True # Boolean to store if the graph is biconnected or not cut_vertex = None # If graph is not biconnected + graph_copy_adjacency = [] # T/F to denote if the source and target of the palm tree edge are # opposite to that of the graph @@ -1939,6 +1940,14 @@ class Triconnectivity: self.vertex_at = [1 for i in xrange(self.n)] self.dfs_counter = 0 self.components_list = [] #list of components + self.graph_copy_adjacency = [[] for i in xrange(self.n)] + + # Build adjacency list + for e in self.graph_copy.edges(): + self.graph_copy_adjacency[e[0]].append(e) + self.graph_copy_adjacency[e[1]].append(e) + + # Triconnectivity algorithm self.split_multi_egdes() self.dfs_counter = 0 # Initialisation for dfs1() self.start_vertex = 0 # Initialisation for dfs1() @@ -2118,7 +2127,7 @@ class Triconnectivity: self.degree[v] = self.graph_copy.degree(v) self.lowpt1[v] = self.lowpt2[v] = self.dfs_number[v] self.nd[v] = 1 - for e in self.graph_copy.edges_incident([v]): + for e in self.graph_copy_adjacency[v]: if self.edge_status[e] != 0 : continue From 4b79b27603881a7c5961117b3c4a405c1bd6ff69 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Mon, 25 Jun 2018 07:38:14 +0530 Subject: [PATCH 008/264] Some changes in coding style --- src/sage/graphs/connectivity.pyx | 88 +++++++++++++++----------------- 1 file changed, 41 insertions(+), 47 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 8eb1fd67066..5faed3c580d 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1916,9 +1916,8 @@ class Triconnectivity: cut_vertex = None # If graph is not biconnected graph_copy_adjacency = [] - # T/F to denote if the source and target of the palm tree edge are - # opposite to that of the graph - edge_reverse = {} + # Edges of the graph which are in the reverse direction in palm tree + reverse_edges = set() def __init__(self, G, check=True): @@ -1928,19 +1927,19 @@ class Triconnectivity: self.n = self.graph_copy.order() self.m = self.graph_copy.size() self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) - self.edge_reverse = dict((e,False) for e in self.graph_copy.edges()) - self.dfs_number = [0 for i in xrange(self.n)] - self.lowpt1 = [None for i in xrange(self.n)] - self.lowpt2 = [None for i in xrange(self.n)] - self.adj = [[] for i in xrange(self.n)] - self.nd = [None for i in xrange(self.n)] - self.parent = [None for i in xrange(self.n)] - self.degree = [None for i in xrange(self.n)] - self.tree_arc = [None for i in xrange(self.n)] - self.vertex_at = [1 for i in xrange(self.n)] + self.reverse_edges = set() + self.dfs_number = [0 for i in range(self.n)] + self.lowpt1 = [None for i in range(self.n)] + self.lowpt2 = [None for i in range(self.n)] + self.adj = [[] for i in range(self.n)] + self.nd = [None for i in range(self.n)] + self.parent = [None for i in range(self.n)] + self.degree = [None for i in range(self.n)] + self.tree_arc = [None for i in range(self.n)] + self.vertex_at = [1 for i in range(self.n)] self.dfs_counter = 0 self.components_list = [] #list of components - self.graph_copy_adjacency = [[] for i in xrange(self.n)] + self.graph_copy_adjacency = [[] for i in range(self.n)] # Build adjacency list for e in self.graph_copy.edges(): @@ -1953,14 +1952,14 @@ class Triconnectivity: self.start_vertex = 0 # Initialisation for dfs1() self.cut_vertex = self.dfs1(self.start_vertex, check=check) - if (check == True): + if check: # graph is disconnected - if (self.dfs_number < self.n): + if self.dfs_number < self.n: self.is_biconnected = False return # graph has a cut vertex - if (self.cut_vertex != None): + if self.cut_vertex != None: self.cut_vertex = self.int_to_vertex[self.cut_vertex] self.is_biconnected = False return @@ -1969,12 +1968,9 @@ class Triconnectivity: # Is there a better way to do it? for e in self.graph_copy.edges(): up = (self.dfs_number[e[1]] - self.dfs_number[e[0]]) > 0 - if ((up and self.edge_status[e]==2) or (not up and self.edge_status[e]==1)): - # Reverse the edge - # self.graph_copy.delete_edge(e) - # self.graph_copy.add_edge(e[1],e[0]) - # self.edge_status[(e[1],e[0],e[2])] = self.edge_status.pop(e) - self.edge_reverse[e] = True + if (up and self.edge_status[e]==2) or (not up and self.edge_status[e]==1): + # Add edge to the set reverse_edges + self.reverse_edges.add(e) self.build_acceptable_adj_struct() @@ -2034,12 +2030,12 @@ class Triconnectivity: self.edge_status[sorted_edges[i]] = 3 # edge removed comp.append(sorted_edges[i]) else: - if len(comp): + if comp: comp.append(sorted_edges[i-1]) comp.append(sorted_edges[i-1]) self.new_component(comp) comp = [] - if len(comp): + if comp: comp.append(sorted_edges[i-1]) comp.append(sorted_edges[i-1]) self.new_component(comp) @@ -2128,27 +2124,27 @@ class Triconnectivity: self.lowpt1[v] = self.lowpt2[v] = self.dfs_number[v] self.nd[v] = 1 for e in self.graph_copy_adjacency[v]: - if self.edge_status[e] != 0 : - continue + if self.edge_status[e]: + continue w = e[0] if e[0] != v else e[1] # Other vertex of edge e - if (self.dfs_number[w] == 0): + if self.dfs_number[w] == 0: self.edge_status[e] = 1 # tree edge - if (first_son == None): + if first_son is None: first_son = w self.tree_arc[w] = e s1 = self.dfs1(w, v, check) - if (check == True): + if check: # check for cut vertex - if ((self.lowpt1[w] >= self.dfs_number[v]) and (w != first_son or u != None)): + if (self.lowpt1[w] >= self.dfs_number[v]) and (w != first_son or u != None): s1 = v - if (self.lowpt1[w] < self.lowpt1[v]): + if self.lowpt1[w] < self.lowpt1[v]: self.lowpt2[v] = min(self.lowpt1[v], self.lowpt2[w]) self.lowpt1[v] = self.lowpt1[w] - elif (self.lowpt1[w] == self.lowpt1[v]): + elif self.lowpt1[w] == self.lowpt1[v]: self.lowpt2[v] = min(self.lowpt2[v], self.lowpt2[w]) else: @@ -2158,10 +2154,10 @@ class Triconnectivity: else: self.edge_status[e] = 2 #frond - if (self.dfs_number[w] < self.lowpt1[v]): + if self.dfs_number[w] < self.lowpt1[v]: self.lowpt2[v] = self.lowpt1[v] self.lowpt1[v] = self.dfs_number[w] - elif (self.dfs_number[w] > self.lowpt1[v]): + elif self.dfs_number[w] > self.lowpt1[v]: self.lowpt2[v] = min(self.lowpt2[v], self.dfs_number[w]) return s1 @@ -2179,34 +2175,32 @@ class Triconnectivity: for e in self.graph_copy.edges(): edge_type = self.edge_status[e] - if (edge_type == 3): + if edge_type == 3: continue # compute phi value - # the if condition for edge_reverse can be avoided if we find - # a different way to implement reversed edges - if (self.edge_reverse[e] == True): - if (edge_type==1): # tree arc - if (self.lowpt2[e[0]] < self.dfs_number[e[1]]): + if e in self.reverse_edges: + if edge_type==1: # tree arc + if self.lowpt2[e[0]] < self.dfs_number[e[1]]: phi = 3*self.lowpt1[e[0]] - elif (self.lowpt2[e[0]] >= self.dfs_number[e[1]]): + elif self.lowpt2[e[0]] >= self.dfs_number[e[1]]: phi = 3*self.lowpt1[e[0]] + 2 else: # tree frond phi = 3*self.dfs_number[e[0]]+1 else: - if (edge_type==1): # tree arc - if (self.lowpt2[e[1]] < self.dfs_number[e[0]]): + if edge_type==1: # tree arc + if self.lowpt2[e[1]] < self.dfs_number[e[0]]: phi = 3*self.lowpt1[e[1]] - elif (self.lowpt2[e[1]] >= self.dfs_number[e[0]]): + elif self.lowpt2[e[1]] >= self.dfs_number[e[0]]: phi = 3*self.lowpt1[e[1]] + 2 else: # tree frond phi = 3*self.dfs_number[e[1]]+1 bucket[phi].append(e) - for i in xrange(1,max+1): + for i in range(1,max+1): for e in bucket[i]: - if (self.edge_reverse[e] == True): + if e in self.reverse_edges: self.adj[e[1]].append(e) self.in_adj[e] = self.adj[e[1]] else: From 65fabbc51b68316f426b82c6a002fdfaed5bef83 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 28 Jun 2018 14:12:07 +0200 Subject: [PATCH 009/264] trac #20416: add parameter solver to is_hamiltonian --- src/sage/graphs/generic_graph.py | 49 ++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 3b1dd88cb13..bc6b2f7c96c 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -20987,35 +20987,52 @@ def is_vertex_transitive(self, partition=None, verbosity=0, return (len(partition) == len(new_partition)) - def is_hamiltonian(self): + def is_hamiltonian(self, solver=None, constraint_generation=None, + verbose=0, verbose_constraints=False): r""" Tests whether the current graph is Hamiltonian. - A graph (resp. digraph) is said to be Hamiltonian - if it contains as a subgraph a cycle (resp. a circuit) - going through all the vertices. + A graph (resp. digraph) is said to be Hamiltonian if it contains as a + subgraph a cycle (resp. a circuit) going through all the vertices. - Testing for Hamiltonicity being NP-Complete, this - algorithm could run for some time depending on - the instance. + Testing for Hamiltonicity being NP-Complete, this algorithm could run + for some time depending on the instance. ALGORITHM: See ``Graph.traveling_salesman_problem``. + 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:`~sage.numerical.mip.MixedIntegerLinearProgram.solve` of + the class :class:`~sage.numerical.mip.MixedIntegerLinearProgram`. + + - ``constraint_generation`` (boolean) -- whether to use constraint + generation when solving the Mixed Integer Linear Program. When + ``constraint_generation = None``, constraint generation is used + whenever the graph has a density larger than 70%. + + - ``verbose`` -- integer (default: ``0``). Sets the level of + verbosity. Set to 0 by default, which means quiet. + + - ``verbose_constraints`` -- whether to display which constraints are + being generated. + OUTPUT: - Returns ``True`` if a Hamiltonian cycle/circuit exists, and - ``False`` otherwise. + Returns ``True`` if a Hamiltonian cycle/circuit exists, and ``False`` + otherwise. NOTE: This function, as ``hamiltonian_cycle`` and - ``traveling_salesman_problem``, computes a Hamiltonian - cycle if it exists: the user should *NOT* test for - Hamiltonicity using ``is_hamiltonian`` before calling - ``hamiltonian_cycle`` or ``traveling_salesman_problem`` - as it would result in computing it twice. + ``traveling_salesman_problem``, computes a Hamiltonian cycle if it + exists: the user should *NOT* test for Hamiltonicity using + ``is_hamiltonian`` before calling ``hamiltonian_cycle`` or + ``traveling_salesman_problem`` as it would result in computing it twice. EXAMPLES: @@ -21047,7 +21064,9 @@ def is_hamiltonian(self): """ from sage.categories.sets_cat import EmptySetError try: - self.traveling_salesman_problem(use_edge_labels=False) + self.traveling_salesman_problem(use_edge_labels=False, solver=solver, + constraint_generation=constraint_generation, + verbose=verbose, verbose_constraints=verbose_constraints) return True except EmptySetError: return False From 1c80268e8224671c442e3249f33e3638231dfb83 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 28 Jun 2018 14:15:48 +0200 Subject: [PATCH 010/264] trac #20416: correct edge_disjoint_spanning_trees --- src/sage/graphs/generic_graph.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index bc6b2f7c96c..6ddf7acc0df 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -5391,7 +5391,7 @@ def steiner_tree(self,vertices, weighted = False, solver = None, verbose = 0): st.delete_vertices([v for v in g if st.degree(v) == 0]) return st - def edge_disjoint_spanning_trees(self,k, root=None, solver = None, verbose = 0): + def edge_disjoint_spanning_trees(self, k, root=None, solver=None, verbose=0): r""" Returns the desired number of edge-disjoint spanning trees/arborescences. @@ -5486,7 +5486,7 @@ def edge_disjoint_spanning_trees(self,k, root=None, solver = None, verbose = 0): from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException - p = MixedIntegerLinearProgram(solver = solver) + p = MixedIntegerLinearProgram(solver=solver) p.set_objective(None) # The colors we can use @@ -5571,7 +5571,7 @@ def edge_disjoint_spanning_trees(self,k, root=None, solver = None, verbose = 0): for v in self: p.add_constraint(p.sum(r_edges[j,(u,v)] for u in self.neighbors(v)), max=1-epsilon) try: - p.solve(log = verbose) + p.solve(log=verbose) except MIPSolverException: from sage.categories.sets_cat import EmptySetError From a506418cfd9774998b2864cbd5ec45c0e5005bb6 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 28 Jun 2018 14:25:43 +0200 Subject: [PATCH 011/264] trac #20416: correct multiway_cut --- src/sage/graphs/generic_graph.py | 81 +++++++++++++++----------------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 6ddf7acc0df..1d935edbb89 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -5956,19 +5956,17 @@ def vertex_cut(self, s, t, value_only=True, vertices=False, solver=None, verbose return tuple(answer) - def multiway_cut(self, vertices, value_only = False, use_edge_labels = False, solver = None, verbose = 0): + def multiway_cut(self, vertices, value_only=False, use_edge_labels=False, solver=None, verbose=0): r""" - Returns a minimum edge multiway cut corresponding to the - given set of vertices - ( cf. http://www.d.kth.se/~viggo/wwwcompendium/node92.html ) - represented by a list of edges. + Return a minimum edge multiway cut. - A multiway cut for a vertex set `S` in a graph or a digraph - `G` is a set `C` of edges such that any two vertices `u,v` - in `S` are disconnected when removing the edges from `C` from `G`. + A multiway cut for a vertex set `S` in a graph or a digraph `G` is a set + `C` of edges such that any two vertices `u,v` in `S` are disconnected + when removing the edges of `C` from `G`. + ( cf. http://www.d.kth.se/~viggo/wwwcompendium/node92.html ) - Such a cut is said to be minimum when its cardinality - (or weight) is minimum. + Such a cut is said to be minimum when its cardinality (or weight) is + minimum. INPUT: @@ -5976,63 +5974,62 @@ def multiway_cut(self, vertices, value_only = False, use_edge_labels = False, so - ``value_only`` (boolean) - - When set to ``True``, only the value of a minimum - multiway cut is returned. + - When set to ``True``, only the value of a minimum multiway cut is + returned. - - When set to ``False`` (default), the list of edges - is returned + - When set to ``False`` (default), the list of edges is returned - ``use_edge_labels`` (boolean) - - When set to ``True``, computes a weighted minimum cut - where each edge has a weight defined by its label. ( if - an edge has no label, `1` is assumed ) + + - When set to ``True``, computes a weighted minimum cut where each + edge has a weight defined by its label. ( if an edge has no label, + `1` is assumed ) - when set to ``False`` (default), each edge has weight `1`. - - ``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 `. + - ``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 + `. - ``verbose`` -- integer (default: ``0``). Sets the level of verbosity. Set to 0 by default, which means quiet. EXAMPLES: - Of course, a multiway cut between two vertices correspond - to a minimum edge cut :: + Of course, a multiway cut between two vertices correspond to a minimum + edge cut :: sage: g = graphs.PetersenGraph() sage: g.edge_cut(0,3) == g.multiway_cut([0,3], value_only = True) True - As Petersen's graph is `3`-regular, a minimum multiway cut - between three vertices contains at most `2\times 3` edges - (which could correspond to the neighborhood of 2 - vertices):: + As Petersen's graph is `3`-regular, a minimum multiway cut between three + vertices contains at most `2\times 3` edges (which could correspond to + the neighborhood of 2 vertices):: sage: g.multiway_cut([0,3,9], value_only = True) == 2*3 True - In this case, though, the vertices are an independent set. - If we pick instead vertices `0,9,` and `7`, we can save `4` - edges in the multiway cut :: + In this case, though, the vertices are an independent set. If we pick + instead vertices `0,9,` and `7`, we can save `4` edges in the multiway + cut :: sage: g.multiway_cut([0,7,9], value_only = True) == 2*3 - 1 True - This example, though, does not work in the directed case anymore, - as it is not possible in Petersen's graph to mutualise edges :: + This example, though, does not work in the directed case anymore, as it + is not possible in Petersen's graph to mutualise edges :: sage: g = DiGraph(g) sage: g.multiway_cut([0,7,9], value_only = True) == 3*3 True - Of course, a multiway cut between the whole vertex set - contains all the edges of the graph:: + Of course, a multiway cut between the whole vertex set contains all the + edges of the graph:: sage: C = g.multiway_cut(g.vertices()) sage: set(C) == set(g.edges()) @@ -6042,13 +6039,13 @@ def multiway_cut(self, vertices, value_only = False, use_edge_labels = False, so from sage.numerical.mip import MixedIntegerLinearProgram from itertools import combinations, chain - p = MixedIntegerLinearProgram(maximization = False, solver= solver) + p = MixedIntegerLinearProgram(maximization=False, solver=solver) # height[c,v] represents the height of vertex v for commodity c height = p.new_variable(nonnegative=True) # cut[e] represents whether e is in the cut - cut = p.new_variable(binary = True) + cut = p.new_variable(binary=True) # Reorder R = lambda x,y : (x,y) if x Date: Thu, 28 Jun 2018 14:28:33 +0200 Subject: [PATCH 012/264] trac #20416: correct max_cut --- src/sage/graphs/generic_graph.py | 40 +++++++++++++++----------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 1d935edbb89..5613f8cea81 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -6105,9 +6105,9 @@ def multiway_cut(self, vertices, value_only=False, use_edge_labels=False, solver def max_cut(self, value_only=True, use_edge_labels=False, vertices=False, solver=None, verbose=0): r""" - Returns a maximum edge cut of the graph. For more information, see the - `Wikipedia article on cuts - `_. + Return a maximum edge cut of the graph. + + For more information, see :wikipedia:`Maximum_cut`. INPUT: @@ -6115,39 +6115,37 @@ def max_cut(self, value_only=True, use_edge_labels=False, vertices=False, solver - When set to ``True`` (default), only the value is returned. - - When set to ``False``, both the value and a maximum edge cut - are returned. + - When set to ``False``, both the value and a maximum edge cut are + returned. - ``use_edge_labels`` -- boolean (default: ``False``) - - When set to ``True``, computes a maximum weighted cut - where each edge has a weight defined by its label. (If - an edge has no label, `1` is assumed.) + - When set to ``True``, computes a maximum weighted cut where each + edge has a weight defined by its label. (If an edge has no label, + `1` is assumed.) - When set to ``False``, each edge has weight `1`. - ``vertices`` -- boolean (default: ``False``) - - When set to ``True``, also returns the two sets of - vertices that are disconnected by the cut. This implies - ``value_only=False``. + - When set to ``True``, also returns the two sets of vertices that are + disconnected by the cut. This implies ``value_only=False``. - - ``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 `. + - ``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 + `. - ``verbose`` -- integer (default: ``0``). Sets the level of verbosity. Set to 0 by default, which means quiet. EXAMPLES: - Quite obviously, the max cut of a bipartite graph - is the number of edges, and the two sets of vertices - are the two sides :: + Quite obviously, the max cut of a bipartite graph is the number of + edges, and the two sets of vertices are the two sides :: sage: g = graphs.CompleteBipartiteGraph(5,6) sage: [ value, edges, [ setA, setB ]] = g.max_cut(vertices=True) From 2927b794c12f536057cf38229711feb197179d03 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 28 Jun 2018 14:31:19 +0200 Subject: [PATCH 013/264] trac #20416: correct longest_path --- src/sage/graphs/generic_graph.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 5613f8cea81..d74e080b4e3 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -6260,7 +6260,7 @@ def max_cut(self, value_only=True, use_edge_labels=False, vertices=False, solver def longest_path(self, s=None, t=None, use_edge_labels=False, algorithm="MILP", solver=None, verbose=0): r""" - Returns a longest path of ``self``. + Return a longest path of ``self``. INPUT: @@ -6291,10 +6291,10 @@ def longest_path(self, s=None, t=None, use_edge_labels=False, algorithm="MILP", - ``solver`` -- (default: ``None``) Specify the 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 `. + the method :meth:`solve + ` of the class + :class:`MixedIntegerLinearProgram + `. - ``verbose`` -- integer (default: ``0``). Sets the level of verbosity. Set to 0 by default, which means quiet. @@ -6327,8 +6327,8 @@ def longest_path(self, s=None, t=None, use_edge_labels=False, algorithm="MILP", The heuristic totally agrees:: sage: g = graphs.PetersenGraph() - sage: g.longest_path(algorithm="backtrack").edges() - [(0, 1, None), (1, 2, None), (2, 3, None), (3, 4, None), (4, 9, None), (5, 7, None), (5, 8, None), (6, 8, None), (6, 9, None)] + sage: g.longest_path(algorithm="backtrack").edges(labels=False) + [(0, 1), (1, 2), (2, 3), (3, 4), (4, 9), (5, 7), (5, 8), (6, 8), (6, 9)] .. PLOT:: From ae072efa93d62ac084c82f7df42e3b09c821ca23 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 28 Jun 2018 14:41:29 +0200 Subject: [PATCH 014/264] trac #20416: add parameter solver to hamiltonian_cycle --- src/sage/graphs/generic_graph.py | 82 ++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index d74e080b4e3..717d58d31e3 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -7035,13 +7035,13 @@ def traveling_salesman_problem(self, use_edge_labels=False, maximize=False, sage: for i in range(20): ....: g = Graph() ....: g.allow_multiple_edges(False) - ....: for u,v in graphs.RandomGNP(n,.2).edges(labels = False): + ....: for u,v in graphs.RandomGNP(n,.2).edges(labels=False): ....: g.add_edge(u,v,round(random(),5)) - ....: for u,v in graphs.CycleGraph(n).edges(labels = False): + ....: for u,v in graphs.CycleGraph(n).edges(labels=False): ....: if not g.has_edge(u,v): ....: g.add_edge(u,v,round(random(),5)) - ....: v1 = g.traveling_salesman_problem(constraint_generation = False, use_edge_labels = True) - ....: v2 = g.traveling_salesman_problem(use_edge_labels = True) + ....: v1 = g.traveling_salesman_problem(constraint_generation=False, use_edge_labels=True) + ....: v2 = g.traveling_salesman_problem(use_edge_labels=True) ....: c1 = sum(map(itemgetter(2), v1.edges())) ....: c2 = sum(map(itemgetter(2), v2.edges())) ....: if c1 != c2: @@ -7056,13 +7056,13 @@ def traveling_salesman_problem(self, use_edge_labels=False, maximize=False, sage: for i in range(20): ....: g = DiGraph() ....: g.allow_multiple_edges(False) - ....: for u,v in digraphs.RandomDirectedGNP(n,.2).edges(labels = False): + ....: for u,v in digraphs.RandomDirectedGNP(n,.2).edges(labels=False): ....: g.add_edge(u,v,round(random(),5)) - ....: for u,v in digraphs.Circuit(n).edges(labels = False): + ....: for u,v in digraphs.Circuit(n).edges(labels=False): ....: if not g.has_edge(u,v): ....: g.add_edge(u,v,round(random(),5)) - ....: v2 = g.traveling_salesman_problem(use_edge_labels = True) - ....: v1 = g.traveling_salesman_problem(constraint_generation = False, use_edge_labels = True) + ....: v2 = g.traveling_salesman_problem(use_edge_labels=True) + ....: v1 = g.traveling_salesman_problem(constraint_generation=False, use_edge_labels=True) ....: c1 = sum(map(itemgetter(2), v1.edges())) ....: c2 = sum(map(itemgetter(2), v2.edges())) ....: if c1 != c2: @@ -7421,27 +7421,47 @@ def traveling_salesman_problem(self, use_edge_labels=False, maximize=False, raise EmptySetError("The given graph is not Hamiltonian") - def hamiltonian_cycle(self, algorithm='tsp' ): + def hamiltonian_cycle(self, algorithm='tsp', solver=None, constraint_generation=None, + verbose=0, verbose_constraints=False): r""" - Returns a Hamiltonian cycle/circuit of the current graph/digraph + Return a Hamiltonian cycle/circuit of the current graph/digraph. - A graph (resp. digraph) is said to be Hamiltonian - if it contains as a subgraph a cycle (resp. a circuit) - going through all the vertices. + A graph (resp. digraph) is said to be Hamiltonian if it contains as a + subgraph a cycle (resp. a circuit) going through all the vertices. - Computing a Hamiltonian cycle/circuit being NP-Complete, - this algorithm could run for some time depending on - the instance. + Computing a Hamiltonian cycle/circuit being NP-Complete, this algorithm + could run for some time depending on the instance. ALGORITHM: - See ``Graph.traveling_salesman_problem`` for 'tsp' algorithm and - ``find_hamiltonian`` from ``sage.graphs.generic_graph_pyx`` - for 'backtrack' algorithm. + See :meth:`~Graph.traveling_salesman_problem` for 'tsp' algorithm and + :meth:`~sage.graphs.generic_graph_pyx.find_hamiltonian` from + :mod:`sage.graphs.generic_graph_pyx` for 'backtrack' algorithm. INPUT: - - ``algorithm`` - one of 'tsp' or 'backtrack'. + - ``algorithm`` -- one of 'tsp' or 'backtrack'. + + - ``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 + `. + + - ``constraint_generation`` (boolean) -- whether to use constraint + generation when solving the Mixed Integer Linear Program. + + When ``constraint_generation = None``, constraint generation is used + whenever the graph has a density larger than 70%. + + - ``verbose`` -- integer (default: ``0``). Sets the level of + verbosity. Set to 0 by default, which means quiet. + + - ``verbose_constraints`` -- whether to display which constraints are + being generated. + OUTPUT: @@ -7449,21 +7469,21 @@ def hamiltonian_cycle(self, algorithm='tsp' ): exists; otherwise, raises a ``EmptySetError`` exception. If using the 'backtrack' algorithm, returns a pair (B,P). If B is True then P is a Hamiltonian cycle and if B is False, P is a longest path found by the - algorithm. Observe that if B is False, the graph may still be Hamiltonian. - The 'backtrack' algorithm is only implemented for undirected - graphs. + algorithm. Observe that if B is False, the graph may still be + Hamiltonian. The 'backtrack' algorithm is only implemented for + undirected graphs. .. WARNING:: - The 'backtrack' algorithm may loop endlessly on graphs - with vertices of degree 1. + The 'backtrack' algorithm may loop endlessly on graphs with vertices + of degree 1. NOTE: - This function, as ``is_hamiltonian``, computes a Hamiltonian - cycle if it exists: the user should *NOT* test for - Hamiltonicity using ``is_hamiltonian`` before calling this - function, as it would result in computing it twice. + This function, as ``is_hamiltonian``, computes a Hamiltonian cycle if it + exists: the user should *NOT* test for Hamiltonicity using + ``is_hamiltonian`` before calling this function, as it would result in + computing it twice. The backtrack algorithm is only implemented for undirected graphs. @@ -7509,7 +7529,9 @@ def hamiltonian_cycle(self, algorithm='tsp' ): from sage.numerical.mip import MIPSolverException try: - return self.traveling_salesman_problem(use_edge_labels = False) + return self.traveling_salesman_problem(use_edge_labels=False, solver=solver, + constraint_generation=constraint_generation, + verbose=verbose, verbose_constraints=verbose_constraints) except MIPSolverException: from sage.categories.sets_cat import EmptySetError raise EmptySetError("The given graph is not Hamiltonian") From c30b59a3e3e1e02b61f6d23041c535074daf63e6 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 28 Jun 2018 14:47:15 +0200 Subject: [PATCH 015/264] trac #20416: improvement of flow --- src/sage/graphs/generic_graph.py | 107 +++++++++++++++---------------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 717d58d31e3..77a8f15f997 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -7561,13 +7561,13 @@ def feedback_vertex_set(self, value_only=False, solver=None, verbose=0, constrai - When set to ``False``, the ``Set`` of vertices of a minimal feedback vertex set is returned. - - ``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 `. + - ``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 + `. - ``verbose`` -- integer (default: ``0``). Sets the level of verbosity. Set to 0 by default, which means quiet. @@ -7775,11 +7775,10 @@ def feedback_vertex_set(self, value_only=False, solver=None, verbose=0, constrai def flow(self, x, y, value_only=True, integer=False, use_edge_labels=True, vertex_bound=False, algorithm = None, solver=None, verbose=0): r""" - Returns a maximum flow in the graph from ``x`` to ``y`` - represented by an optimal valuation of the edges. For more - information, see the - `Wikipedia article on maximum flow - `_. + Return a maximum flow in the graph from ``x`` to ``y``. + + The returned flow is represented by an optimal valuation of the + edges. For more information, see the :wikipedia:`Max_flow`. As an optimization problem, is can be expressed this way : @@ -7797,73 +7796,73 @@ def flow(self, x, y, value_only=True, integer=False, use_edge_labels=True, verte - ``value_only`` -- boolean (default: ``True``) - - When set to ``True``, only the value of a maximal - flow is returned. + - When set to ``True``, only the value of a maximal flow is returned. - - When set to ``False``, is returned a pair whose first element - is the value of the maximum flow, and whose second value is - a flow graph (a copy of the current graph, such that each edge - has the flow using it as a label, the edges without flow being - omitted). + - When set to ``False``, is returned a pair whose first element is the + value of the maximum flow, and whose second value is a flow graph (a + copy of the current graph, such that each edge has the flow using it + as a label, the edges without flow being omitted). - ``integer`` -- boolean (default: ``True``) - When set to ``True``, computes an optimal solution under the - constraint that the flow going through an edge has to be an - integer. + constraint that the flow going through an edge has to be an integer. - ``use_edge_labels`` -- boolean (default: ``True``) - - When set to ``True``, computes a maximum flow - where each edge has a capacity defined by its label. (If - an edge has no label, `1` is assumed.) + - When set to ``True``, computes a maximum flow where each edge has a + capacity defined by its label. (If an edge has no label, `1` is + assumed.) - When set to ``False``, each edge has capacity `1`. - ``vertex_bound`` -- boolean (default: ``False``) - - When set to ``True``, sets the maximum flow leaving - a vertex different from `x` to `1` (useful for vertex - connectivity parameters). + - When set to ``True``, sets the maximum flow leaving a vertex + different from `x` to `1` (useful for vertex connectivity + parameters). - - ``algorithm`` -- There are currently three different - implementations of this method: + - ``algorithm`` -- There are currently three different implementations + of this method: * If ``algorithm = "FF"``, a Python implementation of the Ford-Fulkerson algorithm is used (only available when ``vertex_bound = False``) - * If ``algorithm = "LP"``, the flow problem is solved using - Linear Programming. + * If ``algorithm = "LP"``, the flow problem is solved using Linear + Programming. * If ``algorithm = "igraph"``, the igraph implementation of the - Goldberg-Tarjan algorithm is used (only available when - igraph is installed and ``vertex_bound = False``) + Goldberg-Tarjan algorithm is used (only available when igraph is + installed and ``vertex_bound = False``) * If ``algorithm = None`` (default), we use ``LP`` if - ``vertex_bound = True``, otherwise, we use ``igraph`` if - it is available, ``FF`` if it is not available. + ``vertex_bound = True``, otherwise, we use ``igraph`` if it is + available, ``FF`` if it is not available. - - ``solver`` -- Specify a Linear Program solver to be used. - If set to ``None``, the default one is used. function of - ``MixedIntegerLinearProgram``. See the documentation of - ``MixedIntegerLinearProgram.solve`` for more information. + - ``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 + `. Only useful when LP is used to solve the flow problem. - - ``verbose`` (integer) -- sets the level of verbosity. Set to 0 - by default (quiet). + - ``verbose`` (integer) -- sets the level of verbosity. Set to 0 by + default (quiet). Only useful when LP is used to solve the flow problem. .. NOTE:: - Even though the three different implementations are meant to - return the same Flow values, they can not be expected to - return the same Flow graphs. + Even though the three different implementations are meant to return + the same Flow values, they can not be expected to return the same + Flow graphs. - Besides, the use of Linear Programming may possibly mean a - (slight) numerical noise. + Besides, the use of Linear Programming may possibly mean a (slight) + numerical noise. EXAMPLES: @@ -8006,13 +8005,13 @@ def flow(self, x, y, value_only=True, integer=False, use_edge_labels=True, verte from sage.numerical.mip import MixedIntegerLinearProgram - g=self - p=MixedIntegerLinearProgram(maximization=True, solver = solver) - flow=p.new_variable(nonnegative=True) + g = self + p = MixedIntegerLinearProgram(maximization=True, solver=solver) + flow = p.new_variable(nonnegative=True) if g.is_directed(): # This function return the balance of flow at X - flow_sum=lambda X: p.sum([flow[(X,v)] for (u,v) in g.outgoing_edges([X],labels=None)])-p.sum([flow[(u,X)] for (u,v) in g.incoming_edges([X],labels=None)]) + flow_sum = lambda X: p.sum([flow[(X,v)] for (u,v) in g.outgoing_edges([X],labels=None)])-p.sum([flow[(u,X)] for (u,v) in g.incoming_edges([X],labels=None)]) # The flow leaving x flow_leaving = lambda X : p.sum([flow[(uu,vv)] for (uu,vv) in g.outgoing_edges([X],labels=None)]) @@ -8022,7 +8021,7 @@ def flow(self, x, y, value_only=True, integer=False, use_edge_labels=True, verte else: # This function return the balance of flow at X - flow_sum=lambda X:p.sum([flow[(X,v)]-flow[(v,X)] for v in g[X]]) + flow_sum = lambda X:p.sum([flow[(X,v)]-flow[(v,X)] for v in g[X]]) # The flow leaving x flow_leaving = lambda X : p.sum([flow[(X,vv)] for vv in g[X]]) @@ -8053,14 +8052,14 @@ def flow(self, x, y, value_only=True, integer=False, use_edge_labels=True, verte if value_only: - return p.solve(objective_only=True, log = verbose) + return p.solve(objective_only=True, log=verbose) - obj=p.solve(log = verbose) + obj = p.solve(log=verbose) if integer or use_edge_labels is False: obj = Integer(round(obj)) - flow=p.get_values(flow) + flow = p.get_values(flow) # Builds a clean flow Draph flow_graph = g._build_flow_graph(flow, integer=integer) From 55dc77baed1fe60958dd1c013a027030a2f657f9 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 28 Jun 2018 14:51:28 +0200 Subject: [PATCH 016/264] trac #20416: improvement of multicommodity_flow --- src/sage/graphs/generic_graph.py | 60 ++++++++++++++++---------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 77a8f15f997..b64a9a74cd3 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -8469,19 +8469,17 @@ def _ford_fulkerson(self, s, t, use_edge_labels = False, integer = False, value_ def multicommodity_flow(self, terminals, integer=True, use_edge_labels=False,vertex_bound=False, solver=None, verbose=0): r""" - Solves a multicommodity flow problem. + Solve a multicommodity flow problem. - In the multicommodity flow problem, we are given a set of pairs - `(s_i, t_i)`, called terminals meaning that `s_i` is willing - some flow to `t_i`. + In the multicommodity flow problem, we are given a set of pairs `(s_i, + t_i)`, called terminals meaning that `s_i` is willing some flow to + `t_i`. - Even though it is a natural generalisation of the flow problem - this version of it is NP-Complete to solve when the flows - are required to be integer. + Even though it is a natural generalisation of the flow problem this + version of it is NP-Complete to solve when the flows are required to be + integer. - For more information, see the - :wikipedia:`Wikipedia page on multicommodity flows - `. + For more information, see the :wikipedia:`Multi-commodity_flow_problem`. INPUT: @@ -8502,13 +8500,16 @@ def multicommodity_flow(self, terminals, integer=True, use_edge_labels=False,ver still send or receive several units of flow even though vertex_bound is set to ``True``, as this parameter is meant to represent topological properties. - - ``solver`` -- Specify a Linear Program solver to be used. - If set to ``None``, the default one is used. - function of ``MixedIntegerLinearProgram``. See the documentation of ``MixedIntegerLinearProgram.solve`` - for more informations. + - ``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 + `. - - ``verbose`` (integer) -- sets the level of verbosity. Set to 0 - by default (quiet). + - ``verbose`` (integer) -- sets the level of verbosity. Set to 0 by + default (quiet). ALGORITHM: @@ -8516,9 +8517,8 @@ def multicommodity_flow(self, terminals, integer=True, use_edge_labels=False,ver EXAMPLES: - An easy way to obtain a satisfiable multiflow is to compute - a matching in a graph, and to consider the paired vertices - as terminals :: + An easy way to obtain a satisfiable multiflow is to compute a matching + in a graph, and to consider the paired vertices as terminals :: sage: g = graphs.PetersenGraph() sage: matching = [(u,v) for u,v,_ in g.matching()] @@ -8526,9 +8526,9 @@ def multicommodity_flow(self, terminals, integer=True, use_edge_labels=False,ver sage: len(h) 5 - We could also have considered ``g`` as symmetric and computed - the multiflow in this version instead. In this case, however - edges can be used in both directions at the same time:: + We could also have considered ``g`` as symmetric and computed the + multiflow in this version instead. In this case, however edges can be + used in both directions at the same time:: sage: h = DiGraph(g).multicommodity_flow(matching) sage: len(h) @@ -8543,8 +8543,8 @@ def multicommodity_flow(self, terminals, integer=True, use_edge_labels=False,ver """ self._scream_if_not_simple(allow_loops=True) from sage.numerical.mip import MixedIntegerLinearProgram - g=self - p=MixedIntegerLinearProgram(maximization=True, solver = solver) + g = self + p = MixedIntegerLinearProgram(maximization=True, solver=solver) # Adding the intensity if not present terminals = [(x if len(x) == 3 else (x[0],x[1],1)) for x in terminals] @@ -8556,18 +8556,18 @@ def multicommodity_flow(self, terminals, integer=True, use_edge_labels=False,ver set_terminals.add(t) # flow[i,(u,v)] is the flow of commodity i going from u to v - flow=p.new_variable(nonnegative=True) + flow = p.new_variable(nonnegative=True) # Whether to use edge labels if use_edge_labels: from sage.rings.real_mpfr import RR - capacity=lambda x: x if x in RR else 1 + capacity = lambda x: x if x in RR else 1 else: - capacity=lambda x: 1 + capacity = lambda x: 1 if g.is_directed(): # This function return the balance of flow at X - flow_sum=lambda i,X: p.sum([flow[i,(X,v)] for (u,v) in g.outgoing_edges([X],labels=None)])-p.sum([flow[i,(u,X)] for (u,v) in g.incoming_edges([X],labels=None)]) + flow_sum = lambda i,X: p.sum([flow[i,(X,v)] for (u,v) in g.outgoing_edges([X],labels=None)])-p.sum([flow[i,(u,X)] for (u,v) in g.incoming_edges([X],labels=None)]) # The flow leaving x flow_leaving = lambda i,X : p.sum([flow[i,(uu,vv)] for (uu,vv) in g.outgoing_edges([X],labels=None)]) @@ -8577,7 +8577,7 @@ def multicommodity_flow(self, terminals, integer=True, use_edge_labels=False,ver else: # This function return the balance of flow at X - flow_sum=lambda i,X:p.sum([flow[i,(X,v)]-flow[i,(v,X)] for v in g[X]]) + flow_sum = lambda i,X:p.sum([flow[i,(X,v)]-flow[i,(v,X)] for v in g[X]]) # The flow leaving x flow_leaving = lambda i, X : p.sum([flow[i,(X,vv)] for vv in g[X]]) @@ -8631,7 +8631,7 @@ def multicommodity_flow(self, terminals, integer=True, use_edge_labels=False,ver from sage.categories.sets_cat import EmptySetError raise EmptySetError("The multiflow problem has no solution") - flow=p.get_values(flow) + flow = p.get_values(flow) # building clean flow digraphs flow_graphs = [g._build_flow_graph({e:f for (ii,e),f in iteritems(flow) if ii == i}, integer=integer) From 46abf3e93edd79fb2a669d7b1b8ad1b8acff679c Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 28 Jun 2018 14:53:23 +0200 Subject: [PATCH 017/264] trac #20416: improvement of disjoint_routed_paths --- src/sage/graphs/generic_graph.py | 35 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index b64a9a74cd3..203976bda6b 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -8740,39 +8740,38 @@ def _build_flow_graph(self, flow, integer): def disjoint_routed_paths(self,pairs, solver=None, verbose=0): r""" - Returns a set of disjoint routed paths. + Return a set of disjoint routed paths. - Given a set of pairs `(s_i,t_i)`, a set - of disjoint routed paths is a set of - `s_i-t_i` paths which can intersect at their endpoints - and are vertex-disjoint otherwise. + Given a set of pairs `(s_i,t_i)`, a set of disjoint routed paths is a + set of `s_i-t_i` paths which can intersect at their endpoints and are + vertex-disjoint otherwise. INPUT: - ``pairs`` -- list of pairs of vertices - - ``solver`` -- Specify a Linear Program solver to be used. - If set to ``None``, the default one is used. - function of ``MixedIntegerLinearProgram``. See the documentation of ``MixedIntegerLinearProgram.solve`` - for more informations. + - ``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 + `. - ``verbose`` (integer) -- sets the level of verbosity. Set to `0` by default (quiet). EXAMPLES: - Given a grid, finding two vertex-disjoint - paths, the first one from the top-left corner - to the bottom-left corner, and the second from - the top-right corner to the bottom-right corner - is easy :: + Given a grid, finding two vertex-disjoint paths, the first one from the + top-left corner to the bottom-left corner, and the second from the + top-right corner to the bottom-right corner is easy :: sage: g = graphs.GridGraph([5,5]) sage: p1,p2 = g.disjoint_routed_paths( [((0,0), (0,4)), ((4,4), (4,0))]) - Though there is obviously no solution to the problem - in which each corner is sending information to the opposite - one:: + Though there is obviously no solution to the problem in which each + corner is sending information to the opposite one:: sage: g = graphs.GridGraph([5,5]) sage: p1,p2 = g.disjoint_routed_paths( [((0,0), (4,4)), ((0,4), (4,0))]) @@ -8782,7 +8781,7 @@ def disjoint_routed_paths(self,pairs, solver=None, verbose=0): """ from sage.categories.sets_cat import EmptySetError try: - return self.multicommodity_flow(pairs, vertex_bound = True, solver=solver, verbose=verbose) + return self.multicommodity_flow(pairs, vertex_bound=True, solver=solver, verbose=verbose) except EmptySetError: raise EmptySetError("The disjoint routed paths do not exist.") From 236abc1d6702857bc68d7dc775f4b07e7319e5a3 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 28 Jun 2018 14:58:59 +0200 Subject: [PATCH 018/264] trac #20416: add parameter solver to edge and vertex disjoint paths --- src/sage/graphs/generic_graph.py | 64 ++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 203976bda6b..f6f1675fff6 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -244,8 +244,8 @@ :meth:`~GenericGraph.vertex_cut` | Return a minimum vertex cut between non-adjacent vertices `s` and `t` :meth:`~GenericGraph.flow` | Return a maximum flow in the graph from ``x`` to ``y`` :meth:`~GenericGraph.nowhere_zero_flow` | Return a `k`-nowhere zero flow of the (di)graph. - :meth:`~GenericGraph.edge_disjoint_paths` | Returns a list of edge-disjoint paths between two vertices - :meth:`~GenericGraph.vertex_disjoint_paths` | Return a list of vertex-disjoint paths between two vertices as given by Menger's theorem. + :meth:`~GenericGraph.edge_disjoint_paths` | Return a list of edge-disjoint paths between two vertices + :meth:`~GenericGraph.vertex_disjoint_paths` | Return a list of vertex-disjoint paths between two vertices :meth:`~GenericGraph.edge_connectivity` | Return the edge connectivity of the graph. :meth:`~GenericGraph.vertex_connectivity` | Return the vertex connectivity of the graph. :meth:`~GenericGraph.transitive_closure` | Compute the transitive closure of a graph and returns it. @@ -8785,23 +8785,21 @@ def disjoint_routed_paths(self,pairs, solver=None, verbose=0): except EmptySetError: raise EmptySetError("The disjoint routed paths do not exist.") - def edge_disjoint_paths(self, s, t, algorithm = "FF"): + def edge_disjoint_paths(self, s, t, algorithm="FF", solver=None, verbose=False): r""" - Returns a list of edge-disjoint paths between two - vertices as given by Menger's theorem. + Return a list of edge-disjoint paths between two vertices. - The edge version of Menger's theorem asserts that the size - of the minimum edge cut between two vertices `s` and`t` - (the minimum number of edges whose removal disconnects `s` - and `t`) is equal to the maximum number of pairwise - edge-independent paths from `s` to `t`. + The edge version of Menger's theorem asserts that the size of the + minimum edge cut between two vertices `s` and`t` (the minimum number of + edges whose removal disconnects `s` and `t`) is equal to the maximum + number of pairwise edge-independent paths from `s` to `t`. This function returns a list of such paths. INPUT: - - ``algorithm`` -- There are currently two different - implementations of this method : + - ``algorithm`` -- There are currently two different implementations of + this method : * If ``algorithm = "FF"`` (default), a Python implementation of the Ford-Fulkerson algorithm is used. @@ -8809,10 +8807,21 @@ def edge_disjoint_paths(self, s, t, algorithm = "FF"): * If ``algorithm = "LP"``, the flow problem is solved using Linear Programming. + - ``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 + `. + + - ``verbose`` -- integer (default: ``0``). Sets the level of + verbosity. Set to 0 by default, which means quiet. + .. NOTE:: - This function is topological: it does not take the eventual - weights of the edges into account. + This function is topological: it does not take the eventual weights + of the edges into account. EXAMPLES: @@ -8823,7 +8832,8 @@ def edge_disjoint_paths(self, s, t, algorithm = "FF"): [[0, 2, 1], [0, 3, 1], [0, 4, 1]] """ - [obj, flow_graph] = self.flow(s,t,value_only=False, integer=True, use_edge_labels=False, algorithm=algorithm) + [obj, flow_graph] = self.flow(s,t,value_only=False, integer=True, use_edge_labels=False, + algorithm=algorithm, solver=solver, verbose=verbose) paths = [] @@ -8841,10 +8851,9 @@ def edge_disjoint_paths(self, s, t, algorithm = "FF"): return paths - def vertex_disjoint_paths(self, s, t): + def vertex_disjoint_paths(self, s, t, solver=solver, verbose=verbose): r""" - Return a list of vertex-disjoint paths between two vertices as given by - Menger's theorem. + Return a list of vertex-disjoint paths between two vertices. The vertex version of Menger's theorem asserts that the size of the minimum vertex cut between two vertices `s` and `t` (the minimum number @@ -8853,6 +8862,22 @@ def vertex_disjoint_paths(self, s, t): This function returns a list of such paths. + INPUT: + + - ``s,t`` -- two vertices of the graph. + + - ``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 + `. + + - ``verbose`` -- integer (default: ``0``). Sets the level of + verbosity. Set to 0 by default, which means quiet. + + EXAMPLES: In a complete bipartite graph :: @@ -8871,7 +8896,8 @@ def vertex_disjoint_paths(self, s, t): sage: g.vertex_disjoint_paths(1,0) [] """ - obj, flow_graph = self.flow(s, t, value_only=False, integer=True, use_edge_labels=False, vertex_bound=True) + obj, flow_graph = self.flow(s, t, value_only=False, integer=True, use_edge_labels=False, + vertex_bound=True, solver=solver, verbose=verbose) paths = [] if not obj: From 15b09558743e0cdd79404c037a8b75960336b604 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 28 Jun 2018 15:17:24 +0200 Subject: [PATCH 019/264] trac #20416: corrections in generic_graph.py --- src/sage/graphs/generic_graph.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index f6f1675fff6..a21bddd324f 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -8851,7 +8851,7 @@ def edge_disjoint_paths(self, s, t, algorithm="FF", solver=None, verbose=False): return paths - def vertex_disjoint_paths(self, s, t, solver=solver, verbose=verbose): + def vertex_disjoint_paths(self, s, t, solver=None, verbose=0): r""" Return a list of vertex-disjoint paths between two vertices. @@ -8917,15 +8917,11 @@ def vertex_disjoint_paths(self, s, t, solver=solver, verbose=verbose): def dominating_set(self, independent=False, total=False, value_only=False, solver=None, verbose=0): r""" - Returns a minimum dominating set of the graph - represented by the list of its vertices. For more information, see the - `Wikipedia article on dominating sets - `_. + Return a minimum dominating set of the graph. - A minimum dominating set `S` of a graph `G` is - a set of its vertices of minimal cardinality such - that any vertex of `G` is in `S` or has one of its neighbors - in `S`. + A minimum dominating set `S` of a graph `G` is a set of its vertices of + minimal cardinality such that any vertex of `G` is in `S` or has one of + its neighbors in `S`. See :wikipedia:`Dominating_set`. As an optimization problem, it can be expressed as: @@ -21031,7 +21027,7 @@ def is_vertex_transitive(self, partition=None, verbosity=0, def is_hamiltonian(self, solver=None, constraint_generation=None, verbose=0, verbose_constraints=False): r""" - Tests whether the current graph is Hamiltonian. + Test whether the current graph is Hamiltonian. A graph (resp. digraph) is said to be Hamiltonian if it contains as a subgraph a cycle (resp. a circuit) going through all the vertices. @@ -21041,7 +21037,7 @@ def is_hamiltonian(self, solver=None, constraint_generation=None, ALGORITHM: - See ``Graph.traveling_salesman_problem``. + See :meth:`~Graph.traveling_salesman_problem`. INPUT: From d5d8c6a5bb1ecaf0625d56cc2db6757307222d5e Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 28 Jun 2018 15:53:33 +0200 Subject: [PATCH 020/264] trac #20416: add parameter solver to methods in graph.py --- src/sage/graphs/graph.py | 144 +++++++++++++++++++++++++++------------ 1 file changed, 101 insertions(+), 43 deletions(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index cd00df03e67..0738d1e6367 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -3696,48 +3696,57 @@ 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): + def bounded_outdegree_orientation(self, bound, solver=None, verbose=False): r""" Computes an orientation of ``self`` such that every vertex `v` has out-degree less than `b(v)` INPUT: - - ``bound`` -- Maximum bound on the out-degree. Can be of - three different types : + - ``bound`` -- Maximum bound on the out-degree. Can be of three + different types : - * An integer `k`. In this case, computes an orientation - whose maximum out-degree is less than `k`. + * An integer `k`. In this case, computes an orientation whose maximum + out-degree is less than `k`. - * A dictionary associating to each vertex its associated - maximum out-degree. + * A dictionary associating to each vertex its associated maximum + out-degree. - * A function associating to each vertex its associated - maximum out-degree. + * A function associating to each vertex its associated maximum + out-degree. + + - ``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 + `. + + - ``verbose`` -- integer (default: ``0``). Sets the level of + verbosity. Set to 0 by default, which means quiet. OUTPUT: - A DiGraph representing the orientation if it exists. A - ``ValueError`` exception is raised otherwise. + A DiGraph representing the orientation if it exists. A ``ValueError`` + exception is raised otherwise. ALGORITHM: The problem is solved through a maximum flow : - Given a graph `G`, we create a ``DiGraph`` `D` defined on - `E(G)\cup V(G)\cup \{s,t\}`. We then link `s` to all of `V(G)` - (these edges having a capacity equal to the bound associated - to each element of `V(G)`), and all the elements of `E(G)` to - `t` . We then link each `v \in V(G)` to each of its incident - edges in `G`. A maximum integer flow of value `|E(G)|` - corresponds to an admissible orientation of `G`. Otherwise, + Given a graph `G`, we create a ``DiGraph`` `D` defined on `E(G)\cup + V(G)\cup \{s,t\}`. We then link `s` to all of `V(G)` (these edges having + a capacity equal to the bound associated to each element of `V(G)`), and + all the elements of `E(G)` to `t` . We then link each `v \in V(G)` to + each of its incident edges in `G`. A maximum integer flow of value + `|E(G)|` corresponds to an admissible orientation of `G`. Otherwise, none exists. EXAMPLES: - There is always an orientation of a graph `G` such that a - vertex `v` has out-degree at most `\lceil \frac {d(v)} 2 - \rceil`:: + There is always an orientation of a graph `G` such that a vertex `v` has + out-degree at most `\lceil \frac {d(v)} 2 \rceil`:: sage: g = graphs.RandomGNP(40, .4) sage: b = lambda v : ceil(g.degree(v)/2) @@ -3746,18 +3755,17 @@ def bounded_outdegree_orientation(self, bound): True - Chvatal's graph, being 4-regular, can be oriented in such a - way that its maximum out-degree is 2:: + Chvatal's graph, being 4-regular, can be oriented in such a way that its + maximum out-degree is 2:: sage: g = graphs.ChvatalGraph() sage: D = g.bounded_outdegree_orientation(2) sage: max(D.out_degree()) 2 - For any graph `G`, it is possible to compute an orientation - such that the maximum out-degree is at most the maximum - average degree of `G` divided by 2. Anything less, though, is - impossible. + For any graph `G`, it is possible to compute an orientation such that + the maximum out-degree is at most the maximum average degree of `G` + divided by 2. Anything less, though, is impossible. sage: g = graphs.RandomGNP(40, .4) sage: mad = g.maximum_average_degree() @@ -3827,7 +3835,8 @@ def bounded_outdegree_orientation(self, bound): d.add_edge(v, (u,v), 1) # Solving the maximum flow - value, flow = d.flow('s','t', value_only = False, integer = True, use_edge_labels = True) + value, flow = d.flow('s','t', value_only=False, integer=True, + use_edge_labels=True, solver=solver, verbose=verbose) if value != self.size(): raise ValueError("No orientation exists for the given bound") @@ -4485,8 +4494,7 @@ def matching(self, value_only=False, algorithm="Edmonds", Return a maximum weighted matching of the graph represented by the list of its edges. - For more information, see the `Wikipedia article on matchings - `_. + For more information, see the :wikipedia:`Matching_%28graph_theory%29>`. Given a graph `G` such that each edge `e` has a weight `w_e`, a maximum matching is a subset `S` of the edges of `G` of @@ -4666,7 +4674,7 @@ def weight(x): @doc_index("Algorithmically hard stuff") - def has_homomorphism_to(self, H, core = False, solver = None, verbose = 0): + def has_homomorphism_to(self, H, core=False, solver=None, verbose=0): r""" Checks whether there is a homomorphism between two graphs. @@ -4735,8 +4743,8 @@ def has_homomorphism_to(self, H, core = False, solver = None, verbose = 0): """ self._scream_if_not_simple() from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException - p = MixedIntegerLinearProgram(solver=solver, maximization = False) - b = p.new_variable(binary = True) + p = MixedIntegerLinearProgram(solver=solver, maximization=False) + b = p.new_variable(binary=True) # Each vertex has an image for ug in self: @@ -6116,7 +6124,7 @@ def clique_maximum(self, algorithm="Cliquer"): raise NotImplementedError("Only 'MILP', 'Cliquer' and 'mcqd' are supported.") @doc_index("Clique-related methods") - def clique_number(self, algorithm="Cliquer", cliques=None): + def clique_number(self, algorithm="Cliquer", cliques=None, solver=None, verbose=0): r""" Return the order of the largest clique of the graph @@ -6150,6 +6158,17 @@ def clique_number(self, algorithm="Cliquer", cliques=None): - ``cliques`` - an optional list of cliques that can be input if already computed. Ignored unless ``algorithm=="networkx"``. + - ``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 + `. + + - ``verbose`` -- integer (default: ``0``). Sets the level of + verbosity. Set to 0 by default, which means quiet. + ALGORITHM: This function is based on Cliquer [NisOst2003]_ and [BroKer1973]_. @@ -6197,7 +6216,7 @@ def clique_number(self, algorithm="Cliquer", cliques=None): import networkx return networkx.graph_clique_number(self.networkx_graph(copy=False),cliques) elif algorithm == "MILP": - return len(self.complement().independent_set(algorithm = algorithm)) + return len(self.complement().independent_set(algorithm=algorithm, solver=solver, verbosity=verbose)) elif algorithm == "mcqd": try: from sage.graphs.mcqd import mcqd @@ -6323,7 +6342,7 @@ def cliques_get_clique_bipartite(self, **kwds): 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): + def independent_set(self, algorithm="Cliquer", value_only=False, reduction_rules=True, solver=None, verbosity=0): r""" Return a maximum independent set. @@ -7406,7 +7425,7 @@ def is_polyhedral(self): and self.is_planar()) @doc_index("Graph properties") - def is_circumscribable(self): + def is_circumscribable(self, solver="ppl", verbose=0): """ Test whether the graph is the graph of a circumscribed polyhedron. @@ -7417,6 +7436,19 @@ def is_circumscribable(self): one and the weights on any non-facial cycle add to more than one. If and only if this can be done, the polyhedron can be circumscribed. + INPUT: + + - ``solver`` -- (default: ``"ppl"``) 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 + `. + + - ``verbose`` -- integer (default: ``0``). Sets the level of + verbosity. Set to 0 by default, which means quiet. + EXAMPLES:: sage: C = graphs.CubeGraph(3) @@ -7467,7 +7499,7 @@ def is_circumscribable(self): # introduce a variable c[0] and maximize it. If it is positive, then # the LP has a solution, such that all inequalities are strict # after removing the auxiliary variable c[0]. - M = MixedIntegerLinearProgram(maximization=True, solver="ppl") + M = MixedIntegerLinearProgram(maximization=True, solver=solver) e = M.new_variable(nonnegative=True) c = M.new_variable() M.set_min(c[0], -1) @@ -7507,14 +7539,14 @@ def is_circumscribable(self): from sage.numerical.mip import MIPSolverException try: - solution = M.solve() + solution = M.solve(log=verbose) except MIPSolverException as e: if str(e) == "PPL : There is no feasible solution": return False return solution > 0 @doc_index("Graph properties") - def is_inscribable(self): + def is_inscribable(self, solver="ppl", verbose=0): """ Test whether the graph is the graph of an inscribed polyhedron. @@ -7523,6 +7555,19 @@ def is_inscribable(self): inscribed if and only if its polar dual is circumscribed and hence a graph is inscribable if and only if its planar dual is circumscribable. + INPUT: + + - ``solver`` -- (default: ``"ppl"``) 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 + `. + + - ``verbose`` -- integer (default: ``0``). Sets the level of + verbosity. Set to 0 by default, which means quiet. + EXAMPLES:: sage: H = graphs.HerschelGraph() @@ -7570,7 +7615,7 @@ def is_inscribable(self): """ if not self.is_polyhedral(): raise NotImplementedError('this method only works for polyhedral graphs') - return self.planar_dual().is_circumscribable() + return self.planar_dual().is_circumscribable(solver=solver, verbose=verbose) @doc_index("Graph properties") def is_prime(self): @@ -7794,7 +7839,7 @@ def gomory_hu_tree(self, algorithm=None): return g @doc_index("Leftovers") - def two_factor_petersen(self): + def two_factor_petersen(self, solver=None, verbose=0): r""" Return a decomposition of the graph into 2-factors. @@ -7813,6 +7858,19 @@ def two_factor_petersen(self): graph of maximal degree `2` ( a disjoint union of paths and cycles ). + 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 + `. + + - ``verbose`` -- integer (default: ``0``). Sets the level of + verbosity. Set to 0 by default, which means quiet. + EXAMPLES: The Complete Graph on `7` vertices is a `6`-regular graph, so it can @@ -7853,7 +7911,7 @@ def two_factor_petersen(self): # This new bipartite graph is now edge_colored from sage.graphs.graph_coloring import edge_coloring - classes = edge_coloring(g) + classes = edge_coloring(g, solver=solver, verbose=verbose) # The edges in the classes are of the form ((-1,u),(1,v)) # and have to be translated back to (u,v) From ee6d1d75918b9f3cc577a3dea3f2daee002e5c53 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 28 Jun 2018 16:02:30 +0200 Subject: [PATCH 021/264] trac #20416: add parameter solver to methods in graph_coloring.py --- src/sage/graphs/graph_coloring.py | 40 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/sage/graphs/graph_coloring.py b/src/sage/graphs/graph_coloring.py index 415019cf388..c932405430e 100644 --- a/src/sage/graphs/graph_coloring.py +++ b/src/sage/graphs/graph_coloring.py @@ -344,7 +344,7 @@ def chromatic_number(G): from sage.numerical.mip import MIPSolverException -def vertex_coloring(g, k=None, value_only=False, hex_colors=False, solver = None, verbose = 0): +def vertex_coloring(g, k=None, value_only=False, hex_colors=False, solver=None, verbose=0): r""" Computes the chromatic number of the given graph or tests its `k`-colorability. See :wikipedia:`Graph_coloring` for @@ -458,7 +458,7 @@ class :class:`MixedIntegerLinearProgram while True: # tries to color the graph, increasing k each time it fails. tmp = vertex_coloring(g, k=k, value_only=value_only, - hex_colors=hex_colors, verbose=verbose) + hex_colors=hex_colors, solver=solver, verbose=verbose) if tmp is not False: if value_only: return k @@ -486,7 +486,7 @@ class :class:`MixedIntegerLinearProgram tmp = vertex_coloring(g.subgraph(component), k=k, value_only=value_only, hex_colors=hex_colors, - verbose=verbose) + solver=solver, verbose=verbose) if tmp is False: return False return True @@ -494,7 +494,8 @@ class :class:`MixedIntegerLinearProgram for component in g.connected_components(): tmp = vertex_coloring(g.subgraph(component), k=k, value_only=value_only, - hex_colors=False, verbose=verbose) + hex_colors=False, + solver=solver, verbose=verbose) if tmp is False: return False colorings.append(tmp) @@ -525,11 +526,11 @@ class :class:`MixedIntegerLinearProgram return vertex_coloring(g.subgraph(list(vertices)), k=k, value_only=value_only, hex_colors=hex_colors, - verbose=verbose) + solver=solver, verbose=verbose) value = vertex_coloring(g.subgraph(list(vertices)), k=k, value_only=value_only, hex_colors=False, - verbose=verbose) + solver=solver, verbose=verbose) if value is False: return False while len(deg) > 0: @@ -543,8 +544,8 @@ class :class:`MixedIntegerLinearProgram else: return value - p = MixedIntegerLinearProgram(maximization=True, solver = solver) - color = p.new_variable(binary = True) + p = MixedIntegerLinearProgram(maximization=True, solver=solver) + color = p.new_variable(binary=True) # a vertex has exactly one color for v in g.vertices(): @@ -583,10 +584,9 @@ class :class:`MixedIntegerLinearProgram else: return classes -def grundy_coloring(g, k, value_only = True, solver = None, verbose = 0): +def grundy_coloring(g, k, value_only=True, solver=None, verbose=0): r""" - Computes the worst-case of a first-fit coloring with less than `k` - colors. + Computes the worst-case of a first-fit coloring with less than `k` colors. Definition : @@ -667,24 +667,24 @@ def grundy_coloring(g, k, value_only = True, solver = None, verbose = 0): from sage.numerical.mip import MixedIntegerLinearProgram from sage.numerical.mip import MIPSolverException - p = MixedIntegerLinearProgram(solver = solver) + p = MixedIntegerLinearProgram(solver=solver) # List of colors classes = range(k) # b[v,i] is set to 1 if and only if v is colored with i - b = p.new_variable(binary = True) + b = p.new_variable(binary=True) # is_used[i] is set to 1 if and only if color [i] is used by some # vertex - is_used = p.new_variable(binary = True) + is_used = p.new_variable(binary=True) # Each vertex is in exactly one class for v in g: p.add_constraint(p.sum( b[v,i] for i in classes ), max = 1, min = 1) # Two adjacent vertices have different classes - for u,v in g.edges(labels = None): + for u,v in g.edges(labels=None): for i in classes: p.add_constraint(b[v,i] + b[u,i], max = 1) @@ -710,7 +710,7 @@ def grundy_coloring(g, k, value_only = True, solver = None, verbose = 0): p.set_objective( p.sum( is_used[i] for i in classes ) ) try: - obj = p.solve(log = verbose, objective_only = value_only) + obj = p.solve(log=verbose, objective_only=value_only) from sage.rings.integer import Integer obj = Integer(obj) @@ -734,7 +734,7 @@ def grundy_coloring(g, k, value_only = True, solver = None, verbose = 0): return obj, coloring -def b_coloring(g, k, value_only = True, solver = None, verbose = 0): +def b_coloring(g, k, value_only=True, solver=None, verbose=0): r""" Computes a b-coloring with at most k colors that maximizes the number of colors, if such a coloring exists @@ -850,7 +850,7 @@ def b_coloring(g, k, value_only = True, solver = None, verbose = 0): k = m - p = MixedIntegerLinearProgram(solver = solver) + p = MixedIntegerLinearProgram(solver=solver) # List of possible colors classes = range(k) @@ -911,7 +911,7 @@ def b_coloring(g, k, value_only = True, solver = None, verbose = 0): try: - obj = p.solve(log = verbose, objective_only = value_only) + obj = p.solve(log=verbose, objective_only=value_only) from sage.rings.integer import Integer obj = Integer(obj) @@ -1413,7 +1413,7 @@ def add(uv, i): return answer -def acyclic_edge_coloring(g, hex_colors=False, value_only=False, k=0, solver = None, verbose = 0): +def acyclic_edge_coloring(g, hex_colors=False, value_only=False, k=0, solver=None, verbose=0): r""" Computes an acyclic edge coloring of the current graph. From 4d7cbe54250391ed4d7ce5b61f3fa0252ef180de Mon Sep 17 00:00:00 2001 From: saiharsh Date: Fri, 29 Jun 2018 02:31:42 +0530 Subject: [PATCH 022/264] Added DFS2 and Path Finder --- src/sage/graphs/connectivity.pyx | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 5de0dbbb048..84d913056d5 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1946,6 +1946,10 @@ class Triconnectivity: self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) self.reverse_edges = set() self.dfs_number = [0 for i in range(self.n)] + self.newnum = [0 for i in range(self.n)] + self.highpt = [[] for i in range(self.n)] + self.old_to_new = [0 for i in range(self.n + 1)] + self.nodeAt = [0 for i in range(self.n + 1)] self.lowpt1 = [None for i in range(self.n)] self.lowpt2 = [None for i in range(self.n)] self.adj = [[] for i in range(self.n)] @@ -1990,6 +1994,7 @@ class Triconnectivity: self.reverse_edges.add(e) self.build_acceptable_adj_struct() + self.dfs2() def new_component(self, edges, type_c=0): @@ -2224,3 +2229,42 @@ class Triconnectivity: self.adj[e[0]].append(e) self.in_adj[e] = self.adj[e[0]] + def pathFinder(self, v): + """ + This function is a helper function for `dfs2` function. + """ + self.newnum[v] = self.dfs_counter - self.nd[v] + 1 + for e in self.adj[v]: + u, w, _ = e + if self.edge_status[e] == 1: + if e in self.reverse_edges: + self.pathFinder(u) + else: + self.pathFinder(w) + self.dfs_counter -= 1 + else: + if e in self.reverse_edges: + self.highpt[u].append(self.newnum[w]) + else: + self.highpt[w].append(self.newnum[u]) + + def dfs2(self): + """ + Update the values of lowpt1 and lowpt2 lists with the help of + new numbering obtained from `Path Finder` funciton. + Populate `highpt` values. + """ + self.dfs_counter = self.n + + # As all the vertices are labeled from 0 to n -1, + # the first vertex will be 0. + self.pathFinder(0) + + for v in range(self.n): + self.old_to_new[self.newnum[v]] = self.newnum[v] + + # Update lowpt values. + for v in range(self.n): + self.nodeAt[self.newnum[v]] = v + self.lowpt1[v] = self.old_to_new[self.lowpt1[v]] + self.lowpt2[v] = self.old_to_new[self.lowpt2[v]] \ No newline at end of file From 84cc2cf7ef65f6ea0e8ce33b1fd80961a5f04195 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Sun, 1 Jul 2018 12:26:14 +0530 Subject: [PATCH 023/264] Updated dfs2 and path finder. Added some helper functions for path_search(). --- src/sage/graphs/connectivity.pyx | 92 +++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 84d913056d5..c50ff15ee51 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1918,20 +1918,21 @@ class Triconnectivity: adj = [] # i^th value contains a list of incident edges of vertex i in_adj = {} # this variable is used in the PathSearch() function newnum = [] # new DFS number of vertex i - startsPath = {} # dict of (edge, T/F) to denote if a path starts at edge + starts_path = {} # dict of (edge, T/F) to denote if a path starts at edge highpt = [] # List of fronds entering vertex i in the order they are visited old_to_new = [] # New DFS number of the vertex with i as old DFS number degree = [] # Degree of vertex i parent = [] # Parent vertex of vertex i in the palm tree tree_arc = [] # Tree arc entering the vertex i first_child = [] - high = [] # One of the elements in highpt[i] + in_high = {} # One of the elements in highpt[i] dfs_counter = 0 n = 0 # number of vertices m = 0 # number of edges is_biconnected = True # Boolean to store if the graph is biconnected or not cut_vertex = None # If graph is not biconnected graph_copy_adjacency = [] + new_path = False # Boolean used to store if new path is started # Edges of the graph which are in the reverse direction in palm tree reverse_edges = set() @@ -1946,10 +1947,9 @@ class Triconnectivity: self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) self.reverse_edges = set() self.dfs_number = [0 for i in range(self.n)] - self.newnum = [0 for i in range(self.n)] self.highpt = [[] for i in range(self.n)] self.old_to_new = [0 for i in range(self.n + 1)] - self.nodeAt = [0 for i in range(self.n + 1)] + self.node_at = [0 for i in range(self.n + 1)] self.lowpt1 = [None for i in range(self.n)] self.lowpt2 = [None for i in range(self.n)] self.adj = [[] for i in range(self.n)] @@ -1996,6 +1996,29 @@ class Triconnectivity: self.build_acceptable_adj_struct() self.dfs2() + self.t_stack_h = [None for i in range(2*self.m + 1)] + self.t_stack_a = [None for i in range(2*self.m + 1)] + self.t_stack_b = [None for i in range(2*self.m + 1)] + self.t_stack_top = 0 + self.t_stack_a[self.t_stack_top] = -1 + + #self.path_search(self.start_vertex) + + # Push a triple on Tstack + def tstack_push(self, h, a, b): + self.t_stack_top += 1 + self.t_stack_h[self.t_stack_top] = h + self.t_stack_a[self.t_stack_top] = a + self.t_stack_b[self.t_stack_top] = b + + # Push end-of-stack marker on Tstack + def tstack_push_eos(self): + self.t_stack_top += 1 + self.t_stack_a[self.t_stack_top] = -1 + + # Returns true iff end-of-stack marker is not on top of Tstack + def tstack_not_eos(self): + return self.t_stack_a[self.t_stack_top] != -1 def new_component(self, edges, type_c=0): c = self.Component(edges, type_c) @@ -2004,11 +2027,27 @@ class Triconnectivity: for e in edges: self.edge_status[e] = 3 self.num_components += 1 + return c - def add_edge_to_component(self, comp, e): + def add_edges_to_component(self, comp, e): comp.add_edge(e) self.edge_status[e] = 3 + def high(self, v): + if self.highpt[v]: + return self.highpt[v][0] + else: + return 0 + + def del_high(self, e): + e = self.high[e] + if e is not None: + if e in self.reversed_edges: + v = e[0] + else: + v = e[1] + self.highpt[v].remove(e) + def split_multi_egdes(self): """ This function will remove all the multiple edges present in @@ -2229,24 +2268,24 @@ class Triconnectivity: self.adj[e[0]].append(e) self.in_adj[e] = self.adj[e[0]] - def pathFinder(self, v): + def path_finder(self, v): """ This function is a helper function for `dfs2` function. """ self.newnum[v] = self.dfs_counter - self.nd[v] + 1 for e in self.adj[v]: - u, w, _ = e - if self.edge_status[e] == 1: - if e in self.reverse_edges: - self.pathFinder(u) - else: - self.pathFinder(w) + w = e[1] if e[0] == v else e[0] # opposite vertex of e + if self.new_path: + self.new_path = False + self.starts_path[e] = True + if self.edge_status[e] == 1: # tree arc + self.path_finder(w) self.dfs_counter -= 1 else: - if e in self.reverse_edges: - self.highpt[u].append(self.newnum[w]) - else: - self.highpt[w].append(self.newnum[u]) + self.highpt[w].append(self.newnum[v]) + self.in_high[e] = self.highpt[w] + self.new_path = True + def dfs2(self): """ @@ -2254,17 +2293,22 @@ class Triconnectivity: new numbering obtained from `Path Finder` funciton. Populate `highpt` values. """ + self.highpt = [[] for i in range(self.n)] + self.in_high = dict((e, []) for e in self.graph_copy.edges()) self.dfs_counter = self.n + self.newnum = [0 for i in range(self.n)] + self.starts_path = dict((e, False) for e in self.graph_copy.edges()) + + self.new_path = True - # As all the vertices are labeled from 0 to n -1, - # the first vertex will be 0. - self.pathFinder(0) + # We call the pathFinder function with the start vertex + self.path_finder(self.start_vertex) - for v in range(self.n): - self.old_to_new[self.newnum[v]] = self.newnum[v] + for v in self.graph_copy.vertices(): + self.old_to_new[self.dfs_number[v]] = self.newnum[v] # Update lowpt values. - for v in range(self.n): - self.nodeAt[self.newnum[v]] = v + for v in self.graph_copy.vertices(): + self.node_at[self.newnum[v]] = v self.lowpt1[v] = self.old_to_new[self.lowpt1[v]] - self.lowpt2[v] = self.old_to_new[self.lowpt2[v]] \ No newline at end of file + self.lowpt2[v] = self.old_to_new[self.lowpt2[v]] From a1d1336f2c382a6f4f459b4dea0deff029e9aef8 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Thu, 5 Jul 2018 12:31:31 +0530 Subject: [PATCH 024/264] Added pathsearch() function. --- src/sage/graphs/connectivity.pyx | 299 +++++++++++++++++++++++++++++-- 1 file changed, 286 insertions(+), 13 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index c50ff15ee51..9ab04ea3215 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1900,6 +1900,12 @@ class Triconnectivity: component_type = type_c def add_edge(self, e): self.edge_list.append(e) + def finish_tric_or_poly(self, e): + self.edge_list.append(e) + if len(self.edge_list) >= 4: + component_type = 2 + else: + component_type = 1 graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph @@ -1907,8 +1913,11 @@ class Triconnectivity: start_vertex = 0 num_components = 0 edge_status = {} # status of each edge, unseen=0, tree=1, frond=2, removed=3 - Estack = [] - Tstack = [] + e_stack = [] + t_stack_h = [] + t_stack_a = [] + t_stack_b = [] + t_stack_top = 0 dfs_number = [] # DFS number of vertex i vertex_at = [] # vertex with DFS number of i$ lowpt1 = [] # lowpt1 number of vertex i @@ -1970,7 +1979,7 @@ class Triconnectivity: # Triconnectivity algorithm self.split_multi_egdes() self.dfs_counter = 0 # Initialisation for dfs1() - self.start_vertex = 0 # Initialisation for dfs1() + self.start_vertex = 7 # Initialisation for dfs1() self.cut_vertex = self.dfs1(self.start_vertex, check=check) if check: @@ -2020,7 +2029,12 @@ class Triconnectivity: def tstack_not_eos(self): return self.t_stack_a[self.t_stack_top] != -1 - def new_component(self, edges, type_c=0): + def estack_pop(self): + e = self.e_stack[-1] + self.e_stack = self.e_stack[0:-1] + return e + + def new_component(self, edges=[], type_c=0): c = self.Component(edges, type_c) self.components_list.append(c) # Remove the edges from graph @@ -2029,7 +2043,7 @@ class Triconnectivity: self.num_components += 1 return c - def add_edges_to_component(self, comp, e): + def add_edge_to_component(self, comp, e): comp.add_edge(e) self.edge_status[e] = 3 @@ -2040,13 +2054,13 @@ class Triconnectivity: return 0 def del_high(self, e): - e = self.high[e] - if e is not None: - if e in self.reversed_edges: + it = self.in_high[e] + if it: + if e in self.reverse_edges: v = e[0] else: v = e[1] - self.highpt[v].remove(e) + self.highpt[v].remove(it) def split_multi_egdes(self): """ @@ -2175,7 +2189,6 @@ class Triconnectivity: sage: tric.cut_vertex 3 """ - first_son = None # For testing biconnectivity s1 = None # Storing the cut vertex, if there is one self.dfs_counter += 1 @@ -2240,6 +2253,7 @@ class Triconnectivity: continue # compute phi value + # bucket sort adjacency list by phi values if e in self.reverse_edges: if edge_type==1: # tree arc if self.lowpt2[e[0]] < self.dfs_number[e[1]]: @@ -2263,10 +2277,10 @@ class Triconnectivity: for e in bucket[i]: if e in self.reverse_edges: self.adj[e[1]].append(e) - self.in_adj[e] = self.adj[e[1]] + self.in_adj[e] = e else: self.adj[e[0]].append(e) - self.in_adj[e] = self.adj[e[0]] + self.in_adj[e] = e def path_finder(self, v): """ @@ -2283,7 +2297,7 @@ class Triconnectivity: self.dfs_counter -= 1 else: self.highpt[w].append(self.newnum[v]) - self.in_high[e] = self.highpt[w] + self.in_high[e] = self.newnum[v] self.new_path = True @@ -2312,3 +2326,262 @@ class Triconnectivity: self.node_at[self.newnum[v]] = v self.lowpt1[v] = self.old_to_new[self.lowpt1[v]] self.lowpt2[v] = self.old_to_new[self.lowpt2[v]] + + def path_search(self, v): + """ + Function to find the separation pairs. + """ + y = 0 + vnum = self.newnum[v] + adj = self.adj[v] + outv = len(adj) + for e in adj: + it = e + if e in self.reverse_edges: + w = e[0] # target + else: + w = e[1] + wnum = self.newnum[w] + if self.edge_status[e] == 1: # tree arc + if self.starts_path[e]: + y = 0 + if self.t_stack_a[self.t_stack_top] > self.lowpt1[w]: + while self.t_stack_a[self.t_stack_top] > self.lowpt1[w]: + y = max(y, self.t_stack_h[self.t_stack_top]) + b = self.t_stack_b[self.t_stack_top] + self.t_stack_top -= 1 + self.tstack_push(y, self.lowpt1[w], b) + + else: + self.tstack_push(wnum + self.nd[w] - 1, self.lowpt1[w], vnum) + self.tstack_push_eos() + + self.path_search(w) + + self.e_stack.append(self.tree_arc[w]) + + temp = self.adj[w][0] + if temp in self.reverse_edges: + temp_target = temp[0] + else: + temp_target = temp[1] + # while vnum is not the start_vertex + while vnum != 0 and ((self.t_stack_a[self.t_stack_top] == vnum) or \ + (self.degree[w] == 2 and self.newnum[temp_target] > wnum)): + a = self.t_stack_a[self.t_stack_top] + b = self.t_stack_b[self.t_stack_top] + e_virt = None + + if a == vnum and self.parent[self.node_at[b]] == self.node_at[a]: + self.t_stack_top -= 1 + + else: + e_ab = None + if self.degree[w] == 2 and self.newnum[temp_target] > wnum: + # found type-2 separation pair + print "found type-2 separation pair (",v,", ", temp_target, ")" + e1 = self.estack_pop() + e2 = self.estack_pop() + self.adj[w].remove(self.in_adj[e2]) # check run time + + if e2 in self.reverse_edges: + x = e2[0] + else: + x = e2[1] + + #e_virt = self.graph_copy.add_edge(v, x) # but edge is not returned ? + self.graph_copy.add_edge(v, x) + e_virt = (v, x, None) + self.degree[v] -= 1 + self.degree[x] -= 1 + + if e2 in self.reverse_edges: + e2_source = e2[1] + else: + e2_source = e2[0] + if e2_source != w: # OGDF_ASSERT + raise ValueError("graph is not biconnected?") + + self.new_component([e1, e2, e_virt], 1) + + if self.e_stack: + e1 = self.e_stack[-1] + if e1 in self.reverse_edges: + if e1[1] == x and e1[0] == v: + e_ab = self.estack_pop() + self.adj[x].remove(self.in_adj[e_ab]) + self.del_high(e_ab) + else: + if e1[0] == x and e1[1] == v: + e_ab = self.estack_pop() + self.adj[x].remove(self.in_adj[e_ab]) + self.del_high(e_ab) + + else: # found type-2 separation pair + print "found type-2 separation pair (",a,", ", b, ")" + h = self.t_stack_h[self.t_stack_top] + self.t_stack_top -= 1 + + comp = self.new_component() + while True: + xy = self.e_stack[-1] + if xy in self.reverse_edges: + x = xy[1] + xy_target = xy[0] + else: + x = xy[0] + xy_target = xy[1] + if not (a <= self.newnum[x] and self.newnum[x] <= h and \ + a <= self.newnum[xy_target] and self.newnum[xy_target] <= h): + break + if (self.newnum[x] == a and self.newnum[xy_target] == b) or \ + (self.newnum[xy_target] == a and self.newnum[x] == b): + e_ab = self.estack_pop() + if e_ab in self.reverse_edges: + e_ab_source = e_ab[1] + else: + e_ab_source = e_ab[0] + self.adj[e_ab_source].remove(self.in_adj[e_ab]) + self.del_high(e_ab) + + else: + eh = self.estack_pop() + if eh in self.reverse_edges: + eh_source = eh[1] + else: + eh_source = eh[0] + if it != self.in_adj[eh]: + self.adj[eh_source].remove(self.in_adj[eh]) + self.del_high(eh) + + comp.add_edge(eh) # check + self.degree[x] -= 1 + self.degree[xy_target] -= 1 + + self.graph_copy.add_edge(self.node_at[a], self.node_at[b]) + e_virt = (self.node_at[a], self.node_at[b], None) + comp.finish_tric_or_poly(e_virt) + x = self.node_at[b] + + if e_ab is not None: + comp = self.new_component([e_ab, e_virt], type_c=0) + self.graph_copy.add_edge(v,x) + e_virt = (v,x,None) + comp.add_edge(e_virt) + self.degree[x] -= 1 + self.degree[v] -= 1 + + self.e_stack.append(e_virt) + it = e_virt + self.in_adj[e_virt] = it + self.degree[x] += 1 + self.degree[v] += 1 + self.parent[x] = v + self.tree_arc[x] = e_virt + self.edge_status[e_virt] = 1 + w = x + wnum = self.newnum[w] + + # start type-1 check + if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ + (self.parent[v] != self.start_vertex or outv >= 2): + # type-1 separation pair + print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" + c = self.new_component() + if not self.e_stack: # OGDF_ASSERT + raise ValueError("stack is empty") + while self.e_stack: + xy = self.e_stack[-1] + if xy in self.reverse_edges: + xx = self.newnum[xy[1]] #source + y = self.newnum[xy[0]] #target + else: + xx = self.newnum[xy[0]] #source + y = self.newnum[xy[1]] #target + + if not ((wnum <= xx and xx < wnum + self.nd[w]) or \ + (wnum <= y and y < wnum + self.nd[w])): + break + + comp.add_edge(self.estack_pop()) + self.del_high(xy) + self.degree[self.node_at[xx]] -= 1 + self.degree[self.node_at[y]] -= 1 + + self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) + e_virt = (v, self.node_at[self.lowpt1[w]], None) + comp.finish_tric_or_poly(e_virt); + + if (xx == vnum and y == self.lowpt1[w]) or \ + (y == vnum and xx == self.lowpt1[w]): + comp_bond = self.new_component(type_c = 0) + eh = self.estack_pop() + if self.in_adj[eh] != it: + if eh in self.reverse_edges: + self.adj[eh[1]].remove(self.in_adj[eh]) + else: + self.adj[eh[0]].remove(self.in_adj[eh]) + + comp_bond.add_edge(eh) + comp_bond.add_edge(e_virt) # TODO combine them + self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) + e_virt = (v, self.node_at[self.lowpt1[w]], None) + comp_bond.add_edge(e_virt) + self.in_high[e_virt] = self.in_high[eh] + self.degree[v] -= 1 + self.degree[self.node_at[self.lowpt1[w]]] -= 1 + + if self.node_at[self.lowpt1[w]] != self.parent[v]: + self.e_stack.append(e_virt) + it = e_virt + self.in_adj[e_virt] = it + if not self.in_high[e_virt] and self.high(self.node_at[self.lowpt1[w]]) < vnum: + self.highpt[self.node_at[self.lowpt1[w]]] = [vnum] + self.highpt[self.node_at[self.lowpt1[w]]] + self.in_high[e_virt] = vnum + + self.degree[v] += 1 + self.degree[self.node_at[self.lowpt1[w]]] += 1 + + else: + adj.remove(it) + comp_bond = self.new_component([e_virt], type_c=0) + self.graph_copy.add_edge(self.node_at[self.lowpt1[w]], v) + e_virt = (self.node_at[self.lowpt1[w]], c, None) + comp_bond.add_edge(e_virt) + + eh = self.tree_arc[v]; + comp_bond.add_edge(eh) + + self.tree_arc[v] = e_virt + self.egde_status[e_virt] = 1 + self.in_adj[e_virt] = self.in_adj[eh] + self.in_adj[eh] = e_virt + + # end type-1 search + if self.starts_path[e]: + while self.tstack_not_eos(): + self.t_stack_top -= 1 + self.t_stack_top -= 1 + + while self.tstack_not_eos() and self.t_stack_b[self.t_stack_top] != vnum \ + and self.high(v) > self.t_stack_h[self.t_stack_top]: + self.t_stack_top -= 1 + + outv -= 1 + + else: #frond + if self.starts_path[e]: + y = 0 + if self.t_stack_a[self.t_stack_top] > wnum: + while self.t_stack_a[self.t_stack_top] > wnum: + y = max(y, self.t_stack_h[self.t_stack_top]) + b = self.t_stack_b[self.t_stack_top] + self.t_stack_top -= 1 + self.tstack_push(y, wnum, b) + + else: + self.tstack_push(vnum, wnum, vnum) + self.e_stack.append(e) # add (v,w) to ESTACK + + + From c5a73e9f695128fd5b13b0946917afd764b175d6 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Thu, 5 Jul 2018 12:45:31 +0530 Subject: [PATCH 025/264] Fixed a small error --- src/sage/graphs/connectivity.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 9ab04ea3215..fded485a1f6 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2011,7 +2011,7 @@ class Triconnectivity: self.t_stack_top = 0 self.t_stack_a[self.t_stack_top] = -1 - #self.path_search(self.start_vertex) + self.path_search(self.start_vertex) # Push a triple on Tstack def tstack_push(self, h, a, b): @@ -2487,7 +2487,7 @@ class Triconnectivity: (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" - c = self.new_component() + comp = self.new_component() if not self.e_stack: # OGDF_ASSERT raise ValueError("stack is empty") while self.e_stack: @@ -2546,7 +2546,7 @@ class Triconnectivity: adj.remove(it) comp_bond = self.new_component([e_virt], type_c=0) self.graph_copy.add_edge(self.node_at[self.lowpt1[w]], v) - e_virt = (self.node_at[self.lowpt1[w]], c, None) + e_virt = (self.node_at[self.lowpt1[w]], v, None) comp_bond.add_edge(e_virt) eh = self.tree_arc[v]; From 6ff2b41ef73a5706884653c6bd668a17946aa4aa Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Mon, 16 Jul 2018 00:56:51 +0530 Subject: [PATCH 026/264] Fixed a bug with pathsearch() --- src/sage/graphs/connectivity.pyx | 127 ++++++++++++++++++++++++------- 1 file changed, 98 insertions(+), 29 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index fded485a1f6..d9f20bd8653 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1873,6 +1873,21 @@ def bridges(G, labels=True): from sage.graphs.base.sparse_graph cimport SparseGraph +class Component: + edge_list = [] + component_type = 0 #bond = 0, polygon = 1, triconnected = 2 + def __init__(self, edges, type_c): + self.edge_list = edges + self.component_type = type_c + print "creating new component ", edges + def add_edge(self, e): + self.edge_list.append(e) + def finish_tric_or_poly(self, e): + self.edge_list.append(e) + if len(self.edge_list) >= 4: + self.component_type = 2 + else: + self.component_type = 1 class Triconnectivity: """ @@ -1892,20 +1907,7 @@ class Triconnectivity: sage: tric.components_list [] """ - class Component: - edge_list = [] - component_type = 0 #bond = 0, polygon = 1, triconnected = 2 - def __init__(self, edges, type_c=0): - self.edge_list = edges - component_type = type_c - def add_edge(self, e): - self.edge_list.append(e) - def finish_tric_or_poly(self, e): - self.edge_list.append(e) - if len(self.edge_list) >= 4: - component_type = 2 - else: - component_type = 1 + graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph @@ -1979,7 +1981,7 @@ class Triconnectivity: # Triconnectivity algorithm self.split_multi_egdes() self.dfs_counter = 0 # Initialisation for dfs1() - self.start_vertex = 7 # Initialisation for dfs1() + self.start_vertex = 0 # Initialisation for dfs1() self.cut_vertex = self.dfs1(self.start_vertex, check=check) if check: @@ -2013,6 +2015,16 @@ class Triconnectivity: self.path_search(self.start_vertex) + # last split component + print "last split component ", self.e_stack + c = Component([],0) + print "new component edge list ", c.edge_list + while self.e_stack: + c.add_edge(self.estack_pop()) + c.component_type = 2 if len(c.edge_list) > 4 else 1 + self.components_list.append(c) + c = None + # Push a triple on Tstack def tstack_push(self, h, a, b): self.t_stack_top += 1 @@ -2035,7 +2047,7 @@ class Triconnectivity: return e def new_component(self, edges=[], type_c=0): - c = self.Component(edges, type_c) + c = Component(edges, type_c) self.components_list.append(c) # Remove the edges from graph for e in edges: @@ -2061,6 +2073,7 @@ class Triconnectivity: else: v = e[1] self.highpt[v].remove(it) + print "delhigh test ", e, self.highpt[v], it def split_multi_egdes(self): """ @@ -2103,6 +2116,7 @@ class Triconnectivity: # It will add k - 1 multiple edges to comp if sorted_edges[i] == sorted_edges[i + 1]: self.edge_status[sorted_edges[i]] = 3 # edge removed + print "edge removed: ", sorted_edges[i], self.edge_status[sorted_edges[i]] comp.append(sorted_edges[i]) else: if comp: @@ -2114,6 +2128,9 @@ class Triconnectivity: comp.append(sorted_edges[i-1]) comp.append(sorted_edges[i-1]) self.new_component(comp) + print "Edge status after split_multi_edges():" + for e in self.graph_copy.edges(): + print e, self.edge_status[e] def dfs1(self, v, u=None, check=True): @@ -2333,10 +2350,18 @@ class Triconnectivity: """ y = 0 vnum = self.newnum[v] - adj = self.adj[v] - outv = len(adj) - for e in adj: + outv = len(self.adj[v]) + #print "path_search(v) with parameter ", v, " with vnum ", vnum + #print "PRINTING ADJ IN PATHSEARCH ", v + #print self.adj + #for e in adj: + # ERROR fixed + for i in range(len(self.adj[v])): + #it = e + e = self.adj[v][i] it = e + + print "going through edges of ", v, ": edge ", e if e in self.reverse_edges: w = e[0] # target else: @@ -2365,13 +2390,22 @@ class Triconnectivity: temp_target = temp[0] else: temp_target = temp[1] + #print "w and its adjacency: ", w, " " , temp # while vnum is not the start_vertex - while vnum != 0 and ((self.t_stack_a[self.t_stack_top] == vnum) or \ + #print "checking the while nvum!=1 test ", vnum + #print "stack_top_num=", self.t_stack_top, " :stack_top=",self.t_stack_a[self.t_stack_top] + #print "degree[w]=", self.degree[w], " target=", temp_target, " last val=", self.newnum[temp_target] + #print "WHILECHECK:", self.adj[w] + while vnum != 1 and ((self.t_stack_a[self.t_stack_top] == vnum) or \ (self.degree[w] == 2 and self.newnum[temp_target] > wnum)): + #print "entered the nvum!=1 while loop ", vnum a = self.t_stack_a[self.t_stack_top] b = self.t_stack_b[self.t_stack_top] e_virt = None + print "list indices NONE?? ", a, b + print self.node_at[a] + print self.node_at[b] if a == vnum and self.parent[self.node_at[b]] == self.node_at[a]: self.t_stack_top -= 1 @@ -2402,7 +2436,10 @@ class Triconnectivity: if e2_source != w: # OGDF_ASSERT raise ValueError("graph is not biconnected?") - self.new_component([e1, e2, e_virt], 1) + print "before creating new component ", [e1, e2, e_virt] + comp = Component([e1, e2, e_virt], 1) + self.components_list.append(comp) + comp = None if self.e_stack: e1 = self.e_stack[-1] @@ -2422,7 +2459,8 @@ class Triconnectivity: h = self.t_stack_h[self.t_stack_top] self.t_stack_top -= 1 - comp = self.new_component() + print "before creating new component - empty edge_list" + comp = Component([],0) while True: xy = self.e_stack[-1] if xy in self.reverse_edges: @@ -2461,18 +2499,27 @@ class Triconnectivity: self.graph_copy.add_edge(self.node_at[a], self.node_at[b]) e_virt = (self.node_at[a], self.node_at[b], None) comp.finish_tric_or_poly(e_virt) + self.components_list.append(comp) + comp = None x = self.node_at[b] if e_ab is not None: - comp = self.new_component([e_ab, e_virt], type_c=0) + print "before creating new component ", [e_ab, e_virt] + comp = Component([e_ab, e_virt], type_c=0) self.graph_copy.add_edge(v,x) e_virt = (v,x,None) comp.add_edge(e_virt) self.degree[x] -= 1 self.degree[v] -= 1 + self.components_list.append(comp) + comp = None self.e_stack.append(e_virt) + #it = e_virt + # ERROR fixed + self.adj[v][i] = e_virt it = e_virt + self.in_adj[e_virt] = it self.degree[x] += 1 self.degree[v] += 1 @@ -2482,12 +2529,14 @@ class Triconnectivity: w = x wnum = self.newnum[w] + print "going to start type-1 check" # start type-1 check if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" - comp = self.new_component() + print "before creating new component - empty edgelist" + comp = Component([],0) if not self.e_stack: # OGDF_ASSERT raise ValueError("stack is empty") while self.e_stack: @@ -2508,13 +2557,18 @@ class Triconnectivity: self.degree[self.node_at[xx]] -= 1 self.degree[self.node_at[y]] -= 1 + #print "TYPE1: xx and y,, and vnum and wnum" , xx, y, vnum, self.lowpt1[w] self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) e_virt = (v, self.node_at[self.lowpt1[w]], None) - comp.finish_tric_or_poly(e_virt); + comp.finish_tric_or_poly(e_virt) + self.components_list.append(comp) + comp = None if (xx == vnum and y == self.lowpt1[w]) or \ (y == vnum and xx == self.lowpt1[w]): - comp_bond = self.new_component(type_c = 0) + #print "TYPE1:firstIFcondition" + print "before creating new component - empty edgelist " + comp_bond = Component([],type_c = 0) eh = self.estack_pop() if self.in_adj[eh] != it: if eh in self.reverse_edges: @@ -2531,11 +2585,22 @@ class Triconnectivity: self.degree[v] -= 1 self.degree[self.node_at[self.lowpt1[w]]] -= 1 + self.components_list.append(comp_bond) + comp_bond = None + if self.node_at[self.lowpt1[w]] != self.parent[v]: self.e_stack.append(e_virt) + + #it = e_virt + # ERROR fixed + self.adj[v][i] = e_virt it = e_virt + self.in_adj[e_virt] = it - if not self.in_high[e_virt] and self.high(self.node_at[self.lowpt1[w]]) < vnum: + #print "TYPE1:secondIFcondition ", it, self.in_adj[e_virt] + # ERROR1 fixed: + #if not self.in_high[e_virt] and self.high(self.node_at[self.lowpt1[w]]) < vnum: + if not e_virt in self.in_high and self.high(self.node_at[self.lowpt1[w]]) < vnum: self.highpt[self.node_at[self.lowpt1[w]]] = [vnum] + self.highpt[self.node_at[self.lowpt1[w]]] self.in_high[e_virt] = vnum @@ -2543,8 +2608,9 @@ class Triconnectivity: self.degree[self.node_at[self.lowpt1[w]]] += 1 else: - adj.remove(it) - comp_bond = self.new_component([e_virt], type_c=0) + self.adj[v].remove(it) + print "before creating new component ", [e_virt] + comp_bond = Component([e_virt], type_c=0) self.graph_copy.add_edge(self.node_at[self.lowpt1[w]], v) e_virt = (self.node_at[self.lowpt1[w]], v, None) comp_bond.add_edge(e_virt) @@ -2552,6 +2618,9 @@ class Triconnectivity: eh = self.tree_arc[v]; comp_bond.add_edge(eh) + self.components_list.append(comp_bond) + comp_bond = None + self.tree_arc[v] = e_virt self.egde_status[e_virt] = 1 self.in_adj[e_virt] = self.in_adj[eh] From 79b8cb87abff65aec71c2daacad8b3c8bc85c177 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Mon, 16 Jul 2018 01:17:54 +0530 Subject: [PATCH 027/264] Removed extra print statements. Error with edge_status dictionary. --- src/sage/graphs/connectivity.pyx | 37 ++++---------------------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index d9f20bd8653..f3ffe5dd3d4 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1879,7 +1879,6 @@ class Component: def __init__(self, edges, type_c): self.edge_list = edges self.component_type = type_c - print "creating new component ", edges def add_edge(self, e): self.edge_list.append(e) def finish_tric_or_poly(self, e): @@ -2016,9 +2015,7 @@ class Triconnectivity: self.path_search(self.start_vertex) # last split component - print "last split component ", self.e_stack c = Component([],0) - print "new component edge list ", c.edge_list while self.e_stack: c.add_edge(self.estack_pop()) c.component_type = 2 if len(c.edge_list) > 4 else 1 @@ -2073,7 +2070,6 @@ class Triconnectivity: else: v = e[1] self.highpt[v].remove(it) - print "delhigh test ", e, self.highpt[v], it def split_multi_egdes(self): """ @@ -2110,13 +2106,12 @@ class Triconnectivity: comp = [] if self.graph_copy.has_multiple_edges(): - sorted_edges = sorted(self.graph_copy.multiple_edges(labels=False)) + sorted_edges = sorted(self.graph_copy.multiple_edges(labels=True)) for i in range(len(sorted_edges) - 1): # It will add k - 1 multiple edges to comp if sorted_edges[i] == sorted_edges[i + 1]: self.edge_status[sorted_edges[i]] = 3 # edge removed - print "edge removed: ", sorted_edges[i], self.edge_status[sorted_edges[i]] comp.append(sorted_edges[i]) else: if comp: @@ -2128,9 +2123,6 @@ class Triconnectivity: comp.append(sorted_edges[i-1]) comp.append(sorted_edges[i-1]) self.new_component(comp) - print "Edge status after split_multi_edges():" - for e in self.graph_copy.edges(): - print e, self.edge_status[e] def dfs1(self, v, u=None, check=True): @@ -2351,17 +2343,13 @@ class Triconnectivity: y = 0 vnum = self.newnum[v] outv = len(self.adj[v]) - #print "path_search(v) with parameter ", v, " with vnum ", vnum - #print "PRINTING ADJ IN PATHSEARCH ", v - #print self.adj - #for e in adj: # ERROR fixed + #for e in adj: for i in range(len(self.adj[v])): #it = e e = self.adj[v][i] it = e - print "going through edges of ", v, ": edge ", e if e in self.reverse_edges: w = e[0] # target else: @@ -2390,20 +2378,13 @@ class Triconnectivity: temp_target = temp[0] else: temp_target = temp[1] - #print "w and its adjacency: ", w, " " , temp # while vnum is not the start_vertex - #print "checking the while nvum!=1 test ", vnum - #print "stack_top_num=", self.t_stack_top, " :stack_top=",self.t_stack_a[self.t_stack_top] - #print "degree[w]=", self.degree[w], " target=", temp_target, " last val=", self.newnum[temp_target] - #print "WHILECHECK:", self.adj[w] while vnum != 1 and ((self.t_stack_a[self.t_stack_top] == vnum) or \ (self.degree[w] == 2 and self.newnum[temp_target] > wnum)): - #print "entered the nvum!=1 while loop ", vnum a = self.t_stack_a[self.t_stack_top] b = self.t_stack_b[self.t_stack_top] e_virt = None - print "list indices NONE?? ", a, b print self.node_at[a] print self.node_at[b] if a == vnum and self.parent[self.node_at[b]] == self.node_at[a]: @@ -2436,7 +2417,6 @@ class Triconnectivity: if e2_source != w: # OGDF_ASSERT raise ValueError("graph is not biconnected?") - print "before creating new component ", [e1, e2, e_virt] comp = Component([e1, e2, e_virt], 1) self.components_list.append(comp) comp = None @@ -2459,7 +2439,6 @@ class Triconnectivity: h = self.t_stack_h[self.t_stack_top] self.t_stack_top -= 1 - print "before creating new component - empty edge_list" comp = Component([],0) while True: xy = self.e_stack[-1] @@ -2504,7 +2483,6 @@ class Triconnectivity: x = self.node_at[b] if e_ab is not None: - print "before creating new component ", [e_ab, e_virt] comp = Component([e_ab, e_virt], type_c=0) self.graph_copy.add_edge(v,x) e_virt = (v,x,None) @@ -2515,8 +2493,8 @@ class Triconnectivity: comp = None self.e_stack.append(e_virt) - #it = e_virt # ERROR fixed + #it = e_virt self.adj[v][i] = e_virt it = e_virt @@ -2529,13 +2507,11 @@ class Triconnectivity: w = x wnum = self.newnum[w] - print "going to start type-1 check" # start type-1 check if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" - print "before creating new component - empty edgelist" comp = Component([],0) if not self.e_stack: # OGDF_ASSERT raise ValueError("stack is empty") @@ -2557,7 +2533,6 @@ class Triconnectivity: self.degree[self.node_at[xx]] -= 1 self.degree[self.node_at[y]] -= 1 - #print "TYPE1: xx and y,, and vnum and wnum" , xx, y, vnum, self.lowpt1[w] self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) e_virt = (v, self.node_at[self.lowpt1[w]], None) comp.finish_tric_or_poly(e_virt) @@ -2566,8 +2541,6 @@ class Triconnectivity: if (xx == vnum and y == self.lowpt1[w]) or \ (y == vnum and xx == self.lowpt1[w]): - #print "TYPE1:firstIFcondition" - print "before creating new component - empty edgelist " comp_bond = Component([],type_c = 0) eh = self.estack_pop() if self.in_adj[eh] != it: @@ -2597,8 +2570,7 @@ class Triconnectivity: it = e_virt self.in_adj[e_virt] = it - #print "TYPE1:secondIFcondition ", it, self.in_adj[e_virt] - # ERROR1 fixed: + # ERROR fixed: #if not self.in_high[e_virt] and self.high(self.node_at[self.lowpt1[w]]) < vnum: if not e_virt in self.in_high and self.high(self.node_at[self.lowpt1[w]]) < vnum: self.highpt[self.node_at[self.lowpt1[w]]] = [vnum] + self.highpt[self.node_at[self.lowpt1[w]]] @@ -2609,7 +2581,6 @@ class Triconnectivity: else: self.adj[v].remove(it) - print "before creating new component ", [e_virt] comp_bond = Component([e_virt], type_c=0) self.graph_copy.add_edge(self.node_at[self.lowpt1[w]], v) e_virt = (self.node_at[self.lowpt1[w]], v, None) From d0b96316d81e42ecb4ac9312ea21924386fd5e70 Mon Sep 17 00:00:00 2001 From: saiharsh Date: Mon, 16 Jul 2018 19:36:37 +0530 Subject: [PATCH 028/264] Updated graph_copy and split_multiple_edges function --- src/sage/graphs/connectivity.pyx | 44 +++++++++++++------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index f3ffe5dd3d4..8c3d3d3fc55 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1907,7 +1907,7 @@ class Triconnectivity: [] """ - + graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph int_to_vertex = {} # mapping of integers to original vertices @@ -1949,7 +1949,17 @@ class Triconnectivity: def __init__(self, G, check=True): - self.graph_copy = G.copy(implementation='c_graph') + from sage.graphs.graph import Graph + self.graph_copy = Graph(multiedges=True) + edges = G.edges() + # dict to map new edges with the old edges + self.edge_label_dict = {} + for i in range(len(edges)): + newEdge = tuple([edges[i][0], edges[i][1], i]) + self.graph_copy.add_edge(newEdge) + self.edge_label_dict[newEdge] = edges[i] + self.graph_copy = self.graph_copy.copy(implementation='c_graph') + self.vertex_to_int = self.graph_copy.relabel(inplace=True, return_map=True) self.int_to_vertex = dict([(v,k) for k,v in self.vertex_to_int.items()]) self.n = self.graph_copy.order() @@ -2073,35 +2083,16 @@ class Triconnectivity: def split_multi_egdes(self): """ - This function will remove all the multiple edges present in - graph_copy and append the multiple edges in component list. + This function will mark all the multiple edges present in graph_copy + as removed and append the multiple edges in component list. If there are `k` multiple edges between `u` and `v` then `k+1` - edges will be added to a component. + edges will be added to a component and edge_status will have k-1 edges + marked a 3(i.e edge removed). It won't return anything but update the components_list and graph_copy, which will become simple graph. - Example:: - - An example to list the components build after removal of multiple edges - - sage: G = Graph() - sage: G.add_cycle(vertices=[0,1,2,3,4]) - sage: G.allow_multiple_edges(True) - sage: G.add_edges(G.edges()) - sage: G.add_edges([[0,1],[3, 4]]) - sage: from sage.graphs.connectivity import Triconnectivity - sage: t = Triconnectivity(G) - sage: for l in t.components_list: - ....: print(l.edge_list) - [(0, 1), (0, 1), (0, 1), (0, 1)] - [(0, 4), (0, 4), (0, 4)] - [(1, 2), (1, 2), (1, 2)] - [(2, 3), (2, 3), (2, 3)] - [(3, 4), (3, 4), (3, 4), (3, 4)] - sage: t.num_components - 5 """ comp = [] @@ -2110,7 +2101,8 @@ class Triconnectivity: for i in range(len(sorted_edges) - 1): # It will add k - 1 multiple edges to comp - if sorted_edges[i] == sorted_edges[i + 1]: + if (sorted_edges[i][0] == sorted_edges[i + 1][0]) and \ + (sorted_edges[i][1] == sorted_edges[i + 1][1]): self.edge_status[sorted_edges[i]] = 3 # edge removed comp.append(sorted_edges[i]) else: From a5c4ea76ba1918e1b4c039817c674691c4f2c967 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Thu, 19 Jul 2018 12:33:16 +0530 Subject: [PATCH 029/264] Fixed all bugs in path_search. --- src/sage/graphs/connectivity.pyx | 227 ++++++++++++++++++++++--------- 1 file changed, 165 insertions(+), 62 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 8c3d3d3fc55..62cfce8cfb4 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1888,6 +1888,108 @@ class Component: else: self.component_type = 1 +class AdjNode: + prev = None + next = None + edge = None + def set_edge(self, e): + self.edge = e + def get_edge(self): + return self.edge + +class AdjList: + head = None + curr = None + length = 0 + def remove(self, node): + if node.prev == None and node.next == None: + self.head = None + elif node.prev == None: # node is head + self.head = node.next + node.next.prev = None + elif node.next == None: #node is tail + node.prev.next = None + else: + node.prev.next = node.next + node.next.prev = node.prev + self.length -= 1 + def set_head(self, h): + self.head = h + self.curr = h + self.length = 1 + def append(self, node): + if self.head == None: + self.set_head(node) + else: + self.curr.next = node + node.prev = self.curr + self.curr = node + self.length += 1 + def get_head(self): + return self.head + def get_length(self): + return self.length + def replace(self, node1, node2): + if node1.prev == None and node1.next == None: + self.head = node2 + elif node1.prev == None: # head has to be replaced + node1.next.prev = node2 + node2.next = node1.next + elif node1.next == None: + node1.prev.next = node2 + node2.prev = node1.prev + else: + node1.prev.next = node2 + node1.next.prev = node2 + node2.prev = node1.prev + node2.next = node1.next + +class HighPtNode: + prev = None + next = None + front = None #integer + def set_frond(self, f): + self.frond = f + def get_frond(self): + return self.frond + +class HighPtList: + head = None + curr = None + length = 0 + def remove(self, node): + if node.prev == None and node.next == None: + self.head = None + elif node.prev == None: # node is head + self.head = node.next + node.next.prev = None + elif node.next == None: #node is tail + node.prev.next = None + else: + node.prev.next = node.next + node.next.prev = node.prev + self.length -= 1 + def set_head(self, h): + self.head = h + self.curr = h + self.length = 1 + def append(self, node): + if self.head == None: + self.set_head(node) + else: + self.curr.next = node + node.prev = self.curr + self.curr = node + self.length += 1 + def push_front(self, node): + if self.head == None: + self.head = node + else: + self.head.prev = node + node.next = self.head + self.head = node + + class Triconnectivity: """ This module is not yet complete, it has work to be done. @@ -1906,8 +2008,6 @@ class Triconnectivity: sage: tric.components_list [] """ - - graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph int_to_vertex = {} # mapping of integers to original vertices @@ -1925,7 +2025,7 @@ class Triconnectivity: lowpt2 = [] # lowpt2 number of vertex i nd = [] # number of descendants of vertex i edge_phi = {} # (key, value) = (edge, corresponding phi value) - adj = [] # i^th value contains a list of incident edges of vertex i + adj = [] # i^th value contains a AdjList of incident edges of vertex i in_adj = {} # this variable is used in the PathSearch() function newnum = [] # new DFS number of vertex i starts_path = {} # dict of (edge, T/F) to denote if a path starts at edge @@ -1967,12 +2067,12 @@ class Triconnectivity: self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) self.reverse_edges = set() self.dfs_number = [0 for i in range(self.n)] - self.highpt = [[] for i in range(self.n)] + self.highpt = [HighPtList() for i in range(self.n)] self.old_to_new = [0 for i in range(self.n + 1)] self.node_at = [0 for i in range(self.n + 1)] self.lowpt1 = [None for i in range(self.n)] self.lowpt2 = [None for i in range(self.n)] - self.adj = [[] for i in range(self.n)] + self.adj = [AdjList() for i in range(self.n)] self.nd = [None for i in range(self.n)] self.parent = [None for i in range(self.n)] self.degree = [None for i in range(self.n)] @@ -2062,24 +2162,22 @@ class Triconnectivity: self.num_components += 1 return c - def add_edge_to_component(self, comp, e): - comp.add_edge(e) - self.edge_status[e] = 3 - def high(self, v): - if self.highpt[v]: - return self.highpt[v][0] - else: + head = self.highpt[v].head + if head == None: return 0 + else: + return head.frond def del_high(self, e): - it = self.in_high[e] - if it: - if e in self.reverse_edges: - v = e[0] - else: - v = e[1] - self.highpt[v].remove(it) + if e in self.in_high: + it = self.in_high[e] + if it: + if e in self.reverse_edges: + v = e[0] + else: + v = e[1] + self.highpt[v].remove(it) def split_multi_egdes(self): """ @@ -2094,7 +2192,6 @@ class Triconnectivity: graph_copy, which will become simple graph. """ - comp = [] if self.graph_copy.has_multiple_edges(): sorted_edges = sorted(self.graph_copy.multiple_edges(labels=True)) @@ -2276,19 +2373,25 @@ class Triconnectivity: for i in range(1,max+1): for e in bucket[i]: + node = AdjNode() + node.set_edge(e) if e in self.reverse_edges: - self.adj[e[1]].append(e) - self.in_adj[e] = e + self.adj[e[1]].append(node) + self.in_adj[e] = node else: - self.adj[e[0]].append(e) - self.in_adj[e] = e + self.adj[e[0]].append(node) + self.in_adj[e] = node def path_finder(self, v): """ This function is a helper function for `dfs2` function. """ self.newnum[v] = self.dfs_counter - self.nd[v] + 1 - for e in self.adj[v]: + #for e in self.adj[v]: + e_node = self.adj[v].get_head() + while e_node: + e = e_node.get_edge() + e_node = e_node.next w = e[1] if e[0] == v else e[0] # opposite vertex of e if self.new_path: self.new_path = False @@ -2297,8 +2400,10 @@ class Triconnectivity: self.path_finder(w) self.dfs_counter -= 1 else: - self.highpt[w].append(self.newnum[v]) - self.in_high[e] = self.newnum[v] + highpt_node = HighPtNode() + highpt_node.set_frond(self.newnum[v]) + self.highpt[w].append(highpt_node) + self.in_high[e] = highpt_node self.new_path = True @@ -2308,8 +2413,7 @@ class Triconnectivity: new numbering obtained from `Path Finder` funciton. Populate `highpt` values. """ - self.highpt = [[] for i in range(self.n)] - self.in_high = dict((e, []) for e in self.graph_copy.edges()) + self.in_high = dict((e, None) for e in self.graph_copy.edges()) self.dfs_counter = self.n self.newnum = [0 for i in range(self.n)] self.starts_path = dict((e, False) for e in self.graph_copy.edges()) @@ -2334,13 +2438,11 @@ class Triconnectivity: """ y = 0 vnum = self.newnum[v] - outv = len(self.adj[v]) - # ERROR fixed - #for e in adj: - for i in range(len(self.adj[v])): - #it = e - e = self.adj[v][i] - it = e + outv = self.adj[v].get_length() + e_node = self.adj[v].get_head() + while e_node: + e = e_node.get_edge() + it = e_node if e in self.reverse_edges: w = e[0] # target @@ -2365,7 +2467,8 @@ class Triconnectivity: self.e_stack.append(self.tree_arc[w]) - temp = self.adj[w][0] + temp_node = self.adj[w].get_head() + temp = temp_node.get_edge() if temp in self.reverse_edges: temp_target = temp[0] else: @@ -2377,8 +2480,6 @@ class Triconnectivity: b = self.t_stack_b[self.t_stack_top] e_virt = None - print self.node_at[a] - print self.node_at[b] if a == vnum and self.parent[self.node_at[b]] == self.node_at[a]: self.t_stack_top -= 1 @@ -2386,17 +2487,16 @@ class Triconnectivity: e_ab = None if self.degree[w] == 2 and self.newnum[temp_target] > wnum: # found type-2 separation pair - print "found type-2 separation pair (",v,", ", temp_target, ")" + # print "Found type-2 separation pair (",v,", ", temp_target, ")" e1 = self.estack_pop() e2 = self.estack_pop() - self.adj[w].remove(self.in_adj[e2]) # check run time + self.adj[w].remove(self.in_adj[e2]) if e2 in self.reverse_edges: x = e2[0] else: x = e2[1] - #e_virt = self.graph_copy.add_edge(v, x) # but edge is not returned ? self.graph_copy.add_edge(v, x) e_virt = (v, x, None) self.degree[v] -= 1 @@ -2407,7 +2507,7 @@ class Triconnectivity: else: e2_source = e2[0] if e2_source != w: # OGDF_ASSERT - raise ValueError("graph is not biconnected?") + raise ValueError("graph is not biconnected") comp = Component([e1, e2, e_virt], 1) self.components_list.append(comp) @@ -2427,7 +2527,7 @@ class Triconnectivity: self.del_high(e_ab) else: # found type-2 separation pair - print "found type-2 separation pair (",a,", ", b, ")" + # print "Found type-2 separation pair (",a,", ", b, ")" h = self.t_stack_h[self.t_stack_top] self.t_stack_top -= 1 @@ -2463,7 +2563,7 @@ class Triconnectivity: self.adj[eh_source].remove(self.in_adj[eh]) self.del_high(eh) - comp.add_edge(eh) # check + comp.add_edge(eh) self.degree[x] -= 1 self.degree[xy_target] -= 1 @@ -2485,10 +2585,10 @@ class Triconnectivity: comp = None self.e_stack.append(e_virt) - # ERROR fixed - #it = e_virt - self.adj[v][i] = e_virt - it = e_virt + e_virt_node = AdjNode() + e_virt_node.set_edge(e_virt) + self.adj[v].replace(e_node, e_virt_node) + it = e_virt_node self.in_adj[e_virt] = it self.degree[x] += 1 @@ -2503,7 +2603,7 @@ class Triconnectivity: if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair - print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" + #print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" comp = Component([],0) if not self.e_stack: # OGDF_ASSERT raise ValueError("stack is empty") @@ -2542,7 +2642,7 @@ class Triconnectivity: self.adj[eh[0]].remove(self.in_adj[eh]) comp_bond.add_edge(eh) - comp_bond.add_edge(e_virt) # TODO combine them + comp_bond.add_edge(e_virt) self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) e_virt = (v, self.node_at[self.lowpt1[w]], None) comp_bond.add_edge(e_virt) @@ -2556,17 +2656,17 @@ class Triconnectivity: if self.node_at[self.lowpt1[w]] != self.parent[v]: self.e_stack.append(e_virt) - #it = e_virt - # ERROR fixed - self.adj[v][i] = e_virt - it = e_virt + e_virt_node = AdjNode() + e_virt_node.set_edge(e_virt) + self.adj[v].replace(e_node, e_virt_node) + it = e_virt_node self.in_adj[e_virt] = it - # ERROR fixed: - #if not self.in_high[e_virt] and self.high(self.node_at[self.lowpt1[w]]) < vnum: if not e_virt in self.in_high and self.high(self.node_at[self.lowpt1[w]]) < vnum: - self.highpt[self.node_at[self.lowpt1[w]]] = [vnum] + self.highpt[self.node_at[self.lowpt1[w]]] - self.in_high[e_virt] = vnum + vnum_node = HighPtNode() + vnum_node.set_frond(vnum) + self.highpt[self.node_at[self.lowpt1[w]]].push_front(vnum_node) + self.in_high[e_virt] = vnum_node self.degree[v] += 1 self.degree[self.node_at[self.lowpt1[w]]] += 1 @@ -2585,9 +2685,11 @@ class Triconnectivity: comp_bond = None self.tree_arc[v] = e_virt - self.egde_status[e_virt] = 1 + self.edge_status[e_virt] = 1 self.in_adj[e_virt] = self.in_adj[eh] - self.in_adj[eh] = e_virt + e_virt_node = AdjNode() + e_virt_node.set_edge(e_virt) + self.in_adj[eh] = e_virt_node # end type-1 search if self.starts_path[e]: @@ -2615,5 +2717,6 @@ class Triconnectivity: self.tstack_push(vnum, wnum, vnum) self.e_stack.append(e) # add (v,w) to ESTACK - + # Go to next node in adjacency list + e_node = e_node.next From 1ea9c1b005126e7295f5117d8a5476fdaecf565b Mon Sep 17 00:00:00 2001 From: saiharsh Date: Thu, 19 Jul 2018 16:50:42 +0530 Subject: [PATCH 030/264] Fixed the bug in split_multiple_edges --- src/sage/graphs/connectivity.pyx | 37 ++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 62cfce8cfb4..226fad3e6c3 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2051,6 +2051,12 @@ class Triconnectivity: def __init__(self, G, check=True): from sage.graphs.graph import Graph self.graph_copy = Graph(multiedges=True) + + # Add all the vertices first + # there is a possibility of isolated vertices + for v in G.vertex_iterator(): + graph_copy.append(v) + edges = G.edges() # dict to map new edges with the old edges self.edge_label_dict = {} @@ -2156,9 +2162,6 @@ class Triconnectivity: def new_component(self, edges=[], type_c=0): c = Component(edges, type_c) self.components_list.append(c) - # Remove the edges from graph - for e in edges: - self.edge_status[e] = 3 self.num_components += 1 return c @@ -2192,9 +2195,10 @@ class Triconnectivity: graph_copy, which will become simple graph. """ + comp = [] if self.graph_copy.has_multiple_edges(): - sorted_edges = sorted(self.graph_copy.multiple_edges(labels=True)) + sorted_edges = sorted(self.graph_copy.edges()) for i in range(len(sorted_edges) - 1): # It will add k - 1 multiple edges to comp @@ -2204,15 +2208,30 @@ class Triconnectivity: comp.append(sorted_edges[i]) else: if comp: - comp.append(sorted_edges[i-1]) - comp.append(sorted_edges[i-1]) + comp.append(sorted_edges[i]) + self.edge_status[sorted_edges[i]] = 3 # edge removed + + # Add virtual edge to graph_copy + newVEdge = tuple([sorted_edges[i][0], sorted_edges[i][1], "newVEdge"]) + self.graph_copy.add_edge(newVEdge) + + # mark unseen for newVEdge + self.edge_status[newVEdge] = 0 + + comp.append(newVEdge) self.new_component(comp) comp = [] if comp: - comp.append(sorted_edges[i-1]) - comp.append(sorted_edges[i-1]) - self.new_component(comp) + comp.append(sorted_edges[i+1]) + self.edge_status[sorted_edges[i+1]] = 3 # edge removed + # Add virtual edge to graph_copy + newVEdge = tuple([sorted_edges[i+1][0], sorted_edges[i+1][1], "newVEdge"]) + self.graph_copy.add_edge(newVEdge) + self.edge_status[newVEdge] = 0 + + comp.append(newVEdge) + self.new_component(comp) def dfs1(self, v, u=None, check=True): """ From 2375d00a47c5256380c9a80f1ed4895cdecdea83 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Thu, 19 Jul 2018 17:38:26 +0530 Subject: [PATCH 031/264] Fixed a minor bug related to multi-graphs --- src/sage/graphs/connectivity.pyx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 226fad3e6c3..1c6c8bc83d2 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2055,7 +2055,7 @@ class Triconnectivity: # Add all the vertices first # there is a possibility of isolated vertices for v in G.vertex_iterator(): - graph_copy.append(v) + self.graph_copy.add_vertex(v) edges = G.edges() # dict to map new edges with the old edges @@ -2088,13 +2088,14 @@ class Triconnectivity: self.components_list = [] #list of components self.graph_copy_adjacency = [[] for i in range(self.n)] + # Triconnectivity algorithm + self.split_multi_egdes() + # Build adjacency list for e in self.graph_copy.edges(): self.graph_copy_adjacency[e[0]].append(e) self.graph_copy_adjacency[e[1]].append(e) - # Triconnectivity algorithm - self.split_multi_egdes() self.dfs_counter = 0 # Initialisation for dfs1() self.start_vertex = 0 # Initialisation for dfs1() self.cut_vertex = self.dfs1(self.start_vertex, check=check) From 6b357c7d5323011da8a3b25e6a724212645413c6 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Tue, 24 Jul 2018 19:16:28 +0530 Subject: [PATCH 032/264] Added `assemble_triconnected_components` function. --- src/sage/graphs/connectivity.pyx | 550 ++++++++++++++++++++----------- 1 file changed, 365 insertions(+), 185 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 1c6c8bc83d2..3d5145ab390 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1873,57 +1873,75 @@ def bridges(G, labels=True): from sage.graphs.base.sparse_graph cimport SparseGraph -class Component: - edge_list = [] - component_type = 0 #bond = 0, polygon = 1, triconnected = 2 - def __init__(self, edges, type_c): - self.edge_list = edges - self.component_type = type_c - def add_edge(self, e): - self.edge_list.append(e) - def finish_tric_or_poly(self, e): - self.edge_list.append(e) - if len(self.edge_list) >= 4: - self.component_type = 2 - else: - self.component_type = 1 - -class AdjNode: +class LinkedListNode: + """ + Node in a linked list. + Has pointers to its previous node and next node. + If this node is the `head` of the linked list, reference to the linked list + object is stored in `listobj`. + """ prev = None next = None - edge = None - def set_edge(self, e): - self.edge = e - def get_edge(self): - return self.edge + data = None #edge or int + listobj = None + def set_data(self, e): + self.data = e + def get_data(self): + return self.data + def set_obj(self, l): + self.listobj = l + def clear_obj(self): + self.listobj = None + def replace(self, node): + if self.prev == None and self.next == None: + self.listobj.set_head(node) + elif self.prev == None: + self.listobj.head = node + node.next = self.next + node.listobj = self.listobj + elif self.next == None: + self.prev.next = node + node.prev = self.prev + else: + self.prev.next = node + self.next.prev = node + node.prev = self.prev + node.next = self.next -class AdjList: +class LinkedList: + """ + A linked list with a head and a tail pointer + """ head = None - curr = None + tail = None length = 0 def remove(self, node): if node.prev == None and node.next == None: self.head = None + self.tail = None elif node.prev == None: # node is head self.head = node.next node.next.prev = None + node.next.set_obj(self) elif node.next == None: #node is tail node.prev.next = None + self.tail = node.prev else: node.prev.next = node.next node.next.prev = node.prev self.length -= 1 def set_head(self, h): self.head = h - self.curr = h + self.tail = h self.length = 1 + h.set_obj(self) def append(self, node): if self.head == None: self.set_head(node) else: - self.curr.next = node - node.prev = self.curr - self.curr = node + self.tail.next = node + node.prev = self.tail + self.tail = node self.length += 1 def get_head(self): return self.head @@ -1932,63 +1950,101 @@ class AdjList: def replace(self, node1, node2): if node1.prev == None and node1.next == None: self.head = node2 + self.tail = node2 elif node1.prev == None: # head has to be replaced node1.next.prev = node2 node2.next = node1.next - elif node1.next == None: + self.head = node2 + elif node1.next == None: # tail has to be replaced node1.prev.next = node2 node2.prev = node1.prev + self.tail = node2 else: node1.prev.next = node2 node1.next.prev = node2 node2.prev = node1.prev node2.next = node1.next - -class HighPtNode: - prev = None - next = None - front = None #integer - def set_frond(self, f): - self.frond = f - def get_frond(self): - return self.frond - -class HighPtList: - head = None - curr = None - length = 0 - def remove(self, node): - if node.prev == None and node.next == None: - self.head = None - elif node.prev == None: # node is head - self.head = node.next - node.next.prev = None - elif node.next == None: #node is tail - node.prev.next = None - else: - node.prev.next = node.next - node.next.prev = node.prev - self.length -= 1 - def set_head(self, h): - self.head = h - self.curr = h - self.length = 1 - def append(self, node): - if self.head == None: - self.set_head(node) - else: - self.curr.next = node - node.prev = self.curr - self.curr = node - self.length += 1 def push_front(self, node): if self.head == None: self.head = node + self.tail = node + node.set_obj(self) else: + self.head.clear_obj() self.head.prev = node node.next = self.head self.head = node + node.set_obj(self) + self.length += 1 + def to_string(self): + temp = self.head + s = "" + while temp: + s += " " + str(temp.get_data()) + temp = temp.next + return s + def concatenate(self, lst2): + """ + Concatenates lst2 to self. + Makes lst2 empty. + """ + self.tail.next = lst2.head + lst2.head.prev = self.tail + self.tail = lst2.tail + self.length += lst2.length + lst2.head = None + lst2.length = 0 +class Component: + """ + A connected component. + `edge_list` contains the list of edges belonging to the component. + `component_type` stores the type of the component. + - 0 if bond. + - 1 if polygon. + - 2 is triconnected component. + """ + edge_list = LinkedList() + component_type = 0 #bond = 0, polygon = 1, triconnected = 2 + def __init__(self, edge_list, type_c): + """ + `edge_list` is a list of edges to be added to the component. + `type_c` is the type of the component. + """ + self.edge_list = LinkedList() + for e in edge_list: + e_node = LinkedListNode() + e_node.set_data(e) + self.edge_list.append(e_node) + self.component_type = type_c + def add_edge(self, e): + e_node = LinkedListNode() + e_node.set_data(e) + self.edge_list.append(e_node) + def finish_tric_or_poly(self, e): + """ + Edge `e` is the last edge to be added to the component. + Classify the component as a polygon or triconnected component + depending on the number of edges belonging to it. + """ + e_node = LinkedListNode() + e_node.set_data(e) + self.edge_list.append(e_node) + if self.edge_list.get_length() >= 4: + self.component_type = 2 + else: + self.component_type = 1 + def __str__(self): + """ + Function for printing the component. + """ + if self.component_type == 0: + type_str = "Bond: " + elif self.component_type == 1: + type_str = "Polygon: " + else: + type_str = "Triconnected: " + return type_str + self.edge_list.to_string() class Triconnectivity: """ @@ -2008,48 +2064,11 @@ class Triconnectivity: sage: tric.components_list [] """ - graph_copy = None #type SparseGraph - vertex_to_int = {} # mapping of vertices to integers in c_graph - int_to_vertex = {} # mapping of integers to original vertices - start_vertex = 0 - num_components = 0 - edge_status = {} # status of each edge, unseen=0, tree=1, frond=2, removed=3 - e_stack = [] - t_stack_h = [] - t_stack_a = [] - t_stack_b = [] - t_stack_top = 0 - dfs_number = [] # DFS number of vertex i - vertex_at = [] # vertex with DFS number of i$ - lowpt1 = [] # lowpt1 number of vertex i - lowpt2 = [] # lowpt2 number of vertex i - nd = [] # number of descendants of vertex i - edge_phi = {} # (key, value) = (edge, corresponding phi value) - adj = [] # i^th value contains a AdjList of incident edges of vertex i - in_adj = {} # this variable is used in the PathSearch() function - newnum = [] # new DFS number of vertex i - starts_path = {} # dict of (edge, T/F) to denote if a path starts at edge - highpt = [] # List of fronds entering vertex i in the order they are visited - old_to_new = [] # New DFS number of the vertex with i as old DFS number - degree = [] # Degree of vertex i - parent = [] # Parent vertex of vertex i in the palm tree - tree_arc = [] # Tree arc entering the vertex i - first_child = [] - in_high = {} # One of the elements in highpt[i] - dfs_counter = 0 - n = 0 # number of vertices - m = 0 # number of edges - is_biconnected = True # Boolean to store if the graph is biconnected or not - cut_vertex = None # If graph is not biconnected - graph_copy_adjacency = [] - new_path = False # Boolean used to store if new path is started - - # Edges of the graph which are in the reverse direction in palm tree - reverse_edges = set() - - def __init__(self, G, check=True): from sage.graphs.graph import Graph + # graph_copy is a SparseGraph of the input graph `G` + # We relabel the edges with increasing numbers to be able to + # distinguish between multi-edges self.graph_copy = Graph(multiedges=True) # Add all the vertices first @@ -2064,29 +2083,93 @@ class Triconnectivity: newEdge = tuple([edges[i][0], edges[i][1], i]) self.graph_copy.add_edge(newEdge) self.edge_label_dict[newEdge] = edges[i] + + # type SparseGraph self.graph_copy = self.graph_copy.copy(implementation='c_graph') + # mapping of vertices to integers in c_graph self.vertex_to_int = self.graph_copy.relabel(inplace=True, return_map=True) + + # mapping of integers to original vertices self.int_to_vertex = dict([(v,k) for k,v in self.vertex_to_int.items()]) - self.n = self.graph_copy.order() - self.m = self.graph_copy.size() + self.n = self.graph_copy.order() # number of vertices + self.m = self.graph_copy.size() # number of edges + + print "vertices" + print self.graph_copy.vertices() + print self.vertex_to_int + print self.int_to_vertex + print "edges", self.graph_copy.edges() + + # status of each edge: unseen=0, tree=1, frond=2, removed=3 self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) + + # Edges of the graph which are in the reverse direction in palm tree self.reverse_edges = set() - self.dfs_number = [0 for i in range(self.n)] - self.highpt = [HighPtList() for i in range(self.n)] - self.old_to_new = [0 for i in range(self.n + 1)] - self.node_at = [0 for i in range(self.n + 1)] - self.lowpt1 = [None for i in range(self.n)] - self.lowpt2 = [None for i in range(self.n)] - self.adj = [AdjList() for i in range(self.n)] - self.nd = [None for i in range(self.n)] + self.dfs_number = [0 for i in range(self.n)] # DFS number of vertex i + + # Linked list of fronds entering vertex i in the order they are visited + self.highpt = [LinkedList() for i in range(self.n)] + + # A dictionary whose key is an edge e, value is a pointer to element in + # self.highpt containing the edge e. Used in the `path_search` function. + self.in_high = dict((e, None) for e in self.graph_copy.edges()) + + # New DFS number of the vertex with i as its old DFS number + self.old_to_new = [0 for i in range(self.n+1)] + self.newnum = [0 for i in range(self.n)] # new DFS number of vertex i + self.node_at = [0 for i in range(self.n+1)] # node at dfs number of i + self.lowpt1 = [None for i in range(self.n)] # lowpt1 number of vertex i + self.lowpt2 = [None for i in range(self.n)] # lowpt2 number of vertex i + + # i^th value contains a LinkedList of incident edges of vertex i + self.adj = [LinkedList() for i in range(self.n)] + + # A dictionary whose key is an edge, value is a pointer to element in + # self.adj containing the edge. Used in the `path_search` function. + self.in_adj = {} + self.nd = [None for i in range(self.n)] # number of descendants of vertex i + + # Parent vertex of vertex i in the palm tree self.parent = [None for i in range(self.n)] - self.degree = [None for i in range(self.n)] - self.tree_arc = [None for i in range(self.n)] - self.vertex_at = [1 for i in range(self.n)] + self.degree = [None for i in range(self.n)] # Degree of vertex i + self.tree_arc = [None for i in range(self.n)] # Tree arc entering the vertex i + self.vertex_at = [1 for i in range(self.n)] # vertex with DFS number of i self.dfs_counter = 0 self.components_list = [] #list of components - self.graph_copy_adjacency = [[] for i in range(self.n)] + self.graph_copy_adjacency = [[] for i in range(self.n)] # Stores adjacency list + + # Dictionary of (e, True/False) to denote if a path starts at edge e + self.starts_path = dict((e, False) for e in self.graph_copy.edges()) + + self.is_biconnected = True # Boolean to store if the graph is biconnected or not + self.cut_vertex = None # If graph is not biconnected + + # Label used for virtual edges, incremented at every new virtual edge + self.virtual_edge_num = 0 + + self.new_path = False # Boolean used to store if new path is started + + # Stacks used in `path_search` function + self.e_stack = [] + self.t_stack_h = [None for i in range(2*self.m + 1)] + self.t_stack_a = [None for i in range(2*self.m + 1)] + self.t_stack_b = [None for i in range(2*self.m + 1)] + self.t_stack_top = 0 + self.t_stack_a[self.t_stack_top] = -1 + + + # Trivial cases + if self.n < 2: + raise ValueError("Graph is not biconnected") + if self.n <= 2: + if self.m < 3: + raise ValueError("Graph is not biconnected") + comp = Component([], 0) + for e in self.graph_copy.edges(): + comp.add_edge(e) + self.components_list.append(comp) + return # Triconnectivity algorithm self.split_multi_egdes() @@ -2101,19 +2184,18 @@ class Triconnectivity: self.cut_vertex = self.dfs1(self.start_vertex, check=check) if check: - # graph is disconnected - if self.dfs_number < self.n: + # if graph is disconnected + if self.dfs_counter < self.n: self.is_biconnected = False - return + raise ValueError("Graph is disconnected") - # graph has a cut vertex + # If graph has a cut vertex if self.cut_vertex != None: self.cut_vertex = self.int_to_vertex[self.cut_vertex] self.is_biconnected = False - return + raise ValueError("Graph has a cut vertex") - # Reversing the edges to reflect the palm tree arcs and fronds - # Is there a better way to do it? + # Identify reversed edges to reflect the palm tree arcs and fronds for e in self.graph_copy.edges(): up = (self.dfs_number[e[1]] - self.dfs_number[e[0]]) > 0 if (up and self.edge_status[e]==2) or (not up and self.edge_status[e]==1): @@ -2123,55 +2205,67 @@ class Triconnectivity: self.build_acceptable_adj_struct() self.dfs2() - self.t_stack_h = [None for i in range(2*self.m + 1)] - self.t_stack_a = [None for i in range(2*self.m + 1)] - self.t_stack_b = [None for i in range(2*self.m + 1)] - self.t_stack_top = 0 - self.t_stack_a[self.t_stack_top] = -1 - self.path_search(self.start_vertex) # last split component c = Component([],0) while self.e_stack: c.add_edge(self.estack_pop()) - c.component_type = 2 if len(c.edge_list) > 4 else 1 + c.component_type = 2 if c.edge_list.get_length() > 4 else 1 self.components_list.append(c) c = None - # Push a triple on Tstack + self.assemble_triconnected_components() + self.print_triconnected_components() + def tstack_push(self, h, a, b): + """ + Push `(h,a,b)` triple on Tstack + """ self.t_stack_top += 1 self.t_stack_h[self.t_stack_top] = h self.t_stack_a[self.t_stack_top] = a self.t_stack_b[self.t_stack_top] = b - # Push end-of-stack marker on Tstack def tstack_push_eos(self): + """ + Push end-of-stack marker on Tstack + """ self.t_stack_top += 1 self.t_stack_a[self.t_stack_top] = -1 - # Returns true iff end-of-stack marker is not on top of Tstack def tstack_not_eos(self): + """ + Return true iff end-of-stack marker is not on top of Tstack + """ return self.t_stack_a[self.t_stack_top] != -1 def estack_pop(self): + """ + Pop from estack and return the popped element + """ e = self.e_stack[-1] self.e_stack = self.e_stack[0:-1] return e def new_component(self, edges=[], type_c=0): + """ + Create a new component, add `edges` to it. + type_c = 0 for bond, 1 for polygon, 2 for triconnected component + """ c = Component(edges, type_c) self.components_list.append(c) - self.num_components += 1 return c def high(self, v): + """ + Return the high(v) value, which is the first value in highpt list of `v` + """ head = self.highpt[v].head if head == None: return 0 else: - return head.frond + return head.data def del_high(self, e): if e in self.in_high: @@ -2213,8 +2307,9 @@ class Triconnectivity: self.edge_status[sorted_edges[i]] = 3 # edge removed # Add virtual edge to graph_copy - newVEdge = tuple([sorted_edges[i][0], sorted_edges[i][1], "newVEdge"]) + newVEdge = tuple([sorted_edges[i][0], sorted_edges[i][1], "newVEdge"+str(self.virtual_edge_num)]) self.graph_copy.add_edge(newVEdge) + self.virtual_edge_num += 1 # mark unseen for newVEdge self.edge_status[newVEdge] = 0 @@ -2227,8 +2322,9 @@ class Triconnectivity: self.edge_status[sorted_edges[i+1]] = 3 # edge removed # Add virtual edge to graph_copy - newVEdge = tuple([sorted_edges[i+1][0], sorted_edges[i+1][1], "newVEdge"]) + newVEdge = tuple([sorted_edges[i+1][0], sorted_edges[i+1][1], "newVEdge"+str(self.virtual_edge_num)]) self.graph_copy.add_edge(newVEdge) + self.virtual_edge_num += 1 self.edge_status[newVEdge] = 0 comp.append(newVEdge) @@ -2319,7 +2415,7 @@ class Triconnectivity: if self.edge_status[e]: continue - w = e[0] if e[0] != v else e[1] # Other vertex of edge e + w = e[0] if e[0] != v else e[1] # Opposite vertex of edge e if self.dfs_number[w] == 0: self.edge_status[e] = 1 # tree edge if first_son is None: @@ -2376,7 +2472,7 @@ class Triconnectivity: if edge_type==1: # tree arc if self.lowpt2[e[0]] < self.dfs_number[e[1]]: phi = 3*self.lowpt1[e[0]] - elif self.lowpt2[e[0]] >= self.dfs_number[e[1]]: + else: phi = 3*self.lowpt1[e[0]] + 2 else: # tree frond phi = 3*self.dfs_number[e[0]]+1 @@ -2384,7 +2480,7 @@ class Triconnectivity: if edge_type==1: # tree arc if self.lowpt2[e[1]] < self.dfs_number[e[0]]: phi = 3*self.lowpt1[e[1]] - elif self.lowpt2[e[1]] >= self.dfs_number[e[0]]: + else: phi = 3*self.lowpt1[e[1]] + 2 else: # tree frond phi = 3*self.dfs_number[e[1]]+1 @@ -2393,8 +2489,8 @@ class Triconnectivity: for i in range(1,max+1): for e in bucket[i]: - node = AdjNode() - node.set_edge(e) + node = LinkedListNode() + node.set_data(e) if e in self.reverse_edges: self.adj[e[1]].append(node) self.in_adj[e] = node @@ -2407,10 +2503,9 @@ class Triconnectivity: This function is a helper function for `dfs2` function. """ self.newnum[v] = self.dfs_counter - self.nd[v] + 1 - #for e in self.adj[v]: e_node = self.adj[v].get_head() while e_node: - e = e_node.get_edge() + e = e_node.get_data() e_node = e_node.next w = e[1] if e[0] == v else e[0] # opposite vertex of e if self.new_path: @@ -2420,8 +2515,8 @@ class Triconnectivity: self.path_finder(w) self.dfs_counter -= 1 else: - highpt_node = HighPtNode() - highpt_node.set_frond(self.newnum[v]) + highpt_node = LinkedListNode() + highpt_node.set_data(self.newnum[v]) self.highpt[w].append(highpt_node) self.in_high[e] = highpt_node self.new_path = True @@ -2461,7 +2556,7 @@ class Triconnectivity: outv = self.adj[v].get_length() e_node = self.adj[v].get_head() while e_node: - e = e_node.get_edge() + e = e_node.get_data() it = e_node if e in self.reverse_edges: @@ -2488,7 +2583,7 @@ class Triconnectivity: self.e_stack.append(self.tree_arc[w]) temp_node = self.adj[w].get_head() - temp = temp_node.get_edge() + temp = temp_node.get_data() if temp in self.reverse_edges: temp_target = temp[0] else: @@ -2496,18 +2591,17 @@ class Triconnectivity: # while vnum is not the start_vertex while vnum != 1 and ((self.t_stack_a[self.t_stack_top] == vnum) or \ (self.degree[w] == 2 and self.newnum[temp_target] > wnum)): + a = self.t_stack_a[self.t_stack_top] b = self.t_stack_b[self.t_stack_top] e_virt = None - if a == vnum and self.parent[self.node_at[b]] == self.node_at[a]: self.t_stack_top -= 1 else: e_ab = None if self.degree[w] == 2 and self.newnum[temp_target] > wnum: - # found type-2 separation pair - # print "Found type-2 separation pair (",v,", ", temp_target, ")" + # found type-2 separation pair - (v, temp_target) e1 = self.estack_pop() e2 = self.estack_pop() self.adj[w].remove(self.in_adj[e2]) @@ -2517,8 +2611,9 @@ class Triconnectivity: else: x = e2[1] - self.graph_copy.add_edge(v, x) - e_virt = (v, x, None) + e_virt = tuple([v, x, "newVEdge"+str(self.virtual_edge_num)]) + self.graph_copy.add_edge(e_virt) + self.virtual_edge_num += 1 self.degree[v] -= 1 self.degree[x] -= 1 @@ -2527,7 +2622,7 @@ class Triconnectivity: else: e2_source = e2[0] if e2_source != w: # OGDF_ASSERT - raise ValueError("graph is not biconnected") + raise ValueError("Graph is not biconnected") comp = Component([e1, e2, e_virt], 1) self.components_list.append(comp) @@ -2546,8 +2641,7 @@ class Triconnectivity: self.adj[x].remove(self.in_adj[e_ab]) self.del_high(e_ab) - else: # found type-2 separation pair - # print "Found type-2 separation pair (",a,", ", b, ")" + else: # found type-2 separation pair - (self.node_at[a], self.node_at[b]) h = self.t_stack_h[self.t_stack_top] self.t_stack_top -= 1 @@ -2587,8 +2681,9 @@ class Triconnectivity: self.degree[x] -= 1 self.degree[xy_target] -= 1 - self.graph_copy.add_edge(self.node_at[a], self.node_at[b]) - e_virt = (self.node_at[a], self.node_at[b], None) + e_virt = tuple([self.node_at[a], self.node_at[b], "newVEdge"+str(self.virtual_edge_num)]) + self.graph_copy.add_edge(e_virt) + self.virtual_edge_num += 1 comp.finish_tric_or_poly(e_virt) self.components_list.append(comp) comp = None @@ -2596,8 +2691,9 @@ class Triconnectivity: if e_ab is not None: comp = Component([e_ab, e_virt], type_c=0) - self.graph_copy.add_edge(v,x) - e_virt = (v,x,None) + e_virt = tuple([v, x, "newVEdge"+str(self.virtual_edge_num)]) + self.graph_copy.add_edge(e_virt) + self.virtual_edge_num += 1 comp.add_edge(e_virt) self.degree[x] -= 1 self.degree[v] -= 1 @@ -2605,9 +2701,10 @@ class Triconnectivity: comp = None self.e_stack.append(e_virt) - e_virt_node = AdjNode() - e_virt_node.set_edge(e_virt) - self.adj[v].replace(e_node, e_virt_node) + e_virt_node = LinkedListNode() + e_virt_node.set_data(e_virt) + # Replace `it` node with `e_virt_node` + it.replace(e_virt_node) it = e_virt_node self.in_adj[e_virt] = it @@ -2622,8 +2719,7 @@ class Triconnectivity: # start type-1 check if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ (self.parent[v] != self.start_vertex or outv >= 2): - # type-1 separation pair - #print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" + # type-1 separation pair - (self.node_at[self.lowpt1[w]], v) comp = Component([],0) if not self.e_stack: # OGDF_ASSERT raise ValueError("stack is empty") @@ -2645,8 +2741,9 @@ class Triconnectivity: self.degree[self.node_at[xx]] -= 1 self.degree[self.node_at[y]] -= 1 - self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) - e_virt = (v, self.node_at[self.lowpt1[w]], None) + e_virt = tuple([v, self.node_at[self.lowpt1[w]], "newVEdge"+str(self.virtual_edge_num)]) + self.graph_copy.add_edge(e_virt) + self.virtual_edge_num += 1 comp.finish_tric_or_poly(e_virt) self.components_list.append(comp) comp = None @@ -2663,8 +2760,9 @@ class Triconnectivity: comp_bond.add_edge(eh) comp_bond.add_edge(e_virt) - self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) - e_virt = (v, self.node_at[self.lowpt1[w]], None) + e_virt = tuple([v, self.node_at[self.lowpt1[w]], "newVEdge"+str(self.virtual_edge_num)]) + self.graph_copy.add_edge(e_virt) + self.virtual_edge_num += 1 comp_bond.add_edge(e_virt) self.in_high[e_virt] = self.in_high[eh] self.degree[v] -= 1 @@ -2672,19 +2770,19 @@ class Triconnectivity: self.components_list.append(comp_bond) comp_bond = None - if self.node_at[self.lowpt1[w]] != self.parent[v]: self.e_stack.append(e_virt) - e_virt_node = AdjNode() - e_virt_node.set_edge(e_virt) - self.adj[v].replace(e_node, e_virt_node) + e_virt_node = LinkedListNode() + e_virt_node.set_data(e_virt) + # replace `it` node with `e_virt_node` + it.replace(e_virt_node) it = e_virt_node self.in_adj[e_virt] = it if not e_virt in self.in_high and self.high(self.node_at[self.lowpt1[w]]) < vnum: - vnum_node = HighPtNode() - vnum_node.set_frond(vnum) + vnum_node = LinkedListNode() + vnum_node.set_data(vnum) self.highpt[self.node_at[self.lowpt1[w]]].push_front(vnum_node) self.in_high[e_virt] = vnum_node @@ -2694,8 +2792,9 @@ class Triconnectivity: else: self.adj[v].remove(it) comp_bond = Component([e_virt], type_c=0) - self.graph_copy.add_edge(self.node_at[self.lowpt1[w]], v) - e_virt = (self.node_at[self.lowpt1[w]], v, None) + e_virt = tuple([self.node_at[self.lowpt1[w]], v, "newVEdge"+str(self.virtual_edge_num)]) + self.graph_copy.add_edge(e_virt) + self.virtual_edge_num += 1 comp_bond.add_edge(e_virt) eh = self.tree_arc[v]; @@ -2707,11 +2806,10 @@ class Triconnectivity: self.tree_arc[v] = e_virt self.edge_status[e_virt] = 1 self.in_adj[e_virt] = self.in_adj[eh] - e_virt_node = AdjNode() - e_virt_node.set_edge(e_virt) + e_virt_node = LinkedListNode() + e_virt_node.set_data(e_virt) self.in_adj[eh] = e_virt_node - - # end type-1 search + # end type-1 search if self.starts_path[e]: while self.tstack_not_eos(): self.t_stack_top -= 1 @@ -2740,3 +2838,85 @@ class Triconnectivity: # Go to next node in adjacency list e_node = e_node.next + def assemble_triconnected_components(self): + """ + Iterates through all the components built by `path_finder` and merges + the components wherever possible for contructing the final + triconnected components. + """ + comp1 = {} # The index of first component that an edge belongs to + comp2 = {} # The index of second component that an edge belongs to + item1 = {} # Pointer to the edge node in component1 + item2 = {} # Pointer to the edge node in component2 + num_components = len(self.components_list) + visited = [False for i in range(num_components)] + + # For each edge, we populate the comp1, comp2, item1 and item2 values + for i in range(num_components): # for each component + e_node = self.components_list[i].edge_list.get_head() + while e_node: # for each edge + e = e_node.get_data() + if e not in item1: + comp1[e] = i + item1[e] = e_node + else: + comp2[e] = i + item2[e] = e_node + + e_node = e_node.next + + for i in range(num_components): + c1 = self.components_list[i] + c1_type = c1.component_type + l1 = c1.edge_list + visited[i] = True + + if l1.get_length() == 0: + continue + + if c1_type == 0 or c1_type == 1: + e_node = self.components_list[i].edge_list.get_head() + while e_node: + e = e_node.get_data() + e_node_next = e_node.next + # the label of virtual edges is a string + if not isinstance(e[2], str): + e_node = e_node_next + continue + + j = comp1[e] + if visited[j]: + j = comp2[e] + if visited[j]: + e_node = e_node_next + continue + e_node2 = item2[e] + else: + e_node2 = item1[e] + + c2 = self.components_list[j] + if (c1_type != c2.component_type): + e_node = e_node_next + continue + + visited[j] = True + l2 = c2.edge_list + + l2.remove(e_node2) + l1.concatenate(l2) + + if not e_node_next: + e_node_next = e_node.next + + l1.remove(e_node) + + e_node = e_node_next + + def print_triconnected_components(self): + """ + Print all the triconnected components along with the type of + each component. + """ + for i in range(len(self.components_list)): + if self.components_list[i].edge_list.get_length() > 0: + print self.components_list[i] From b1d93554a20791c18752608886f089e832287281 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Wed, 25 Jul 2018 01:05:34 +0530 Subject: [PATCH 033/264] Formatted output to change vertices/edges to original labels. --- src/sage/graphs/connectivity.pyx | 59 ++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 3d5145ab390..e1e9c14a639 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2045,6 +2045,16 @@ class Component: else: type_str = "Triconnected: " return type_str + self.edge_list.to_string() + def get_edge_list(self): + """ + Return a list of edges belonging to the component. + """ + e_list = [] + e_node = self.edge_list.get_head() + while e_node: + e_list.append(e_node.get_data()) + e_node = e_node.next + return e_list class Triconnectivity: """ @@ -2095,12 +2105,6 @@ class Triconnectivity: self.n = self.graph_copy.order() # number of vertices self.m = self.graph_copy.size() # number of edges - print "vertices" - print self.graph_copy.vertices() - print self.vertex_to_int - print self.int_to_vertex - print "edges", self.graph_copy.edges() - # status of each edge: unseen=0, tree=1, frond=2, removed=3 self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) @@ -2136,7 +2140,7 @@ class Triconnectivity: self.tree_arc = [None for i in range(self.n)] # Tree arc entering the vertex i self.vertex_at = [1 for i in range(self.n)] # vertex with DFS number of i self.dfs_counter = 0 - self.components_list = [] #list of components + self.components_list = [] # list of components of `graph_copy` self.graph_copy_adjacency = [[] for i in range(self.n)] # Stores adjacency list # Dictionary of (e, True/False) to denote if a path starts at edge e @@ -2158,6 +2162,10 @@ class Triconnectivity: self.t_stack_top = 0 self.t_stack_a[self.t_stack_top] = -1 + # The final triconnected components are stored + self.comp_list_new = [] # i^th entry is list of edges in i^th component + self.comp_type = [] # i^th entry is type of i^th component + # Trivial cases if self.n < 2: @@ -2184,7 +2192,7 @@ class Triconnectivity: self.cut_vertex = self.dfs1(self.start_vertex, check=check) if check: - # if graph is disconnected + # If graph is disconnected if self.dfs_counter < self.n: self.is_biconnected = False raise ValueError("Graph is disconnected") @@ -2843,6 +2851,9 @@ class Triconnectivity: Iterates through all the components built by `path_finder` and merges the components wherever possible for contructing the final triconnected components. + Formats the triconnected components into original vertices and edges. + The triconnected components are stored in `self.comp_list_new` and + `self.comp_type`. """ comp1 = {} # The index of first component that an edge belongs to comp2 = {} # The index of second component that an edge belongs to @@ -2912,11 +2923,37 @@ class Triconnectivity: e_node = e_node_next + # Convert connected components into original graph vertices and edges + self.comp_list_new = [] + self.comp_type = [] + for i in range(len(self.components_list)): + if self.components_list[i].edge_list.get_length() > 0: + e_list = self.components_list[i].get_edge_list() + e_list_new = [] + # For each edge, get the original source, target and label + for e in e_list: + source = self.int_to_vertex[e[0]] + target = self.int_to_vertex[e[1]] + if isinstance(e[2], str): + label = e[2] + else: + label = self.edge_label_dict[(source, target,e[2])][2] + e_list_new.append(tuple([source, target, label])) + # Add the component data to `comp_list_new` and `comp_type` + self.comp_type.append(self.components_list[i].component_type) + self.comp_list_new.append(e_list_new) + def print_triconnected_components(self): """ Print all the triconnected components along with the type of each component. """ - for i in range(len(self.components_list)): - if self.components_list[i].edge_list.get_length() > 0: - print self.components_list[i] + for i in range(len(self.comp_list_new)): + if self.comp_type[i] == 0: + print "Bond: ", + elif self.comp_type[i] == 1: + print "Polygon: ", + else: + print "Triconnected: ", + print self.comp_list_new[i] + From 78de7b5d2eb7d192509d4a1bcff6f8ed31096273 Mon Sep 17 00:00:00 2001 From: Aaron Lauve Date: Thu, 26 Jul 2018 18:18:23 -0400 Subject: [PATCH 034/264] overwrote default implementation of orthogonal idempotents with the faster cpis method. --- src/sage/combinat/symmetric_group_algebra.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index 978cb247454..7e5f2e4986e 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -981,6 +981,8 @@ def cpis(self): """ return [self.cpi(p) for p in partition.Partitions_n(self.n)] + central_orthogonal_idempotents = cpis + def cpi(self, p): """ Return the centrally primitive idempotent for the symmetric group @@ -2658,7 +2660,7 @@ def jucys_murphy(self, k): distinct=True ) v += q ** (k-1) * self.one() return v - + #old algorithm: # left = 1 # right = 1 From d64660771934181709ebc3b43cbd790a2137954d Mon Sep 17 00:00:00 2001 From: Aaron Lauve Date: Thu, 26 Jul 2018 23:49:50 -0400 Subject: [PATCH 035/264] added caching; fixed an error in coefficient computation within cpi method --- ...ensional_semisimple_algebras_with_basis.py | 20 +++++++++++-------- src/sage/combinat/symmetric_group_algebra.py | 17 +++++++++++++--- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py b/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py index 402d7d8a9f8..4dbecbd06ea 100644 --- a/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py @@ -80,17 +80,21 @@ def central_orthogonal_idempotents(self): EXAMPLES: - For the algebra of the symmetric group `S_3`, we - recover the sum and alternating sum of all - permutations, together with a third idempotent:: + For the algebra of the (abelian) alternating group `A_3`, + we recover three idempotents corresponding to the three + one-dimensional representations `V_i` on which `(1,2,3)` + acts on `V_i` as multiplication by the `i`th power of a + cube root of unity:: - sage: A3 = SymmetricGroup(3).algebra(QQ) + sage: A3 = AlternatingGroup(3).algebra(QQ) sage: idempotents = A3.central_orthogonal_idempotents() sage: idempotents - (1/6*() + 1/6*(2,3) + 1/6*(1,2) + 1/6*(1,2,3) + 1/6*(1,3,2) + 1/6*(1,3), - 2/3*() - 1/3*(1,2,3) - 1/3*(1,3,2), - 1/6*() - 1/6*(2,3) - 1/6*(1,2) + 1/6*(1,2,3) + 1/6*(1,3,2) - 1/6*(1,3)) - sage: A3.is_identity_decomposition_into_orthogonal_idempotents(idempotents) + [1/3*() + 1/3*(1,2,3) + 1/3*(1,3,2), + 1/3*() - (0.1666666666666667?+0.2886751345948129?*I)*(1,2,3) + - (0.1666666666666667?-0.2886751345948129?*I)*(1,3,2), + 1/3*() - (0.1666666666666667?-0.2886751345948129?*I)*(1,2,3) + - (0.1666666666666667?+0.2886751345948129?*I)*(1,3,2)] + sage: A4.is_identity_decomposition_into_orthogonal_idempotents(idempotents) True For the semisimple quotient of a quiver algebra, diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index 7e5f2e4986e..03d8c517687 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -959,6 +959,10 @@ def cpis(self): Return a list of the centrally primitive idempotents of ``self``. + This method supercedes the default implementation of + central orthogonal idempotents found within + :class:`sage.categories.finite_dimensional_semisimple_algebras_with_basis.FiniteDimensionalSemisimpleAlgebrasWithBasis`. + EXAMPLES:: sage: QS3 = SymmetricGroupAlgebra(QQ,3) @@ -967,6 +971,8 @@ def cpis(self): 1/6*[1, 2, 3] + 1/6*[1, 3, 2] + 1/6*[2, 1, 3] + 1/6*[2, 3, 1] + 1/6*[3, 1, 2] + 1/6*[3, 2, 1] sage: a[1] # [2, 1] 2/3*[1, 2, 3] - 1/3*[2, 3, 1] - 1/3*[3, 1, 2] + sage: QS3.cpis == QS3.central_orthogonal_idempotents + True TESTS: @@ -979,10 +985,11 @@ def cpis(self): sage: a[1] 2/3*() - 1/3*(1,2,3) - 1/3*(1,3,2) """ - return [self.cpi(p) for p in partition.Partitions_n(self.n)] + return tuple(self.cpi(p) for p in partition.Partitions_n(self.n)) central_orthogonal_idempotents = cpis + @cached_method def cpi(self, p): """ Return the centrally primitive idempotent for the symmetric group @@ -1010,6 +1017,7 @@ def cpi(self, p): ... TypeError: p (= [2, 2]) must be a partition of n (= 3) """ + R = self.base_ring() if p not in partition.Partitions_n(self.n): raise TypeError("p (= {p}) must be a partition of n (= {n})".format(p=p, n=self.n)) @@ -1023,9 +1031,12 @@ def cpi(self, p): character_row = character_table[p_index] P = Permutations(self.n) - dct = { self._indices(g): big_coeff * character_row[np.index(g.cycle_type())] + R = self.base_ring() + try: + dct = { self._indices(g): R(big_coeff * character_row[np.index(g.cycle_type())]) for g in P } - + except ZeroDivisionError, AssertionError: + raise AssertionError("`self` is not a semisimple algebra over %s"%self.base_ring()) return self._from_dict(dct) @cached_method From 7e6c7ef6bf91335b776b8bf0c36ebff9dd326282 Mon Sep 17 00:00:00 2001 From: Aaron Lauve Date: Fri, 27 Jul 2018 00:34:36 -0400 Subject: [PATCH 036/264] work on docstrings --- ...ensional_semisimple_algebras_with_basis.py | 12 +++---- src/sage/combinat/symmetric_group_algebra.py | 35 ++++++++++++++----- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py b/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py index 4dbecbd06ea..a8f0b4dccf1 100644 --- a/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py @@ -86,15 +86,11 @@ def central_orthogonal_idempotents(self): acts on `V_i` as multiplication by the `i`th power of a cube root of unity:: - sage: A3 = AlternatingGroup(3).algebra(QQ) + sage: A3 = AlternatingGroup(3).algebra(QQbar) sage: idempotents = A3.central_orthogonal_idempotents() - sage: idempotents - [1/3*() + 1/3*(1,2,3) + 1/3*(1,3,2), - 1/3*() - (0.1666666666666667?+0.2886751345948129?*I)*(1,2,3) - - (0.1666666666666667?-0.2886751345948129?*I)*(1,3,2), - 1/3*() - (0.1666666666666667?-0.2886751345948129?*I)*(1,2,3) - - (0.1666666666666667?+0.2886751345948129?*I)*(1,3,2)] - sage: A4.is_identity_decomposition_into_orthogonal_idempotents(idempotents) + sage: idempotents[0] + 1/3*() + 1/3*(1,2,3) + 1/3*(1,3,2) + sage: A3.is_identity_decomposition_into_orthogonal_idempotents(idempotents) True For the semisimple quotient of a quiver algebra, diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index 03d8c517687..dccfb25ed47 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -999,11 +999,11 @@ def cpi(self, p): EXAMPLES:: sage: QS3 = SymmetricGroupAlgebra(QQ,3) - sage: QS3.cpi([2,1]) + sage: QS3.cpi(Partition([2,1])) 2/3*[1, 2, 3] - 1/3*[2, 3, 1] - 1/3*[3, 1, 2] - sage: QS3.cpi([3]) + sage: QS3.cpi(Partition([3])) 1/6*[1, 2, 3] + 1/6*[1, 3, 2] + 1/6*[2, 1, 3] + 1/6*[2, 3, 1] + 1/6*[3, 1, 2] + 1/6*[3, 2, 1] - sage: QS3.cpi([1,1,1]) + sage: QS3.cpi(Partition([1,1,1])) 1/6*[1, 2, 3] - 1/6*[1, 3, 2] - 1/6*[2, 1, 3] + 1/6*[2, 3, 1] + 1/6*[3, 1, 2] - 1/6*[3, 2, 1] sage: QS0 = SymmetricGroupAlgebra(QQ, 0) @@ -1012,10 +1012,25 @@ def cpi(self, p): TESTS:: - sage: QS3.cpi([2,2]) + sage: QS3.cpi(Partition([2,2])) Traceback (most recent call last): ... TypeError: p (= [2, 2]) must be a partition of n (= 3) + sage: QS4_Z3 = SymmetricGroupAlgebra(GF(3), 4) + sage: QS4_Z3.cpi(Partition([1,3])) + Traceback (most recent call last): + ... + ValueError: [1, 3] is not an element of Partitions + sage: QS4_Z3.cpi([3,1]) + Traceback (most recent call last): + ... + TypeError: unhashable type: 'list' + + sage: QS4_Z3.cpi(Partition([2,2])) + Traceback (most recent call last): + ... + TypeError: Symmetric group algebra of order 4 + over Finite Field of size 3 is not a semisimple algebra """ R = self.base_ring() if p not in partition.Partitions_n(self.n): @@ -1032,11 +1047,13 @@ def cpi(self, p): character_row = character_table[p_index] P = Permutations(self.n) R = self.base_ring() - try: - dct = { self._indices(g): R(big_coeff * character_row[np.index(g.cycle_type())]) - for g in P } - except ZeroDivisionError, AssertionError: - raise AssertionError("`self` is not a semisimple algebra over %s"%self.base_ring()) + dct = {} + for g in P: + coeff = big_coeff * character_row[np.index(g.cycle_type())] + if not R(coeff.denominator()): + raise TypeError("%s is not a semisimple algebra"%self) + else: + dct[self._indices(g)] = R(coeff) return self._from_dict(dct) @cached_method From 4d232bd9db42da3c2bcb7135a76ef49a334b3c12 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Fri, 27 Jul 2018 16:12:12 +0530 Subject: [PATCH 037/264] Added comments and documentation. --- src/sage/graphs/connectivity.pyx | 426 ++++++++++++++++++++----------- 1 file changed, 271 insertions(+), 155 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index e1e9c14a639..12bd0440a94 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1871,10 +1871,9 @@ def bridges(G, labels=True): return my_bridges -from sage.graphs.base.sparse_graph cimport SparseGraph - class LinkedListNode: """ + Helper class for ``Triconnectivity``. Node in a linked list. Has pointers to its previous node and next node. If this node is the `head` of the linked list, reference to the linked list @@ -1893,6 +1892,9 @@ class LinkedListNode: def clear_obj(self): self.listobj = None def replace(self, node): + """ + Replace self node with ``node`` in the corresponding linked list. + """ if self.prev == None and self.next == None: self.listobj.set_head(node) elif self.prev == None: @@ -1910,12 +1912,16 @@ class LinkedListNode: class LinkedList: """ + A helper class for ``Triconnectivity``. A linked list with a head and a tail pointer """ head = None tail = None length = 0 def remove(self, node): + """ + Remove the node ``node`` from the linked list. + """ if node.prev == None and node.next == None: self.head = None self.tail = None @@ -1931,11 +1937,17 @@ class LinkedList: node.next.prev = node.prev self.length -= 1 def set_head(self, h): + """ + Set the node ``h`` as the head of the linked list. + """ self.head = h self.tail = h self.length = 1 h.set_obj(self) def append(self, node): + """ + Append the node ``node`` to the linked list. + """ if self.head == None: self.set_head(node) else: @@ -1948,6 +1960,9 @@ class LinkedList: def get_length(self): return self.length def replace(self, node1, node2): + """ + Replace the node ``node1`` with ``node2`` in the linked list. + """ if node1.prev == None and node1.next == None: self.head = node2 self.tail = node2 @@ -1965,6 +1980,9 @@ class LinkedList: node2.prev = node1.prev node2.next = node1.next def push_front(self, node): + """ + Add node ``node`` to the beginning of the linked list. + """ if self.head == None: self.head = node self.tail = node @@ -1997,6 +2015,7 @@ class LinkedList: class Component: """ + A helper class for ``Triconnectivity``. A connected component. `edge_list` contains the list of edges belonging to the component. `component_type` stores the type of the component. @@ -2056,23 +2075,135 @@ class Component: e_node = e_node.next return e_list +from sage.graphs.base.sparse_graph cimport SparseGraph + class Triconnectivity: """ - This module is not yet complete, it has work to be done. - This module implements the algorithm for finding the triconnected components of a biconnected graph. - Refer to [Gut2001]_ and [Hopcroft1973]_ for the algorithm. + A biconnected graph is a graph where deletion of any one vertex does + not disconnect the graph. + + INPUT: + + - ``G`` -- The input graph. + + - ``check`` (default: ``True``) -- Boolean to indicate whether ``G`` + needs to be tested for biconnectivity. + + OUTPUT: + + No output, the triconnected components are printed. + The triconnected components are stored in `comp_list_new and `comp_type`. + `comp_list_new` is a list of components, with `comp_list_new[i]` contains + the list of edges in the $i^{th}$ component. `comp_type[i]` stores the type + of the $i^{th}$ component - 1 for bond, 2 for polygon, 3 for triconnected + component. The output can be accessed through these variables. + + ALGORITHM: + + We implement the algorithm proposed by Tarjan in [Tarjan72]_. The + original version is recursive. We emulate the recursion using a stack. + + ALGORITHM:: + We implement the algorithm proposed by Hopcroft and Tarjan in + [Hopcroft1973]_ and later corrected by Gutwenger and Mutzel in + [Gut2001]_. + + .. SEEALSO:: + + - :meth:`~Graph.is_biconnected` EXAMPLES:: - An example to show how the triconnected components can be accessed: + An example from [Hopcroft1973]_: sage: from sage.graphs.connectivity import Triconnectivity - sage: g = Graph([['a','b',1],['a','c',1],['b','c',10]], weighted=True) - sage: tric = Triconnectivity(g) - sage: tric.components_list - [] + sage: G = Graph() + sage: G.add_edges([(1,2),(1,4),(1,8),(1,12),(1,13),(2,3),(2,13),(3,4)]) + sage: G.add_edges([(3,13),(4,5),(4,7),(5,6),(5,7),(5,8),(6,7),(8,9),(8,11)]) + sage: G.add_edges([(8,12),(9,10),(9,11),(9,12),(10,11),(10,12)]) + sage: tric = Triconnectivity(G) + Triconnected: [(8, 9, None), (9, 10, None), (10, 11, None), (9, 11, None), (8, 11, None), (10, 12, None), (9, 12, None), (8, 12, 'newVEdge0')] + Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] + Polygon: [(8, 12, 'newVEdge1'), (1, 12, None), (8, 1, 'newVEdge2')] + Bond: [(1, 8, None), (8, 1, 'newVEdge2'), (8, 1, 'newVEdge3')] + Polygon: [(5, 8, None), (8, 1, 'newVEdge3'), (4, 5, 'newVEdge8'), (4, 1, 'newVEdge9')] + Polygon: [(5, 6, None), (6, 7, None), (5, 7, 'newVEdge5')] + Bond: [(5, 7, None), (5, 7, 'newVEdge5'), (5, 7, 'newVEdge6')] + Polygon: [(5, 7, 'newVEdge6'), (4, 7, None), (5, 4, 'newVEdge7')] + Bond: [(5, 4, 'newVEdge7'), (4, 5, 'newVEdge8'), (4, 5, None)] + Bond: [(1, 4, None), (4, 1, 'newVEdge9'), (4, 1, 'newVEdge10')] + Polygon: [(3, 4, None), (4, 1, 'newVEdge10'), (3, 1, 'newVEdge11')] + Triconnected: [(1, 2, None), (2, 3, None), (3, 1, 'newVEdge11'), (3, 13, None), (2, 13, None), (1, 13, None)] + + An example from [Gut2001]_ + + sage: G = Graph() + sage: G.add_edges([(1,2),(1,4),(2,3),(2,5),(3,4),(3,5),(4,5),(4,6),(5,7),(5,8)]) + sage: G.add_edges([(5,14),(6,8),(7,14),(8,9),(8,10),(8,11),(8,12),(9,10),(10,13)]) + sage: G.add_edges([(10,14),(10,15),(10,16),(11,12),(11,13),(12,13),(14,15),(14,16),(15,16)]) + sage: tric = Triconnectivity(G) + Polygon: [(6, 8, None), (4, 6, None), (5, 8, 'newVEdge12'), (5, 4, 'newVEdge13')] + Polygon: [(8, 9, None), (9, 10, None), (8, 10, 'newVEdge1')] + Bond: [(8, 10, 'newVEdge1'), (8, 10, None), (8, 10, 'newVEdge4'), (10, 8, 'newVEdge5')] + Triconnected: [(8, 11, None), (11, 12, None), (8, 12, None), (12, 13, None), (11, 13, None), (8, 13, 'newVEdge3')] + Polygon: [(8, 13, 'newVEdge3'), (10, 13, None), (8, 10, 'newVEdge4')] + Triconnected: [(10, 15, None), (14, 15, None), (15, 16, None), (10, 16, None), (14, 16, None), (10, 14, 'newVEdge6')] + Bond: [(10, 14, 'newVEdge6'), (14, 10, 'newVEdge7'), (10, 14, None)] + Polygon: [(14, 10, 'newVEdge7'), (10, 8, 'newVEdge5'), (5, 14, 'newVEdge10'), (5, 8, 'newVEdge11')] + Polygon: [(5, 7, None), (7, 14, None), (5, 14, 'newVEdge9')] + Bond: [(5, 14, None), (5, 14, 'newVEdge9'), (5, 14, 'newVEdge10')] + Bond: [(5, 8, None), (5, 8, 'newVEdge11'), (5, 8, 'newVEdge12')] + Bond: [(5, 4, 'newVEdge13'), (4, 5, 'newVEdge14'), (4, 5, None)] + Triconnected: [(2, 3, None), (3, 4, None), (4, 5, 'newVEdge14'), (3, 5, None), (2, 5, None), (2, 4, 'newVEdge15')] + Polygon: [(1, 2, None), (2, 4, 'newVEdge15'), (1, 4, None)] + + An example with multi-edges and accessing the triconnected components: + + sage: G = Graph() + sage: G.allow_multiple_edges(True) + sage: G.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(1,5),(2,3)]) + sage: tric = Triconnectivity(G) + Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] + Bond: [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')] + Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, + None), (2, 3, 'newVEdge1')] + sage: tric.comp_list_new + [[(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')], + [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')], + [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, 'newVEdge1')]] + sage: tric.comp_type + [0, 0, 1] + + An example of a triconnected graph: + + sage: G2 = Graph() + sage: G2.allow_multiple_edges(True) + sage: G2.add_edges([('a','b'),('a','c'),('a','d'),('b','c'),('b','d'),('c','d')]) + sage: tric2 = Triconnectivity(G2) + Triconnected: [('a', 'b', None), ('b', 'c', None), ('a', 'c', None), ('c', 'd', None), ('b', 'd', None), ('a', 'd', None)] + + TESTS:: + + A disconnected graph: + + sage: from sage.graphs.connectivity import Triconnectivity + sage: G = Graph([(1,2),(3,5)]) + sage: tric = Triconnectivity(G) + Traceback (most recent call last): + ... + ValueError: Graph is disconnected + + A graph with a cut vertex: + + sage: from sage.graphs.connectivity import Triconnectivity + sage: G = Graph([(1,2),(1,3),(2,3),(3,4),(3,5),(4,5)]) + sage: tric = Triconnectivity(G) + Traceback (most recent call last): + ... + ValueError: Graph has a cut vertex + """ def __init__(self, G, check=True): from sage.graphs.graph import Graph @@ -2119,9 +2250,9 @@ class Triconnectivity: # self.highpt containing the edge e. Used in the `path_search` function. self.in_high = dict((e, None) for e in self.graph_copy.edges()) - # New DFS number of the vertex with i as its old DFS number + # Translates DFS number of a vertex to its new number self.old_to_new = [0 for i in range(self.n+1)] - self.newnum = [0 for i in range(self.n)] # new DFS number of vertex i + self.newnum = [0 for i in range(self.n)] # new number of vertex i self.node_at = [0 for i in range(self.n+1)] # node at dfs number of i self.lowpt1 = [None for i in range(self.n)] # lowpt1 number of vertex i self.lowpt2 = [None for i in range(self.n)] # lowpt2 number of vertex i @@ -2143,7 +2274,7 @@ class Triconnectivity: self.components_list = [] # list of components of `graph_copy` self.graph_copy_adjacency = [[] for i in range(self.n)] # Stores adjacency list - # Dictionary of (e, True/False) to denote if a path starts at edge e + # Dictionary of (e, True/False) to denote if edge e starts a path self.starts_path = dict((e, False) for e in self.graph_copy.edges()) self.is_biconnected = True # Boolean to store if the graph is biconnected or not @@ -2170,7 +2301,7 @@ class Triconnectivity: # Trivial cases if self.n < 2: raise ValueError("Graph is not biconnected") - if self.n <= 2: + if self.n == 2: if self.m < 3: raise ValueError("Graph is not biconnected") comp = Component([], 0) @@ -2180,7 +2311,7 @@ class Triconnectivity: return # Triconnectivity algorithm - self.split_multi_egdes() + self.__split_multi_egdes() # Build adjacency list for e in self.graph_copy.edges(): @@ -2189,7 +2320,7 @@ class Triconnectivity: self.dfs_counter = 0 # Initialisation for dfs1() self.start_vertex = 0 # Initialisation for dfs1() - self.cut_vertex = self.dfs1(self.start_vertex, check=check) + self.cut_vertex = self.__dfs1(self.start_vertex, check=check) if check: # If graph is disconnected @@ -2210,23 +2341,23 @@ class Triconnectivity: # Add edge to the set reverse_edges self.reverse_edges.add(e) - self.build_acceptable_adj_struct() - self.dfs2() + self.__build_acceptable_adj_struct() + self.__dfs2() - self.path_search(self.start_vertex) + self.__path_search(self.start_vertex) # last split component c = Component([],0) while self.e_stack: - c.add_edge(self.estack_pop()) + c.add_edge(self.__estack_pop()) c.component_type = 2 if c.edge_list.get_length() > 4 else 1 self.components_list.append(c) c = None - self.assemble_triconnected_components() + self.__assemble_triconnected_components() self.print_triconnected_components() - def tstack_push(self, h, a, b): + def __tstack_push(self, h, a, b): """ Push `(h,a,b)` triple on Tstack """ @@ -2235,20 +2366,20 @@ class Triconnectivity: self.t_stack_a[self.t_stack_top] = a self.t_stack_b[self.t_stack_top] = b - def tstack_push_eos(self): + def __tstack_push_eos(self): """ Push end-of-stack marker on Tstack """ self.t_stack_top += 1 self.t_stack_a[self.t_stack_top] = -1 - def tstack_not_eos(self): + def __tstack_not_eos(self): """ Return true iff end-of-stack marker is not on top of Tstack """ return self.t_stack_a[self.t_stack_top] != -1 - def estack_pop(self): + def __estack_pop(self): """ Pop from estack and return the popped element """ @@ -2256,7 +2387,7 @@ class Triconnectivity: self.e_stack = self.e_stack[0:-1] return e - def new_component(self, edges=[], type_c=0): + def __new_component(self, edges=[], type_c=0): """ Create a new component, add `edges` to it. type_c = 0 for bond, 1 for polygon, 2 for triconnected component @@ -2265,7 +2396,7 @@ class Triconnectivity: self.components_list.append(c) return c - def high(self, v): + def __high(self, v): """ Return the high(v) value, which is the first value in highpt list of `v` """ @@ -2275,7 +2406,7 @@ class Triconnectivity: else: return head.data - def del_high(self, e): + def __del_high(self, e): if e in self.in_high: it = self.in_high[e] if it: @@ -2285,26 +2416,28 @@ class Triconnectivity: v = e[1] self.highpt[v].remove(it) - def split_multi_egdes(self): + def __split_multi_egdes(self): """ - This function will mark all the multiple edges present in graph_copy - as removed and append the multiple edges in component list. + Iterate through all the edges, and constructs bonds wherever + multiedges are present. - If there are `k` multiple edges between `u` and `v` then `k+1` - edges will be added to a component and edge_status will have k-1 edges - marked a 3(i.e edge removed). + If there are `k` multiple edges between `u` and `v`, then `k+1` + edges will be added to a new component (one of them is a virtual edge), + all the `k` edges are removed from the graph and a virtual edge is + between `u` and `v` is added to the graph. Instead of deleting edges + from the graph, they are instead marked as removed, i.e., 3 in + the dictionary `edge_status`. - It won't return anything but update the components_list and - graph_copy, which will become simple graph. + No return value. Update the `components_list` and `graph_copy`. + `graph_copy` will become simple graph after this function. """ - comp = [] if self.graph_copy.has_multiple_edges(): sorted_edges = sorted(self.graph_copy.edges()) for i in range(len(sorted_edges) - 1): - # It will add k - 1 multiple edges to comp + # Find multi edges and add to component and delete from graph if (sorted_edges[i][0] == sorted_edges[i + 1][0]) and \ (sorted_edges[i][1] == sorted_edges[i + 1][1]): self.edge_status[sorted_edges[i]] = 3 # edge removed @@ -2323,7 +2456,7 @@ class Triconnectivity: self.edge_status[newVEdge] = 0 comp.append(newVEdge) - self.new_component(comp) + self.__new_component(comp) comp = [] if comp: comp.append(sorted_edges[i+1]) @@ -2336,13 +2469,13 @@ class Triconnectivity: self.edge_status[newVEdge] = 0 comp.append(newVEdge) - self.new_component(comp) + self.__new_component(comp) - def dfs1(self, v, u=None, check=True): + def __dfs1(self, v, u=None, check=True): """ - This function builds the palm tree of the graph. + This function builds the palm tree of the graph using a dfs traversal. Also populates the lists lowpt1, lowpt2, nd, parent, and dfs_number. - It updates the dict edge_status to reflect palm tree arcs and fronds. + It updates the dict `edge_status` to reflect palm tree arcs and fronds. Input:: @@ -2357,59 +2490,8 @@ class Triconnectivity: Output:: - If ``check`` is set to True, and a cut vertex is found, the cut vertex - is returned. If no cut vertex is found, return value if None. + is returned. If no cut vertex is found, return value is None. If ``check`` is set to False, ``None`` is returned. - - Example:: - - An example to build the palm tree: - - sage: from sage.graphs.connectivity import Triconnectivity - sage: G = Graph() - sage: G.add_edges([(1,2),(1,13),(1,12),(1,8),(1,4),(2,13),(2,3),(3,13)]) - sage: G.add_edges([(3,4),(4,5),(4,7),(5,6),(5,7),(5,8),(6,7),(8,9),(8,12)]) - sage: G.add_edges([(8,11),(9,10),(9,12),(9,11),(10,11),(10,12)]) - sage: tric = Triconnectivity(G, check=False) - sage: tric.edge_status - {(0, 1, None): 1, - (0, 3, None): 2, - (0, 7, None): 2, - (0, 11, None): 2, - (0, 12, None): 2, - (1, 2, None): 1, - (1, 12, None): 2, - (2, 3, None): 1, - (2, 12, None): 1, - (3, 4, None): 1, - (3, 6, None): 2, - (4, 5, None): 1, - (4, 6, None): 2, - (4, 7, None): 1, - (5, 6, None): 1, - (7, 8, None): 1, - (7, 10, None): 2, - (7, 11, None): 2, - (8, 9, None): 1, - (8, 10, None): 2, - (8, 11, None): 2, - (9, 10, None): 1, - (9, 11, None): 1} - sage: tric.lowpt1 - [1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 8, 1, 1] - sage: tric.lowpt2 - [1, 2, 2, 4, 4, 5, 5, 8, 8, 8, 9, 8, 2] - sage: tric.parent - [None, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 9, 2] - - An example of a disconnected graph: - - sage: G = Graph() - sage: G.add_edges([(1,2),(1,3),(2,3),(3,4),(3,5),(4,5)]) - sage: tric = Triconnectivity(G, check=True) - sage: tric.is_biconnected - False - sage: tric.cut_vertex - 3 """ first_son = None # For testing biconnectivity s1 = None # Storing the cut vertex, if there is one @@ -2429,13 +2511,19 @@ class Triconnectivity: if first_son is None: first_son = w self.tree_arc[w] = e - s1 = self.dfs1(w, v, check) + s1 = self.__dfs1(w, v, check) if check: - # check for cut vertex + # Check for cut vertex. + # The situation in which there is no path from w to an + # ancestor of v : we have identified a cut vertex if (self.lowpt1[w] >= self.dfs_number[v]) and (w != first_son or u != None): s1 = v + # Calculate the `lowpt1` and `lowpt2` values. + # `lowpt1` is the smallest vertex (the vertex x with smallest + # dfs_number[x]) that can be reached from v. + # `lowpt2` is the next smallest vertex that can be reached from v. if self.lowpt1[w] < self.lowpt1[v]: self.lowpt2[v] = min(self.lowpt1[v], self.lowpt2[w]) self.lowpt1[v] = self.lowpt1[w] @@ -2456,22 +2544,26 @@ class Triconnectivity: elif self.dfs_number[w] > self.lowpt1[v]: self.lowpt2[v] = min(self.lowpt2[v], self.dfs_number[w]) - return s1 + return s1 # s1 is None is graph does not have a cut vertex - def build_acceptable_adj_struct(self): + def __build_acceptable_adj_struct(self): """ Builds the adjacency lists for each vertex with certain properties of the ordering, using the ``lowpt1`` and ``lowpt2`` values. The list ``adj`` and the dictionary ``in_adj`` are populated. + + `phi` values of each edge are calculated using the `lowpt` values of + incident vertices. The edges are then sorted by the `phi` values and + added to adjacency list. """ max = 3*self.n + 2 bucket = [[] for i in range(max+1)] for e in self.graph_copy.edges(): edge_type = self.edge_status[e] - if edge_type == 3: + if edge_type == 3: # If edge status is `removed`, go to next edge continue # compute phi value @@ -2495,6 +2587,7 @@ class Triconnectivity: bucket[phi].append(e) + # Populate `adj` and `in_adj` with the sorted edges for i in range(1,max+1): for e in bucket[i]: node = LinkedListNode() @@ -2506,9 +2599,10 @@ class Triconnectivity: self.adj[e[0]].append(node) self.in_adj[e] = node - def path_finder(self, v): + def __path_finder(self, v): """ This function is a helper function for `dfs2` function. + Calculate `newnum[v]` and identify the edges which start a new path. """ self.newnum[v] = self.dfs_counter - self.nd[v] + 1 e_node = self.adj[v].get_head() @@ -2520,9 +2614,10 @@ class Triconnectivity: self.new_path = False self.starts_path[e] = True if self.edge_status[e] == 1: # tree arc - self.path_finder(w) + self.__path_finder(w) self.dfs_counter -= 1 else: + # Identified a new frond that enters `w`. Add to `highpt[w]`. highpt_node = LinkedListNode() highpt_node.set_data(self.newnum[v]) self.highpt[w].append(highpt_node) @@ -2530,7 +2625,7 @@ class Triconnectivity: self.new_path = True - def dfs2(self): + def __dfs2(self): """ Update the values of lowpt1 and lowpt2 lists with the help of new numbering obtained from `Path Finder` funciton. @@ -2544,20 +2639,24 @@ class Triconnectivity: self.new_path = True # We call the pathFinder function with the start vertex - self.path_finder(self.start_vertex) + self.__path_finder(self.start_vertex) + # Update `old_to_new` values with the calculated `newnum` values for v in self.graph_copy.vertices(): self.old_to_new[self.dfs_number[v]] = self.newnum[v] - # Update lowpt values. + # Update lowpt values according to `newnum` values. for v in self.graph_copy.vertices(): self.node_at[self.newnum[v]] = v self.lowpt1[v] = self.old_to_new[self.lowpt1[v]] self.lowpt2[v] = self.old_to_new[self.lowpt2[v]] - def path_search(self, v): + def __path_search(self, v): """ - Function to find the separation pairs. + Find the separation pairs and construct the split components. + Check for type-1 and type-2 separation pairs, and construct + the split components while also creating new virtual edges wherever + required. """ y = 0 vnum = self.newnum[v] @@ -2572,21 +2671,22 @@ class Triconnectivity: else: w = e[1] wnum = self.newnum[w] - if self.edge_status[e] == 1: # tree arc - if self.starts_path[e]: + if self.edge_status[e] == 1: # e is a tree arc + if self.starts_path[e]: # if a new path starts at edge e y = 0 + # Pop all (h,a,b) from tstack where a > lowpt1[w] if self.t_stack_a[self.t_stack_top] > self.lowpt1[w]: while self.t_stack_a[self.t_stack_top] > self.lowpt1[w]: y = max(y, self.t_stack_h[self.t_stack_top]) b = self.t_stack_b[self.t_stack_top] self.t_stack_top -= 1 - self.tstack_push(y, self.lowpt1[w], b) + self.__tstack_push(y, self.lowpt1[w], b) else: - self.tstack_push(wnum + self.nd[w] - 1, self.lowpt1[w], vnum) - self.tstack_push_eos() + self.__tstack_push(wnum + self.nd[w] - 1, self.lowpt1[w], vnum) + self.__tstack_push_eos() - self.path_search(w) + self.__path_search(w) self.e_stack.append(self.tree_arc[w]) @@ -2596,7 +2696,9 @@ class Triconnectivity: temp_target = temp[0] else: temp_target = temp[1] - # while vnum is not the start_vertex + + # Type-2 separation pair check + # while v is not the start_vertex while vnum != 1 and ((self.t_stack_a[self.t_stack_top] == vnum) or \ (self.degree[w] == 2 and self.newnum[temp_target] > wnum)): @@ -2610,14 +2712,14 @@ class Triconnectivity: e_ab = None if self.degree[w] == 2 and self.newnum[temp_target] > wnum: # found type-2 separation pair - (v, temp_target) - e1 = self.estack_pop() - e2 = self.estack_pop() + e1 = self.__estack_pop() + e2 = self.__estack_pop() self.adj[w].remove(self.in_adj[e2]) if e2 in self.reverse_edges: - x = e2[0] + x = e2[0] # target else: - x = e2[1] + x = e2[1] # target e_virt = tuple([v, x, "newVEdge"+str(self.virtual_edge_num)]) self.graph_copy.add_edge(e_virt) @@ -2626,10 +2728,10 @@ class Triconnectivity: self.degree[x] -= 1 if e2 in self.reverse_edges: - e2_source = e2[1] + e2_source = e2[1] # target else: e2_source = e2[0] - if e2_source != w: # OGDF_ASSERT + if e2_source != w: raise ValueError("Graph is not biconnected") comp = Component([e1, e2, e_virt], 1) @@ -2640,14 +2742,14 @@ class Triconnectivity: e1 = self.e_stack[-1] if e1 in self.reverse_edges: if e1[1] == x and e1[0] == v: - e_ab = self.estack_pop() + e_ab = self.__estack_pop() self.adj[x].remove(self.in_adj[e_ab]) - self.del_high(e_ab) + self.__del_high(e_ab) else: if e1[0] == x and e1[1] == v: - e_ab = self.estack_pop() + e_ab = self.__estack_pop() self.adj[x].remove(self.in_adj[e_ab]) - self.del_high(e_ab) + self.__del_high(e_ab) else: # found type-2 separation pair - (self.node_at[a], self.node_at[b]) h = self.t_stack_h[self.t_stack_top] @@ -2667,23 +2769,23 @@ class Triconnectivity: break if (self.newnum[x] == a and self.newnum[xy_target] == b) or \ (self.newnum[xy_target] == a and self.newnum[x] == b): - e_ab = self.estack_pop() + e_ab = self.__estack_pop() if e_ab in self.reverse_edges: - e_ab_source = e_ab[1] + e_ab_source = e_ab[1] # source else: - e_ab_source = e_ab[0] + e_ab_source = e_ab[0] # source self.adj[e_ab_source].remove(self.in_adj[e_ab]) - self.del_high(e_ab) + self.__del_high(e_ab) else: - eh = self.estack_pop() + eh = self.__estack_pop() if eh in self.reverse_edges: eh_source = eh[1] else: eh_source = eh[0] if it != self.in_adj[eh]: self.adj[eh_source].remove(self.in_adj[eh]) - self.del_high(eh) + self.__del_high(eh) comp.add_edge(eh) self.degree[x] -= 1 @@ -2728,8 +2830,10 @@ class Triconnectivity: if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair - (self.node_at[self.lowpt1[w]], v) + + # Create a new component and add edges to it comp = Component([],0) - if not self.e_stack: # OGDF_ASSERT + if not self.e_stack: raise ValueError("stack is empty") while self.e_stack: xy = self.e_stack[-1] @@ -2744,22 +2848,22 @@ class Triconnectivity: (wnum <= y and y < wnum + self.nd[w])): break - comp.add_edge(self.estack_pop()) - self.del_high(xy) + comp.add_edge(self.__estack_pop()) + self.__del_high(xy) self.degree[self.node_at[xx]] -= 1 self.degree[self.node_at[y]] -= 1 e_virt = tuple([v, self.node_at[self.lowpt1[w]], "newVEdge"+str(self.virtual_edge_num)]) - self.graph_copy.add_edge(e_virt) + self.graph_copy.add_edge(e_virt) # Add virtual edge to graph self.virtual_edge_num += 1 - comp.finish_tric_or_poly(e_virt) + comp.finish_tric_or_poly(e_virt) # Add virtual edge to component self.components_list.append(comp) comp = None if (xx == vnum and y == self.lowpt1[w]) or \ (y == vnum and xx == self.lowpt1[w]): - comp_bond = Component([],type_c = 0) - eh = self.estack_pop() + comp_bond = Component([],type_c = 0) # new triple bond + eh = self.__estack_pop() if self.in_adj[eh] != it: if eh in self.reverse_edges: self.adj[eh[1]].remove(self.in_adj[eh]) @@ -2788,7 +2892,7 @@ class Triconnectivity: it = e_virt_node self.in_adj[e_virt] = it - if not e_virt in self.in_high and self.high(self.node_at[self.lowpt1[w]]) < vnum: + if not e_virt in self.in_high and self.__high(self.node_at[self.lowpt1[w]]) < vnum: vnum_node = LinkedListNode() vnum_node.set_data(vnum) self.highpt[self.node_at[self.lowpt1[w]]].push_front(vnum_node) @@ -2818,42 +2922,45 @@ class Triconnectivity: e_virt_node.set_data(e_virt) self.in_adj[eh] = e_virt_node # end type-1 search + + # if an path starts at edge e, empty the tstack. if self.starts_path[e]: - while self.tstack_not_eos(): + while self.__tstack_not_eos(): self.t_stack_top -= 1 self.t_stack_top -= 1 - while self.tstack_not_eos() and self.t_stack_b[self.t_stack_top] != vnum \ - and self.high(v) > self.t_stack_h[self.t_stack_top]: + while self.__tstack_not_eos() and self.t_stack_b[self.t_stack_top] != vnum \ + and self.__high(v) > self.t_stack_h[self.t_stack_top]: self.t_stack_top -= 1 outv -= 1 - else: #frond + else: # e is a frond if self.starts_path[e]: y = 0 + # pop all (h,a,b) from tstack where a > w if self.t_stack_a[self.t_stack_top] > wnum: while self.t_stack_a[self.t_stack_top] > wnum: y = max(y, self.t_stack_h[self.t_stack_top]) b = self.t_stack_b[self.t_stack_top] self.t_stack_top -= 1 - self.tstack_push(y, wnum, b) + self.__tstack_push(y, wnum, b) else: - self.tstack_push(vnum, wnum, vnum) + self.__tstack_push(vnum, wnum, vnum) self.e_stack.append(e) # add (v,w) to ESTACK - # Go to next node in adjacency list + # Go to next edge in adjacency list e_node = e_node.next - def assemble_triconnected_components(self): + def __assemble_triconnected_components(self): """ - Iterates through all the components built by `path_finder` and merges - the components wherever possible for contructing the final - triconnected components. - Formats the triconnected components into original vertices and edges. - The triconnected components are stored in `self.comp_list_new` and - `self.comp_type`. + Iterate through all the split components built by `path_finder` and + merges two bonds or two polygons that share an edge for contructing the + final triconnected components. + Subsequently, convert the edges in triconnected components into original + vertices and edges. The triconnected components are stored in + `self.comp_list_new` and `self.comp_type`. """ comp1 = {} # The index of first component that an edge belongs to comp2 = {} # The index of second component that an edge belongs to @@ -2876,6 +2983,8 @@ class Triconnectivity: e_node = e_node.next + # For each edge in a component, if the edge is a virtual edge, merge + # the two components the edge belongs to for i in range(num_components): c1 = self.components_list[i] c1_type = c1.component_type @@ -2887,10 +2996,11 @@ class Triconnectivity: if c1_type == 0 or c1_type == 1: e_node = self.components_list[i].edge_list.get_head() + # Iterate through each edge in the component while e_node: e = e_node.get_data() e_node_next = e_node.next - # the label of virtual edges is a string + # The label of a virtual edge is a string if not isinstance(e[2], str): e_node = e_node_next continue @@ -2906,18 +3016,24 @@ class Triconnectivity: e_node2 = item1[e] c2 = self.components_list[j] + + # If the two components are not the same type, do not merge if (c1_type != c2.component_type): - e_node = e_node_next + e_node = e_node_next # Go to next edge continue visited[j] = True l2 = c2.edge_list + # Remove the corresponding virtual edges in both the components + # and merge the components l2.remove(e_node2) l1.concatenate(l2) + # if `e_node_next` was empty, after merging two components, + # more edges are added to the component. if not e_node_next: - e_node_next = e_node.next + e_node_next = e_node.next # Go to next edge l1.remove(e_node) From 0f6d767d4ca700be62ad70e227c61752fea03fa4 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Sat, 28 Jul 2018 00:12:11 +0530 Subject: [PATCH 038/264] Bug fix. --- src/sage/graphs/connectivity.pyx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 12bd0440a94..527b1524831 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2383,9 +2383,7 @@ class Triconnectivity: """ Pop from estack and return the popped element """ - e = self.e_stack[-1] - self.e_stack = self.e_stack[0:-1] - return e + return self.e_stack.pop() def __new_component(self, edges=[], type_c=0): """ @@ -2830,7 +2828,6 @@ class Triconnectivity: if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair - (self.node_at[self.lowpt1[w]], v) - # Create a new component and add edges to it comp = Component([],0) if not self.e_stack: @@ -2876,7 +2873,8 @@ class Triconnectivity: self.graph_copy.add_edge(e_virt) self.virtual_edge_num += 1 comp_bond.add_edge(e_virt) - self.in_high[e_virt] = self.in_high[eh] + if eh in self.in_high: + self.in_high[e_virt] = self.in_high[eh] self.degree[v] -= 1 self.degree[self.node_at[self.lowpt1[w]]] -= 1 @@ -2917,7 +2915,8 @@ class Triconnectivity: self.tree_arc[v] = e_virt self.edge_status[e_virt] = 1 - self.in_adj[e_virt] = self.in_adj[eh] + if eh in self.in_adj: + self.in_adj[e_virt] = self.in_adj[eh] e_virt_node = LinkedListNode() e_virt_node.set_data(e_virt) self.in_adj[eh] = e_virt_node From 885b5631e2d34c0e38cfb8c1a473f4796044d08a Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Wed, 1 Aug 2018 23:47:49 +0530 Subject: [PATCH 039/264] Deleting edges instead of setting edge_status to removed. --- src/sage/graphs/connectivity.pyx | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 527b1524831..f69327e6edb 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2236,7 +2236,7 @@ class Triconnectivity: self.n = self.graph_copy.order() # number of vertices self.m = self.graph_copy.size() # number of edges - # status of each edge: unseen=0, tree=1, frond=2, removed=3 + # status of each edge: unseen=0, tree=1, frond=2 self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) # Edges of the graph which are in the reverse direction in palm tree @@ -2421,10 +2421,8 @@ class Triconnectivity: If there are `k` multiple edges between `u` and `v`, then `k+1` edges will be added to a new component (one of them is a virtual edge), - all the `k` edges are removed from the graph and a virtual edge is - between `u` and `v` is added to the graph. Instead of deleting edges - from the graph, they are instead marked as removed, i.e., 3 in - the dictionary `edge_status`. + all the `k` edges are deleted from the graph and a virtual edge is + between `u` and `v` is added to the graph. No return value. Update the `components_list` and `graph_copy`. `graph_copy` will become simple graph after this function. @@ -2438,12 +2436,12 @@ class Triconnectivity: # Find multi edges and add to component and delete from graph if (sorted_edges[i][0] == sorted_edges[i + 1][0]) and \ (sorted_edges[i][1] == sorted_edges[i + 1][1]): - self.edge_status[sorted_edges[i]] = 3 # edge removed + self.graph_copy.delete_edge(sorted_edges[i]) comp.append(sorted_edges[i]) else: if comp: comp.append(sorted_edges[i]) - self.edge_status[sorted_edges[i]] = 3 # edge removed + self.graph_copy.delete_edge(sorted_edges[i]) # Add virtual edge to graph_copy newVEdge = tuple([sorted_edges[i][0], sorted_edges[i][1], "newVEdge"+str(self.virtual_edge_num)]) @@ -2458,7 +2456,7 @@ class Triconnectivity: comp = [] if comp: comp.append(sorted_edges[i+1]) - self.edge_status[sorted_edges[i+1]] = 3 # edge removed + self.graph_copy.delete_edge(sorted_edges[i+1]) # Add virtual edge to graph_copy newVEdge = tuple([sorted_edges[i+1][0], sorted_edges[i+1][1], "newVEdge"+str(self.virtual_edge_num)]) @@ -2561,8 +2559,6 @@ class Triconnectivity: for e in self.graph_copy.edges(): edge_type = self.edge_status[e] - if edge_type == 3: # If edge status is `removed`, go to next edge - continue # compute phi value # bucket sort adjacency list by phi values From 6d0d44b5a3fdcf19427cee7ea3291bda4473fdea Mon Sep 17 00:00:00 2001 From: Ben Hutz Date: Thu, 2 Aug 2018 13:07:09 -0500 Subject: [PATCH 040/264] 25952: initial implementation of smallest coefficients --- src/doc/en/reference/references/index.rst | 3 + .../endPN_minimal_model.py | 269 +++++++- .../arithmetic_dynamics/projective_ds.py | 231 +++++-- .../rings/polynomial/binary_form_reduce.py | 589 ++++++++++++++++++ .../rings/polynomial/multi_polynomial.pyx | 307 ++++----- 5 files changed, 1174 insertions(+), 225 deletions(-) create mode 100644 src/sage/rings/polynomial/binary_form_reduce.py diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 9acc946cb2b..259396f37a0 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -781,6 +781,9 @@ REFERENCES: error-correcting codes from game theory*, IEEE Trans. Infor. Theory **32** (1986) 337-348. +.. [CS2003] \John E. Cremona and Michael Stoll. On The Reduction Theory of Binary Forms. + Journal für die reine und angewandte Mathematik, 565 (2003), 79-99. + .. [Cu1984] \R. Curtis, The Steiner system `S(5,6,12)`, the Mathieu group `M_{12}`, and the kitten. *Computational group theory*, ed. M. Atkinson, Academic Press, 1984. diff --git a/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py b/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py index 8e3b7fdb9bf..2229911ba99 100644 --- a/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py +++ b/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py @@ -22,13 +22,18 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.functions.hyperbolic import cosh from sage.matrix.constructor import matrix from sage.matrix.matrix_space import MatrixSpace +from sage.rings.all import CC +from sage.rings.complex_field import ComplexField from sage.rings.finite_rings.integer_mod_ring import Zmod from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.polynomial.binary_form_reduce import covariant_z0, epsinv from sage.rings.rational_field import QQ from sage.schemes.affine.affine_space import AffineSpace +from sage.symbolic.constants import e from sage.arith.all import gcd from copy import copy @@ -145,7 +150,8 @@ def blift(LF, Li, p, k, S=None, all_orbits=False): sage: R. = PolynomialRing(QQ) sage: from sage.dynamics.arithmetic_dynamics.endPN_minimal_model import blift - sage: blift([8*b^3 + 12*b^2 + 6*b + 1, 48*b^2 + 483*b + 117, 72*b + 1341, -24*b^2 + 411*b + 99, -144*b + 1233, -216*b], 2, 3, 2) + sage: blift([8*b^3 + 12*b^2 + 6*b + 1, 48*b^2 + 483*b + 117, 72*b + 1341,\ + ....: -24*b^2 + 411*b + 99, -144*b + 1233, -216*b], 2, 3, 2) [[True, 4]] """ @@ -897,3 +903,264 @@ def HS_all_minimal(f, return_transformation=False, D=None): else: return [funct for funct, matr in M] +####################### +#functionality for smallest coefficients +# +# Ben Hutz July 2018 +#####################################3 + +def get_bound_dynamical(F, f, m=1, dynatomic=True, prec=53, emb=None): + """ + The hyperbolic distance from `j` which must contain the smallest map. + + This defines the maximum possible distance from `j` to the `z_0` covariant + of the assocaited binary form `F` in the hyperbolic 3-space + for which the map `f`` could have smaller coefficients. + + INPUT: + + - ``F`` -- binary form of degree at least 3 with no multiple roots associated + to ``f`` + + - ``f`` -- a dynamical system on `P^1` + + - ``m`` - positive integer. the period used to create ``F`` + + - ``dyantomic`` -- boolean. whether ``F`` is the periodic points or the + formal periodic points of period ``m`` for ``f`` + + - ``prec``-- positive integer. precision to use in CC + + - ``emb`` -- embedding into CC + + OUTPUT: a positive real number + + EXAMPLES:: + + sage: from sage.dynamics.arithmetic_dynamics.endPN_minimal_model import get_bound_dynamical + sage: P. = ProjectiveSpace(QQ,1) + sage: f = DynamicalSystem([50*x^2 + 795*x*y + 2120*y^2, 265*x^2 + 106*y^2]) + sage: get_bound_dynamical(f.dynatomic_polynomial(1), f) + 35.5546923182219 + """ + def coshdelta(z): + #The cosh of the hyperbolic distance from z = t+uj to j + return (z.norm() + 1)/(2*z.imag()) + if F.base_ring() != ComplexField(prec=prec): + if emb is None: + compF = F.change_ring(ComplexField(prec=prec)) + else: + compF = F.change_ring(emb) + else: + compF = F + n = F.degree() + + z0F, thetaF = covariant_z0(compF, prec=prec, emb=emb) + cosh_delta = coshdelta(z0F) + d = f.degree() + hF = e**f.global_height(prec=prec) + #get precomputed constants C,k + if m == 1: + C = 4*d+2;k=2 + else: + Ck_values = {(False, 2, 2): (322, 6), (False, 2, 3): (385034, 14),\ + (False, 2, 4): (4088003923454, 30), (False, 3, 2): (18044, 8),\ + (False, 4, 2): (1761410, 10), (False, 5, 2): (269283820, 12),\ + (True, 2, 2): (43, 4), (True, 2, 3): (106459, 12),\ + (True, 2, 4): (39216735905, 24), (True, 3, 2): (1604, 6),\ + (True, 4, 2): (114675, 8), (True, 5, 2): (14158456, 10)} + try: + C,k = Ck_values[(dynatomic,d,m)] + except KeyError: + raise ValueError("constants not computed for this (m,d) pair") + if n == 2 and d == 2: + #bound with epsilonF = 1 + bound = 2*((2*C*(hF**k))/(thetaF)) + else: + bound = cosh(epsinv(F, (2**(n-1))*C*(hF**k)/thetaF, prec=prec)) + return bound + + +def smallest_dynamical(f, dynatomic=True, start_n=1, prec=53, emb=None, algorithm='HS', check_minimal=True): + """ + Determine the poly with smallest coefficients in `SL(2,\Z)` orbit of ``F`` + + Smallest is in the sense of global height. + The method is the algorithm in Hutz-Stoll [HS2018]_. + A binary form defining the periodic points is associated to ``f``. + From this polynomial a bound on the search space can be determined. + + ``f`` should already me a minimal model or finding the orbit + representatives may give wrong results. + + INPUT: + + - ``f`` -- a dynamical system on `P^1` + + - ``dyantomic`` -- boolean. whether ``F`` is the periodic points or the + formal periodic points of period ``m`` for ``f`` + + - ``start_n`` - positive integer. the period used to start trying to + create associate binary form ``F`` + + - ``prec``-- positive integer. precision to use in CC + + - ``emb`` -- embedding into CC + + - ``algorithm`` -- (optional) string; either ``'BM'`` for the Bruin-Molnar + algorithm or ``'HS'`` for the Hutz-Stoll algorithm. If not specified, + properties of the map are utilized to choose how to compute minimal + orbit representatives + + + OUTPUT: pair [dynamical system, matrix] + + EXAMPLES:: + + sage: from sage.dynamics.arithmetic_dynamics.endPN_minimal_model import smallest_dynamical + sage: P. = ProjectiveSpace(QQ,1) + sage: f = DynamicalSystem([50*x^2 + 795*x*y + 2120*y^2, 265*x^2 + 106*y^2]) + sage: smallest_dynamical(f) #long time + [ + Dynamical System of Projective Space of dimension 1 over Rational Field + Defn: Defined on coordinates by sending (x : y) to + (-480*x^2 - 1125*x*y + 1578*y^2 : 265*x^2 + 1060*x*y + 1166*y^2), + + [1 2] + [0 1] + ] + """ + def insert_item(pts, item, index): + # binary insertion to maintain list of points left to consider + N = len(pts) + if N == 0: + return [item] + elif N == 1: + if item[index] > pts[0][index]: + pts.insert(0,item) + else: + pts.append(item) + return pts + else: # binary insertion + left = 1 + right = N + mid = ((left + right)/2)# these are ints so this is .floor() + if item[index] > pts[mid][index]: # item goes into first half + return insert_item(pts[:mid], item, index) + pts[mid:N] + else: # item goes into second half + return pts[:mid] + insert_item(pts[mid:N], item, index) + + def coshdelta(z): + # The cosh of the hyperbolic distance from z = t+uj to j + return (z.norm() + 1)/(2*z.imag()) + #g,M = f.minimal_model(return_transformation=True) + # can't be smaller if height 0 + f.normalize_coordinates() + if f.global_height(prec=prec) == 0: + return [f, matrix(ZZ,2,2,[1,0,0,1])] + all_min = f.all_minimal_models(return_transformation=True, algorithm=algorithm, check_minimal=check_minimal) + # make conjugation current + #for i in range(len(all_min)): + # all_min[i][1] = M*all_min[i][1] + + current_min = None + current_size = None + # search for minimum over all orbits + for g,M in all_min: + PS = g.domain() + CR = PS.coordinate_ring() + x,y = CR.gens() + n = start_n # sometimes you get a problem later with 0,infty as roots + if dynatomic: + pts_poly = g.dynatomic_polynomial(n) + else: + gn = g.nth_iterate_map(n) + pts_poly = y*gn[0] - x*gn[1] + d = ZZ(pts_poly.degree()) + # repeated_roots = (max([ex for p,ex in pts_poly.factor()]) > 1) + max_mult = max([ex for p,ex in pts_poly.factor()]) + while ((d < 3) or (max_mult >= d/2) and (n < 5)): + n = n+1 + if dynatomic: + pts_poly = g.dynatomic_polynomial(n) + else: + gn = g.nth_iterate_map(n) + pts_poly = y*gn[0] - x*gn[1] + d = ZZ(pts_poly.degree()) + max_mult = max([ex for p,ex in pts_poly.factor()]) + # repeated_roots = (max([ex for p,ex in pts_poly.factor()]) > 1) + assert(n<=4), "n > 4, failed to find usable poly" + + R = get_bound_dynamical(pts_poly, g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) + try: + G,MG = pts_poly.reduced_form(prec=prec, emb=emb, smallest_coeffs=False) + if G != pts_poly: + red_g = f.conjugate(M*MG) + R2 = get_bound_dynamical(G, red_g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) + if R2 < R: + R = R2 + else: + #use pts_poly since the search space is smaller + G = pts_poly + MG = matrix(ZZ,2,2,[1,0,0,1]) + except: # any failure -> skip + G = pts_poly + MG = matrix(ZZ,2,2,[1,0,0,1]) + + red_g = f.conjugate(M*MG) + red_g.normalize_coordinates() + if red_g.global_height(prec=prec) == 0: + return [red_g, M*MG] + + # height + if current_size is None: + current_size = e**red_g.global_height(prec=prec) + v0, th = covariant_z0(G, prec=prec, emb=emb) + rep = 2*CC.gen(0) + from math import isnan + if isnan(v0.abs()): + raise ValueError("invalid covariant: %s"%v0) + + # get orbit + S = matrix(ZZ,2,2,[0,-1,1,0]) + T = matrix(ZZ,2,2,[1,1,0,1]) + TI = matrix(ZZ,2,2,[1,-1,0,1]) + + count = 0 + pts = [[G, red_g, v0, rep, M*MG, coshdelta(v0), 0]] #label - 0:None, 1:S, 2:T, 3:T^(-1) + if current_min is None: + current_min = [G, red_g, v0, rep, M*MG, coshdelta(v0)] + while pts != []: + G, g, v, rep, M, D, label = pts.pop() + #apply ST and keep z, Sz + if D > R: + break #all remaining pts are too far away + #check if it is smaller. If so, we can improve the bound + count += 1 + new_size = e**g.global_height(prec=prec) + if new_size < current_size: + current_min = [G ,g, v, rep, M, coshdelta(v)] + current_size = new_size + new_R = get_bound_dynamical(G, g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) + if new_R < R: + R = new_R + + #add new points to check + if label != 1 and min((rep+1).norm(), (rep-1).norm()) >= 1: #don't undo S + #do inversion if outside "bad" domain + z = -1/v + new_pt = [G.subs({x:-y, y:x}), g.conjugate(S), z, -1/rep, M*S, coshdelta(z), 1] + pts = insert_item(pts, new_pt, 5) + if label != 3: #don't undo T on g + #do right shift + z = v-1 + new_pt = [G.subs({x:x+y}), g.conjugate(TI), z, rep-1, M*TI, coshdelta(z), 2] + pts = insert_item(pts, new_pt, 5) + if label != 2: #don't undo TI on g + #do left shift + z = v+1 + new_pt = [G.subs({x:x-y}), g.conjugate(T), z, rep+1, M*T, coshdelta(z), 3] + pts = insert_item(pts, new_pt, 5) + + return [current_min[1], current_min[4]] + diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index 090e15f840b..60483edebc2 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -71,6 +71,7 @@ class initialization directly. from sage.rings.all import Integer, CIF from sage.arith.all import gcd, lcm, next_prime, binomial, primes, moebius from sage.categories.finite_fields import FiniteFields +from sage.rings.complex_field import ComplexField from sage.rings.finite_rings.finite_field_constructor import (is_FiniteField, GF, is_PrimeFiniteField) from sage.rings.finite_rings.integer_mod_ring import Zmod @@ -2690,7 +2691,7 @@ def all_minimal_models(self, return_transformation=False, prime_list=None, elif prime_list is None: prime_list = ZZ(f.resultant()).prime_divisors() if prime_list == []: - models = [f,m] + models = [[f,m]] elif max(prime_list) > 500: from .endPN_minimal_model import BM_all_minimal models = BM_all_minimal(f, return_transformation=True, D=prime_list) @@ -3776,26 +3777,31 @@ def sigma_invariants(self, n, formal=False, embedding=None, type='point'): sig = [sig[i] * (-1)**(i+1) / den for i in range(len(sig))] return sig - def reduced_form(self, prec=300, return_conjugation=True, error_limit=0.000001): + def reduced_form(self, **kwds): r""" Return reduced form of this dynamical system. The reduced form is the `SL(2, \ZZ)` equivalent morphism obtained by applying the binary form reduction algorithm from Stoll and - Cremona [SC]_ to the homogeneous polynomial defining the periodic + Cremona [CS2003]_ to the homogeneous polynomial defining the periodic points (the dynatomic polynomial). The smallest period `n` with - enough periodic points is used. + enough periodic points is used and without roots of too large + multiplicity. - This should also minimize the sum of the squares of the coefficients, - but this is not always the case. + This should also minimize the size of the coefficients, + but this is not always the case. By default the coefficient minimizing + algorithm in [HS2018]_ is applied. See :meth:`sage.rings.polynomial.multi_polynomial.reduced_form` for the information on binary form reduction. Implemented by Rebecca Lauren Miller as part of GSOC 2016. + Minimal height added by Ben Hutz July 2018. INPUT: + keywords: + - ``prec`` -- (default: 300) integer, desired precision - ``return_conjuagtion`` -- (default: ``True``) boolean; return @@ -3804,6 +3810,27 @@ def reduced_form(self, prec=300, return_conjugation=True, error_limit=0.000001): - ``error_limit`` -- (default: 0.000001) a real number, sets the error tolerance + - ``smallest_coeffs`` -- (default: True), boolean, whether to find the + model with smallest coefficients + + - ``dynatomic`` -- (default: True) boolean, to use formal periodic points + + - ``start_n`` -- (default: 1), positive integer, firs period to rry to find + appropriate binary form + + - ``emb`` -- (optional) embedding of based field into CC + + - ``algorithm`` -- (optional) which algorithm to use to find all + minimal models. Can be one of the following: + + * ``'BM'`` -- Bruin-Molnar algorithm [BM2012]_ + * ``'HS'`` -- Hutz-Stoll algorithm [HS2018]_ + + - ``check_minimal`` + + - ``smallest_coeffs`` -- (default: True), boolean, whether to find the + model with smallest coefficients + OUTPUT: - a projective morphism @@ -3814,20 +3841,21 @@ def reduced_form(self, prec=300, return_conjugation=True, error_limit=0.000001): sage: PS. = ProjectiveSpace(QQ, 1) sage: f = DynamicalSystem_projective([x^3 + x*y^2, y^3]) - sage: m = matrix(QQ, 2, 2, [-221, -1, 1, 0]) + sage: m = matrix(QQ, 2, 2, [-201221, -1, 1, 0]) sage: f = f.conjugate(m) - sage: f.reduced_form(prec=100) #needs 2 periodic + sage: f.reduced_form(prec=50, smallest_coeffs=False) #needs 2 periodic Traceback (most recent call last): ... - ValueError: not enough precision - sage: f.reduced_form() + ValueError: accuracy of Newton's root not within tolerance(0.000066950849420871 > 1e-06), increase precision + sage: f.reduced_form(smallest_coeffs=False) ( Dynamical System of Projective Space of dimension 1 over Rational Field - Defn: Defined on coordinates by sending (x : y) to - (x^3 + x*y^2 : y^3) + Defn: Defined on coordinates by sending (x : y) to + (x^3 + x*y^2 : y^3) , - [ 0 -1] - [ 1 221] + + [ 0 -1] + [ 1 201221] ) :: @@ -3836,7 +3864,7 @@ def reduced_form(self, prec=300, return_conjugation=True, error_limit=0.000001): sage: f = DynamicalSystem_projective([x^2+ x*y, y^2]) #needs 3 periodic sage: m = matrix(QQ, 2, 2, [-221, -1, 1, 0]) sage: f = f.conjugate(m) - sage: f.reduced_form(prec=200) + sage: f.reduced_form(prec=200, smallest_coeffs=False) ( Dynamical System of Projective Space of dimension 1 over Integer Ring Defn: Defined on coordinates by sending (x : y) to @@ -3850,15 +3878,15 @@ def reduced_form(self, prec=300, return_conjugation=True, error_limit=0.000001): sage: P. = ProjectiveSpace(QQ, 1) sage: f = DynamicalSystem_projective([x^3, y^3]) - sage: f.reduced_form() + sage: f.reduced_form(smallest_coeffs=False) ( Dynamical System of Projective Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (x : y) to (x^3 : y^3) , - [-1 0] - [ 0 -1] + [1 0] + [0 1] ) :: @@ -3866,11 +3894,11 @@ def reduced_form(self, prec=300, return_conjugation=True, error_limit=0.000001): sage: PS. = ProjectiveSpace(QQ,1) sage: f = DynamicalSystem_projective([7365*X^4 + 12564*X^3*Y + 8046*X^2*Y^2 + 2292*X*Y^3 + 245*Y^4,\ -12329*X^4 - 21012*X^3*Y - 13446*X^2*Y^2 - 3828*X*Y^3 - 409*Y^4]) - sage: f.reduced_form(prec=30) + sage: f.reduced_form(prec=30, smallest_coeffs=False) Traceback (most recent call last): ... - ValueError: accuracy of Newton's root not within tolerance(1.2519612 > 1e-06), increase precision - sage: f.reduced_form() + ValueError: accuracy of Newton's root not within tolerance(0.000087401733 > 1e-06), increase precision + sage: f.reduced_form(smallest_coeffs=False) ( Dynamical System of Projective Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (X : Y) to @@ -3886,15 +3914,15 @@ def reduced_form(self, prec=300, return_conjugation=True, error_limit=0.000001): sage: f = DynamicalSystem_projective([x^4, RR(sqrt(2))*y^4]) sage: m = matrix(RR, 2, 2, [1,12,0,1]) sage: f = f.conjugate(m) - sage: f.reduced_form() + sage: f.reduced_form(smallest_coeffs=False) ( Dynamical System of Projective Space of dimension 1 over Real Field with 53 bits of precision Defn: Defined on coordinates by sending (x : y) to - (-x^4 + 2.86348722511320e-12*y^4 : -1.41421356237310*y^4) + (x^4 - 2.86348722511320e-12*y^4 : 1.41421356237310*y^4) , - [-1 12] - [ 0 -1] + [ 1 -12] + [ 0 1] ) :: @@ -3903,14 +3931,15 @@ def reduced_form(self, prec=300, return_conjugation=True, error_limit=0.000001): sage: f = DynamicalSystem_projective([x^4, CC(sqrt(-2))*y^4]) sage: m = matrix(CC, 2, 2, [1,12,0,1]) sage: f = f.conjugate(m) - sage: f.reduced_form() + sage: f.reduced_form(smallest_coeffs=False) ( Dynamical System of Projective Space of dimension 1 over Complex Field with 53 bits of precision Defn: Defined on coordinates by sending (x : y) to - (-x^4 + (-1.03914726748259e-15)*y^4 : (-8.65956056235493e-17 - 1.41421356237309*I)*y^4) , + (x^4 + (1.03914726748259e-15)*y^4 : (8.65956056235493e-17 + 1.41421356237309*I)*y^4) + , - [-1 12] - [ 0 -1] + [ 1 -12] + [ 0 1] ) :: @@ -3920,15 +3949,15 @@ def reduced_form(self, prec=300, return_conjugation=True, error_limit=0.000001): sage: f = DynamicalSystem_projective([x^3, w*y^3]) sage: m = matrix(K, 2, 2, [1,12,0,1]) sage: f = f.conjugate(m) - sage: f.reduced_form() + sage: f.reduced_form(smallest_coeffs=False) ( Dynamical System of Projective Space of dimension 1 over Number Field in w with defining polynomial x^2 - 2 Defn: Defined on coordinates by sending (x : y) to (x^3 : (w)*y^3) , - [-1 12] - [ 0 -1] + [ 1 -12] + [ 0 1] ) :: @@ -3939,7 +3968,7 @@ def reduced_form(self, prec=300, return_conjugation=True, error_limit=0.000001): sage: f = DynamicalSystem_projective([12*x^3, 2334*w*y^3]) sage: m = matrix(K, 2, 2, [-12,1,1,0]) sage: f = f.conjugate(m) - sage: f.reduced_form() + sage: f.reduced_form(smallest_coeffs=False) ( Dynamical System of Projective Space of dimension 1 over Number Field in w with defining polynomial x^5 + x - 3 @@ -3949,22 +3978,130 @@ def reduced_form(self, prec=300, return_conjugation=True, error_limit=0.000001): [ 0 -1] [ 1 -12] ) + + :: + + sage: P. = QQ[] + sage: f = DynamicalSystem([-4*y^2, 9*x^2 - 12*x*y]) + sage: f.reduced_form() + ( + Dynamical System of Projective Space of dimension 1 over Rational Field + Defn: Defined on coordinates by sending (x : y) to + (-2*x^2 - y^2 : 2*x^2 - 2*y^2) + , + + [2 2] + [0 3] + ) + + :: + + sage: P. = QQ[] + sage: f = DynamicalSystem([-2*x^3 - 9*x^2*y - 12*x*y^2 - 6*y^3 , y^3]) + sage: f.reduced_form() + ( + Dynamical System of Projective Space of dimension 1 over Rational Field + Defn: Defined on coordinates by sending (x : y) to + (x^3 + 3*x^2*y : 3*x*y^2 + y^3) + , + + [-1 -2] + [ 1 1] + ) + + :: + + sage: P. = QQ[] + sage: f = DynamicalSystem([4*x^2 - 7*y^2, 4*y^2]) + sage: f.reduced_form(start_n=2, dynatomic=False) #long time + ( + Dynamical System of Projective Space of dimension 1 over Rational Field + Defn: Defined on coordinates by sending (x : y) to + (x^2 - x*y - y^2 : y^2) + , + + [ 2 -1] + [ 0 2] + ) + + :: + + sage: P. = QQ[] + sage: f = DynamicalSystem([4*x^2 + y^2, 4*y^2]) + sage: f.reduced_form() #long time + ( + Dynamical System of Projective Space of dimension 1 over Rational Field + Defn: Defined on coordinates by sending (x : y) to + (x^2 + x*y : y^2) + , + + [2 1] + [0 2] + ) """ - R = self.coordinate_ring() - F = R(self.dynatomic_polynomial(1)) - x,y = R.gens() - d = gcd(F, F.derivative(x)).degree() #counts multiple roots - n = 2 - # Checks to make sure there are enough distinct, roots we need 3 - # if there are not it finds the nth periodic points until there are enough - while F.degree() - d <= 2: - F = self.dynatomic_polynomial(n) # finds n periodic points - d = gcd(F, F.derivative(x)).degree() #counts multiple roots - n += 1 - G,m = F.reduced_form(prec=prec, return_conjugation=return_conjugation) + if self.domain().ambient_space().dimension_relative() != 1: + return NotImplementedError('only implmeneted for dimension 1') + return_conjugation = kwds.get('return_conjugation', True) + emb = kwds.get('emb', None) + prec = kwds.get('prec', 300) + start_n = kwds.get('start_n', 1) + algorithm = kwds.get('algorithm', None) + dynatomic = algorithm = kwds.get('dynatomic', True) + smallest_coeffs = kwds.get('smallest_coeffs', True) + if smallest_coeffs: + if self.base_ring() not in [ZZ,QQ]: + raise NotImplementedError("smallest coeff only over ZZ or QQ") + check_min = kwds.get('check_minimal', True) + from sage.dynamics.arithmetic_dynamics.endPN_minimal_model import smallest_dynamical + sm_f, m = smallest_dynamical(self, dynatomic=dynatomic, start_n=start_n,\ + prec=prec, emb=emb, algorithm=algorithm, check_minimal=check_min) + else: + #reduce via covariant + PS = self.domain() + CR = PS.coordinate_ring() + x,y = CR.gens() + n = start_n # sometimes you get a problem later with 0,infty as roots + pts_poly = self.dynatomic_polynomial(n) + d = ZZ(pts_poly.degree()) + try: + max_mult = max([ex for p,ex in pts_poly.factor()]) + except NotImplementedError: #not factorization in numericla rings + CF = ComplexField(prec=prec) + if pts_poly.base_ring() != CF: + if emb == None: + pts_poly_CF = pts_poly.change_ring(CF) + else: + pts_poly_CF = pts_poly.change_ring(emb) + pp_d = pts_poly.degree() + pts_poly_CF = pts_poly_CF.subs({pts_poly_CF.parent().gen(1):1}).univariate_polynomial() + max_mult = max([pp_d - pts_poly_CF.degree()] + [ex for p,ex in pts_poly_CF.roots()]) + while ((d < 3) or (max_mult >= d/2) and (n < 5)): + n = n+1 + if dynatomic: + pts_poly = self.dynatomic_polynomial(n) + else: + gn = self.nth_iterate_map(n) + pts_poly = y*gn[0] - x*gn[1] + d = ZZ(pts_poly.degree()) + try: + max_mult = max([ex for p,ex in pts_poly.factor()]) + except NotImplementedError: #not factorization in numericla rings + CF = ComplexField(prec=prec) + if pts_poly.base_ring() != CF: + if emb == None: + pts_poly_CF = pts_poly.change_ring(CF) + else: + pts_poly_CF = pts_poly.change_ring(emb) + pp_d = pts_poly.degree() + pts_poly_CF = pts_poly_CF.subs({pts_poly_CF.parent().gen(1):1}).univariate_polynomial() + max_mult = max([pp_d - pts_poly_CF.degree()] + [ex for p,ex in pts_poly_CF.roots()]) + assert(n<=4), "n > 4, failed to find usable poly" + G,m = pts_poly.reduced_form(prec=prec, emb=emb, smallest_coeffs=False) + sm_f = self.conjugate(m) + if return_conjugation: - return (self.conjugate(m), m) - return self.conjugate(m) + return (sm_f, m) + return sm_f def _is_preperiodic(self, P, err=0.1, return_period=False): r""" diff --git a/src/sage/rings/polynomial/binary_form_reduce.py b/src/sage/rings/polynomial/binary_form_reduce.py new file mode 100644 index 00000000000..a7976ab7363 --- /dev/null +++ b/src/sage/rings/polynomial/binary_form_reduce.py @@ -0,0 +1,589 @@ +# -*- coding: utf-8 -*- +""" +Helper functions for reduction of binary forms. + +The algorithm for reducing is from Stoll and Cremona's "On the Reduction Theory of +Binary Forms" [CS2003]_. This takes a two variable homogenous polynomial and finds a +reduced form. This is an `SL(2,\ZZ)`-equivalent binary form whose covariant in +the upper half plane is in the fundamental domain. Further, the algorithm +from Hutz and Stoll [HS2018]_ allows the form to be further minimized so that +the coefficients have either smallest height or smallest `L_2` norm. + +AUTHORS: + +- Rebecca Lauren Miller -- initial version of reduction as part of GSOC 2016 + +- Ben Hutz (2018-7) -- improvements to reduce and implement smallest coefficient model +""" + +#***************************************************************************** +# Copyright (C) 2018 Benjamin Hutz +# +# 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.arith.misc import gcd +from sage.calculus.functions import jacobian +from sage.functions.hyperbolic import cosh, sinh +from sage.matrix.constructor import matrix +from sage.misc.misc_c import prod +from sage.modules.free_module_element import vector +from sage.rings.all import CC +from sage.rings.complex_field import ComplexField +from sage.rings.complex_interval_field import ComplexIntervalField +from sage.rings.integer_ring import ZZ +from sage.rings.laurent_series_ring import LaurentSeriesRing +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.rational_field import QQ +from sage.rings.real_mpfr import RealField +from sage.symbolic.constants import e + +def covariant_z0(F, z0_inv=False, prec=53, emb=None, error_limit = 0.000001): + """ + Return the `z_0` covariant and Julia invariant from Cremona-Stoll [CS2003]_. + + In [CS2003]_ and [HS2018]_ the Julia invariant is denoted as `\Theta(F)` + or `R(F, z(F))`. Note that you may get faster convergence if you first move + `z_0(F)` to the fundamental domain before computing the true invariant + + INPUT: + + - ``F`` -- binary form of degree at least 3 with no multiple roots + + - ``z0_inv`` -- boolean, compute only the `z_0` invariant. Otherwise, solve + the minimization problem + + - ``prec``-- positive integer. precision to use in CC + + - ``emb`` -- embedding into CC + + - ``error_limit`` -- sets the error tolerance (default:0.000001) + + + OUTPUT: a complex number, a real number + + EXAMPLES:: + + sage: from sage.rings.polynomial.binary_form_reduce import covariant_z0 + sage: R. = QQ[] + sage: F = 19*x^8 - 262*x^7*y + 1507*x^6*y^2 - 4784*x^5*y^3 + 9202*x^4*y^4\ + ....: - 10962*x^3*y^5 + 7844*x^2*y^6 - 3040*x*y^7 + 475*y^8 + sage: covariant_z0(F, prec=80, z0_inv=True) + (1.3832330115323681438175 + 0.31233552177413614978744*I, + 3358.4074848663492819259) + sage: F = -x^8 + 6*x^7*y - 7*x^6*y^2 - 12*x^5*y^3 + 27*x^4*y^4\ + ....: - 4*x^3*y^5 - 19*x^2*y^6 + 10*x*y^7 - 5*y^8 + sage: covariant_z0(F, prec=80) + (0.64189877107807122203366 + 1.1852516565091601348355*I, + 3134.5148284344627168276) + + :: + + sage: R. = QQ[] + sage: covariant_z0(x^3 + 2*x^2*y - 3*x*y^2, z0_inv=True)[0] + 0.230769230769231 + 0.799408065031789*I + sage: -1/covariant_z0(-y^3 + 2*y^2*x + 3*y*x^2, z0_inv=True)[0] + 0.230769230769231 + 0.799408065031789*I + + :: + + sage: R. = QQ[] + sage: covariant_z0(2*x^2*y - 3*x*y^2, z0_inv=True)[0] + 0.750000000000000 + 1.29903810567666*I + sage: -1/covariant_z0(-x^3 - x^2*y + 2*x*y^2, z0_inv=True)[0] + 1 + 0.750000000000000 + 1.29903810567666*I + + :: + + sage: R. = QQ[] + sage: covariant_z0(x^2*y - x*y^2, prec=100) + (0.50000000000000000000000000003 + 0.86602540378443864676372317076*I, + 1.5396007178390020386910634147) + + TESTS:: + + sage: R.=QQ[] + sage: covariant_z0(x^2 + 24*x*y + y^2) + Traceback (most recent call last): + ... + ValueError: must be at least degree 3 + sage: covariant_z0((x+y)^3, z0_inv=True) + Traceback (most recent call last): + ... + ValueError: cannot have multiple roots for z0 invariant + sage: covariant_z0(x^3 + 3*x*y + y) + Traceback (most recent call last): + ... + TypeError: must be a binary form + sage: covariant_z0(-2*x^2*y^3 + 3*x*y^4 + 127*y^5) + Traceback (most recent call last): + ... + ValueError: cannot have a root with multiplicity >= 5/2 + sage: covariant_z0((x^2+2*y^2)^2) + Traceback (most recent call last): + ... + ValueError: must have at least 3 distinct roots + """ + R = F.parent() + d = ZZ(F.degree()) + if R.ngens() != 2 or any([sum(t) != d for t in F.exponents()]): + raise TypeError('must be a binary form') + if d < 3: + raise ValueError('must be at least degree 3') + + f = F.subs({R.gen(1):1}).univariate_polynomial() + if f.degree() < d: + # we have a root at infinity + if f.constant_coefficient() != 0: + # invert so we find all roots! + mat = matrix(ZZ,2,2,[0,-1,1,0]) + else: + t = 0 + poly_ring = f.parent() + while f(t) == 0: + t += 1 + mat = matrix(ZZ,2,2,[t,-1,1,0]) + else: + mat = matrix(ZZ,2,2,[1,0,0,1]) + f = F(list(mat * vector(R.gens()))).subs({R.gen(1):1}).univariate_polynomial() + # now we have a single variable polynomial with all the roots of F + K = ComplexField(prec=prec) + if f.base_ring() != K: + if emb == None: + f = f.change_ring(K) + else: + f = f.change_ring(emb) + roots = f.roots() + if (max([ex for p,ex in roots]) > 1)\ + or (f.degree() < d-1): + if z0_inv: + raise ValueError('cannot have multiple roots for z0 invariant') + else: + # just need a starting point for Newton's method + f = f.lc()*prod([p for p,ex in f.factor()])# removes multiple roots + if f.degree() < 3: + raise ValueError('must have at least 3 distinct roots') + roots = f.roots() + roots = [p for p,ex in roots] + + # finding quadratic Q_0, gives us our covariant, z_0 + dF = f.derivative() + n = ZZ(f.degree()) + PR = PolynomialRing(K,'x,y') + x,y = PR.gens() + Q = [] + # finds Stoll and Cremona's Q_0 + for j in range(len(roots)): + k = (1/(dF(roots[j]).abs()**(2/(n-2)))) * ((x-(roots[j]*y)) * (x-(roots[j].conjugate()*y))) + Q.append(k) + # this is Q_0 , always positive def as long as F has distinct roots + q = sum([Q[i] for i in range(len(Q))]) + A = q.monomial_coefficient(x**2) + B = q.monomial_coefficient(x*y) + C = q.monomial_coefficient(y**2) + # need positive root + try: + z = ((-B + ((B**2)-(4*A*C)).sqrt())/(2*A)) + except ValueError: + raise ValueError("not enough precision") + if z.imag() < 0: + z = (-B - ((B**2)-(4*A*C)).sqrt())/(2*A) + + if z0_inv: + FM = f #for Julia's invariant + else: + # solve the minimization problem for 'true' covariant + CF = ComplexIntervalField(prec=prec) # keeps trac of our precision error + RF = RealField(prec=prec) + z = CF(z) + FM = F(list(mat * vector(R.gens()))).subs({R.gen(1):1}).univariate_polynomial() + from sage.rings.polynomial.complex_roots import complex_roots + L1 = complex_roots(FM, min_prec=prec) + L = [] + err = z.diameter() + # making sure multiplicity isn't too large using convergence conditions in paper + for p,e in L1: + if e >= d/2: + raise ValueError('cannot have a root with multiplicity >= %s/2'%d) + for l in range(e): + L.append(p) + RCF = PolynomialRing(CF, 'u,t') + a = RCF(0) + c = RCF(0) + u,t = RCF.gens() + for j in range(len(L)): + a += u**2/((t-L[j]) * (t-L[j].conjugate()) + u**2) + c += (t-L[j].real())/((t-L[j]) * (t-L[j].conjugate()) + u**2) + # Newton's Method, to find solutions. Error bound is less than diameter of our z + err = z.diameter() + zz = z.diameter() + g1 = a.numerator() - d/2*a.denominator() + g2 = c.numerator() + G = vector([g1, g2]) + J = jacobian(G, [u, t]) + v0 = vector([z.imag(), z.real()]) # z0 as starting point + # finds our correct z + while err <= zz: + NJ = J.subs({u:v0[0], t:v0[1]}) + NJinv = NJ.inverse() + # inverse for CIF matrix seems to return fractions not CIF elements, fix them + if NJinv.base_ring() != CF: + NJinv = matrix(CF, 2, 2, [CF(zw.numerator()/zw.denominator()) for zw in NJinv.list()]) + w = z + v0 = v0 - NJinv*G.subs({u:v0[0], t:v0[1]}) + z = v0[1].constant_coefficient() + v0[0].constant_coefficient()*CF.gen(0) + err = z.diameter() # precision + zz = (w - z).abs() # difference in w and z + else: + if err > error_limit or err.is_NaN(): + raise ValueError("accuracy of Newton's root not within tolerance(%s > %s), increase precision"%(err, error_limit)) + if z.imag() <= z.diameter(): + raise ArithmeticError("Newton's method converged to z not in the upper half plane") + z = z.center() + + # Julia's invariant + if FM.base_ring() != ComplexField(prec=prec): + FM = FM.change_ring(ComplexField(prec=prec)) + tF = z.real() + uF = z.imag() + th = FM.lc().abs()**2 + for r,ex in FM.roots(): + for k in range(ex): + th = th * ((((r-tF).abs())**2 + uF**2)/uF) + + # undo shift and invert (if needed) + # since F \cdot m ~ m^(-1)\cdot z + # we apply m to z to undo m acting on F + l = mat*vector([z,1]) + return l[0]/l[1], th + + +#// compute inverse of eps_F +#from Michael Stoll +def epsinv(F, target, prec=53, target_tol = 0.001, z = None, emb=None): + """ + Compute a bound on the hyperbolic distance. + + The true minimum will be within the computed bound. + It is computed as the inverse of epsilon_F from [HS2018]_. + + INPUT: + + - ``F`` -- binary form of degree at least 3 with no multiple roots + + - ``target`` -- positive real number. The value we want to attain, i.e., + the value we are taking the inverse of + + - ``prec``-- positive integer. precision to use in CC + + - ``target_tol`` -- positive real number. The tolerance with which we + attain the target value. + + - ``z`` -- complex number. ``z_0`` covariant for F. + + - ``emb`` -- embedding into CC + + OUTPUT: a real number delta satisfying target + target_tol > eps_F(delta) > target. + + EXAMPLES:: + + sage: from sage.rings.polynomial.binary_form_reduce import epsinv + sage: R. = QQ[] + sage: epsinv(-2*x^3 + 2*x^2*y + 3*x*y^2 + 127*y^3, 31.5022020249597) + 4.02520895942207 + """ + def coshdelta(z): + #The cosh of the hyperbolic distance from z = t+uj to j + return (z.norm() + 1)/(2*z.imag()) + + def RQ(delta): + cd = cosh(delta).n(prec=prec) + sd = sinh(delta).n(prec=prec) + return prod([cd + (cost*phi[0] + sint*phi[1])*sd for phi in phis]) + + def epsF(delta): + pol = RQ(delta); + S = PolynomialRing(C, 'v') + g = S([(i-d)*pol[i-d] for i in range(2*d+1)]) + drts = [e for e in g.roots(ring=C, multiplicities=False) if (e.norm()-1).abs() < 0.1] + #print(("A", pol)) + #print([pol(r/r.abs()).real() for r in drts]) + return min([pol(r/r.abs()).real() for r in drts]) + + C = ComplexField(prec=prec) + if F.base_ring() != C: + if emb is None: + compF = F.change_ring(C) + else: + compF = F.change_ring(emb) + else: + compF = F + + R = F.parent() + d = F.degree() + if z is None: + z, th = covariant_z0(F, prec=prec, emb=emb) + else: #need to do our own input checking + if R.ngens() != 2 or any([sum(t) != d for t in F.exponents()]): + raise TypeError('must be a binary form') + if d < 3: + raise ValueError('must be at least degree 3') + + f = F.subs({R.gen(1):1}).univariate_polynomial() + #now we have a single variable polynomial + x = f.parent().gen() + if (max([ex for p,ex in f.roots(ring=C)]) >= QQ(d)/2)\ + or (f.degree() < QQ(d)/2): + raise ValueError('cannot have root with multiplicity >= deg(F)/2') + + R = RealField(prec=prec) + PR = PolynomialRing(R, 't') + t = PR.gen(0) + # compute phi_1, ..., phi_k + # first find F_0 and its roots + rts = f(z.imag()*t + z.real()).roots(ring=C) + phis = [] + for r,e in rts: + phis.extend([[2*r.real()/(r.norm()+1), (r.norm()-1)/(r.norm()+1)]]) + if d != f.degree(): + phis.extend([(d-f.degree())*[0,1]]) + LC = LaurentSeriesRing(C, 'u', default_prec=2*d+2) + u = LC.gen(0) + cost = (u + u**(-1))/2; + sint = (u - u**(-1))/(2*C.gen(0)); + # first find an interval containing the desired value, then use regula falsi on log eps_F + #d -> delta value in interval [0,1]? + #v in value in interval [1,epsF(1)] + dl = R(0.0); vl = R(1.0); + du = R(1.0); vu = epsF(du); + while vu < target: + #compute the next value of epsF for delta = 2*delta + dl = du; vl = vu; + du *= 2; vu = epsF(du); + # now dl < delta <= du + #print(("B",dl,du,vl,vu)) + logt = target.log() + l2 = (vu.log() -logt).n(prec=prec) + l1 = (vl.log()-logt).n(prec=prec) + dn = (dl*l2 - du*l1)/(l2 - l1) + #print(("C",dn)) + vn = epsF(dn); + #print(("d",vn)) + dl = du; vl = vu; + du = dn; vu = vn; + while (du-dl).abs() >= target_tol or max(vl, vu) < target: + #print((dl,du,vl,vu)) + l2 = (vu.log() -logt).n(prec=prec) + l1 = (vl.log()-logt).n(prec=prec) + dn = (dl*l2 - du*l1)/(l2 - l1) + vn = epsF(dn); + dl = du; vl = vu; + du = dn; vu = vn; + return max(dl, du); + +################### +def get_bound_poly(F, prec=53, norm_type='norm', emb=None): + """ + The hyperbolic distance from `j` which must contain the smallest poly. + + This defines the maximum possible distance from `j` to the `z_0` covariant + in the hyperbolic 3-space for which the associated `F` could have smaller + coefficients. + + INPUT: + + - ``F`` -- binary form of degree at least 3 with no multiple roots + + - ``prec``-- positive integer. precision to use in CC + + - ``norm_type`` -- string, either norm or height + + - ``emb`` -- embedding into CC + + OUTPUT: a positive real number + + EXAMPLES:: + + sage: from sage.rings.polynomial.binary_form_reduce import get_bound_poly + sage: R. = QQ[] + sage: F = -2*x^3 + 2*x^2*y + 3*x*y^2 + 127*y^3 + sage: get_bound_poly(F) + 28.0049336543295 + sage: get_bound_poly(F, norm_type='height') + 111.890642019092 + """ + def coshdelta(z): + #The cosh of the hyperbolic distance from z = t+uj to j + return (z.norm() + 1)/(2*z.imag()) + + if F.base_ring() != ComplexField(prec=prec): + if emb is None: + compF = F.change_ring(ComplexField(prec=prec)) + else: + compF = F.change_ring(emb) + else: + compF = F + n = F.degree() + assert(n>2), "degree 2 polynomial" + + z0F, thetaF = covariant_z0(compF, prec=prec, emb=emb) + cosh_delta = coshdelta(z0F) + if norm_type == 'norm': + #euclidean norm squared + normF = (sum([abs(i)**2 for i in compF.coefficients()])) + target = (2**(n-1))*normF/thetaF + #print(normF,target) + elif norm_type == 'height': + hF = e**max([c.global_height(prec=prec) for c in F.coefficients()]) #height + target = (2**(n-1))*(n+1)*(hF**2)/thetaF + else: + raise ValueError('type must be norm or height') + #print(epsinv(F, target, prec=prec)) + return cosh(epsinv(F, target, prec=prec)) + + +def smallest_poly(F, prec=53, norm_type='norm', emb=None): + """ + Determine the poly with smallest coefficients in `SL(2,\Z)` orbit of ``F`` + + Smallest can be in the sense of `L_2` norm or height. + The method is the algorithm in Hutz-Stoll [HS2018]_. + + ``F`` needs to be a binary form with no multiple roots of degree + at least 3. It should already be reduced in the sense of + Cremona-Stoll [CS2003]_. + + INPUT: + + - ``F`` -- binary form of degree at least 3 with no multiple roots + + - ``norm_type`` -- string - ``norm`` or ``height`` controlling what ``smallest`` + means for the coefficients. + + OUTPUT: pair [poly, matrix] + + EXAMPLES:: + + sage: from sage.rings.polynomial.binary_form_reduce import smallest_poly + sage: R. = QQ[] + sage: F = -x^8 + 6*x^7*y - 7*x^6*y^2 - 12*x^5*y^3 + 27*x^4*y^4\ + ....: - 4*x^3*y^5 - 19*x^2*y^6 + 10*x*y^7 - 5*y^8 + sage: smallest_poly(F, prec=100) #long time + [ + -x^8 - 2*x^7*y + 7*x^6*y^2 + 16*x^5*y^3 + 2*x^4*y^4 - 2*x^3*y^5 + 4*x^2*y^6 - 5*y^8, + + [1 1] + [0 1] + ] + + :: + + sage: R. = QQ[] + sage: F = -2*x^3 + 2*x^2*y + 3*x*y^2 + 127*y^3 + sage: smallest_poly(F) + [ + [1 4] + -2*x^3 - 22*x^2*y - 77*x*y^2 + 43*y^3, [0 1] + ] + sage: smallest_poly(F, norm_type='height') + [ + [5 4] + -58*x^3 - 47*x^2*y + 52*x*y^2 + 43*y^3, [1 1] + ] + + an example with a multiple root:: + + sage: R. = QQ[] + sage: F = -16*x^7 - 114*x^6*y - 345*x^5*y^2 - 599*x^4*y^3 - 666*x^3*y^4\ + ....: - 481*x^2*y^5 - 207*x*y^6 - 40*y^7 + sage: F.reduced_form() + ( + [-1 -1] + -x^5*y^2 - 24*x^3*y^4 - 3*x^2*y^5 - 2*x*y^6 + 16*y^7, [ 1 0] + ) + """ + def insert_item(pts, item, index): + #binary insertion to maintain list of points left to consider + N = len(pts) + if N == 0: + return [item] + elif N == 1: + if item[index] > pts[0][index]: + pts.insert(0,item) + else: + pts.append(item) + return pts + else: #binary insertion + left = 1 + right = N + mid = ((left + right)/2)# these are ints so this is .floor() + if item[index] > pts[mid][index]: # item goes into first half + return insert_item(pts[:mid], item, index) + pts[mid:N] + else: # item goes into second half + return pts[:mid] + insert_item(pts[mid:N], item, index) + def coshdelta(z): + #The cosh of the hyperbolic distance from z = t+uj to j + return (z.norm() + 1)/(2*z.imag())#reduce in the sense of Cremona-Stoll + #G,MG = F.reduced_form(prec=prec) #F \circ MG = G + G = F + MG = matrix(ZZ,2,2,[1,0,0,1]) + x,y = G.parent().gens() + if norm_type == 'norm': + current_size = sum([abs(i)**2 for i in G.coefficients()]) #euclidean norm squared + elif norm_type == 'height': #height + current_size = e**max([c.global_height(prec=prec) for c in G.coefficients()]) + else: + raise ValueError('type must be norm or height') + v0, th = covariant_z0(G, prec=prec, emb=emb) + rep = 2*CC.gen(0) #representative point in fundamental domain + from math import isnan + if isnan(v0.abs()): + raise ValueError("invalid covariant: %s"%v0) + R = get_bound_poly(G, prec=prec, norm_type=norm_type) + + #check orbit + S = matrix(ZZ,2,2,[0,-1,1,0]) + T = matrix(ZZ,2,2,[1,1,0,1]) + TI = matrix(ZZ,2,2,[1,-1,0,1]) + + count = 0 + pts = [[G, v0, rep, MG, coshdelta(v0), 0]] #label - 0:None, 1:S, 2:T, 3:T^(-1) + current_min = [G, v0, rep, MG, coshdelta(v0)] + while pts != []: + G, v, rep, M, D, label = pts.pop() + #apply ST and keep z, Sz + if D > R: + break #all remaining pts are too far away + #check if it is smaller. If so, we can improve the bound + count += 1 + if norm_type == 'norm': + new_size = sum([abs(i)**2 for i in G.coefficients()]) #euclidean norm squared + else: #height + new_size = e**max([c.global_height(prec=prec) for c in G.coefficients()]) + if new_size < current_size: + current_min = [G, v, rep, M, coshdelta(v)] + current_size = new_size + R = get_bound_poly(G, norm_type=norm_type, prec=prec, emb=emb) + + #add new points to check + if label != 1 and min((rep+1).norm(), (rep-1).norm()) >= 1: #don't undo S + #do inversion if outside "bad" domain + z = -1/v + new_pt = [G.subs({x:-y, y:x}), z, -1/rep, M*S, coshdelta(z), 1] + pts = insert_item(pts, new_pt, 4) + if label != 3: #don't undo TI + #do right shift + z = v-1 + new_pt = [G.subs({x:x+y}), z, rep-1, M*T, coshdelta(z), 2] + pts = insert_item(pts, new_pt, 4) + if label != 2: #don't undo T + #do left shift + z = v+1 + new_pt = [G.subs({x:x-y}), z, rep+1, M*TI, coshdelta(z), 3] + pts = insert_item(pts, new_pt, 4) + + return [current_min[0], current_min[3]] diff --git a/src/sage/rings/polynomial/multi_polynomial.pyx b/src/sage/rings/polynomial/multi_polynomial.pyx index 4fde55730e9..8f69435e22c 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -2054,37 +2054,57 @@ cdef class MPolynomial(CommutativeRingElement): phi = SpecializationMorphism(self.parent(),D) return phi(self) - def reduced_form(self, prec=300, return_conjugation=True, error_limit=0.000001): + def reduced_form(self, **kwds): r""" Returns a reduced form of this polynomial. - The algorithm is from Stoll and Cremona's "On the Reduction Theory of Binary Forms" [SC]_. - This takes a two variable homogenous polynomial and finds a reduced form. This is a - `SL(2,\ZZ)`-equivalent binary form whose covariant in the upper half plane is in the fundamental - domain. This should also minimize the sum of the squares of the coefficients, - but this is not always the case. - - A portion of the algorithm uses Newton's method to find a solution to a system of equations. - If Newton's method fails to converge to a point in the upper half plane, the function - will use the less precise `Q_0` covariant as defined in [SC]_. Additionally, if this polynomial has - a root with multiplicity at lease half the total degree of the polynomial, then - we must also use the `Q_0` covariant. See [SC]_ for details. - - Note that, if the covariant is within ``error_limit`` of the boundry but outside - the fundamental domain, our function will erroneously move it to within the - fundamental domain, hence our conjugation will be off by 1. If you don't want - this to happen, decrease your ``error_limit`` and increase your precision. - - Implemented by Rebecca Lauren Miller as part of GSOC 2016. + The algorithm is from Stoll and Cremona's "On the Reduction Theory of + Binary Forms" [CS2003]_. This takes a two variable homogenous polynomial and + finds a reduced form. This is a `SL(2,\ZZ)`-equivalent binary form + whose covariant in the upper half plane is in the fundamental domain. + If the polynomial has multiple roots, they are removed and the algorithm + is applied to the portion without multiple roots. + + This reduction should also minimize the sum of the squares of the coefficients, + but this is not always the case. By default the coefficient minimizing + algorithm in [HS2018]_ is applied. The coefficients can be minimized + either with respect to the sum of their squares of the maximum of their + global heights. + + A portion of the algorithm uses Newton's method to find a solution to + a system of equations. If Newton's method fails to converge to a point + in the upper half plane, the function will use the less precise `Q_0` + covariant as defined in [CS2003]_. Additionally, if this polynomial has + a root with multiplicity at lease half the total degree of the polynomial, + then we must also use the `Q_0` covariant. See [CS2003]_ for details. + + Note that, if the covariant is within ``error_limit`` of the boundry + but outside the fundamental domain, our function will erroneously move + it to within the fundamental domain, hence our conjugation will be off + by 1. If you don't want this to happen, decrease your ``error_limit`` + and increase your precision. + + Implemented by Rebecca Lauren Miller as part of GSOC 2016. Smallest + coefficients added by Ben Hutz July 2018. INPUT: + keywords: + - ``prec`` -- integer, sets the precision (default:300) - ``return_conjugation`` -- boolean. Returns element of `SL(2, \ZZ)` (default:True) - ``error_limit`` -- sets the error tolerance (default:0.000001) + - ``smallest_coeffs`` -- (default: True), boolean, whether to find the + model with smallest coefficients + + - ``norm_type`` -- either ``'norm'`` or ``'height'``. What type of norm + to use for smallest coefficients + + - ``emb`` -- (optional) embedding of based field into CC + OUTPUT: - a polynomial (reduced binary form) @@ -2095,18 +2115,12 @@ cdef class MPolynomial(CommutativeRingElement): Now we just return z0. It would be better to modify and find the unique root in the upper half plane. - - REFERENCES: - - .. [SC] Michael Stoll and John E. Cremona. On The Reduction Theory of Binary Forms. - Journal für die reine und angewandte Mathematik, 565 (2003), 79-99. - EXAMPLES:: sage: R. = PolynomialRing(QQ) sage: f = 19*x^8 - 262*x^7*h + 1507*x^6*h^2 - 4784*x^5*h^3 + 9202*x^4*h^4\ -10962*x^3*h^5 + 7844*x^2*h^6 - 3040*x*h^7 + 475*h^8 - sage: f.reduced_form(prec=200) + sage: f.reduced_form(prec=200, smallest_coeffs=False) ( -x^8 - 2*x^7*h + 7*x^6*h^2 + 16*x^5*h^3 + 2*x^4*h^4 - 2*x^3*h^5 + 4*x^2*h^6 - 5*h^8, @@ -2114,67 +2128,47 @@ cdef class MPolynomial(CommutativeRingElement): [ 1 -1] ) - An example were the multiplicity is too high:: + An example where the multiplicity is too high:: sage: R. = PolynomialRing(QQ) sage: f = x^3 + 378666*x^2*y - 12444444*x*y^2 + 1234567890*y^3 sage: j = f * (x-545*y)^9 - sage: j.reduced_form(prec=200) - ( - x^12 + 374553*x^11*y - 1587470292*x^10*y^2 + 2960311881270*x^9*y^3 - 3189673382015880*x^8*y^4 - + 2180205736473134502*x^7*y^5 - 972679603186995463284*x^6*y^6 + 278555935048988817910176*x^5*y^7 - - 47339497613591564056277355*x^4*y^8 + 3719790227462793441137663545*x^3*y^9 - + 4017321423785434880978464176*x^2*y^10 + 1605293849731195593699202674738*x*y^11 - - 2738526775493743375819069013598582*y^12, - - [ 1 66] - [ 0 1] - ) + sage: j.reduced_form(prec=200, smallest_coeffs=False) + Traceback (most recent call last): + ... + ValueError: cannot have a root with multiplicity >= 12/2 An example where Newton's Method doesnt find the right root:: - sage: R. = PolynomialRing(QQ) - sage: f = 234*x^11*h + 104832*x^10*h^2 + 21346884*x^9*h^3 + 2608021728*x^8*h^4\ - + 212413000410*x^7*h^5 + 12109691106162*x^6*h^6 + 493106447396862*x^5*h^7\ - + 14341797993350646*x^4*h^8 + 291976289803277118*x^3*h^9 +3962625618555930456*x^2*h^10\ - + 32266526239647689652*x*h^11 + 119421058057217196228*h^12 - sage: f.reduced_form(prec=600) # long time - ( - 234*x^11*h - 702*x^10*h^2 + 234*x^9*h^3 - 1638*x^8*h^4 + 17550*x^7*h^5 - 35568*x^6*h^6 - - 42120*x^5*h^7 - 248508*x^4*h^8 + 35802*x^3*h^9 + 23868*x^2*h^10 - 936*x*h^11 - 468*h^12, - - [ 1 -41] - [ 0 1] - ) + sage: R. = PolynomialRing(QQ) + sage: F = x^6 + 3*x^5*y - 8*x^4*y^2 - 2*x^3*y^3 - 44*x^2*y^4 - 8*x*y^5 + sage: F.reduced_form(smallest_coeffs=False, prec=400) + Traceback (most recent call last): + ... + ArithmeticError: Newton's method converged to z not in the upper half plane - An example with covariant on the boundary, therefore a non-unique form also a_0 is 0:: + An example with covariant on the boundary, therefore a non-unique form:: - sage: R. = PolynomialRing(QQ) - sage: g = -1872*x^5*h - 1375452*x^4*h^2 - 404242956*x^3*h^3 - 59402802888*x^2*h^4\ - -4364544068352*x*h^5 - 128270946360960*h^6 - sage: g.reduced_form() + sage: R. = PolynomialRing(QQ) + sage: F = 5*x^2*y - 5*x*y^2 - 30*y^3 + sage: F.reduced_form(smallest_coeffs=False) ( - -1872*x^5*h + 468*x^4*h^2 + 2340*x^3*h^3 - 2340*x^2*h^4 - 468*x*h^5 + 1872*h^6, - - [ -1 147] - [ 0 -1] + [1 1] + 5*x^2*y + 5*x*y^2 - 30*y^3, [0 1] ) An example where precision needs to be increased:: - sage: R. = PolynomialRing(QQ) - sage: f = -1872*x^5*h - 1375452*x^4*h^2 - 404242956*x^3*h^3 - 59402802888*x^2*h^4\ - -4364544068352*x*h^5 - 128270946360960*h^6 - sage: f.reduced_form(prec=200) + sage: R. = PolynomialRing(QQ) + sage: F=-16*x^7 - 114*x^6*y - 345*x^5*y^2 - 599*x^4*y^3 - 666*x^3*y^4 - 481*x^2*y^5 - 207*x*y^6 - 40*y^7 + sage: F.reduced_form(prec=50, smallest_coeffs=False) Traceback (most recent call last): ... - ValueError: accuracy of Newton's root not within tolerance(1.5551623876686905871173822301513235862915980531542297136320 > 1e-06), increase precision - sage: f.reduced_form(prec=400) + ValueError: accuracy of Newton's root not within tolerance(0.000012462581882703 > 1e-06), increase precision + sage: F.reduced_form(prec=100, smallest_coeffs=False) ( - -1872*x^5*h + 468*x^4*h^2 + 2340*x^3*h^3 - 2340*x^2*h^4 - 468*x*h^5 + 1872*h^6, - - [ -1 147] - [ 0 -1] + [-1 -1] + -x^5*y^2 - 24*x^3*y^4 - 3*x^2*y^5 - 2*x*y^6 + 16*y^7, [ 1 0] ) :: @@ -2184,6 +2178,26 @@ cdef class MPolynomial(CommutativeRingElement): sage: F.reduced_form(return_conjugation=False) x^4 + 9*x^3*y - 3*x*y^3 - 8*y^4 + :: + + sage: R. = QQ[] + sage: F = -2*x^3 + 2*x^2*y + 3*x*y^2 + 127*y^3 + sage: F.reduced_form() + ( + [1 4] + -2*x^3 - 22*x^2*y - 77*x*y^2 + 43*y^3, [0 1] + ) + + :: + + sage: R. = QQ[] + sage: F = -2*x^3 + 2*x^2*y + 3*x*y^2 + 127*y^3 + sage: F.reduced_form(norm_type='height') + ( + [5 4] + -58*x^3 - 47*x^2*y + 52*x*y^2 + 43*y^3, [1 1] + ) + :: sage: R. = PolynomialRing(QQ) @@ -2208,7 +2222,7 @@ cdef class MPolynomial(CommutativeRingElement): sage: R. = PolynomialRing(RR) sage: F = 217.992172373276*x^3 + 96023.1505442490*x^2*y + 1.40987971253579e7*x*y^2\ + 6.90016027113216e8*y^3 - sage: F.reduced_form() + sage: F.reduced_form(smallest_coeffs=False) ( -39.5673942565918*x^3 + 111.874026298523*x^2*y + 231.052762985229*x*y^2 - 138.380829811096*y^3, @@ -2221,7 +2235,7 @@ cdef class MPolynomial(CommutativeRingElement): sage: R. = PolynomialRing(CC) sage: F = (0.759099196558145 + 0.845425869641446*CC.0)*x^3 + (84.8317207268542 + 93.8840848648033*CC.0)*x^2*y\ + (3159.07040755858 + 3475.33037377779*CC.0)*x*y^2 + (39202.5965389079 + 42882.5139724962*CC.0)*y^3 - sage: F.reduced_form() + sage: F.reduced_form(smallest_coeffs=False) ( (-0.759099196558145 - 0.845425869641446*I)*x^3 + (-0.571709908900118 - 0.0418133346027929*I)*x^2*y + (0.856525964330103 - 0.0721403997649759*I)*x*y^2 + (-0.965531044130330 + 0.754252314465703*I)*y^3, @@ -2231,48 +2245,33 @@ cdef class MPolynomial(CommutativeRingElement): ) """ from sage.matrix.constructor import matrix - from sage.calculus.functions import jacobian if self.parent().ngens() != 2: raise ValueError("(=%s) must have two variables"%self) if not self.is_homogeneous(): raise ValueError("(=%s) must be homogenous"%self) + prec = kwds.get('prec', 300) + return_conjugation =kwds.get('return_conjugation', True) + error_limit = kwds.get('error_limit', 0.000001) + emb = kwds.get('emb', None) + #getting a numerical approximation of the roots of our polynomial CF = ComplexIntervalField(prec=prec) # keeps trac of our precision error RF = RealField(prec=prec) R = self.parent() - S = PolynomialRing(R.base_ring(),'z') - phi = R.hom([S.gen(0), 1], S)# dehomogenization - F = phi(self).quo_rem(gcd(phi(self), phi(self).derivative()))[0] # removes multiple roots - from sage.rings.polynomial.complex_roots import complex_roots - roots = [p for p,e in complex_roots(F, min_prec=prec)] - #roots = F.roots(ring=CF, multiplicities=False) + x,y = R.gens() #finding quadratic Q_0, gives us our convariant, z_0 - dF = F.change_ring(CF).derivative() - n = F.degree() - R = PolynomialRing(CF,'x,y') - x,y = R.gens() - Q = [] - # finds Stoll and Cremona's Q_0 - for j in range(len(roots)): - k = (1/(dF(roots[j]).abs()**(2/(n-2)))) * ((x-(roots[j]*y)) * (x-(roots[j].conjugate()*y))) - Q.append(k) - # this is Q_o , always positive def as long as F HAS DISTINCT ROOTS - q = sum([Q[i] for i in range(len(Q))]) - A = q.monomial_coefficient(x**2) - B = q.monomial_coefficient(x*y) - C = q.monomial_coefficient(y**2) - # need positive root this will be our first z + from sage.rings.polynomial.binary_form_reduce import covariant_z0 try: - z = (-B + ((B**2)-(4*A*C)).sqrt())/(2*A)# this is z_o - except ValueError: - raise ValueError("not enough precision") - if z.imag()<0: - z = (-B - ((B**2)-(4*A*C)).sqrt())/(2*A) - - # this moves z to our fundamental domain using the three steps laid out in the algorithim by [SC] + z, th = covariant_z0(self, prec=prec, emb=emb, z0_inv=True) + except ValueError:# multiple roots + F = self.lc()*prod([p for p,e in self.factor()]) + z, th = covariant_z0(F, prec=prec, emb=emb, z0_inv=True) + z = CF(z) + # this moves z to our fundamental domain using the three steps laid + # out in the algorithim by [CS2003] # this is found in section 5 of their paper M = matrix(QQ, [[1,0], [0,1]]) # used to keep track of how our z is moved. zc = z.center() @@ -2283,7 +2282,7 @@ cdef class MPolynomial(CommutativeRingElement): Qm = QQ(m) M = M * matrix(QQ, [[1,-Qm], [0,1]]) # move z += m # M.inverse()*z is supposed to move z by m - elif zc.real()>=RF(0.5): #moves z into fundamental domain by m + elif zc.real() >= RF(0.5): # moves z into fundamental domain by m m = zc.real().round() Qm = QQ(m) M = M * matrix(QQ, [[1,Qm], [0,1]]) #move z @@ -2292,83 +2291,37 @@ cdef class MPolynomial(CommutativeRingElement): z = -1/z M = M * matrix(QQ, [[0,-1], [1,0]])# multiply on left because we are taking inverse matrices zc = z.center() - z0 = z - # creates and solves equations 4.4 in [SC], gives us a new z - x,y = self.parent().gens() - F = S(phi(self(tuple((M * vector([x, y])))))) # New self, S pushes it to polynomial ring - #L1 = F.roots(ring=CF, multiplicities=True) - L1 = complex_roots(F, min_prec=prec) - L=[] - newton = True - err = z.diameter() - # making sure multiplicity isn't too large using convergence conditions in paper - for p,e in L1: - if e > self.degree()/2: - newton = False - break - for l in range(e): - L.append(p) - if newton: - a = 0 - c = 0 - RCF = PolynomialRing(CF, 'u,t') - u,t = RCF.gens() - for j in range(len(L)): - b = u**2/((t-(L[j])) * (t-(L[j].conjugate()))+ u**2) - a += b - d = (t-(L[j].real()))/((t-(L[j])) * (t-(L[j].conjugate())) + u**2) - c += d - #Newton's Method, to find solutions. Error bound is while less than diameter of our z - err = z.diameter() - zz = z.diameter() - n = F.degree() - g1 = a.numerator() - n/2*a.denominator() - g2 = c.numerator() - G = vector([g1, g2]) - J = jacobian(G, [u,t]) - v0 = vector([z.imag(), z.real()]) #z0 as starting point - #finds our correct z - while err <= zz: - NJ = J.subs({u:v0[0], t:v0[1]}) - NJinv = NJ.inverse() - #inverse for CIF matrix seems to return fractions not CIF elements, fix them - if NJinv.base_ring() != CF: - NJinv = matrix(CF,2,2,[CF(zw.numerator()/zw.denominator()) for zw in NJinv.list()]) - w = z - v0 = v0 - NJinv*G.subs({u:v0[0], t:v0[1]}) - z = v0[1].constant_coefficient() + v0[0].constant_coefficient()*CF.gen(0) - err = z.diameter() # precision - zz = (w - z).abs() #difference in w and z - else: - if err > error_limit: - raise ValueError("accuracy of Newton's root not within tolerance(%s > %s), increase precision"%(err, error_limit)) + + smallest_coeffs = kwds.get('smallest_coeffs', True) + if smallest_coeffs: + # since we are searching anyway, don't need the 'true' reduced covariant + from sage.rings.polynomial.binary_form_reduce import smallest_poly + norm_type = kwds.get('norm_type', 'norm') + sm_F, sm_m = smallest_poly(self(tuple(M * vector([x,y]))), prec=prec, norm_type=norm_type, emb=emb) + M = M*sm_m + else: + # solve the minimization problem for 'true' covariant + z, th = covariant_z0(self(tuple(M * vector([x,y]))), prec=prec, emb=emb) + z = CF(z) zc = z.center() # moves our z to fundamental domain as before - if zc.imag()> z.diameter(): - while zc.real() < RF(-0.5) or zc.real() >= RF(0.5) or (zc.real() <= RF(0) and zc.abs() < RF(1))\ - or (zc.real() > RF(0) and zc.abs() <= RF(1)): - if zc.real() < RF(-0.5): - m = zc.real().abs().round() - Qm = QQ(m) - M = M*matrix(QQ, [[1,-Qm], [0,1]]) - z += m # M.inverse()*Z is supposed to move z by m - elif zc.real() >= RF(0.5): #else if - m = zc.real().round() - Qm = QQ(m) - M = M * matrix(QQ, [[1,Qm], [0,1]]) - z -= m - elif (zc.real() <= RF(0) and zc.abs() < RF(1)) or (zc.real() > RF(0) and zc.abs() <= RF(1)): - z = -1/z - M = M * matrix(QQ, [[0,-1],[ 1,0]]) - zc = z.center() - else: - #verbose("Warning: Newton's method converged to z not in the upper half plane.", level=1) - z = z0 - else: - # means multiplicity of roots is too high, need to use Q0 reduced form, z0 - z=z0 - err = z.diameter() - x,y = self.parent().gens() + while zc.real() < RF(-0.5) or zc.real() >= RF(0.5) or (zc.real() <= RF(0) and zc.abs() < RF(1))\ + or (zc.real() > RF(0) and zc.abs() <= RF(1)): + if zc.real() < RF(-0.5): + m = zc.real().abs().round() + Qm = QQ(m) + M = M*matrix(QQ, [[1,-Qm], [0,1]]) + z += m # M.inverse()*Z is supposed to move z by m + elif zc.real() >= RF(0.5): #else if + m = zc.real().round() + Qm = QQ(m) + M = M * matrix(QQ, [[1,Qm], [0,1]]) + z -= m + elif (zc.real() <= RF(0) and zc.abs() < RF(1)) or (zc.real() > RF(0) and zc.abs() <= RF(1)): + z = -1/z + M = M * matrix(QQ, [[0,-1],[ 1,0]]) + zc = z.center() + if return_conjugation: return (self(tuple(M * vector([x,y]))), M) return self(tuple(M * vector([x,y]))) From a998f65be84cc6072378d207056b3925a99fa13b Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Sat, 4 Aug 2018 01:08:48 +0530 Subject: [PATCH 041/264] Fixed a bug related to the case of directed grapg input. --- src/sage/graphs/connectivity.pyx | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index f69327e6edb..c395231c8ea 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2100,15 +2100,12 @@ class Triconnectivity: of the $i^{th}$ component - 1 for bond, 2 for polygon, 3 for triconnected component. The output can be accessed through these variables. - ALGORITHM: - - We implement the algorithm proposed by Tarjan in [Tarjan72]_. The - original version is recursive. We emulate the recursion using a stack. - ALGORITHM:: We implement the algorithm proposed by Hopcroft and Tarjan in [Hopcroft1973]_ and later corrected by Gutwenger and Mutzel in [Gut2001]_. + In the case of a digraph, the computation is done on the underlying + graph. .. SEEALSO:: @@ -2184,6 +2181,15 @@ class Triconnectivity: sage: tric2 = Triconnectivity(G2) Triconnected: [('a', 'b', None), ('b', 'c', None), ('a', 'c', None), ('c', 'd', None), ('b', 'd', None), ('a', 'd', None)] + An example of a directed graph with multi-edges: + + sage: G3 = DiGraph() + sage: G3.allow_multiple_edges(True) + sage: G3.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(5,1)]) + sage: tric2 = Triconnectivity(G3) + Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] + Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, None)] + TESTS:: A disconnected graph: @@ -2206,7 +2212,7 @@ class Triconnectivity: """ def __init__(self, G, check=True): - from sage.graphs.graph import Graph + from sage.graphs.graph import Graph, DiGraph # graph_copy is a SparseGraph of the input graph `G` # We relabel the edges with increasing numbers to be able to # distinguish between multi-edges @@ -2683,7 +2689,6 @@ class Triconnectivity: self.__path_search(w) self.e_stack.append(self.tree_arc[w]) - temp_node = self.adj[w].get_head() temp = temp_node.get_data() if temp in self.reverse_edges: @@ -3048,7 +3053,14 @@ class Triconnectivity: if isinstance(e[2], str): label = e[2] else: - label = self.edge_label_dict[(source, target,e[2])][2] + # If the original input graph was a directed graph, the + # source and target can in reversed in the edge. + # Hence, we check for both. Since we use unique edge + # labels, this does not fetch a wrong edge. + if (source, target, e[2]) in self.edge_label_dict: + label = self.edge_label_dict[(source, target, e[2])][2] + else: + label = self.edge_label_dict[(target, source, e[2])][2] e_list_new.append(tuple([source, target, label])) # Add the component data to `comp_list_new` and `comp_type` self.comp_type.append(self.components_list[i].component_type) From e8c9410f2a5188cf73c83dc9d4119271cb4b2f0f Mon Sep 17 00:00:00 2001 From: saiharsh Date: Mon, 6 Aug 2018 18:23:15 +0530 Subject: [PATCH 042/264] Added staticSPQRTree --- src/sage/graphs/connectivity.pyx | 126 ++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index c395231c8ea..4a08849f93e 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2121,6 +2121,7 @@ class Triconnectivity: sage: G.add_edges([(3,13),(4,5),(4,7),(5,6),(5,7),(5,8),(6,7),(8,9),(8,11)]) sage: G.add_edges([(8,12),(9,10),(9,11),(9,12),(10,11),(10,12)]) sage: tric = Triconnectivity(G) + sage: tric.print_triconnected_components() Triconnected: [(8, 9, None), (9, 10, None), (10, 11, None), (9, 11, None), (8, 11, None), (10, 12, None), (9, 12, None), (8, 12, 'newVEdge0')] Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] Polygon: [(8, 12, 'newVEdge1'), (1, 12, None), (8, 1, 'newVEdge2')] @@ -2141,6 +2142,7 @@ class Triconnectivity: sage: G.add_edges([(5,14),(6,8),(7,14),(8,9),(8,10),(8,11),(8,12),(9,10),(10,13)]) sage: G.add_edges([(10,14),(10,15),(10,16),(11,12),(11,13),(12,13),(14,15),(14,16),(15,16)]) sage: tric = Triconnectivity(G) + sage: tric.print_triconnected_components() Polygon: [(6, 8, None), (4, 6, None), (5, 8, 'newVEdge12'), (5, 4, 'newVEdge13')] Polygon: [(8, 9, None), (9, 10, None), (8, 10, 'newVEdge1')] Bond: [(8, 10, 'newVEdge1'), (8, 10, None), (8, 10, 'newVEdge4'), (10, 8, 'newVEdge5')] @@ -2162,6 +2164,7 @@ class Triconnectivity: sage: G.allow_multiple_edges(True) sage: G.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(1,5),(2,3)]) sage: tric = Triconnectivity(G) + sage: tric.print_triconnected_components() Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] Bond: [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')] Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, @@ -2178,7 +2181,8 @@ class Triconnectivity: sage: G2 = Graph() sage: G2.allow_multiple_edges(True) sage: G2.add_edges([('a','b'),('a','c'),('a','d'),('b','c'),('b','d'),('c','d')]) - sage: tric2 = Triconnectivity(G2) + sage: tric = Triconnectivity(G2) + sage: tric.print_triconnected_components() Triconnected: [('a', 'b', None), ('b', 'c', None), ('a', 'c', None), ('c', 'd', None), ('b', 'd', None), ('a', 'd', None)] An example of a directed graph with multi-edges: @@ -2186,7 +2190,8 @@ class Triconnectivity: sage: G3 = DiGraph() sage: G3.allow_multiple_edges(True) sage: G3.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(5,1)]) - sage: tric2 = Triconnectivity(G3) + sage: tric = Triconnectivity(G3) + sage: tric.print_triconnected_components() Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, None)] @@ -2361,7 +2366,7 @@ class Triconnectivity: c = None self.__assemble_triconnected_components() - self.print_triconnected_components() + #self.print_triconnected_components() def __tstack_push(self, h, a, b): """ @@ -3080,3 +3085,118 @@ class Triconnectivity: print "Triconnected: ", print self.comp_list_new[i] +def staticSPQRTree(G): + r""" + Construct a static spqr tree using the output generated by triconnectivity + which gives bond, polygon and triconnected components present in graph `G`. + Iterate over all the components and create respective Tree nodes, based on + component edges and graph `G` edges construct edges between two tree nodes. + First construct the tree with 0..n-1 labeled vertices then replace them + with original vertex labels. + + EXAMPLES:: + + sage: from sage.graphs.connectivity import staticSPQRTree, spqr_tree_to_graph + sage: G = Graph(2) + sage: for i in range(3): + ....: G.add_clique([0, 1, G.add_vertex(), G.add_vertex()]) + sage: Tree = staticSPQRTree(G) + sage: K4 = graphs.CompleteGraph(4) + sage: all(u[1].is_isomorphic(K4) for u in Tree.vertices() if u[0] == 'R') + True + sage: from sage.graphs.connectivity import spqr_tree_to_graph + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + + sage: G = Graph(2) + sage: for i in range(3): + ....: G.add_path([0, G.add_vertex(), G.add_vertex(), 1]) + sage: Tree = staticSPQRTree(G) + sage: C4 = graphs.CycleGraph(4) + sage: all(u[1].is_isomorphic(C4) for u in Tree.vertices() if u[0] == 'S') + True + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + + sage: G.allow_multiple_edges(True) + sage: G.add_edges(G.edges()) + sage: Tree = staticSPQRTree(G) + sage: all(u[1].is_isomorphic(C4) for u in Tree.vertices() if u[0] == 'S') + True + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + + sage: G = graphs.CycleGraph(6) + sage: Tree = staticSPQRTree(G) + sage: Tree.order() + 1 + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + sage: G.add_edge(0, 3) + sage: Tree = staticSPQRTree(G) + sage: Tree.order() + 3 + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + """ + from sage.graphs.graph import Graph + tric = Triconnectivity(G) + int_to_treeVertex = [] + Tree = Graph(multiedges=False) + partnerNode = {e:None for e in tric.graph_copy.edges()} + + for i in range(len(tric.comp_list_new)): + + # Do this case exist? + if not len(tric.comp_list_new[i]): + continue + + # Create a vertex in tree + treeVertex = Tree.add_vertex() + + if tric.comp_type[i] == 0: + int_to_treeVertex.append(("P", Graph(tric.comp_list_new[i], immutable=True, multiedges=True))) + + elif tric.comp_type[i] == 1: + int_to_treeVertex.append(("S", Graph(tric.comp_list_new[i], immutable=True, multiedges=True))) + + else: + int_to_treeVertex.append(("R", Graph(tric.comp_list_new[i], immutable=True, multiedges=True))) + + for originalEdge in tric.comp_list_new[i]: + + # (u, v) are vertices of graph_copy, construction on graph_copy + # is available in Triconnectivity + u, v = tric.vertex_to_int[originalEdge[0]], tric.vertex_to_int[originalEdge[1]] + label = originalEdge[2] + + # e is an edge of graph_copy + e = tuple([u, v, label]) + + # check if edge e is an original graph G. + # if yes assign graph_edge as original form of e. + # if not assign graph_edge as None + if e[2] and "newVEdge" in e[2]: + graph_edge = None + else: + if e in tric.reverse_edges: + graph_edge = tuple([originalEdge[1], originalEdge[0]]) + else: + graph_edge = tuple([originalEdge[0], originalEdge[1]]) + + if not graph_edge: + try: + if partnerNode[e] == None: + partnerNode[e] = treeVertex + else: + Tree.add_edge(partnerNode[e], treeVertex) + except: + new_e = tuple([v, u, label]) + if partnerNode[new_e] == None: + partnerNode[new_e] = treeVertex + else: + Tree.add_edge(partnerNode[new_e], treeVertex) + + Tree.relabel(int_to_treeVertex) + return Tree + From 70c6160a6c23892c9707580d2d7673820aa5ce8e Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Mon, 18 Jun 2018 11:40:38 +0530 Subject: [PATCH 043/264] Added the basic class structure of the Triconnectivity module. --- src/doc/en/reference/references/index.rst | 7 ++ src/sage/graphs/connectivity.pyx | 97 +++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 959994e73ce..bb67247d9dd 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -1285,6 +1285,10 @@ REFERENCES: .. [Gu] GUAVA manual, http://www.gap-system.org/Packages/guava.html +.. [Gut2001] Carsten Gutwenger and Petra Mutzel. *A Linear Time Implementation + of SPQR-Trees*, International Symposium on Graph Drawing, + (2001) 77-90 + .. [GW1999] Frederick M. Goodman and Hans Wenzl. *Crystal bases of quantum affine algebras and affine Kazhdan-Lusztig polyonmials*. Int. Math. Res. Notices **5** (1999), 251-275. @@ -1360,6 +1364,9 @@ REFERENCES: .. [Hoc] Winfried Hochstaettler, "About the Tic-Tac-Toe Matroid", preprint. +.. [Hopcroft1973] J. E. Hopcroft and R. E. Tarjan. *Dividing a Graph into + Triconnected Components*, SIAM J. Comput., 2(3), 135–158 + .. [Hopkins2017] Sam Hopkins, *RSK via local transformations*, http://web.mit.edu/~shopkins/docs/rsk.pdf diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 12479bb4a73..eb7ac291fa6 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2348,3 +2348,100 @@ def spqr_tree_to_graph(T): G.add_edge(e) return G + +from sage.graphs.base.sparse_graph cimport SparseGraph + + +class Triconnectivity: + """ + This module is not yet complete, it has work to be done. + + This module implements the algorithm for finding the triconnected + components of a biconnected graph. + Refer to [Gut2001]_ and [Hopcroft1973]_ for the algorithm. + + EXAMPLES:: + + An example to show how the triconnected components can be accessed: + + sage: from sage.graphs.connectivity import Triconnectivity + sage: g = Graph([['a','b',1],['a','c',1],['b','c',10]], weighted=True) + sage: tric = Triconnectivity(g) + sage: tric.components_list + [] + """ + class Component: + edge_list = [] + component_type = 0 #bond = 0, polygon = 1, triconnected = 2 + def __init__(self, edges, type_c=0): + self.edge_list = edges + component_type = type_c + def add_edge(self, e): + self.edge_list.append(e) + + graph_copy = None #type SparseGraph + vertex_to_int = {} # mapping of vertices to integers in c_graph + start_vertex = 0 + components_list = [] #list of components + num_components = 0 + edge_status = {} #status of each edge, unseen=0, tree=1, frond=2, removed=3 + Estack = [] + Tstack = [] + number = [] # DFS number of vertex i + vertex_at = [] # vertex with DFS number of i$ + lowpt1 = [] # lowpt1 number of vertex i + lowpt2 = [] # lowpt2 number of vertex i + nd = [] # number of descendants of vertex i + edge_phi = {} # (key, value) = (edge, corresponding phi value) + adj = [] # i^th value contains a list of incident edges of vertex i + in_adj = {} # this variable is used in the PathSearch() function + newnum = [] # new DFS number of vertex i + startsPath = {} # dict of (edge, True/False) to denote if a path starts at edge + highpt = [] # List of fronds entering vertex i in the order they are visited + old_to_new = [] # New DFS number of the vertex with i as old DFS number + degree = [] # Degree of vertex i + parent = [] # Parent vertex of vertex i in the palm tree + tree_arc = [] # Tree arc entering the vertex i + first_child = [] + high = [] # One of the elements in highpt[i] + dfs_counter = 0 + n = 0 # number of vertices + m = 0 # number of edges + + + def __init__(self, G): + self.graph_copy = G.copy(implementation='c_graph') + self.vertex_to_int = self.graph_copy.relabel(inplace=True, return_map=True) + self.n = self.graph_copy.order() + self.m = self.graph_copy.num_edges() + self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) + self.number = [0]*self.n + self.lowpt1 = [None]*self.n + self.lowpt2 = [None]*self.n + self.nd = [None]*self.n + self.parent = [None]*self.n + self.degree = [None]*self.n + self.tree_arc = [None]*self.n + self.vertex_at = [1]*self.n + self.dfs_counter = 0 + # We call the function split_multi_egdes() next. + + def new_component(self, edges, type_c=0): + c = self.Component(edges, type_c) + self.components_list.append(c) + # Remove the edges from graph + for e in edges: + self.edge_status[e] = 3 + + def add_edge_to_component(self, comp, e): + comp.add_edge(e) + self.edge_status[e] = 3 + + + + + + + + + From 5bb9d000ef6f9c4080fe4fda5f21793dd7b21a5d Mon Sep 17 00:00:00 2001 From: saiharsh Date: Tue, 19 Jun 2018 23:19:00 +0530 Subject: [PATCH 044/264] Split_multiple_edges function is added. --- src/sage/graphs/connectivity.pyx | 67 ++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index eb7ac291fa6..bfe96828c86 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2382,7 +2382,6 @@ class Triconnectivity: graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph start_vertex = 0 - components_list = [] #list of components num_components = 0 edge_status = {} #status of each edge, unseen=0, tree=1, frond=2, removed=3 Estack = [] @@ -2413,7 +2412,7 @@ class Triconnectivity: self.graph_copy = G.copy(implementation='c_graph') self.vertex_to_int = self.graph_copy.relabel(inplace=True, return_map=True) self.n = self.graph_copy.order() - self.m = self.graph_copy.num_edges() + self.m = self.graph_copy.size() self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) self.number = [0]*self.n self.lowpt1 = [None]*self.n @@ -2424,7 +2423,8 @@ class Triconnectivity: self.tree_arc = [None]*self.n self.vertex_at = [1]*self.n self.dfs_counter = 0 - # We call the function split_multi_egdes() next. + self.components_list = [] #list of components + self.split_multi_egdes() def new_component(self, edges, type_c=0): c = self.Component(edges, type_c) @@ -2432,16 +2432,61 @@ class Triconnectivity: # Remove the edges from graph for e in edges: self.edge_status[e] = 3 + self.num_components += 1 def add_edge_to_component(self, comp, e): comp.add_edge(e) self.edge_status[e] = 3 - - - - - - - - + def split_multi_egdes(self): + """ + This function will remove all the multiple edges present in + graph_copy and append the multiple edges in component list. + + If there are `k` multiple edges between `u` and `v` then `k+1` + edges will be added to a component. + + It won't return anything but update the components_list and + graph_copy, which will become simple graph. + + Example:: + + An example to list the components build after removal of multiple edges + + sage: G = Graph() + sage: G.add_cycle(vertices=[0,1,2,3,4]) + sage: G.allow_multiple_edges(True) + sage: G.add_edges(G.edges()) + sage: G.add_edges([[0,1],[3, 4]]) + sage: from sage.graphs.connectivity import Triconnectivity + sage: t = Triconnectivity(G) + sage: for l in t.components_list: + ....: print l.edge_list + [(0, 1), (0, 1), (0, 1), (0, 1)] + [(0, 4), (0, 4), (0, 4)] + [(1, 2), (1, 2), (1, 2)] + [(2, 3), (2, 3), (2, 3)] + [(3, 4), (3, 4)] + sage: t.num_components + 5 + """ + + comp = [] + if self.graph_copy.has_multiple_edges(): + sorted_edges = sorted(self.graph_copy.multiple_edges(labels=False)) + for i in range(len(sorted_edges) - 1): + + # It will add k - 1 multiple edges to comp + if sorted_edges[i] == sorted_edges[i + 1]: + self.graph_copy.delete_edge(sorted_edges[i]) + comp.append(sorted_edges[i]) + else: + if len(comp): + comp.append(sorted_edges[i-1]) + comp.append(sorted_edges[i-1]) + self.new_component(comp) + comp = [] + if len(comp): + comp.append(sorted_edges[i-1]) + comp.append(sorted_edges[i-1]) + self.new_component(comp) \ No newline at end of file From 90bd3bc386a0bd6e4426857bc8def63b3e8eb884 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Fri, 22 Jun 2018 01:24:28 +0530 Subject: [PATCH 045/264] Added the function dfs1() which builds the palm tree. --- src/doc/en/reference/references/index.rst | 2 +- src/sage/graphs/connectivity.pyx | 117 +++++++++++++++++++--- 2 files changed, 106 insertions(+), 13 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index bb67247d9dd..4d58d528b6b 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -1286,7 +1286,7 @@ REFERENCES: .. [Gu] GUAVA manual, http://www.gap-system.org/Packages/guava.html .. [Gut2001] Carsten Gutwenger and Petra Mutzel. *A Linear Time Implementation - of SPQR-Trees*, International Symposium on Graph Drawing, + of SPQR-Trees*, International Symposium on Graph Drawing, (2001) 77-90 .. [GW1999] Frederick M. Goodman and Hans Wenzl. *Crystal bases of quantum diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index bfe96828c86..070b81d065d 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2350,14 +2350,14 @@ def spqr_tree_to_graph(T): return G from sage.graphs.base.sparse_graph cimport SparseGraph - + class Triconnectivity: """ This module is not yet complete, it has work to be done. This module implements the algorithm for finding the triconnected - components of a biconnected graph. + components of a biconnected graph. Refer to [Gut2001]_ and [Hopcroft1973]_ for the algorithm. EXAMPLES:: @@ -2378,7 +2378,7 @@ class Triconnectivity: component_type = type_c def add_edge(self, e): self.edge_list.append(e) - + graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph start_vertex = 0 @@ -2386,7 +2386,7 @@ class Triconnectivity: edge_status = {} #status of each edge, unseen=0, tree=1, frond=2, removed=3 Estack = [] Tstack = [] - number = [] # DFS number of vertex i + dfs_number = [] # DFS number of vertex i vertex_at = [] # vertex with DFS number of i$ lowpt1 = [] # lowpt1 number of vertex i lowpt2 = [] # lowpt2 number of vertex i @@ -2406,7 +2406,7 @@ class Triconnectivity: dfs_counter = 0 n = 0 # number of vertices m = 0 # number of edges - + def __init__(self, G): self.graph_copy = G.copy(implementation='c_graph') @@ -2414,7 +2414,7 @@ class Triconnectivity: self.n = self.graph_copy.order() self.m = self.graph_copy.size() self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) - self.number = [0]*self.n + self.dfs_number = [0]*self.n self.lowpt1 = [None]*self.n self.lowpt2 = [None]*self.n self.nd = [None]*self.n @@ -2425,7 +2425,10 @@ class Triconnectivity: self.dfs_counter = 0 self.components_list = [] #list of components self.split_multi_egdes() - + self.dfs_counter = 0 # Initialisation for dfs1() + self.start_vertex = 0 # Initialisation for dfs1() + self.dfs1(self.start_vertex) + def new_component(self, edges, type_c=0): c = self.Component(edges, type_c) self.components_list.append(c) @@ -2433,11 +2436,11 @@ class Triconnectivity: for e in edges: self.edge_status[e] = 3 self.num_components += 1 - + def add_edge_to_component(self, comp, e): comp.add_edge(e) self.edge_status[e] = 3 - + def split_multi_egdes(self): """ This function will remove all the multiple edges present in @@ -2461,12 +2464,12 @@ class Triconnectivity: sage: from sage.graphs.connectivity import Triconnectivity sage: t = Triconnectivity(G) sage: for l in t.components_list: - ....: print l.edge_list + ....: print(l.edge_list) [(0, 1), (0, 1), (0, 1), (0, 1)] [(0, 4), (0, 4), (0, 4)] [(1, 2), (1, 2), (1, 2)] [(2, 3), (2, 3), (2, 3)] - [(3, 4), (3, 4)] + [(3, 4), (3, 4), (3, 4), (3, 4)] sage: t.num_components 5 """ @@ -2489,4 +2492,94 @@ class Triconnectivity: if len(comp): comp.append(sorted_edges[i-1]) comp.append(sorted_edges[i-1]) - self.new_component(comp) \ No newline at end of file + self.new_component(comp) + + def dfs1(self, v, u=None): + """ + This function builds the palm tree of the graph. + Also populates the lists lowpt1, lowpt2, nd, parent, and dfs_number. + It updates the dict edge_status to reflect palm tree arcs and fronds. + + Input:: + + - ``v`` -- The start vertex for DFS. + + - ``u`` -- The parent vertex of ``v`` in the palm tree. + + Example:: + + An example to list the components build after removal of multiple edges + + sage: from sage.graphs.connectivity import Triconnectivity + sage: G = Graph() + sage: G.add_edges([(1,2),(1,13),(1,12),(1,8),(1,4),(2,13),(2,3),(3,13)]) + sage: G.add_edges([(3,4),(4,5),(4,7),(5,6),(5,7),(5,8),(6,7),(8,9),(8,12)]) + sage: G.add_edges([(8,11),(9,10),(9,12),(9,11),(10,11),(10,12)]) + sage: tric = Triconnectivity(G) + sage: tric.edge_status + {(0, 1, None): 1, + (0, 3, None): 2, + (0, 7, None): 2, + (0, 11, None): 2, + (0, 12, None): 2, + (1, 2, None): 1, + (1, 12, None): 2, + (2, 3, None): 1, + (2, 12, None): 1, + (3, 4, None): 1, + (3, 6, None): 2, + (4, 5, None): 1, + (4, 6, None): 2, + (4, 7, None): 1, + (5, 6, None): 1, + (7, 8, None): 1, + (7, 10, None): 2, + (7, 11, None): 2, + (8, 9, None): 1, + (8, 10, None): 2, + (8, 11, None): 2, + (9, 10, None): 1, + (9, 11, None): 1} + sage: tric.lowpt1 + [1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 8, 1, 1] + sage: tric.lowpt2 + [1, 2, 2, 4, 4, 5, 5, 8, 8, 8, 9, 8, 2] + sage: tric.parent + [None, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 9, 2] + """ + + self.dfs_counter += 1 + self.dfs_number[v] = self.dfs_counter + self.parent[v] = u + self.lowpt1[v] = self.lowpt2[v] = self.dfs_number[v] + self.nd[v] = 1 + for e in self.graph_copy.edges_incident([v]): + if self.edge_status[e] != 0 : + continue + + w = e[0] if e[0] != v else e[1] # Other vertex of edge e + if (self.dfs_number[w] == 0): + self.edge_status[e] = 1 # tree edge + self.tree_arc[w] = e + self.dfs1(w, v) + + if (self.lowpt1[w] < self.lowpt1[v]): + self.lowpt2[v] = min(self.lowpt1[v], self.lowpt2[w]) + self.lowpt1[v] = self.lowpt1[w] + + elif (self.lowpt1[w] == self.lowpt1[v]): + self.lowpt2[v] = min(self.lowpt2[v], self.lowpt2[w]) + + else: + self.lowpt2[v] = min(self.lowpt2[v], self.lowpt1[w]) + + self.nd[v] += self.nd[w] + + else: + self.edge_status[e] = 2 #frond + if (self.dfs_number[w] < self.lowpt1[v]): + self.lowpt2[v] = self.lowpt1[v] + self.lowpt1[v] = self.dfs_number[w] + elif (self.dfs_number[w] > self.lowpt1[v]): + self.lowpt2[v] = min(self.lowpt2[v], self.dfs_number[w]) + From 6bb279a8b9a07aa68fb3352dea204932dc6e2be2 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Fri, 22 Jun 2018 01:28:41 +0530 Subject: [PATCH 046/264] Fixed a small error in split_multi_edges() --- src/sage/graphs/connectivity.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 070b81d065d..720628f1be2 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2481,7 +2481,7 @@ class Triconnectivity: # It will add k - 1 multiple edges to comp if sorted_edges[i] == sorted_edges[i + 1]: - self.graph_copy.delete_edge(sorted_edges[i]) + self.edge_status[sorted_edges[i]] = 3 # edge removed comp.append(sorted_edges[i]) else: if len(comp): From d7002b473bcce2f0495f166378d724d6f5b9720c Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Fri, 22 Jun 2018 08:45:44 +0530 Subject: [PATCH 047/264] Modified dfs1() to check biconnectivity --- src/sage/graphs/connectivity.pyx | 70 ++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 720628f1be2..fcd00bf1b60 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2381,6 +2381,7 @@ class Triconnectivity: graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph + int_to_vertex = {} # mapping of integers to original vertices start_vertex = 0 num_components = 0 edge_status = {} #status of each edge, unseen=0, tree=1, frond=2, removed=3 @@ -2395,7 +2396,7 @@ class Triconnectivity: adj = [] # i^th value contains a list of incident edges of vertex i in_adj = {} # this variable is used in the PathSearch() function newnum = [] # new DFS number of vertex i - startsPath = {} # dict of (edge, True/False) to denote if a path starts at edge + startsPath = {} # dict of (edge, T/F) to denote if a path starts at edge highpt = [] # List of fronds entering vertex i in the order they are visited old_to_new = [] # New DFS number of the vertex with i as old DFS number degree = [] # Degree of vertex i @@ -2406,11 +2407,16 @@ class Triconnectivity: dfs_counter = 0 n = 0 # number of vertices m = 0 # number of edges + check = True # If the graph needs to be tested for biconnectivity + is_biconnected = True # Boolean to store if the graph is biconnected or not + cut_vertex = None # If graph is not biconnected - def __init__(self, G): + def __init__(self, G, check=True): self.graph_copy = G.copy(implementation='c_graph') + self.check = check self.vertex_to_int = self.graph_copy.relabel(inplace=True, return_map=True) + self.int_to_vertex = dict([(v,k) for k,v in self.vertex_to_int.items()]) self.n = self.graph_copy.order() self.m = self.graph_copy.size() self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) @@ -2427,7 +2433,20 @@ class Triconnectivity: self.split_multi_egdes() self.dfs_counter = 0 # Initialisation for dfs1() self.start_vertex = 0 # Initialisation for dfs1() - self.dfs1(self.start_vertex) + self.cut_vertex = self.dfs1(self.start_vertex, check=check) + + if (check == True): + # graph is disconnected + if (self.dfs_number < self.n): + self.is_biconnected = False + return + + # graph has a cut vertex + if (self.cut_vertex != None): + self.cut_vertex = self.int_to_vertex[self.cut_vertex] + self.is_biconnected = False + return + def new_component(self, edges, type_c=0): c = self.Component(edges, type_c) @@ -2494,7 +2513,8 @@ class Triconnectivity: comp.append(sorted_edges[i-1]) self.new_component(comp) - def dfs1(self, v, u=None): + + def dfs1(self, v, u=None, check=True): """ This function builds the palm tree of the graph. Also populates the lists lowpt1, lowpt2, nd, parent, and dfs_number. @@ -2506,16 +2526,26 @@ class Triconnectivity: - ``u`` -- The parent vertex of ``v`` in the palm tree. + - ``check`` -- if True, the graph is tested for biconnectivity. If the + graph has a cut vertex, the cut vertex is returned. If set to False, + the graph is assumed to be biconnected, function returns None. + + Output:: + + - If ``check`` is set to True, and a cut vertex is found, the cut vertex + is returned. If no cut vertex is found, return value if None. + If ``check`` is set to False, ``None`` is returned. + Example:: - An example to list the components build after removal of multiple edges + An example to build the palm tree: sage: from sage.graphs.connectivity import Triconnectivity sage: G = Graph() sage: G.add_edges([(1,2),(1,13),(1,12),(1,8),(1,4),(2,13),(2,3),(3,13)]) sage: G.add_edges([(3,4),(4,5),(4,7),(5,6),(5,7),(5,8),(6,7),(8,9),(8,12)]) sage: G.add_edges([(8,11),(9,10),(9,12),(9,11),(10,11),(10,12)]) - sage: tric = Triconnectivity(G) + sage: tric = Triconnectivity(G, check=False) sage: tric.edge_status {(0, 1, None): 1, (0, 3, None): 2, @@ -2546,11 +2576,24 @@ class Triconnectivity: [1, 2, 2, 4, 4, 5, 5, 8, 8, 8, 9, 8, 2] sage: tric.parent [None, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 9, 2] + + An example of a disconnected graph: + + sage: G = Graph() + sage: G.add_edges([(1,2),(1,3),(2,3),(3,4),(3,5),(4,5)]) + sage: tric = Triconnectivity(G, check=True) + sage: tric.is_biconnected + False + sage: tric.cut_vertex + 3 """ + first_son = None # For testing biconnectivity + s1 = None # Storing the cut vertex, if there is one self.dfs_counter += 1 self.dfs_number[v] = self.dfs_counter self.parent[v] = u + self.degree[v] = self.graph_copy.degree(v) self.lowpt1[v] = self.lowpt2[v] = self.dfs_number[v] self.nd[v] = 1 for e in self.graph_copy.edges_incident([v]): @@ -2560,12 +2603,19 @@ class Triconnectivity: w = e[0] if e[0] != v else e[1] # Other vertex of edge e if (self.dfs_number[w] == 0): self.edge_status[e] = 1 # tree edge + if (first_son == None): + first_son = w self.tree_arc[w] = e - self.dfs1(w, v) + s1 = self.dfs1(w, v, check) + + if (check == True): + # check for cut vertex + if ((self.lowpt1[w] >= self.dfs_number[v]) and (w != first_son or u != None)): + s1 = v if (self.lowpt1[w] < self.lowpt1[v]): - self.lowpt2[v] = min(self.lowpt1[v], self.lowpt2[w]) - self.lowpt1[v] = self.lowpt1[w] + self.lowpt2[v] = min(self.lowpt1[v], self.lowpt2[w]) + self.lowpt1[v] = self.lowpt1[w] elif (self.lowpt1[w] == self.lowpt1[v]): self.lowpt2[v] = min(self.lowpt2[v], self.lowpt2[w]) @@ -2583,3 +2633,5 @@ class Triconnectivity: elif (self.dfs_number[w] > self.lowpt1[v]): self.lowpt2[v] = min(self.lowpt2[v], self.dfs_number[w]) + return s1 + From c45c5addce4f78aa23f297aad7d8941991aab825 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Fri, 22 Jun 2018 23:48:01 +0530 Subject: [PATCH 048/264] Added the function build_acceptable_adjacency_structure() --- src/sage/graphs/connectivity.pyx | 90 +++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 13 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index fcd00bf1b60..59c21d4562d 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2384,7 +2384,7 @@ class Triconnectivity: int_to_vertex = {} # mapping of integers to original vertices start_vertex = 0 num_components = 0 - edge_status = {} #status of each edge, unseen=0, tree=1, frond=2, removed=3 + edge_status = {} # status of each edge, unseen=0, tree=1, frond=2, removed=3 Estack = [] Tstack = [] dfs_number = [] # DFS number of vertex i @@ -2407,27 +2407,31 @@ class Triconnectivity: dfs_counter = 0 n = 0 # number of vertices m = 0 # number of edges - check = True # If the graph needs to be tested for biconnectivity is_biconnected = True # Boolean to store if the graph is biconnected or not cut_vertex = None # If graph is not biconnected + # T/F to denote if the source and target of the palm tree edge are + # opposite to that of the graph + edge_reverse = {} + def __init__(self, G, check=True): self.graph_copy = G.copy(implementation='c_graph') - self.check = check self.vertex_to_int = self.graph_copy.relabel(inplace=True, return_map=True) self.int_to_vertex = dict([(v,k) for k,v in self.vertex_to_int.items()]) self.n = self.graph_copy.order() self.m = self.graph_copy.size() self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) - self.dfs_number = [0]*self.n - self.lowpt1 = [None]*self.n - self.lowpt2 = [None]*self.n - self.nd = [None]*self.n - self.parent = [None]*self.n - self.degree = [None]*self.n - self.tree_arc = [None]*self.n - self.vertex_at = [1]*self.n + self.edge_reverse = dict((e,False) for e in self.graph_copy.edges()) + self.dfs_number = [0 for i in xrange(self.n)] + self.lowpt1 = [None for i in xrange(self.n)] + self.lowpt2 = [None for i in xrange(self.n)] + self.adj = [[] for i in xrange(self.n)] + self.nd = [None for i in xrange(self.n)] + self.parent = [None for i in xrange(self.n)] + self.degree = [None for i in xrange(self.n)] + self.tree_arc = [None for i in xrange(self.n)] + self.vertex_at = [1 for i in xrange(self.n)] self.dfs_counter = 0 self.components_list = [] #list of components self.split_multi_egdes() @@ -2446,7 +2450,20 @@ class Triconnectivity: self.cut_vertex = self.int_to_vertex[self.cut_vertex] self.is_biconnected = False return - + + # Reversing the edges to reflect the palm tree arcs and fronds + # Is there a better way to do it? + for e in self.graph_copy.edges(): + up = (self.dfs_number[e[1]] - self.dfs_number[e[0]]) > 0 + if ((up and self.edge_status[e]==2) or (not up and self.edge_status[e]==1)): + # Reverse the edge + # self.graph_copy.delete_edge(e) + # self.graph_copy.add_edge(e[1],e[0]) + # self.edge_status[(e[1],e[0],e[2])] = self.edge_status.pop(e) + self.edge_reverse[e] = True + + self.build_acceptable_adj_struct() + def new_component(self, edges, type_c=0): c = self.Component(edges, type_c) @@ -2634,4 +2651,51 @@ class Triconnectivity: self.lowpt2[v] = min(self.lowpt2[v], self.dfs_number[w]) return s1 - + + + def build_acceptable_adj_struct(self): + """ + Builds the adjacency lists for each vertex with certain properties + of the ordering, using the ``lowpt1`` and ``lowpt2`` values. + + The list ``adj`` and the dictionary ``in_adj`` are populated. + """ + max = 3*self.n + 2 + bucket = [[] for i in range(max+1)] + + for e in self.graph_copy.edges(): + edge_type = self.edge_status[e] + if (edge_type == 3): + continue + + # compute phi value + # the if condition for edge_reverse can be avoided if we find + # a different way to implement reversed edges + if (self.edge_reverse[e] == True): + if (edge_type==1): # tree arc + if (self.lowpt2[e[0]] < self.dfs_number[e[1]]): + phi = 3*self.lowpt1[e[0]] + elif (self.lowpt2[e[0]] >= self.dfs_number[e[1]]): + phi = 3*self.lowpt1[e[0]] + 2 + else: # tree frond + phi = 3*self.dfs_number[e[0]]+1 + else: + if (edge_type==1): # tree arc + if (self.lowpt2[e[1]] < self.dfs_number[e[0]]): + phi = 3*self.lowpt1[e[1]] + elif (self.lowpt2[e[1]] >= self.dfs_number[e[0]]): + phi = 3*self.lowpt1[e[1]] + 2 + else: # tree frond + phi = 3*self.dfs_number[e[1]]+1 + + bucket[phi].append(e) + + for i in xrange(1,max+1): + for e in bucket[i]: + if (self.edge_reverse[e] == True): + self.adj[e[1]].append(e) + self.in_adj[e] = self.adj[e[1]] + else: + self.adj[e[0]].append(e) + self.in_adj[e] = self.adj[e[0]] + From 3582000e8bd102e50edfddbfd91154ee784503f1 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Sun, 24 Jun 2018 10:12:08 +0530 Subject: [PATCH 049/264] Added adjacency list to make adjacency queries linear time. --- src/sage/graphs/connectivity.pyx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 59c21d4562d..f8b811b48d4 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2409,6 +2409,7 @@ class Triconnectivity: m = 0 # number of edges is_biconnected = True # Boolean to store if the graph is biconnected or not cut_vertex = None # If graph is not biconnected + graph_copy_adjacency = [] # T/F to denote if the source and target of the palm tree edge are # opposite to that of the graph @@ -2434,6 +2435,14 @@ class Triconnectivity: self.vertex_at = [1 for i in xrange(self.n)] self.dfs_counter = 0 self.components_list = [] #list of components + self.graph_copy_adjacency = [[] for i in xrange(self.n)] + + # Build adjacency list + for e in self.graph_copy.edges(): + self.graph_copy_adjacency[e[0]].append(e) + self.graph_copy_adjacency[e[1]].append(e) + + # Triconnectivity algorithm self.split_multi_egdes() self.dfs_counter = 0 # Initialisation for dfs1() self.start_vertex = 0 # Initialisation for dfs1() @@ -2613,7 +2622,7 @@ class Triconnectivity: self.degree[v] = self.graph_copy.degree(v) self.lowpt1[v] = self.lowpt2[v] = self.dfs_number[v] self.nd[v] = 1 - for e in self.graph_copy.edges_incident([v]): + for e in self.graph_copy_adjacency[v]: if self.edge_status[e] != 0 : continue From b263a72b2482e062c8ef498f093e70ce0acea04c Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Mon, 25 Jun 2018 07:38:14 +0530 Subject: [PATCH 050/264] Some changes in coding style --- src/sage/graphs/connectivity.pyx | 88 +++++++++++++++----------------- 1 file changed, 41 insertions(+), 47 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index f8b811b48d4..5f8ab532fcd 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2411,9 +2411,8 @@ class Triconnectivity: cut_vertex = None # If graph is not biconnected graph_copy_adjacency = [] - # T/F to denote if the source and target of the palm tree edge are - # opposite to that of the graph - edge_reverse = {} + # Edges of the graph which are in the reverse direction in palm tree + reverse_edges = set() def __init__(self, G, check=True): @@ -2423,19 +2422,19 @@ class Triconnectivity: self.n = self.graph_copy.order() self.m = self.graph_copy.size() self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) - self.edge_reverse = dict((e,False) for e in self.graph_copy.edges()) - self.dfs_number = [0 for i in xrange(self.n)] - self.lowpt1 = [None for i in xrange(self.n)] - self.lowpt2 = [None for i in xrange(self.n)] - self.adj = [[] for i in xrange(self.n)] - self.nd = [None for i in xrange(self.n)] - self.parent = [None for i in xrange(self.n)] - self.degree = [None for i in xrange(self.n)] - self.tree_arc = [None for i in xrange(self.n)] - self.vertex_at = [1 for i in xrange(self.n)] + self.reverse_edges = set() + self.dfs_number = [0 for i in range(self.n)] + self.lowpt1 = [None for i in range(self.n)] + self.lowpt2 = [None for i in range(self.n)] + self.adj = [[] for i in range(self.n)] + self.nd = [None for i in range(self.n)] + self.parent = [None for i in range(self.n)] + self.degree = [None for i in range(self.n)] + self.tree_arc = [None for i in range(self.n)] + self.vertex_at = [1 for i in range(self.n)] self.dfs_counter = 0 self.components_list = [] #list of components - self.graph_copy_adjacency = [[] for i in xrange(self.n)] + self.graph_copy_adjacency = [[] for i in range(self.n)] # Build adjacency list for e in self.graph_copy.edges(): @@ -2448,14 +2447,14 @@ class Triconnectivity: self.start_vertex = 0 # Initialisation for dfs1() self.cut_vertex = self.dfs1(self.start_vertex, check=check) - if (check == True): + if check: # graph is disconnected - if (self.dfs_number < self.n): + if self.dfs_number < self.n: self.is_biconnected = False return # graph has a cut vertex - if (self.cut_vertex != None): + if self.cut_vertex != None: self.cut_vertex = self.int_to_vertex[self.cut_vertex] self.is_biconnected = False return @@ -2464,12 +2463,9 @@ class Triconnectivity: # Is there a better way to do it? for e in self.graph_copy.edges(): up = (self.dfs_number[e[1]] - self.dfs_number[e[0]]) > 0 - if ((up and self.edge_status[e]==2) or (not up and self.edge_status[e]==1)): - # Reverse the edge - # self.graph_copy.delete_edge(e) - # self.graph_copy.add_edge(e[1],e[0]) - # self.edge_status[(e[1],e[0],e[2])] = self.edge_status.pop(e) - self.edge_reverse[e] = True + if (up and self.edge_status[e]==2) or (not up and self.edge_status[e]==1): + # Add edge to the set reverse_edges + self.reverse_edges.add(e) self.build_acceptable_adj_struct() @@ -2529,12 +2525,12 @@ class Triconnectivity: self.edge_status[sorted_edges[i]] = 3 # edge removed comp.append(sorted_edges[i]) else: - if len(comp): + if comp: comp.append(sorted_edges[i-1]) comp.append(sorted_edges[i-1]) self.new_component(comp) comp = [] - if len(comp): + if comp: comp.append(sorted_edges[i-1]) comp.append(sorted_edges[i-1]) self.new_component(comp) @@ -2623,27 +2619,27 @@ class Triconnectivity: self.lowpt1[v] = self.lowpt2[v] = self.dfs_number[v] self.nd[v] = 1 for e in self.graph_copy_adjacency[v]: - if self.edge_status[e] != 0 : - continue + if self.edge_status[e]: + continue w = e[0] if e[0] != v else e[1] # Other vertex of edge e - if (self.dfs_number[w] == 0): + if self.dfs_number[w] == 0: self.edge_status[e] = 1 # tree edge - if (first_son == None): + if first_son is None: first_son = w self.tree_arc[w] = e s1 = self.dfs1(w, v, check) - if (check == True): + if check: # check for cut vertex - if ((self.lowpt1[w] >= self.dfs_number[v]) and (w != first_son or u != None)): + if (self.lowpt1[w] >= self.dfs_number[v]) and (w != first_son or u != None): s1 = v - if (self.lowpt1[w] < self.lowpt1[v]): + if self.lowpt1[w] < self.lowpt1[v]: self.lowpt2[v] = min(self.lowpt1[v], self.lowpt2[w]) self.lowpt1[v] = self.lowpt1[w] - elif (self.lowpt1[w] == self.lowpt1[v]): + elif self.lowpt1[w] == self.lowpt1[v]: self.lowpt2[v] = min(self.lowpt2[v], self.lowpt2[w]) else: @@ -2653,10 +2649,10 @@ class Triconnectivity: else: self.edge_status[e] = 2 #frond - if (self.dfs_number[w] < self.lowpt1[v]): + if self.dfs_number[w] < self.lowpt1[v]: self.lowpt2[v] = self.lowpt1[v] self.lowpt1[v] = self.dfs_number[w] - elif (self.dfs_number[w] > self.lowpt1[v]): + elif self.dfs_number[w] > self.lowpt1[v]: self.lowpt2[v] = min(self.lowpt2[v], self.dfs_number[w]) return s1 @@ -2674,34 +2670,32 @@ class Triconnectivity: for e in self.graph_copy.edges(): edge_type = self.edge_status[e] - if (edge_type == 3): + if edge_type == 3: continue # compute phi value - # the if condition for edge_reverse can be avoided if we find - # a different way to implement reversed edges - if (self.edge_reverse[e] == True): - if (edge_type==1): # tree arc - if (self.lowpt2[e[0]] < self.dfs_number[e[1]]): + if e in self.reverse_edges: + if edge_type==1: # tree arc + if self.lowpt2[e[0]] < self.dfs_number[e[1]]: phi = 3*self.lowpt1[e[0]] - elif (self.lowpt2[e[0]] >= self.dfs_number[e[1]]): + elif self.lowpt2[e[0]] >= self.dfs_number[e[1]]: phi = 3*self.lowpt1[e[0]] + 2 else: # tree frond phi = 3*self.dfs_number[e[0]]+1 else: - if (edge_type==1): # tree arc - if (self.lowpt2[e[1]] < self.dfs_number[e[0]]): + if edge_type==1: # tree arc + if self.lowpt2[e[1]] < self.dfs_number[e[0]]: phi = 3*self.lowpt1[e[1]] - elif (self.lowpt2[e[1]] >= self.dfs_number[e[0]]): + elif self.lowpt2[e[1]] >= self.dfs_number[e[0]]: phi = 3*self.lowpt1[e[1]] + 2 else: # tree frond phi = 3*self.dfs_number[e[1]]+1 bucket[phi].append(e) - for i in xrange(1,max+1): + for i in range(1,max+1): for e in bucket[i]: - if (self.edge_reverse[e] == True): + if e in self.reverse_edges: self.adj[e[1]].append(e) self.in_adj[e] = self.adj[e[1]] else: From 90b9a10def453fff42974d3ab875436c994f9a6d Mon Sep 17 00:00:00 2001 From: saiharsh Date: Fri, 29 Jun 2018 02:31:42 +0530 Subject: [PATCH 051/264] Added DFS2 and Path Finder --- src/sage/graphs/connectivity.pyx | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 5f8ab532fcd..13e69159589 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2424,6 +2424,10 @@ class Triconnectivity: self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) self.reverse_edges = set() self.dfs_number = [0 for i in range(self.n)] + self.newnum = [0 for i in range(self.n)] + self.highpt = [[] for i in range(self.n)] + self.old_to_new = [0 for i in range(self.n + 1)] + self.nodeAt = [0 for i in range(self.n + 1)] self.lowpt1 = [None for i in range(self.n)] self.lowpt2 = [None for i in range(self.n)] self.adj = [[] for i in range(self.n)] @@ -2468,6 +2472,7 @@ class Triconnectivity: self.reverse_edges.add(e) self.build_acceptable_adj_struct() + self.dfs2() def new_component(self, edges, type_c=0): @@ -2702,3 +2707,42 @@ class Triconnectivity: self.adj[e[0]].append(e) self.in_adj[e] = self.adj[e[0]] + def pathFinder(self, v): + """ + This function is a helper function for `dfs2` function. + """ + self.newnum[v] = self.dfs_counter - self.nd[v] + 1 + for e in self.adj[v]: + u, w, _ = e + if self.edge_status[e] == 1: + if e in self.reverse_edges: + self.pathFinder(u) + else: + self.pathFinder(w) + self.dfs_counter -= 1 + else: + if e in self.reverse_edges: + self.highpt[u].append(self.newnum[w]) + else: + self.highpt[w].append(self.newnum[u]) + + def dfs2(self): + """ + Update the values of lowpt1 and lowpt2 lists with the help of + new numbering obtained from `Path Finder` funciton. + Populate `highpt` values. + """ + self.dfs_counter = self.n + + # As all the vertices are labeled from 0 to n -1, + # the first vertex will be 0. + self.pathFinder(0) + + for v in range(self.n): + self.old_to_new[self.newnum[v]] = self.newnum[v] + + # Update lowpt values. + for v in range(self.n): + self.nodeAt[self.newnum[v]] = v + self.lowpt1[v] = self.old_to_new[self.lowpt1[v]] + self.lowpt2[v] = self.old_to_new[self.lowpt2[v]] \ No newline at end of file From b1479a406b3bbe785752a41d1b260daa1f8ea78a Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Sun, 1 Jul 2018 12:26:14 +0530 Subject: [PATCH 052/264] Updated dfs2 and path finder. Added some helper functions for path_search(). --- src/sage/graphs/connectivity.pyx | 92 +++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 13e69159589..85e5c60c611 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2396,20 +2396,21 @@ class Triconnectivity: adj = [] # i^th value contains a list of incident edges of vertex i in_adj = {} # this variable is used in the PathSearch() function newnum = [] # new DFS number of vertex i - startsPath = {} # dict of (edge, T/F) to denote if a path starts at edge + starts_path = {} # dict of (edge, T/F) to denote if a path starts at edge highpt = [] # List of fronds entering vertex i in the order they are visited old_to_new = [] # New DFS number of the vertex with i as old DFS number degree = [] # Degree of vertex i parent = [] # Parent vertex of vertex i in the palm tree tree_arc = [] # Tree arc entering the vertex i first_child = [] - high = [] # One of the elements in highpt[i] + in_high = {} # One of the elements in highpt[i] dfs_counter = 0 n = 0 # number of vertices m = 0 # number of edges is_biconnected = True # Boolean to store if the graph is biconnected or not cut_vertex = None # If graph is not biconnected graph_copy_adjacency = [] + new_path = False # Boolean used to store if new path is started # Edges of the graph which are in the reverse direction in palm tree reverse_edges = set() @@ -2424,10 +2425,9 @@ class Triconnectivity: self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) self.reverse_edges = set() self.dfs_number = [0 for i in range(self.n)] - self.newnum = [0 for i in range(self.n)] self.highpt = [[] for i in range(self.n)] self.old_to_new = [0 for i in range(self.n + 1)] - self.nodeAt = [0 for i in range(self.n + 1)] + self.node_at = [0 for i in range(self.n + 1)] self.lowpt1 = [None for i in range(self.n)] self.lowpt2 = [None for i in range(self.n)] self.adj = [[] for i in range(self.n)] @@ -2474,6 +2474,29 @@ class Triconnectivity: self.build_acceptable_adj_struct() self.dfs2() + self.t_stack_h = [None for i in range(2*self.m + 1)] + self.t_stack_a = [None for i in range(2*self.m + 1)] + self.t_stack_b = [None for i in range(2*self.m + 1)] + self.t_stack_top = 0 + self.t_stack_a[self.t_stack_top] = -1 + + #self.path_search(self.start_vertex) + + # Push a triple on Tstack + def tstack_push(self, h, a, b): + self.t_stack_top += 1 + self.t_stack_h[self.t_stack_top] = h + self.t_stack_a[self.t_stack_top] = a + self.t_stack_b[self.t_stack_top] = b + + # Push end-of-stack marker on Tstack + def tstack_push_eos(self): + self.t_stack_top += 1 + self.t_stack_a[self.t_stack_top] = -1 + + # Returns true iff end-of-stack marker is not on top of Tstack + def tstack_not_eos(self): + return self.t_stack_a[self.t_stack_top] != -1 def new_component(self, edges, type_c=0): c = self.Component(edges, type_c) @@ -2482,11 +2505,27 @@ class Triconnectivity: for e in edges: self.edge_status[e] = 3 self.num_components += 1 + return c - def add_edge_to_component(self, comp, e): + def add_edges_to_component(self, comp, e): comp.add_edge(e) self.edge_status[e] = 3 + def high(self, v): + if self.highpt[v]: + return self.highpt[v][0] + else: + return 0 + + def del_high(self, e): + e = self.high[e] + if e is not None: + if e in self.reversed_edges: + v = e[0] + else: + v = e[1] + self.highpt[v].remove(e) + def split_multi_egdes(self): """ This function will remove all the multiple edges present in @@ -2707,24 +2746,24 @@ class Triconnectivity: self.adj[e[0]].append(e) self.in_adj[e] = self.adj[e[0]] - def pathFinder(self, v): + def path_finder(self, v): """ This function is a helper function for `dfs2` function. """ self.newnum[v] = self.dfs_counter - self.nd[v] + 1 for e in self.adj[v]: - u, w, _ = e - if self.edge_status[e] == 1: - if e in self.reverse_edges: - self.pathFinder(u) - else: - self.pathFinder(w) + w = e[1] if e[0] == v else e[0] # opposite vertex of e + if self.new_path: + self.new_path = False + self.starts_path[e] = True + if self.edge_status[e] == 1: # tree arc + self.path_finder(w) self.dfs_counter -= 1 else: - if e in self.reverse_edges: - self.highpt[u].append(self.newnum[w]) - else: - self.highpt[w].append(self.newnum[u]) + self.highpt[w].append(self.newnum[v]) + self.in_high[e] = self.highpt[w] + self.new_path = True + def dfs2(self): """ @@ -2732,17 +2771,22 @@ class Triconnectivity: new numbering obtained from `Path Finder` funciton. Populate `highpt` values. """ + self.highpt = [[] for i in range(self.n)] + self.in_high = dict((e, []) for e in self.graph_copy.edges()) self.dfs_counter = self.n + self.newnum = [0 for i in range(self.n)] + self.starts_path = dict((e, False) for e in self.graph_copy.edges()) + + self.new_path = True - # As all the vertices are labeled from 0 to n -1, - # the first vertex will be 0. - self.pathFinder(0) + # We call the pathFinder function with the start vertex + self.path_finder(self.start_vertex) - for v in range(self.n): - self.old_to_new[self.newnum[v]] = self.newnum[v] + for v in self.graph_copy.vertices(): + self.old_to_new[self.dfs_number[v]] = self.newnum[v] # Update lowpt values. - for v in range(self.n): - self.nodeAt[self.newnum[v]] = v + for v in self.graph_copy.vertices(): + self.node_at[self.newnum[v]] = v self.lowpt1[v] = self.old_to_new[self.lowpt1[v]] - self.lowpt2[v] = self.old_to_new[self.lowpt2[v]] \ No newline at end of file + self.lowpt2[v] = self.old_to_new[self.lowpt2[v]] From 910cf62321dec5d45cf8919d77d756da841a088b Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Thu, 5 Jul 2018 12:31:31 +0530 Subject: [PATCH 053/264] Added pathsearch() function. --- src/sage/graphs/connectivity.pyx | 299 +++++++++++++++++++++++++++++-- 1 file changed, 286 insertions(+), 13 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 85e5c60c611..7e2b72eadd1 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2378,6 +2378,12 @@ class Triconnectivity: component_type = type_c def add_edge(self, e): self.edge_list.append(e) + def finish_tric_or_poly(self, e): + self.edge_list.append(e) + if len(self.edge_list) >= 4: + component_type = 2 + else: + component_type = 1 graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph @@ -2385,8 +2391,11 @@ class Triconnectivity: start_vertex = 0 num_components = 0 edge_status = {} # status of each edge, unseen=0, tree=1, frond=2, removed=3 - Estack = [] - Tstack = [] + e_stack = [] + t_stack_h = [] + t_stack_a = [] + t_stack_b = [] + t_stack_top = 0 dfs_number = [] # DFS number of vertex i vertex_at = [] # vertex with DFS number of i$ lowpt1 = [] # lowpt1 number of vertex i @@ -2448,7 +2457,7 @@ class Triconnectivity: # Triconnectivity algorithm self.split_multi_egdes() self.dfs_counter = 0 # Initialisation for dfs1() - self.start_vertex = 0 # Initialisation for dfs1() + self.start_vertex = 7 # Initialisation for dfs1() self.cut_vertex = self.dfs1(self.start_vertex, check=check) if check: @@ -2498,7 +2507,12 @@ class Triconnectivity: def tstack_not_eos(self): return self.t_stack_a[self.t_stack_top] != -1 - def new_component(self, edges, type_c=0): + def estack_pop(self): + e = self.e_stack[-1] + self.e_stack = self.e_stack[0:-1] + return e + + def new_component(self, edges=[], type_c=0): c = self.Component(edges, type_c) self.components_list.append(c) # Remove the edges from graph @@ -2507,7 +2521,7 @@ class Triconnectivity: self.num_components += 1 return c - def add_edges_to_component(self, comp, e): + def add_edge_to_component(self, comp, e): comp.add_edge(e) self.edge_status[e] = 3 @@ -2518,13 +2532,13 @@ class Triconnectivity: return 0 def del_high(self, e): - e = self.high[e] - if e is not None: - if e in self.reversed_edges: + it = self.in_high[e] + if it: + if e in self.reverse_edges: v = e[0] else: v = e[1] - self.highpt[v].remove(e) + self.highpt[v].remove(it) def split_multi_egdes(self): """ @@ -2653,7 +2667,6 @@ class Triconnectivity: sage: tric.cut_vertex 3 """ - first_son = None # For testing biconnectivity s1 = None # Storing the cut vertex, if there is one self.dfs_counter += 1 @@ -2718,6 +2731,7 @@ class Triconnectivity: continue # compute phi value + # bucket sort adjacency list by phi values if e in self.reverse_edges: if edge_type==1: # tree arc if self.lowpt2[e[0]] < self.dfs_number[e[1]]: @@ -2741,10 +2755,10 @@ class Triconnectivity: for e in bucket[i]: if e in self.reverse_edges: self.adj[e[1]].append(e) - self.in_adj[e] = self.adj[e[1]] + self.in_adj[e] = e else: self.adj[e[0]].append(e) - self.in_adj[e] = self.adj[e[0]] + self.in_adj[e] = e def path_finder(self, v): """ @@ -2761,7 +2775,7 @@ class Triconnectivity: self.dfs_counter -= 1 else: self.highpt[w].append(self.newnum[v]) - self.in_high[e] = self.highpt[w] + self.in_high[e] = self.newnum[v] self.new_path = True @@ -2790,3 +2804,262 @@ class Triconnectivity: self.node_at[self.newnum[v]] = v self.lowpt1[v] = self.old_to_new[self.lowpt1[v]] self.lowpt2[v] = self.old_to_new[self.lowpt2[v]] + + def path_search(self, v): + """ + Function to find the separation pairs. + """ + y = 0 + vnum = self.newnum[v] + adj = self.adj[v] + outv = len(adj) + for e in adj: + it = e + if e in self.reverse_edges: + w = e[0] # target + else: + w = e[1] + wnum = self.newnum[w] + if self.edge_status[e] == 1: # tree arc + if self.starts_path[e]: + y = 0 + if self.t_stack_a[self.t_stack_top] > self.lowpt1[w]: + while self.t_stack_a[self.t_stack_top] > self.lowpt1[w]: + y = max(y, self.t_stack_h[self.t_stack_top]) + b = self.t_stack_b[self.t_stack_top] + self.t_stack_top -= 1 + self.tstack_push(y, self.lowpt1[w], b) + + else: + self.tstack_push(wnum + self.nd[w] - 1, self.lowpt1[w], vnum) + self.tstack_push_eos() + + self.path_search(w) + + self.e_stack.append(self.tree_arc[w]) + + temp = self.adj[w][0] + if temp in self.reverse_edges: + temp_target = temp[0] + else: + temp_target = temp[1] + # while vnum is not the start_vertex + while vnum != 0 and ((self.t_stack_a[self.t_stack_top] == vnum) or \ + (self.degree[w] == 2 and self.newnum[temp_target] > wnum)): + a = self.t_stack_a[self.t_stack_top] + b = self.t_stack_b[self.t_stack_top] + e_virt = None + + if a == vnum and self.parent[self.node_at[b]] == self.node_at[a]: + self.t_stack_top -= 1 + + else: + e_ab = None + if self.degree[w] == 2 and self.newnum[temp_target] > wnum: + # found type-2 separation pair + print "found type-2 separation pair (",v,", ", temp_target, ")" + e1 = self.estack_pop() + e2 = self.estack_pop() + self.adj[w].remove(self.in_adj[e2]) # check run time + + if e2 in self.reverse_edges: + x = e2[0] + else: + x = e2[1] + + #e_virt = self.graph_copy.add_edge(v, x) # but edge is not returned ? + self.graph_copy.add_edge(v, x) + e_virt = (v, x, None) + self.degree[v] -= 1 + self.degree[x] -= 1 + + if e2 in self.reverse_edges: + e2_source = e2[1] + else: + e2_source = e2[0] + if e2_source != w: # OGDF_ASSERT + raise ValueError("graph is not biconnected?") + + self.new_component([e1, e2, e_virt], 1) + + if self.e_stack: + e1 = self.e_stack[-1] + if e1 in self.reverse_edges: + if e1[1] == x and e1[0] == v: + e_ab = self.estack_pop() + self.adj[x].remove(self.in_adj[e_ab]) + self.del_high(e_ab) + else: + if e1[0] == x and e1[1] == v: + e_ab = self.estack_pop() + self.adj[x].remove(self.in_adj[e_ab]) + self.del_high(e_ab) + + else: # found type-2 separation pair + print "found type-2 separation pair (",a,", ", b, ")" + h = self.t_stack_h[self.t_stack_top] + self.t_stack_top -= 1 + + comp = self.new_component() + while True: + xy = self.e_stack[-1] + if xy in self.reverse_edges: + x = xy[1] + xy_target = xy[0] + else: + x = xy[0] + xy_target = xy[1] + if not (a <= self.newnum[x] and self.newnum[x] <= h and \ + a <= self.newnum[xy_target] and self.newnum[xy_target] <= h): + break + if (self.newnum[x] == a and self.newnum[xy_target] == b) or \ + (self.newnum[xy_target] == a and self.newnum[x] == b): + e_ab = self.estack_pop() + if e_ab in self.reverse_edges: + e_ab_source = e_ab[1] + else: + e_ab_source = e_ab[0] + self.adj[e_ab_source].remove(self.in_adj[e_ab]) + self.del_high(e_ab) + + else: + eh = self.estack_pop() + if eh in self.reverse_edges: + eh_source = eh[1] + else: + eh_source = eh[0] + if it != self.in_adj[eh]: + self.adj[eh_source].remove(self.in_adj[eh]) + self.del_high(eh) + + comp.add_edge(eh) # check + self.degree[x] -= 1 + self.degree[xy_target] -= 1 + + self.graph_copy.add_edge(self.node_at[a], self.node_at[b]) + e_virt = (self.node_at[a], self.node_at[b], None) + comp.finish_tric_or_poly(e_virt) + x = self.node_at[b] + + if e_ab is not None: + comp = self.new_component([e_ab, e_virt], type_c=0) + self.graph_copy.add_edge(v,x) + e_virt = (v,x,None) + comp.add_edge(e_virt) + self.degree[x] -= 1 + self.degree[v] -= 1 + + self.e_stack.append(e_virt) + it = e_virt + self.in_adj[e_virt] = it + self.degree[x] += 1 + self.degree[v] += 1 + self.parent[x] = v + self.tree_arc[x] = e_virt + self.edge_status[e_virt] = 1 + w = x + wnum = self.newnum[w] + + # start type-1 check + if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ + (self.parent[v] != self.start_vertex or outv >= 2): + # type-1 separation pair + print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" + c = self.new_component() + if not self.e_stack: # OGDF_ASSERT + raise ValueError("stack is empty") + while self.e_stack: + xy = self.e_stack[-1] + if xy in self.reverse_edges: + xx = self.newnum[xy[1]] #source + y = self.newnum[xy[0]] #target + else: + xx = self.newnum[xy[0]] #source + y = self.newnum[xy[1]] #target + + if not ((wnum <= xx and xx < wnum + self.nd[w]) or \ + (wnum <= y and y < wnum + self.nd[w])): + break + + comp.add_edge(self.estack_pop()) + self.del_high(xy) + self.degree[self.node_at[xx]] -= 1 + self.degree[self.node_at[y]] -= 1 + + self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) + e_virt = (v, self.node_at[self.lowpt1[w]], None) + comp.finish_tric_or_poly(e_virt); + + if (xx == vnum and y == self.lowpt1[w]) or \ + (y == vnum and xx == self.lowpt1[w]): + comp_bond = self.new_component(type_c = 0) + eh = self.estack_pop() + if self.in_adj[eh] != it: + if eh in self.reverse_edges: + self.adj[eh[1]].remove(self.in_adj[eh]) + else: + self.adj[eh[0]].remove(self.in_adj[eh]) + + comp_bond.add_edge(eh) + comp_bond.add_edge(e_virt) # TODO combine them + self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) + e_virt = (v, self.node_at[self.lowpt1[w]], None) + comp_bond.add_edge(e_virt) + self.in_high[e_virt] = self.in_high[eh] + self.degree[v] -= 1 + self.degree[self.node_at[self.lowpt1[w]]] -= 1 + + if self.node_at[self.lowpt1[w]] != self.parent[v]: + self.e_stack.append(e_virt) + it = e_virt + self.in_adj[e_virt] = it + if not self.in_high[e_virt] and self.high(self.node_at[self.lowpt1[w]]) < vnum: + self.highpt[self.node_at[self.lowpt1[w]]] = [vnum] + self.highpt[self.node_at[self.lowpt1[w]]] + self.in_high[e_virt] = vnum + + self.degree[v] += 1 + self.degree[self.node_at[self.lowpt1[w]]] += 1 + + else: + adj.remove(it) + comp_bond = self.new_component([e_virt], type_c=0) + self.graph_copy.add_edge(self.node_at[self.lowpt1[w]], v) + e_virt = (self.node_at[self.lowpt1[w]], c, None) + comp_bond.add_edge(e_virt) + + eh = self.tree_arc[v]; + comp_bond.add_edge(eh) + + self.tree_arc[v] = e_virt + self.egde_status[e_virt] = 1 + self.in_adj[e_virt] = self.in_adj[eh] + self.in_adj[eh] = e_virt + + # end type-1 search + if self.starts_path[e]: + while self.tstack_not_eos(): + self.t_stack_top -= 1 + self.t_stack_top -= 1 + + while self.tstack_not_eos() and self.t_stack_b[self.t_stack_top] != vnum \ + and self.high(v) > self.t_stack_h[self.t_stack_top]: + self.t_stack_top -= 1 + + outv -= 1 + + else: #frond + if self.starts_path[e]: + y = 0 + if self.t_stack_a[self.t_stack_top] > wnum: + while self.t_stack_a[self.t_stack_top] > wnum: + y = max(y, self.t_stack_h[self.t_stack_top]) + b = self.t_stack_b[self.t_stack_top] + self.t_stack_top -= 1 + self.tstack_push(y, wnum, b) + + else: + self.tstack_push(vnum, wnum, vnum) + self.e_stack.append(e) # add (v,w) to ESTACK + + + From 600a1bbf5f5c3f5aec79908689809af20f29c5d1 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Thu, 5 Jul 2018 12:45:31 +0530 Subject: [PATCH 054/264] Fixed a small error --- src/sage/graphs/connectivity.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 7e2b72eadd1..a8afbc080cb 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2489,7 +2489,7 @@ class Triconnectivity: self.t_stack_top = 0 self.t_stack_a[self.t_stack_top] = -1 - #self.path_search(self.start_vertex) + self.path_search(self.start_vertex) # Push a triple on Tstack def tstack_push(self, h, a, b): @@ -2965,7 +2965,7 @@ class Triconnectivity: (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" - c = self.new_component() + comp = self.new_component() if not self.e_stack: # OGDF_ASSERT raise ValueError("stack is empty") while self.e_stack: @@ -3024,7 +3024,7 @@ class Triconnectivity: adj.remove(it) comp_bond = self.new_component([e_virt], type_c=0) self.graph_copy.add_edge(self.node_at[self.lowpt1[w]], v) - e_virt = (self.node_at[self.lowpt1[w]], c, None) + e_virt = (self.node_at[self.lowpt1[w]], v, None) comp_bond.add_edge(e_virt) eh = self.tree_arc[v]; From 18f7b587fdead5b0a382060621efaf3a477f4d88 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Mon, 16 Jul 2018 00:56:51 +0530 Subject: [PATCH 055/264] Fixed a bug with pathsearch() --- src/sage/graphs/connectivity.pyx | 127 ++++++++++++++++++++++++------- 1 file changed, 98 insertions(+), 29 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index a8afbc080cb..2b8d52e2f01 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2351,6 +2351,21 @@ def spqr_tree_to_graph(T): from sage.graphs.base.sparse_graph cimport SparseGraph +class Component: + edge_list = [] + component_type = 0 #bond = 0, polygon = 1, triconnected = 2 + def __init__(self, edges, type_c): + self.edge_list = edges + self.component_type = type_c + print "creating new component ", edges + def add_edge(self, e): + self.edge_list.append(e) + def finish_tric_or_poly(self, e): + self.edge_list.append(e) + if len(self.edge_list) >= 4: + self.component_type = 2 + else: + self.component_type = 1 class Triconnectivity: """ @@ -2370,20 +2385,7 @@ class Triconnectivity: sage: tric.components_list [] """ - class Component: - edge_list = [] - component_type = 0 #bond = 0, polygon = 1, triconnected = 2 - def __init__(self, edges, type_c=0): - self.edge_list = edges - component_type = type_c - def add_edge(self, e): - self.edge_list.append(e) - def finish_tric_or_poly(self, e): - self.edge_list.append(e) - if len(self.edge_list) >= 4: - component_type = 2 - else: - component_type = 1 + graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph @@ -2457,7 +2459,7 @@ class Triconnectivity: # Triconnectivity algorithm self.split_multi_egdes() self.dfs_counter = 0 # Initialisation for dfs1() - self.start_vertex = 7 # Initialisation for dfs1() + self.start_vertex = 0 # Initialisation for dfs1() self.cut_vertex = self.dfs1(self.start_vertex, check=check) if check: @@ -2491,6 +2493,16 @@ class Triconnectivity: self.path_search(self.start_vertex) + # last split component + print "last split component ", self.e_stack + c = Component([],0) + print "new component edge list ", c.edge_list + while self.e_stack: + c.add_edge(self.estack_pop()) + c.component_type = 2 if len(c.edge_list) > 4 else 1 + self.components_list.append(c) + c = None + # Push a triple on Tstack def tstack_push(self, h, a, b): self.t_stack_top += 1 @@ -2513,7 +2525,7 @@ class Triconnectivity: return e def new_component(self, edges=[], type_c=0): - c = self.Component(edges, type_c) + c = Component(edges, type_c) self.components_list.append(c) # Remove the edges from graph for e in edges: @@ -2539,6 +2551,7 @@ class Triconnectivity: else: v = e[1] self.highpt[v].remove(it) + print "delhigh test ", e, self.highpt[v], it def split_multi_egdes(self): """ @@ -2581,6 +2594,7 @@ class Triconnectivity: # It will add k - 1 multiple edges to comp if sorted_edges[i] == sorted_edges[i + 1]: self.edge_status[sorted_edges[i]] = 3 # edge removed + print "edge removed: ", sorted_edges[i], self.edge_status[sorted_edges[i]] comp.append(sorted_edges[i]) else: if comp: @@ -2592,6 +2606,9 @@ class Triconnectivity: comp.append(sorted_edges[i-1]) comp.append(sorted_edges[i-1]) self.new_component(comp) + print "Edge status after split_multi_edges():" + for e in self.graph_copy.edges(): + print e, self.edge_status[e] def dfs1(self, v, u=None, check=True): @@ -2811,10 +2828,18 @@ class Triconnectivity: """ y = 0 vnum = self.newnum[v] - adj = self.adj[v] - outv = len(adj) - for e in adj: + outv = len(self.adj[v]) + #print "path_search(v) with parameter ", v, " with vnum ", vnum + #print "PRINTING ADJ IN PATHSEARCH ", v + #print self.adj + #for e in adj: + # ERROR fixed + for i in range(len(self.adj[v])): + #it = e + e = self.adj[v][i] it = e + + print "going through edges of ", v, ": edge ", e if e in self.reverse_edges: w = e[0] # target else: @@ -2843,13 +2868,22 @@ class Triconnectivity: temp_target = temp[0] else: temp_target = temp[1] + #print "w and its adjacency: ", w, " " , temp # while vnum is not the start_vertex - while vnum != 0 and ((self.t_stack_a[self.t_stack_top] == vnum) or \ + #print "checking the while nvum!=1 test ", vnum + #print "stack_top_num=", self.t_stack_top, " :stack_top=",self.t_stack_a[self.t_stack_top] + #print "degree[w]=", self.degree[w], " target=", temp_target, " last val=", self.newnum[temp_target] + #print "WHILECHECK:", self.adj[w] + while vnum != 1 and ((self.t_stack_a[self.t_stack_top] == vnum) or \ (self.degree[w] == 2 and self.newnum[temp_target] > wnum)): + #print "entered the nvum!=1 while loop ", vnum a = self.t_stack_a[self.t_stack_top] b = self.t_stack_b[self.t_stack_top] e_virt = None + print "list indices NONE?? ", a, b + print self.node_at[a] + print self.node_at[b] if a == vnum and self.parent[self.node_at[b]] == self.node_at[a]: self.t_stack_top -= 1 @@ -2880,7 +2914,10 @@ class Triconnectivity: if e2_source != w: # OGDF_ASSERT raise ValueError("graph is not biconnected?") - self.new_component([e1, e2, e_virt], 1) + print "before creating new component ", [e1, e2, e_virt] + comp = Component([e1, e2, e_virt], 1) + self.components_list.append(comp) + comp = None if self.e_stack: e1 = self.e_stack[-1] @@ -2900,7 +2937,8 @@ class Triconnectivity: h = self.t_stack_h[self.t_stack_top] self.t_stack_top -= 1 - comp = self.new_component() + print "before creating new component - empty edge_list" + comp = Component([],0) while True: xy = self.e_stack[-1] if xy in self.reverse_edges: @@ -2939,18 +2977,27 @@ class Triconnectivity: self.graph_copy.add_edge(self.node_at[a], self.node_at[b]) e_virt = (self.node_at[a], self.node_at[b], None) comp.finish_tric_or_poly(e_virt) + self.components_list.append(comp) + comp = None x = self.node_at[b] if e_ab is not None: - comp = self.new_component([e_ab, e_virt], type_c=0) + print "before creating new component ", [e_ab, e_virt] + comp = Component([e_ab, e_virt], type_c=0) self.graph_copy.add_edge(v,x) e_virt = (v,x,None) comp.add_edge(e_virt) self.degree[x] -= 1 self.degree[v] -= 1 + self.components_list.append(comp) + comp = None self.e_stack.append(e_virt) + #it = e_virt + # ERROR fixed + self.adj[v][i] = e_virt it = e_virt + self.in_adj[e_virt] = it self.degree[x] += 1 self.degree[v] += 1 @@ -2960,12 +3007,14 @@ class Triconnectivity: w = x wnum = self.newnum[w] + print "going to start type-1 check" # start type-1 check if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" - comp = self.new_component() + print "before creating new component - empty edgelist" + comp = Component([],0) if not self.e_stack: # OGDF_ASSERT raise ValueError("stack is empty") while self.e_stack: @@ -2986,13 +3035,18 @@ class Triconnectivity: self.degree[self.node_at[xx]] -= 1 self.degree[self.node_at[y]] -= 1 + #print "TYPE1: xx and y,, and vnum and wnum" , xx, y, vnum, self.lowpt1[w] self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) e_virt = (v, self.node_at[self.lowpt1[w]], None) - comp.finish_tric_or_poly(e_virt); + comp.finish_tric_or_poly(e_virt) + self.components_list.append(comp) + comp = None if (xx == vnum and y == self.lowpt1[w]) or \ (y == vnum and xx == self.lowpt1[w]): - comp_bond = self.new_component(type_c = 0) + #print "TYPE1:firstIFcondition" + print "before creating new component - empty edgelist " + comp_bond = Component([],type_c = 0) eh = self.estack_pop() if self.in_adj[eh] != it: if eh in self.reverse_edges: @@ -3009,11 +3063,22 @@ class Triconnectivity: self.degree[v] -= 1 self.degree[self.node_at[self.lowpt1[w]]] -= 1 + self.components_list.append(comp_bond) + comp_bond = None + if self.node_at[self.lowpt1[w]] != self.parent[v]: self.e_stack.append(e_virt) + + #it = e_virt + # ERROR fixed + self.adj[v][i] = e_virt it = e_virt + self.in_adj[e_virt] = it - if not self.in_high[e_virt] and self.high(self.node_at[self.lowpt1[w]]) < vnum: + #print "TYPE1:secondIFcondition ", it, self.in_adj[e_virt] + # ERROR1 fixed: + #if not self.in_high[e_virt] and self.high(self.node_at[self.lowpt1[w]]) < vnum: + if not e_virt in self.in_high and self.high(self.node_at[self.lowpt1[w]]) < vnum: self.highpt[self.node_at[self.lowpt1[w]]] = [vnum] + self.highpt[self.node_at[self.lowpt1[w]]] self.in_high[e_virt] = vnum @@ -3021,8 +3086,9 @@ class Triconnectivity: self.degree[self.node_at[self.lowpt1[w]]] += 1 else: - adj.remove(it) - comp_bond = self.new_component([e_virt], type_c=0) + self.adj[v].remove(it) + print "before creating new component ", [e_virt] + comp_bond = Component([e_virt], type_c=0) self.graph_copy.add_edge(self.node_at[self.lowpt1[w]], v) e_virt = (self.node_at[self.lowpt1[w]], v, None) comp_bond.add_edge(e_virt) @@ -3030,6 +3096,9 @@ class Triconnectivity: eh = self.tree_arc[v]; comp_bond.add_edge(eh) + self.components_list.append(comp_bond) + comp_bond = None + self.tree_arc[v] = e_virt self.egde_status[e_virt] = 1 self.in_adj[e_virt] = self.in_adj[eh] From d49175bd8f246f0d21f1b3f00a5db7ef8459828e Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Mon, 16 Jul 2018 01:17:54 +0530 Subject: [PATCH 056/264] Removed extra print statements. Error with edge_status dictionary. --- src/sage/graphs/connectivity.pyx | 37 ++++---------------------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 2b8d52e2f01..2eff802bb1f 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2357,7 +2357,6 @@ class Component: def __init__(self, edges, type_c): self.edge_list = edges self.component_type = type_c - print "creating new component ", edges def add_edge(self, e): self.edge_list.append(e) def finish_tric_or_poly(self, e): @@ -2494,9 +2493,7 @@ class Triconnectivity: self.path_search(self.start_vertex) # last split component - print "last split component ", self.e_stack c = Component([],0) - print "new component edge list ", c.edge_list while self.e_stack: c.add_edge(self.estack_pop()) c.component_type = 2 if len(c.edge_list) > 4 else 1 @@ -2551,7 +2548,6 @@ class Triconnectivity: else: v = e[1] self.highpt[v].remove(it) - print "delhigh test ", e, self.highpt[v], it def split_multi_egdes(self): """ @@ -2588,13 +2584,12 @@ class Triconnectivity: comp = [] if self.graph_copy.has_multiple_edges(): - sorted_edges = sorted(self.graph_copy.multiple_edges(labels=False)) + sorted_edges = sorted(self.graph_copy.multiple_edges(labels=True)) for i in range(len(sorted_edges) - 1): # It will add k - 1 multiple edges to comp if sorted_edges[i] == sorted_edges[i + 1]: self.edge_status[sorted_edges[i]] = 3 # edge removed - print "edge removed: ", sorted_edges[i], self.edge_status[sorted_edges[i]] comp.append(sorted_edges[i]) else: if comp: @@ -2606,9 +2601,6 @@ class Triconnectivity: comp.append(sorted_edges[i-1]) comp.append(sorted_edges[i-1]) self.new_component(comp) - print "Edge status after split_multi_edges():" - for e in self.graph_copy.edges(): - print e, self.edge_status[e] def dfs1(self, v, u=None, check=True): @@ -2829,17 +2821,13 @@ class Triconnectivity: y = 0 vnum = self.newnum[v] outv = len(self.adj[v]) - #print "path_search(v) with parameter ", v, " with vnum ", vnum - #print "PRINTING ADJ IN PATHSEARCH ", v - #print self.adj - #for e in adj: # ERROR fixed + #for e in adj: for i in range(len(self.adj[v])): #it = e e = self.adj[v][i] it = e - print "going through edges of ", v, ": edge ", e if e in self.reverse_edges: w = e[0] # target else: @@ -2868,20 +2856,13 @@ class Triconnectivity: temp_target = temp[0] else: temp_target = temp[1] - #print "w and its adjacency: ", w, " " , temp # while vnum is not the start_vertex - #print "checking the while nvum!=1 test ", vnum - #print "stack_top_num=", self.t_stack_top, " :stack_top=",self.t_stack_a[self.t_stack_top] - #print "degree[w]=", self.degree[w], " target=", temp_target, " last val=", self.newnum[temp_target] - #print "WHILECHECK:", self.adj[w] while vnum != 1 and ((self.t_stack_a[self.t_stack_top] == vnum) or \ (self.degree[w] == 2 and self.newnum[temp_target] > wnum)): - #print "entered the nvum!=1 while loop ", vnum a = self.t_stack_a[self.t_stack_top] b = self.t_stack_b[self.t_stack_top] e_virt = None - print "list indices NONE?? ", a, b print self.node_at[a] print self.node_at[b] if a == vnum and self.parent[self.node_at[b]] == self.node_at[a]: @@ -2914,7 +2895,6 @@ class Triconnectivity: if e2_source != w: # OGDF_ASSERT raise ValueError("graph is not biconnected?") - print "before creating new component ", [e1, e2, e_virt] comp = Component([e1, e2, e_virt], 1) self.components_list.append(comp) comp = None @@ -2937,7 +2917,6 @@ class Triconnectivity: h = self.t_stack_h[self.t_stack_top] self.t_stack_top -= 1 - print "before creating new component - empty edge_list" comp = Component([],0) while True: xy = self.e_stack[-1] @@ -2982,7 +2961,6 @@ class Triconnectivity: x = self.node_at[b] if e_ab is not None: - print "before creating new component ", [e_ab, e_virt] comp = Component([e_ab, e_virt], type_c=0) self.graph_copy.add_edge(v,x) e_virt = (v,x,None) @@ -2993,8 +2971,8 @@ class Triconnectivity: comp = None self.e_stack.append(e_virt) - #it = e_virt # ERROR fixed + #it = e_virt self.adj[v][i] = e_virt it = e_virt @@ -3007,13 +2985,11 @@ class Triconnectivity: w = x wnum = self.newnum[w] - print "going to start type-1 check" # start type-1 check if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" - print "before creating new component - empty edgelist" comp = Component([],0) if not self.e_stack: # OGDF_ASSERT raise ValueError("stack is empty") @@ -3035,7 +3011,6 @@ class Triconnectivity: self.degree[self.node_at[xx]] -= 1 self.degree[self.node_at[y]] -= 1 - #print "TYPE1: xx and y,, and vnum and wnum" , xx, y, vnum, self.lowpt1[w] self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) e_virt = (v, self.node_at[self.lowpt1[w]], None) comp.finish_tric_or_poly(e_virt) @@ -3044,8 +3019,6 @@ class Triconnectivity: if (xx == vnum and y == self.lowpt1[w]) or \ (y == vnum and xx == self.lowpt1[w]): - #print "TYPE1:firstIFcondition" - print "before creating new component - empty edgelist " comp_bond = Component([],type_c = 0) eh = self.estack_pop() if self.in_adj[eh] != it: @@ -3075,8 +3048,7 @@ class Triconnectivity: it = e_virt self.in_adj[e_virt] = it - #print "TYPE1:secondIFcondition ", it, self.in_adj[e_virt] - # ERROR1 fixed: + # ERROR fixed: #if not self.in_high[e_virt] and self.high(self.node_at[self.lowpt1[w]]) < vnum: if not e_virt in self.in_high and self.high(self.node_at[self.lowpt1[w]]) < vnum: self.highpt[self.node_at[self.lowpt1[w]]] = [vnum] + self.highpt[self.node_at[self.lowpt1[w]]] @@ -3087,7 +3059,6 @@ class Triconnectivity: else: self.adj[v].remove(it) - print "before creating new component ", [e_virt] comp_bond = Component([e_virt], type_c=0) self.graph_copy.add_edge(self.node_at[self.lowpt1[w]], v) e_virt = (self.node_at[self.lowpt1[w]], v, None) From df3dcb29df4e1218d646912f7efd4d3cf46a8817 Mon Sep 17 00:00:00 2001 From: saiharsh Date: Mon, 16 Jul 2018 19:36:37 +0530 Subject: [PATCH 057/264] Updated graph_copy and split_multiple_edges function --- src/sage/graphs/connectivity.pyx | 44 +++++++++++++------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 2eff802bb1f..fe62c8be148 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2385,7 +2385,7 @@ class Triconnectivity: [] """ - + graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph int_to_vertex = {} # mapping of integers to original vertices @@ -2427,7 +2427,17 @@ class Triconnectivity: def __init__(self, G, check=True): - self.graph_copy = G.copy(implementation='c_graph') + from sage.graphs.graph import Graph + self.graph_copy = Graph(multiedges=True) + edges = G.edges() + # dict to map new edges with the old edges + self.edge_label_dict = {} + for i in range(len(edges)): + newEdge = tuple([edges[i][0], edges[i][1], i]) + self.graph_copy.add_edge(newEdge) + self.edge_label_dict[newEdge] = edges[i] + self.graph_copy = self.graph_copy.copy(implementation='c_graph') + self.vertex_to_int = self.graph_copy.relabel(inplace=True, return_map=True) self.int_to_vertex = dict([(v,k) for k,v in self.vertex_to_int.items()]) self.n = self.graph_copy.order() @@ -2551,35 +2561,16 @@ class Triconnectivity: def split_multi_egdes(self): """ - This function will remove all the multiple edges present in - graph_copy and append the multiple edges in component list. + This function will mark all the multiple edges present in graph_copy + as removed and append the multiple edges in component list. If there are `k` multiple edges between `u` and `v` then `k+1` - edges will be added to a component. + edges will be added to a component and edge_status will have k-1 edges + marked a 3(i.e edge removed). It won't return anything but update the components_list and graph_copy, which will become simple graph. - Example:: - - An example to list the components build after removal of multiple edges - - sage: G = Graph() - sage: G.add_cycle(vertices=[0,1,2,3,4]) - sage: G.allow_multiple_edges(True) - sage: G.add_edges(G.edges()) - sage: G.add_edges([[0,1],[3, 4]]) - sage: from sage.graphs.connectivity import Triconnectivity - sage: t = Triconnectivity(G) - sage: for l in t.components_list: - ....: print(l.edge_list) - [(0, 1), (0, 1), (0, 1), (0, 1)] - [(0, 4), (0, 4), (0, 4)] - [(1, 2), (1, 2), (1, 2)] - [(2, 3), (2, 3), (2, 3)] - [(3, 4), (3, 4), (3, 4), (3, 4)] - sage: t.num_components - 5 """ comp = [] @@ -2588,7 +2579,8 @@ class Triconnectivity: for i in range(len(sorted_edges) - 1): # It will add k - 1 multiple edges to comp - if sorted_edges[i] == sorted_edges[i + 1]: + if (sorted_edges[i][0] == sorted_edges[i + 1][0]) and \ + (sorted_edges[i][1] == sorted_edges[i + 1][1]): self.edge_status[sorted_edges[i]] = 3 # edge removed comp.append(sorted_edges[i]) else: From c7752c9c9889bfff16f31173fd50d0cdaa0140b5 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Thu, 19 Jul 2018 12:33:16 +0530 Subject: [PATCH 058/264] Fixed all bugs in path_search. --- src/sage/graphs/connectivity.pyx | 227 ++++++++++++++++++++++--------- 1 file changed, 165 insertions(+), 62 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index fe62c8be148..beb88cf91e7 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2366,6 +2366,108 @@ class Component: else: self.component_type = 1 +class AdjNode: + prev = None + next = None + edge = None + def set_edge(self, e): + self.edge = e + def get_edge(self): + return self.edge + +class AdjList: + head = None + curr = None + length = 0 + def remove(self, node): + if node.prev == None and node.next == None: + self.head = None + elif node.prev == None: # node is head + self.head = node.next + node.next.prev = None + elif node.next == None: #node is tail + node.prev.next = None + else: + node.prev.next = node.next + node.next.prev = node.prev + self.length -= 1 + def set_head(self, h): + self.head = h + self.curr = h + self.length = 1 + def append(self, node): + if self.head == None: + self.set_head(node) + else: + self.curr.next = node + node.prev = self.curr + self.curr = node + self.length += 1 + def get_head(self): + return self.head + def get_length(self): + return self.length + def replace(self, node1, node2): + if node1.prev == None and node1.next == None: + self.head = node2 + elif node1.prev == None: # head has to be replaced + node1.next.prev = node2 + node2.next = node1.next + elif node1.next == None: + node1.prev.next = node2 + node2.prev = node1.prev + else: + node1.prev.next = node2 + node1.next.prev = node2 + node2.prev = node1.prev + node2.next = node1.next + +class HighPtNode: + prev = None + next = None + front = None #integer + def set_frond(self, f): + self.frond = f + def get_frond(self): + return self.frond + +class HighPtList: + head = None + curr = None + length = 0 + def remove(self, node): + if node.prev == None and node.next == None: + self.head = None + elif node.prev == None: # node is head + self.head = node.next + node.next.prev = None + elif node.next == None: #node is tail + node.prev.next = None + else: + node.prev.next = node.next + node.next.prev = node.prev + self.length -= 1 + def set_head(self, h): + self.head = h + self.curr = h + self.length = 1 + def append(self, node): + if self.head == None: + self.set_head(node) + else: + self.curr.next = node + node.prev = self.curr + self.curr = node + self.length += 1 + def push_front(self, node): + if self.head == None: + self.head = node + else: + self.head.prev = node + node.next = self.head + self.head = node + + class Triconnectivity: """ This module is not yet complete, it has work to be done. @@ -2384,8 +2486,6 @@ class Triconnectivity: sage: tric.components_list [] """ - - graph_copy = None #type SparseGraph vertex_to_int = {} # mapping of vertices to integers in c_graph int_to_vertex = {} # mapping of integers to original vertices @@ -2403,7 +2503,7 @@ class Triconnectivity: lowpt2 = [] # lowpt2 number of vertex i nd = [] # number of descendants of vertex i edge_phi = {} # (key, value) = (edge, corresponding phi value) - adj = [] # i^th value contains a list of incident edges of vertex i + adj = [] # i^th value contains a AdjList of incident edges of vertex i in_adj = {} # this variable is used in the PathSearch() function newnum = [] # new DFS number of vertex i starts_path = {} # dict of (edge, T/F) to denote if a path starts at edge @@ -2445,12 +2545,12 @@ class Triconnectivity: self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) self.reverse_edges = set() self.dfs_number = [0 for i in range(self.n)] - self.highpt = [[] for i in range(self.n)] + self.highpt = [HighPtList() for i in range(self.n)] self.old_to_new = [0 for i in range(self.n + 1)] self.node_at = [0 for i in range(self.n + 1)] self.lowpt1 = [None for i in range(self.n)] self.lowpt2 = [None for i in range(self.n)] - self.adj = [[] for i in range(self.n)] + self.adj = [AdjList() for i in range(self.n)] self.nd = [None for i in range(self.n)] self.parent = [None for i in range(self.n)] self.degree = [None for i in range(self.n)] @@ -2540,24 +2640,22 @@ class Triconnectivity: self.num_components += 1 return c - def add_edge_to_component(self, comp, e): - comp.add_edge(e) - self.edge_status[e] = 3 - def high(self, v): - if self.highpt[v]: - return self.highpt[v][0] - else: + head = self.highpt[v].head + if head == None: return 0 + else: + return head.frond def del_high(self, e): - it = self.in_high[e] - if it: - if e in self.reverse_edges: - v = e[0] - else: - v = e[1] - self.highpt[v].remove(it) + if e in self.in_high: + it = self.in_high[e] + if it: + if e in self.reverse_edges: + v = e[0] + else: + v = e[1] + self.highpt[v].remove(it) def split_multi_egdes(self): """ @@ -2572,7 +2670,6 @@ class Triconnectivity: graph_copy, which will become simple graph. """ - comp = [] if self.graph_copy.has_multiple_edges(): sorted_edges = sorted(self.graph_copy.multiple_edges(labels=True)) @@ -2754,19 +2851,25 @@ class Triconnectivity: for i in range(1,max+1): for e in bucket[i]: + node = AdjNode() + node.set_edge(e) if e in self.reverse_edges: - self.adj[e[1]].append(e) - self.in_adj[e] = e + self.adj[e[1]].append(node) + self.in_adj[e] = node else: - self.adj[e[0]].append(e) - self.in_adj[e] = e + self.adj[e[0]].append(node) + self.in_adj[e] = node def path_finder(self, v): """ This function is a helper function for `dfs2` function. """ self.newnum[v] = self.dfs_counter - self.nd[v] + 1 - for e in self.adj[v]: + #for e in self.adj[v]: + e_node = self.adj[v].get_head() + while e_node: + e = e_node.get_edge() + e_node = e_node.next w = e[1] if e[0] == v else e[0] # opposite vertex of e if self.new_path: self.new_path = False @@ -2775,8 +2878,10 @@ class Triconnectivity: self.path_finder(w) self.dfs_counter -= 1 else: - self.highpt[w].append(self.newnum[v]) - self.in_high[e] = self.newnum[v] + highpt_node = HighPtNode() + highpt_node.set_frond(self.newnum[v]) + self.highpt[w].append(highpt_node) + self.in_high[e] = highpt_node self.new_path = True @@ -2786,8 +2891,7 @@ class Triconnectivity: new numbering obtained from `Path Finder` funciton. Populate `highpt` values. """ - self.highpt = [[] for i in range(self.n)] - self.in_high = dict((e, []) for e in self.graph_copy.edges()) + self.in_high = dict((e, None) for e in self.graph_copy.edges()) self.dfs_counter = self.n self.newnum = [0 for i in range(self.n)] self.starts_path = dict((e, False) for e in self.graph_copy.edges()) @@ -2812,13 +2916,11 @@ class Triconnectivity: """ y = 0 vnum = self.newnum[v] - outv = len(self.adj[v]) - # ERROR fixed - #for e in adj: - for i in range(len(self.adj[v])): - #it = e - e = self.adj[v][i] - it = e + outv = self.adj[v].get_length() + e_node = self.adj[v].get_head() + while e_node: + e = e_node.get_edge() + it = e_node if e in self.reverse_edges: w = e[0] # target @@ -2843,7 +2945,8 @@ class Triconnectivity: self.e_stack.append(self.tree_arc[w]) - temp = self.adj[w][0] + temp_node = self.adj[w].get_head() + temp = temp_node.get_edge() if temp in self.reverse_edges: temp_target = temp[0] else: @@ -2855,8 +2958,6 @@ class Triconnectivity: b = self.t_stack_b[self.t_stack_top] e_virt = None - print self.node_at[a] - print self.node_at[b] if a == vnum and self.parent[self.node_at[b]] == self.node_at[a]: self.t_stack_top -= 1 @@ -2864,17 +2965,16 @@ class Triconnectivity: e_ab = None if self.degree[w] == 2 and self.newnum[temp_target] > wnum: # found type-2 separation pair - print "found type-2 separation pair (",v,", ", temp_target, ")" + # print "Found type-2 separation pair (",v,", ", temp_target, ")" e1 = self.estack_pop() e2 = self.estack_pop() - self.adj[w].remove(self.in_adj[e2]) # check run time + self.adj[w].remove(self.in_adj[e2]) if e2 in self.reverse_edges: x = e2[0] else: x = e2[1] - #e_virt = self.graph_copy.add_edge(v, x) # but edge is not returned ? self.graph_copy.add_edge(v, x) e_virt = (v, x, None) self.degree[v] -= 1 @@ -2885,7 +2985,7 @@ class Triconnectivity: else: e2_source = e2[0] if e2_source != w: # OGDF_ASSERT - raise ValueError("graph is not biconnected?") + raise ValueError("graph is not biconnected") comp = Component([e1, e2, e_virt], 1) self.components_list.append(comp) @@ -2905,7 +3005,7 @@ class Triconnectivity: self.del_high(e_ab) else: # found type-2 separation pair - print "found type-2 separation pair (",a,", ", b, ")" + # print "Found type-2 separation pair (",a,", ", b, ")" h = self.t_stack_h[self.t_stack_top] self.t_stack_top -= 1 @@ -2941,7 +3041,7 @@ class Triconnectivity: self.adj[eh_source].remove(self.in_adj[eh]) self.del_high(eh) - comp.add_edge(eh) # check + comp.add_edge(eh) self.degree[x] -= 1 self.degree[xy_target] -= 1 @@ -2963,10 +3063,10 @@ class Triconnectivity: comp = None self.e_stack.append(e_virt) - # ERROR fixed - #it = e_virt - self.adj[v][i] = e_virt - it = e_virt + e_virt_node = AdjNode() + e_virt_node.set_edge(e_virt) + self.adj[v].replace(e_node, e_virt_node) + it = e_virt_node self.in_adj[e_virt] = it self.degree[x] += 1 @@ -2981,7 +3081,7 @@ class Triconnectivity: if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair - print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" + #print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" comp = Component([],0) if not self.e_stack: # OGDF_ASSERT raise ValueError("stack is empty") @@ -3020,7 +3120,7 @@ class Triconnectivity: self.adj[eh[0]].remove(self.in_adj[eh]) comp_bond.add_edge(eh) - comp_bond.add_edge(e_virt) # TODO combine them + comp_bond.add_edge(e_virt) self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) e_virt = (v, self.node_at[self.lowpt1[w]], None) comp_bond.add_edge(e_virt) @@ -3034,17 +3134,17 @@ class Triconnectivity: if self.node_at[self.lowpt1[w]] != self.parent[v]: self.e_stack.append(e_virt) - #it = e_virt - # ERROR fixed - self.adj[v][i] = e_virt - it = e_virt + e_virt_node = AdjNode() + e_virt_node.set_edge(e_virt) + self.adj[v].replace(e_node, e_virt_node) + it = e_virt_node self.in_adj[e_virt] = it - # ERROR fixed: - #if not self.in_high[e_virt] and self.high(self.node_at[self.lowpt1[w]]) < vnum: if not e_virt in self.in_high and self.high(self.node_at[self.lowpt1[w]]) < vnum: - self.highpt[self.node_at[self.lowpt1[w]]] = [vnum] + self.highpt[self.node_at[self.lowpt1[w]]] - self.in_high[e_virt] = vnum + vnum_node = HighPtNode() + vnum_node.set_frond(vnum) + self.highpt[self.node_at[self.lowpt1[w]]].push_front(vnum_node) + self.in_high[e_virt] = vnum_node self.degree[v] += 1 self.degree[self.node_at[self.lowpt1[w]]] += 1 @@ -3063,9 +3163,11 @@ class Triconnectivity: comp_bond = None self.tree_arc[v] = e_virt - self.egde_status[e_virt] = 1 + self.edge_status[e_virt] = 1 self.in_adj[e_virt] = self.in_adj[eh] - self.in_adj[eh] = e_virt + e_virt_node = AdjNode() + e_virt_node.set_edge(e_virt) + self.in_adj[eh] = e_virt_node # end type-1 search if self.starts_path[e]: @@ -3093,5 +3195,6 @@ class Triconnectivity: self.tstack_push(vnum, wnum, vnum) self.e_stack.append(e) # add (v,w) to ESTACK - + # Go to next node in adjacency list + e_node = e_node.next From 5ee5fa0f4368c340cd626c845c1152847c68426d Mon Sep 17 00:00:00 2001 From: saiharsh Date: Thu, 19 Jul 2018 16:50:42 +0530 Subject: [PATCH 059/264] Fixed the bug in split_multiple_edges --- src/sage/graphs/connectivity.pyx | 37 ++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index beb88cf91e7..abce20c6768 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2529,6 +2529,12 @@ class Triconnectivity: def __init__(self, G, check=True): from sage.graphs.graph import Graph self.graph_copy = Graph(multiedges=True) + + # Add all the vertices first + # there is a possibility of isolated vertices + for v in G.vertex_iterator(): + graph_copy.append(v) + edges = G.edges() # dict to map new edges with the old edges self.edge_label_dict = {} @@ -2634,9 +2640,6 @@ class Triconnectivity: def new_component(self, edges=[], type_c=0): c = Component(edges, type_c) self.components_list.append(c) - # Remove the edges from graph - for e in edges: - self.edge_status[e] = 3 self.num_components += 1 return c @@ -2670,9 +2673,10 @@ class Triconnectivity: graph_copy, which will become simple graph. """ + comp = [] if self.graph_copy.has_multiple_edges(): - sorted_edges = sorted(self.graph_copy.multiple_edges(labels=True)) + sorted_edges = sorted(self.graph_copy.edges()) for i in range(len(sorted_edges) - 1): # It will add k - 1 multiple edges to comp @@ -2682,15 +2686,30 @@ class Triconnectivity: comp.append(sorted_edges[i]) else: if comp: - comp.append(sorted_edges[i-1]) - comp.append(sorted_edges[i-1]) + comp.append(sorted_edges[i]) + self.edge_status[sorted_edges[i]] = 3 # edge removed + + # Add virtual edge to graph_copy + newVEdge = tuple([sorted_edges[i][0], sorted_edges[i][1], "newVEdge"]) + self.graph_copy.add_edge(newVEdge) + + # mark unseen for newVEdge + self.edge_status[newVEdge] = 0 + + comp.append(newVEdge) self.new_component(comp) comp = [] if comp: - comp.append(sorted_edges[i-1]) - comp.append(sorted_edges[i-1]) - self.new_component(comp) + comp.append(sorted_edges[i+1]) + self.edge_status[sorted_edges[i+1]] = 3 # edge removed + # Add virtual edge to graph_copy + newVEdge = tuple([sorted_edges[i+1][0], sorted_edges[i+1][1], "newVEdge"]) + self.graph_copy.add_edge(newVEdge) + self.edge_status[newVEdge] = 0 + + comp.append(newVEdge) + self.new_component(comp) def dfs1(self, v, u=None, check=True): """ From 27653f7657d6ff5f0572824b52fe33b52f9661ca Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Thu, 19 Jul 2018 17:38:26 +0530 Subject: [PATCH 060/264] Fixed a minor bug related to multi-graphs --- src/sage/graphs/connectivity.pyx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index abce20c6768..6532181e915 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2533,7 +2533,7 @@ class Triconnectivity: # Add all the vertices first # there is a possibility of isolated vertices for v in G.vertex_iterator(): - graph_copy.append(v) + self.graph_copy.add_vertex(v) edges = G.edges() # dict to map new edges with the old edges @@ -2566,13 +2566,14 @@ class Triconnectivity: self.components_list = [] #list of components self.graph_copy_adjacency = [[] for i in range(self.n)] + # Triconnectivity algorithm + self.split_multi_egdes() + # Build adjacency list for e in self.graph_copy.edges(): self.graph_copy_adjacency[e[0]].append(e) self.graph_copy_adjacency[e[1]].append(e) - # Triconnectivity algorithm - self.split_multi_egdes() self.dfs_counter = 0 # Initialisation for dfs1() self.start_vertex = 0 # Initialisation for dfs1() self.cut_vertex = self.dfs1(self.start_vertex, check=check) From 443fa84114ac355a7b81f1a7b8bc6ceee2c2771b Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Tue, 24 Jul 2018 19:16:28 +0530 Subject: [PATCH 061/264] Added `assemble_triconnected_components` function. --- src/sage/graphs/connectivity.pyx | 550 ++++++++++++++++++++----------- 1 file changed, 365 insertions(+), 185 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 6532181e915..8f08830ca1d 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2351,57 +2351,75 @@ def spqr_tree_to_graph(T): from sage.graphs.base.sparse_graph cimport SparseGraph -class Component: - edge_list = [] - component_type = 0 #bond = 0, polygon = 1, triconnected = 2 - def __init__(self, edges, type_c): - self.edge_list = edges - self.component_type = type_c - def add_edge(self, e): - self.edge_list.append(e) - def finish_tric_or_poly(self, e): - self.edge_list.append(e) - if len(self.edge_list) >= 4: - self.component_type = 2 - else: - self.component_type = 1 - -class AdjNode: +class LinkedListNode: + """ + Node in a linked list. + Has pointers to its previous node and next node. + If this node is the `head` of the linked list, reference to the linked list + object is stored in `listobj`. + """ prev = None next = None - edge = None - def set_edge(self, e): - self.edge = e - def get_edge(self): - return self.edge + data = None #edge or int + listobj = None + def set_data(self, e): + self.data = e + def get_data(self): + return self.data + def set_obj(self, l): + self.listobj = l + def clear_obj(self): + self.listobj = None + def replace(self, node): + if self.prev == None and self.next == None: + self.listobj.set_head(node) + elif self.prev == None: + self.listobj.head = node + node.next = self.next + node.listobj = self.listobj + elif self.next == None: + self.prev.next = node + node.prev = self.prev + else: + self.prev.next = node + self.next.prev = node + node.prev = self.prev + node.next = self.next -class AdjList: +class LinkedList: + """ + A linked list with a head and a tail pointer + """ head = None - curr = None + tail = None length = 0 def remove(self, node): if node.prev == None and node.next == None: self.head = None + self.tail = None elif node.prev == None: # node is head self.head = node.next node.next.prev = None + node.next.set_obj(self) elif node.next == None: #node is tail node.prev.next = None + self.tail = node.prev else: node.prev.next = node.next node.next.prev = node.prev self.length -= 1 def set_head(self, h): self.head = h - self.curr = h + self.tail = h self.length = 1 + h.set_obj(self) def append(self, node): if self.head == None: self.set_head(node) else: - self.curr.next = node - node.prev = self.curr - self.curr = node + self.tail.next = node + node.prev = self.tail + self.tail = node self.length += 1 def get_head(self): return self.head @@ -2410,63 +2428,101 @@ class AdjList: def replace(self, node1, node2): if node1.prev == None and node1.next == None: self.head = node2 + self.tail = node2 elif node1.prev == None: # head has to be replaced node1.next.prev = node2 node2.next = node1.next - elif node1.next == None: + self.head = node2 + elif node1.next == None: # tail has to be replaced node1.prev.next = node2 node2.prev = node1.prev + self.tail = node2 else: node1.prev.next = node2 node1.next.prev = node2 node2.prev = node1.prev node2.next = node1.next - -class HighPtNode: - prev = None - next = None - front = None #integer - def set_frond(self, f): - self.frond = f - def get_frond(self): - return self.frond - -class HighPtList: - head = None - curr = None - length = 0 - def remove(self, node): - if node.prev == None and node.next == None: - self.head = None - elif node.prev == None: # node is head - self.head = node.next - node.next.prev = None - elif node.next == None: #node is tail - node.prev.next = None - else: - node.prev.next = node.next - node.next.prev = node.prev - self.length -= 1 - def set_head(self, h): - self.head = h - self.curr = h - self.length = 1 - def append(self, node): - if self.head == None: - self.set_head(node) - else: - self.curr.next = node - node.prev = self.curr - self.curr = node - self.length += 1 def push_front(self, node): if self.head == None: self.head = node + self.tail = node + node.set_obj(self) else: + self.head.clear_obj() self.head.prev = node node.next = self.head self.head = node + node.set_obj(self) + self.length += 1 + def to_string(self): + temp = self.head + s = "" + while temp: + s += " " + str(temp.get_data()) + temp = temp.next + return s + def concatenate(self, lst2): + """ + Concatenates lst2 to self. + Makes lst2 empty. + """ + self.tail.next = lst2.head + lst2.head.prev = self.tail + self.tail = lst2.tail + self.length += lst2.length + lst2.head = None + lst2.length = 0 +class Component: + """ + A connected component. + `edge_list` contains the list of edges belonging to the component. + `component_type` stores the type of the component. + - 0 if bond. + - 1 if polygon. + - 2 is triconnected component. + """ + edge_list = LinkedList() + component_type = 0 #bond = 0, polygon = 1, triconnected = 2 + def __init__(self, edge_list, type_c): + """ + `edge_list` is a list of edges to be added to the component. + `type_c` is the type of the component. + """ + self.edge_list = LinkedList() + for e in edge_list: + e_node = LinkedListNode() + e_node.set_data(e) + self.edge_list.append(e_node) + self.component_type = type_c + def add_edge(self, e): + e_node = LinkedListNode() + e_node.set_data(e) + self.edge_list.append(e_node) + def finish_tric_or_poly(self, e): + """ + Edge `e` is the last edge to be added to the component. + Classify the component as a polygon or triconnected component + depending on the number of edges belonging to it. + """ + e_node = LinkedListNode() + e_node.set_data(e) + self.edge_list.append(e_node) + if self.edge_list.get_length() >= 4: + self.component_type = 2 + else: + self.component_type = 1 + def __str__(self): + """ + Function for printing the component. + """ + if self.component_type == 0: + type_str = "Bond: " + elif self.component_type == 1: + type_str = "Polygon: " + else: + type_str = "Triconnected: " + return type_str + self.edge_list.to_string() class Triconnectivity: """ @@ -2486,48 +2542,11 @@ class Triconnectivity: sage: tric.components_list [] """ - graph_copy = None #type SparseGraph - vertex_to_int = {} # mapping of vertices to integers in c_graph - int_to_vertex = {} # mapping of integers to original vertices - start_vertex = 0 - num_components = 0 - edge_status = {} # status of each edge, unseen=0, tree=1, frond=2, removed=3 - e_stack = [] - t_stack_h = [] - t_stack_a = [] - t_stack_b = [] - t_stack_top = 0 - dfs_number = [] # DFS number of vertex i - vertex_at = [] # vertex with DFS number of i$ - lowpt1 = [] # lowpt1 number of vertex i - lowpt2 = [] # lowpt2 number of vertex i - nd = [] # number of descendants of vertex i - edge_phi = {} # (key, value) = (edge, corresponding phi value) - adj = [] # i^th value contains a AdjList of incident edges of vertex i - in_adj = {} # this variable is used in the PathSearch() function - newnum = [] # new DFS number of vertex i - starts_path = {} # dict of (edge, T/F) to denote if a path starts at edge - highpt = [] # List of fronds entering vertex i in the order they are visited - old_to_new = [] # New DFS number of the vertex with i as old DFS number - degree = [] # Degree of vertex i - parent = [] # Parent vertex of vertex i in the palm tree - tree_arc = [] # Tree arc entering the vertex i - first_child = [] - in_high = {} # One of the elements in highpt[i] - dfs_counter = 0 - n = 0 # number of vertices - m = 0 # number of edges - is_biconnected = True # Boolean to store if the graph is biconnected or not - cut_vertex = None # If graph is not biconnected - graph_copy_adjacency = [] - new_path = False # Boolean used to store if new path is started - - # Edges of the graph which are in the reverse direction in palm tree - reverse_edges = set() - - def __init__(self, G, check=True): from sage.graphs.graph import Graph + # graph_copy is a SparseGraph of the input graph `G` + # We relabel the edges with increasing numbers to be able to + # distinguish between multi-edges self.graph_copy = Graph(multiedges=True) # Add all the vertices first @@ -2542,29 +2561,93 @@ class Triconnectivity: newEdge = tuple([edges[i][0], edges[i][1], i]) self.graph_copy.add_edge(newEdge) self.edge_label_dict[newEdge] = edges[i] + + # type SparseGraph self.graph_copy = self.graph_copy.copy(implementation='c_graph') + # mapping of vertices to integers in c_graph self.vertex_to_int = self.graph_copy.relabel(inplace=True, return_map=True) + + # mapping of integers to original vertices self.int_to_vertex = dict([(v,k) for k,v in self.vertex_to_int.items()]) - self.n = self.graph_copy.order() - self.m = self.graph_copy.size() + self.n = self.graph_copy.order() # number of vertices + self.m = self.graph_copy.size() # number of edges + + print "vertices" + print self.graph_copy.vertices() + print self.vertex_to_int + print self.int_to_vertex + print "edges", self.graph_copy.edges() + + # status of each edge: unseen=0, tree=1, frond=2, removed=3 self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) + + # Edges of the graph which are in the reverse direction in palm tree self.reverse_edges = set() - self.dfs_number = [0 for i in range(self.n)] - self.highpt = [HighPtList() for i in range(self.n)] - self.old_to_new = [0 for i in range(self.n + 1)] - self.node_at = [0 for i in range(self.n + 1)] - self.lowpt1 = [None for i in range(self.n)] - self.lowpt2 = [None for i in range(self.n)] - self.adj = [AdjList() for i in range(self.n)] - self.nd = [None for i in range(self.n)] + self.dfs_number = [0 for i in range(self.n)] # DFS number of vertex i + + # Linked list of fronds entering vertex i in the order they are visited + self.highpt = [LinkedList() for i in range(self.n)] + + # A dictionary whose key is an edge e, value is a pointer to element in + # self.highpt containing the edge e. Used in the `path_search` function. + self.in_high = dict((e, None) for e in self.graph_copy.edges()) + + # New DFS number of the vertex with i as its old DFS number + self.old_to_new = [0 for i in range(self.n+1)] + self.newnum = [0 for i in range(self.n)] # new DFS number of vertex i + self.node_at = [0 for i in range(self.n+1)] # node at dfs number of i + self.lowpt1 = [None for i in range(self.n)] # lowpt1 number of vertex i + self.lowpt2 = [None for i in range(self.n)] # lowpt2 number of vertex i + + # i^th value contains a LinkedList of incident edges of vertex i + self.adj = [LinkedList() for i in range(self.n)] + + # A dictionary whose key is an edge, value is a pointer to element in + # self.adj containing the edge. Used in the `path_search` function. + self.in_adj = {} + self.nd = [None for i in range(self.n)] # number of descendants of vertex i + + # Parent vertex of vertex i in the palm tree self.parent = [None for i in range(self.n)] - self.degree = [None for i in range(self.n)] - self.tree_arc = [None for i in range(self.n)] - self.vertex_at = [1 for i in range(self.n)] + self.degree = [None for i in range(self.n)] # Degree of vertex i + self.tree_arc = [None for i in range(self.n)] # Tree arc entering the vertex i + self.vertex_at = [1 for i in range(self.n)] # vertex with DFS number of i self.dfs_counter = 0 self.components_list = [] #list of components - self.graph_copy_adjacency = [[] for i in range(self.n)] + self.graph_copy_adjacency = [[] for i in range(self.n)] # Stores adjacency list + + # Dictionary of (e, True/False) to denote if a path starts at edge e + self.starts_path = dict((e, False) for e in self.graph_copy.edges()) + + self.is_biconnected = True # Boolean to store if the graph is biconnected or not + self.cut_vertex = None # If graph is not biconnected + + # Label used for virtual edges, incremented at every new virtual edge + self.virtual_edge_num = 0 + + self.new_path = False # Boolean used to store if new path is started + + # Stacks used in `path_search` function + self.e_stack = [] + self.t_stack_h = [None for i in range(2*self.m + 1)] + self.t_stack_a = [None for i in range(2*self.m + 1)] + self.t_stack_b = [None for i in range(2*self.m + 1)] + self.t_stack_top = 0 + self.t_stack_a[self.t_stack_top] = -1 + + + # Trivial cases + if self.n < 2: + raise ValueError("Graph is not biconnected") + if self.n <= 2: + if self.m < 3: + raise ValueError("Graph is not biconnected") + comp = Component([], 0) + for e in self.graph_copy.edges(): + comp.add_edge(e) + self.components_list.append(comp) + return # Triconnectivity algorithm self.split_multi_egdes() @@ -2579,19 +2662,18 @@ class Triconnectivity: self.cut_vertex = self.dfs1(self.start_vertex, check=check) if check: - # graph is disconnected - if self.dfs_number < self.n: + # if graph is disconnected + if self.dfs_counter < self.n: self.is_biconnected = False - return + raise ValueError("Graph is disconnected") - # graph has a cut vertex + # If graph has a cut vertex if self.cut_vertex != None: self.cut_vertex = self.int_to_vertex[self.cut_vertex] self.is_biconnected = False - return + raise ValueError("Graph has a cut vertex") - # Reversing the edges to reflect the palm tree arcs and fronds - # Is there a better way to do it? + # Identify reversed edges to reflect the palm tree arcs and fronds for e in self.graph_copy.edges(): up = (self.dfs_number[e[1]] - self.dfs_number[e[0]]) > 0 if (up and self.edge_status[e]==2) or (not up and self.edge_status[e]==1): @@ -2601,55 +2683,67 @@ class Triconnectivity: self.build_acceptable_adj_struct() self.dfs2() - self.t_stack_h = [None for i in range(2*self.m + 1)] - self.t_stack_a = [None for i in range(2*self.m + 1)] - self.t_stack_b = [None for i in range(2*self.m + 1)] - self.t_stack_top = 0 - self.t_stack_a[self.t_stack_top] = -1 - self.path_search(self.start_vertex) # last split component c = Component([],0) while self.e_stack: c.add_edge(self.estack_pop()) - c.component_type = 2 if len(c.edge_list) > 4 else 1 + c.component_type = 2 if c.edge_list.get_length() > 4 else 1 self.components_list.append(c) c = None - # Push a triple on Tstack + self.assemble_triconnected_components() + self.print_triconnected_components() + def tstack_push(self, h, a, b): + """ + Push `(h,a,b)` triple on Tstack + """ self.t_stack_top += 1 self.t_stack_h[self.t_stack_top] = h self.t_stack_a[self.t_stack_top] = a self.t_stack_b[self.t_stack_top] = b - # Push end-of-stack marker on Tstack def tstack_push_eos(self): + """ + Push end-of-stack marker on Tstack + """ self.t_stack_top += 1 self.t_stack_a[self.t_stack_top] = -1 - # Returns true iff end-of-stack marker is not on top of Tstack def tstack_not_eos(self): + """ + Return true iff end-of-stack marker is not on top of Tstack + """ return self.t_stack_a[self.t_stack_top] != -1 def estack_pop(self): + """ + Pop from estack and return the popped element + """ e = self.e_stack[-1] self.e_stack = self.e_stack[0:-1] return e def new_component(self, edges=[], type_c=0): + """ + Create a new component, add `edges` to it. + type_c = 0 for bond, 1 for polygon, 2 for triconnected component + """ c = Component(edges, type_c) self.components_list.append(c) - self.num_components += 1 return c def high(self, v): + """ + Return the high(v) value, which is the first value in highpt list of `v` + """ head = self.highpt[v].head if head == None: return 0 else: - return head.frond + return head.data def del_high(self, e): if e in self.in_high: @@ -2691,8 +2785,9 @@ class Triconnectivity: self.edge_status[sorted_edges[i]] = 3 # edge removed # Add virtual edge to graph_copy - newVEdge = tuple([sorted_edges[i][0], sorted_edges[i][1], "newVEdge"]) + newVEdge = tuple([sorted_edges[i][0], sorted_edges[i][1], "newVEdge"+str(self.virtual_edge_num)]) self.graph_copy.add_edge(newVEdge) + self.virtual_edge_num += 1 # mark unseen for newVEdge self.edge_status[newVEdge] = 0 @@ -2705,8 +2800,9 @@ class Triconnectivity: self.edge_status[sorted_edges[i+1]] = 3 # edge removed # Add virtual edge to graph_copy - newVEdge = tuple([sorted_edges[i+1][0], sorted_edges[i+1][1], "newVEdge"]) + newVEdge = tuple([sorted_edges[i+1][0], sorted_edges[i+1][1], "newVEdge"+str(self.virtual_edge_num)]) self.graph_copy.add_edge(newVEdge) + self.virtual_edge_num += 1 self.edge_status[newVEdge] = 0 comp.append(newVEdge) @@ -2797,7 +2893,7 @@ class Triconnectivity: if self.edge_status[e]: continue - w = e[0] if e[0] != v else e[1] # Other vertex of edge e + w = e[0] if e[0] != v else e[1] # Opposite vertex of edge e if self.dfs_number[w] == 0: self.edge_status[e] = 1 # tree edge if first_son is None: @@ -2854,7 +2950,7 @@ class Triconnectivity: if edge_type==1: # tree arc if self.lowpt2[e[0]] < self.dfs_number[e[1]]: phi = 3*self.lowpt1[e[0]] - elif self.lowpt2[e[0]] >= self.dfs_number[e[1]]: + else: phi = 3*self.lowpt1[e[0]] + 2 else: # tree frond phi = 3*self.dfs_number[e[0]]+1 @@ -2862,7 +2958,7 @@ class Triconnectivity: if edge_type==1: # tree arc if self.lowpt2[e[1]] < self.dfs_number[e[0]]: phi = 3*self.lowpt1[e[1]] - elif self.lowpt2[e[1]] >= self.dfs_number[e[0]]: + else: phi = 3*self.lowpt1[e[1]] + 2 else: # tree frond phi = 3*self.dfs_number[e[1]]+1 @@ -2871,8 +2967,8 @@ class Triconnectivity: for i in range(1,max+1): for e in bucket[i]: - node = AdjNode() - node.set_edge(e) + node = LinkedListNode() + node.set_data(e) if e in self.reverse_edges: self.adj[e[1]].append(node) self.in_adj[e] = node @@ -2885,10 +2981,9 @@ class Triconnectivity: This function is a helper function for `dfs2` function. """ self.newnum[v] = self.dfs_counter - self.nd[v] + 1 - #for e in self.adj[v]: e_node = self.adj[v].get_head() while e_node: - e = e_node.get_edge() + e = e_node.get_data() e_node = e_node.next w = e[1] if e[0] == v else e[0] # opposite vertex of e if self.new_path: @@ -2898,8 +2993,8 @@ class Triconnectivity: self.path_finder(w) self.dfs_counter -= 1 else: - highpt_node = HighPtNode() - highpt_node.set_frond(self.newnum[v]) + highpt_node = LinkedListNode() + highpt_node.set_data(self.newnum[v]) self.highpt[w].append(highpt_node) self.in_high[e] = highpt_node self.new_path = True @@ -2939,7 +3034,7 @@ class Triconnectivity: outv = self.adj[v].get_length() e_node = self.adj[v].get_head() while e_node: - e = e_node.get_edge() + e = e_node.get_data() it = e_node if e in self.reverse_edges: @@ -2966,7 +3061,7 @@ class Triconnectivity: self.e_stack.append(self.tree_arc[w]) temp_node = self.adj[w].get_head() - temp = temp_node.get_edge() + temp = temp_node.get_data() if temp in self.reverse_edges: temp_target = temp[0] else: @@ -2974,18 +3069,17 @@ class Triconnectivity: # while vnum is not the start_vertex while vnum != 1 and ((self.t_stack_a[self.t_stack_top] == vnum) or \ (self.degree[w] == 2 and self.newnum[temp_target] > wnum)): + a = self.t_stack_a[self.t_stack_top] b = self.t_stack_b[self.t_stack_top] e_virt = None - if a == vnum and self.parent[self.node_at[b]] == self.node_at[a]: self.t_stack_top -= 1 else: e_ab = None if self.degree[w] == 2 and self.newnum[temp_target] > wnum: - # found type-2 separation pair - # print "Found type-2 separation pair (",v,", ", temp_target, ")" + # found type-2 separation pair - (v, temp_target) e1 = self.estack_pop() e2 = self.estack_pop() self.adj[w].remove(self.in_adj[e2]) @@ -2995,8 +3089,9 @@ class Triconnectivity: else: x = e2[1] - self.graph_copy.add_edge(v, x) - e_virt = (v, x, None) + e_virt = tuple([v, x, "newVEdge"+str(self.virtual_edge_num)]) + self.graph_copy.add_edge(e_virt) + self.virtual_edge_num += 1 self.degree[v] -= 1 self.degree[x] -= 1 @@ -3005,7 +3100,7 @@ class Triconnectivity: else: e2_source = e2[0] if e2_source != w: # OGDF_ASSERT - raise ValueError("graph is not biconnected") + raise ValueError("Graph is not biconnected") comp = Component([e1, e2, e_virt], 1) self.components_list.append(comp) @@ -3024,8 +3119,7 @@ class Triconnectivity: self.adj[x].remove(self.in_adj[e_ab]) self.del_high(e_ab) - else: # found type-2 separation pair - # print "Found type-2 separation pair (",a,", ", b, ")" + else: # found type-2 separation pair - (self.node_at[a], self.node_at[b]) h = self.t_stack_h[self.t_stack_top] self.t_stack_top -= 1 @@ -3065,8 +3159,9 @@ class Triconnectivity: self.degree[x] -= 1 self.degree[xy_target] -= 1 - self.graph_copy.add_edge(self.node_at[a], self.node_at[b]) - e_virt = (self.node_at[a], self.node_at[b], None) + e_virt = tuple([self.node_at[a], self.node_at[b], "newVEdge"+str(self.virtual_edge_num)]) + self.graph_copy.add_edge(e_virt) + self.virtual_edge_num += 1 comp.finish_tric_or_poly(e_virt) self.components_list.append(comp) comp = None @@ -3074,8 +3169,9 @@ class Triconnectivity: if e_ab is not None: comp = Component([e_ab, e_virt], type_c=0) - self.graph_copy.add_edge(v,x) - e_virt = (v,x,None) + e_virt = tuple([v, x, "newVEdge"+str(self.virtual_edge_num)]) + self.graph_copy.add_edge(e_virt) + self.virtual_edge_num += 1 comp.add_edge(e_virt) self.degree[x] -= 1 self.degree[v] -= 1 @@ -3083,9 +3179,10 @@ class Triconnectivity: comp = None self.e_stack.append(e_virt) - e_virt_node = AdjNode() - e_virt_node.set_edge(e_virt) - self.adj[v].replace(e_node, e_virt_node) + e_virt_node = LinkedListNode() + e_virt_node.set_data(e_virt) + # Replace `it` node with `e_virt_node` + it.replace(e_virt_node) it = e_virt_node self.in_adj[e_virt] = it @@ -3100,8 +3197,7 @@ class Triconnectivity: # start type-1 check if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ (self.parent[v] != self.start_vertex or outv >= 2): - # type-1 separation pair - #print "Found type-1 separation pair (", self.node_at[self.lowpt1[w]], ", ", v, ")" + # type-1 separation pair - (self.node_at[self.lowpt1[w]], v) comp = Component([],0) if not self.e_stack: # OGDF_ASSERT raise ValueError("stack is empty") @@ -3123,8 +3219,9 @@ class Triconnectivity: self.degree[self.node_at[xx]] -= 1 self.degree[self.node_at[y]] -= 1 - self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) - e_virt = (v, self.node_at[self.lowpt1[w]], None) + e_virt = tuple([v, self.node_at[self.lowpt1[w]], "newVEdge"+str(self.virtual_edge_num)]) + self.graph_copy.add_edge(e_virt) + self.virtual_edge_num += 1 comp.finish_tric_or_poly(e_virt) self.components_list.append(comp) comp = None @@ -3141,8 +3238,9 @@ class Triconnectivity: comp_bond.add_edge(eh) comp_bond.add_edge(e_virt) - self.graph_copy.add_edge(v, self.node_at[self.lowpt1[w]]) - e_virt = (v, self.node_at[self.lowpt1[w]], None) + e_virt = tuple([v, self.node_at[self.lowpt1[w]], "newVEdge"+str(self.virtual_edge_num)]) + self.graph_copy.add_edge(e_virt) + self.virtual_edge_num += 1 comp_bond.add_edge(e_virt) self.in_high[e_virt] = self.in_high[eh] self.degree[v] -= 1 @@ -3150,19 +3248,19 @@ class Triconnectivity: self.components_list.append(comp_bond) comp_bond = None - if self.node_at[self.lowpt1[w]] != self.parent[v]: self.e_stack.append(e_virt) - e_virt_node = AdjNode() - e_virt_node.set_edge(e_virt) - self.adj[v].replace(e_node, e_virt_node) + e_virt_node = LinkedListNode() + e_virt_node.set_data(e_virt) + # replace `it` node with `e_virt_node` + it.replace(e_virt_node) it = e_virt_node self.in_adj[e_virt] = it if not e_virt in self.in_high and self.high(self.node_at[self.lowpt1[w]]) < vnum: - vnum_node = HighPtNode() - vnum_node.set_frond(vnum) + vnum_node = LinkedListNode() + vnum_node.set_data(vnum) self.highpt[self.node_at[self.lowpt1[w]]].push_front(vnum_node) self.in_high[e_virt] = vnum_node @@ -3172,8 +3270,9 @@ class Triconnectivity: else: self.adj[v].remove(it) comp_bond = Component([e_virt], type_c=0) - self.graph_copy.add_edge(self.node_at[self.lowpt1[w]], v) - e_virt = (self.node_at[self.lowpt1[w]], v, None) + e_virt = tuple([self.node_at[self.lowpt1[w]], v, "newVEdge"+str(self.virtual_edge_num)]) + self.graph_copy.add_edge(e_virt) + self.virtual_edge_num += 1 comp_bond.add_edge(e_virt) eh = self.tree_arc[v]; @@ -3185,11 +3284,10 @@ class Triconnectivity: self.tree_arc[v] = e_virt self.edge_status[e_virt] = 1 self.in_adj[e_virt] = self.in_adj[eh] - e_virt_node = AdjNode() - e_virt_node.set_edge(e_virt) + e_virt_node = LinkedListNode() + e_virt_node.set_data(e_virt) self.in_adj[eh] = e_virt_node - - # end type-1 search + # end type-1 search if self.starts_path[e]: while self.tstack_not_eos(): self.t_stack_top -= 1 @@ -3218,3 +3316,85 @@ class Triconnectivity: # Go to next node in adjacency list e_node = e_node.next + def assemble_triconnected_components(self): + """ + Iterates through all the components built by `path_finder` and merges + the components wherever possible for contructing the final + triconnected components. + """ + comp1 = {} # The index of first component that an edge belongs to + comp2 = {} # The index of second component that an edge belongs to + item1 = {} # Pointer to the edge node in component1 + item2 = {} # Pointer to the edge node in component2 + num_components = len(self.components_list) + visited = [False for i in range(num_components)] + + # For each edge, we populate the comp1, comp2, item1 and item2 values + for i in range(num_components): # for each component + e_node = self.components_list[i].edge_list.get_head() + while e_node: # for each edge + e = e_node.get_data() + if e not in item1: + comp1[e] = i + item1[e] = e_node + else: + comp2[e] = i + item2[e] = e_node + + e_node = e_node.next + + for i in range(num_components): + c1 = self.components_list[i] + c1_type = c1.component_type + l1 = c1.edge_list + visited[i] = True + + if l1.get_length() == 0: + continue + + if c1_type == 0 or c1_type == 1: + e_node = self.components_list[i].edge_list.get_head() + while e_node: + e = e_node.get_data() + e_node_next = e_node.next + # the label of virtual edges is a string + if not isinstance(e[2], str): + e_node = e_node_next + continue + + j = comp1[e] + if visited[j]: + j = comp2[e] + if visited[j]: + e_node = e_node_next + continue + e_node2 = item2[e] + else: + e_node2 = item1[e] + + c2 = self.components_list[j] + if (c1_type != c2.component_type): + e_node = e_node_next + continue + + visited[j] = True + l2 = c2.edge_list + + l2.remove(e_node2) + l1.concatenate(l2) + + if not e_node_next: + e_node_next = e_node.next + + l1.remove(e_node) + + e_node = e_node_next + + def print_triconnected_components(self): + """ + Print all the triconnected components along with the type of + each component. + """ + for i in range(len(self.components_list)): + if self.components_list[i].edge_list.get_length() > 0: + print self.components_list[i] From 737a2af529586d5cfc5156b677a73c3deb0fba22 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Wed, 25 Jul 2018 01:05:34 +0530 Subject: [PATCH 062/264] Formatted output to change vertices/edges to original labels. --- src/sage/graphs/connectivity.pyx | 59 ++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 8f08830ca1d..1fbbefb18a4 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2523,6 +2523,16 @@ class Component: else: type_str = "Triconnected: " return type_str + self.edge_list.to_string() + def get_edge_list(self): + """ + Return a list of edges belonging to the component. + """ + e_list = [] + e_node = self.edge_list.get_head() + while e_node: + e_list.append(e_node.get_data()) + e_node = e_node.next + return e_list class Triconnectivity: """ @@ -2573,12 +2583,6 @@ class Triconnectivity: self.n = self.graph_copy.order() # number of vertices self.m = self.graph_copy.size() # number of edges - print "vertices" - print self.graph_copy.vertices() - print self.vertex_to_int - print self.int_to_vertex - print "edges", self.graph_copy.edges() - # status of each edge: unseen=0, tree=1, frond=2, removed=3 self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) @@ -2614,7 +2618,7 @@ class Triconnectivity: self.tree_arc = [None for i in range(self.n)] # Tree arc entering the vertex i self.vertex_at = [1 for i in range(self.n)] # vertex with DFS number of i self.dfs_counter = 0 - self.components_list = [] #list of components + self.components_list = [] # list of components of `graph_copy` self.graph_copy_adjacency = [[] for i in range(self.n)] # Stores adjacency list # Dictionary of (e, True/False) to denote if a path starts at edge e @@ -2636,6 +2640,10 @@ class Triconnectivity: self.t_stack_top = 0 self.t_stack_a[self.t_stack_top] = -1 + # The final triconnected components are stored + self.comp_list_new = [] # i^th entry is list of edges in i^th component + self.comp_type = [] # i^th entry is type of i^th component + # Trivial cases if self.n < 2: @@ -2662,7 +2670,7 @@ class Triconnectivity: self.cut_vertex = self.dfs1(self.start_vertex, check=check) if check: - # if graph is disconnected + # If graph is disconnected if self.dfs_counter < self.n: self.is_biconnected = False raise ValueError("Graph is disconnected") @@ -3321,6 +3329,9 @@ class Triconnectivity: Iterates through all the components built by `path_finder` and merges the components wherever possible for contructing the final triconnected components. + Formats the triconnected components into original vertices and edges. + The triconnected components are stored in `self.comp_list_new` and + `self.comp_type`. """ comp1 = {} # The index of first component that an edge belongs to comp2 = {} # The index of second component that an edge belongs to @@ -3390,11 +3401,37 @@ class Triconnectivity: e_node = e_node_next + # Convert connected components into original graph vertices and edges + self.comp_list_new = [] + self.comp_type = [] + for i in range(len(self.components_list)): + if self.components_list[i].edge_list.get_length() > 0: + e_list = self.components_list[i].get_edge_list() + e_list_new = [] + # For each edge, get the original source, target and label + for e in e_list: + source = self.int_to_vertex[e[0]] + target = self.int_to_vertex[e[1]] + if isinstance(e[2], str): + label = e[2] + else: + label = self.edge_label_dict[(source, target,e[2])][2] + e_list_new.append(tuple([source, target, label])) + # Add the component data to `comp_list_new` and `comp_type` + self.comp_type.append(self.components_list[i].component_type) + self.comp_list_new.append(e_list_new) + def print_triconnected_components(self): """ Print all the triconnected components along with the type of each component. """ - for i in range(len(self.components_list)): - if self.components_list[i].edge_list.get_length() > 0: - print self.components_list[i] + for i in range(len(self.comp_list_new)): + if self.comp_type[i] == 0: + print "Bond: ", + elif self.comp_type[i] == 1: + print "Polygon: ", + else: + print "Triconnected: ", + print self.comp_list_new[i] + From 9dd2aa7ac6d4951f108aa07fd966cfac88cf8693 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Fri, 27 Jul 2018 16:12:12 +0530 Subject: [PATCH 063/264] Added comments and documentation. --- src/sage/graphs/connectivity.pyx | 426 ++++++++++++++++++++----------- 1 file changed, 271 insertions(+), 155 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 1fbbefb18a4..e7fa4f0704b 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2349,10 +2349,9 @@ def spqr_tree_to_graph(T): return G -from sage.graphs.base.sparse_graph cimport SparseGraph - class LinkedListNode: """ + Helper class for ``Triconnectivity``. Node in a linked list. Has pointers to its previous node and next node. If this node is the `head` of the linked list, reference to the linked list @@ -2371,6 +2370,9 @@ class LinkedListNode: def clear_obj(self): self.listobj = None def replace(self, node): + """ + Replace self node with ``node`` in the corresponding linked list. + """ if self.prev == None and self.next == None: self.listobj.set_head(node) elif self.prev == None: @@ -2388,12 +2390,16 @@ class LinkedListNode: class LinkedList: """ + A helper class for ``Triconnectivity``. A linked list with a head and a tail pointer """ head = None tail = None length = 0 def remove(self, node): + """ + Remove the node ``node`` from the linked list. + """ if node.prev == None and node.next == None: self.head = None self.tail = None @@ -2409,11 +2415,17 @@ class LinkedList: node.next.prev = node.prev self.length -= 1 def set_head(self, h): + """ + Set the node ``h`` as the head of the linked list. + """ self.head = h self.tail = h self.length = 1 h.set_obj(self) def append(self, node): + """ + Append the node ``node`` to the linked list. + """ if self.head == None: self.set_head(node) else: @@ -2426,6 +2438,9 @@ class LinkedList: def get_length(self): return self.length def replace(self, node1, node2): + """ + Replace the node ``node1`` with ``node2`` in the linked list. + """ if node1.prev == None and node1.next == None: self.head = node2 self.tail = node2 @@ -2443,6 +2458,9 @@ class LinkedList: node2.prev = node1.prev node2.next = node1.next def push_front(self, node): + """ + Add node ``node`` to the beginning of the linked list. + """ if self.head == None: self.head = node self.tail = node @@ -2475,6 +2493,7 @@ class LinkedList: class Component: """ + A helper class for ``Triconnectivity``. A connected component. `edge_list` contains the list of edges belonging to the component. `component_type` stores the type of the component. @@ -2534,23 +2553,135 @@ class Component: e_node = e_node.next return e_list +from sage.graphs.base.sparse_graph cimport SparseGraph + class Triconnectivity: """ - This module is not yet complete, it has work to be done. - This module implements the algorithm for finding the triconnected components of a biconnected graph. - Refer to [Gut2001]_ and [Hopcroft1973]_ for the algorithm. + A biconnected graph is a graph where deletion of any one vertex does + not disconnect the graph. + + INPUT: + + - ``G`` -- The input graph. + + - ``check`` (default: ``True``) -- Boolean to indicate whether ``G`` + needs to be tested for biconnectivity. + + OUTPUT: + + No output, the triconnected components are printed. + The triconnected components are stored in `comp_list_new and `comp_type`. + `comp_list_new` is a list of components, with `comp_list_new[i]` contains + the list of edges in the $i^{th}$ component. `comp_type[i]` stores the type + of the $i^{th}$ component - 1 for bond, 2 for polygon, 3 for triconnected + component. The output can be accessed through these variables. + + ALGORITHM: + + We implement the algorithm proposed by Tarjan in [Tarjan72]_. The + original version is recursive. We emulate the recursion using a stack. + + ALGORITHM:: + We implement the algorithm proposed by Hopcroft and Tarjan in + [Hopcroft1973]_ and later corrected by Gutwenger and Mutzel in + [Gut2001]_. + + .. SEEALSO:: + + - :meth:`~Graph.is_biconnected` EXAMPLES:: - An example to show how the triconnected components can be accessed: + An example from [Hopcroft1973]_: sage: from sage.graphs.connectivity import Triconnectivity - sage: g = Graph([['a','b',1],['a','c',1],['b','c',10]], weighted=True) - sage: tric = Triconnectivity(g) - sage: tric.components_list - [] + sage: G = Graph() + sage: G.add_edges([(1,2),(1,4),(1,8),(1,12),(1,13),(2,3),(2,13),(3,4)]) + sage: G.add_edges([(3,13),(4,5),(4,7),(5,6),(5,7),(5,8),(6,7),(8,9),(8,11)]) + sage: G.add_edges([(8,12),(9,10),(9,11),(9,12),(10,11),(10,12)]) + sage: tric = Triconnectivity(G) + Triconnected: [(8, 9, None), (9, 10, None), (10, 11, None), (9, 11, None), (8, 11, None), (10, 12, None), (9, 12, None), (8, 12, 'newVEdge0')] + Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] + Polygon: [(8, 12, 'newVEdge1'), (1, 12, None), (8, 1, 'newVEdge2')] + Bond: [(1, 8, None), (8, 1, 'newVEdge2'), (8, 1, 'newVEdge3')] + Polygon: [(5, 8, None), (8, 1, 'newVEdge3'), (4, 5, 'newVEdge8'), (4, 1, 'newVEdge9')] + Polygon: [(5, 6, None), (6, 7, None), (5, 7, 'newVEdge5')] + Bond: [(5, 7, None), (5, 7, 'newVEdge5'), (5, 7, 'newVEdge6')] + Polygon: [(5, 7, 'newVEdge6'), (4, 7, None), (5, 4, 'newVEdge7')] + Bond: [(5, 4, 'newVEdge7'), (4, 5, 'newVEdge8'), (4, 5, None)] + Bond: [(1, 4, None), (4, 1, 'newVEdge9'), (4, 1, 'newVEdge10')] + Polygon: [(3, 4, None), (4, 1, 'newVEdge10'), (3, 1, 'newVEdge11')] + Triconnected: [(1, 2, None), (2, 3, None), (3, 1, 'newVEdge11'), (3, 13, None), (2, 13, None), (1, 13, None)] + + An example from [Gut2001]_ + + sage: G = Graph() + sage: G.add_edges([(1,2),(1,4),(2,3),(2,5),(3,4),(3,5),(4,5),(4,6),(5,7),(5,8)]) + sage: G.add_edges([(5,14),(6,8),(7,14),(8,9),(8,10),(8,11),(8,12),(9,10),(10,13)]) + sage: G.add_edges([(10,14),(10,15),(10,16),(11,12),(11,13),(12,13),(14,15),(14,16),(15,16)]) + sage: tric = Triconnectivity(G) + Polygon: [(6, 8, None), (4, 6, None), (5, 8, 'newVEdge12'), (5, 4, 'newVEdge13')] + Polygon: [(8, 9, None), (9, 10, None), (8, 10, 'newVEdge1')] + Bond: [(8, 10, 'newVEdge1'), (8, 10, None), (8, 10, 'newVEdge4'), (10, 8, 'newVEdge5')] + Triconnected: [(8, 11, None), (11, 12, None), (8, 12, None), (12, 13, None), (11, 13, None), (8, 13, 'newVEdge3')] + Polygon: [(8, 13, 'newVEdge3'), (10, 13, None), (8, 10, 'newVEdge4')] + Triconnected: [(10, 15, None), (14, 15, None), (15, 16, None), (10, 16, None), (14, 16, None), (10, 14, 'newVEdge6')] + Bond: [(10, 14, 'newVEdge6'), (14, 10, 'newVEdge7'), (10, 14, None)] + Polygon: [(14, 10, 'newVEdge7'), (10, 8, 'newVEdge5'), (5, 14, 'newVEdge10'), (5, 8, 'newVEdge11')] + Polygon: [(5, 7, None), (7, 14, None), (5, 14, 'newVEdge9')] + Bond: [(5, 14, None), (5, 14, 'newVEdge9'), (5, 14, 'newVEdge10')] + Bond: [(5, 8, None), (5, 8, 'newVEdge11'), (5, 8, 'newVEdge12')] + Bond: [(5, 4, 'newVEdge13'), (4, 5, 'newVEdge14'), (4, 5, None)] + Triconnected: [(2, 3, None), (3, 4, None), (4, 5, 'newVEdge14'), (3, 5, None), (2, 5, None), (2, 4, 'newVEdge15')] + Polygon: [(1, 2, None), (2, 4, 'newVEdge15'), (1, 4, None)] + + An example with multi-edges and accessing the triconnected components: + + sage: G = Graph() + sage: G.allow_multiple_edges(True) + sage: G.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(1,5),(2,3)]) + sage: tric = Triconnectivity(G) + Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] + Bond: [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')] + Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, + None), (2, 3, 'newVEdge1')] + sage: tric.comp_list_new + [[(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')], + [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')], + [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, 'newVEdge1')]] + sage: tric.comp_type + [0, 0, 1] + + An example of a triconnected graph: + + sage: G2 = Graph() + sage: G2.allow_multiple_edges(True) + sage: G2.add_edges([('a','b'),('a','c'),('a','d'),('b','c'),('b','d'),('c','d')]) + sage: tric2 = Triconnectivity(G2) + Triconnected: [('a', 'b', None), ('b', 'c', None), ('a', 'c', None), ('c', 'd', None), ('b', 'd', None), ('a', 'd', None)] + + TESTS:: + + A disconnected graph: + + sage: from sage.graphs.connectivity import Triconnectivity + sage: G = Graph([(1,2),(3,5)]) + sage: tric = Triconnectivity(G) + Traceback (most recent call last): + ... + ValueError: Graph is disconnected + + A graph with a cut vertex: + + sage: from sage.graphs.connectivity import Triconnectivity + sage: G = Graph([(1,2),(1,3),(2,3),(3,4),(3,5),(4,5)]) + sage: tric = Triconnectivity(G) + Traceback (most recent call last): + ... + ValueError: Graph has a cut vertex + """ def __init__(self, G, check=True): from sage.graphs.graph import Graph @@ -2597,9 +2728,9 @@ class Triconnectivity: # self.highpt containing the edge e. Used in the `path_search` function. self.in_high = dict((e, None) for e in self.graph_copy.edges()) - # New DFS number of the vertex with i as its old DFS number + # Translates DFS number of a vertex to its new number self.old_to_new = [0 for i in range(self.n+1)] - self.newnum = [0 for i in range(self.n)] # new DFS number of vertex i + self.newnum = [0 for i in range(self.n)] # new number of vertex i self.node_at = [0 for i in range(self.n+1)] # node at dfs number of i self.lowpt1 = [None for i in range(self.n)] # lowpt1 number of vertex i self.lowpt2 = [None for i in range(self.n)] # lowpt2 number of vertex i @@ -2621,7 +2752,7 @@ class Triconnectivity: self.components_list = [] # list of components of `graph_copy` self.graph_copy_adjacency = [[] for i in range(self.n)] # Stores adjacency list - # Dictionary of (e, True/False) to denote if a path starts at edge e + # Dictionary of (e, True/False) to denote if edge e starts a path self.starts_path = dict((e, False) for e in self.graph_copy.edges()) self.is_biconnected = True # Boolean to store if the graph is biconnected or not @@ -2648,7 +2779,7 @@ class Triconnectivity: # Trivial cases if self.n < 2: raise ValueError("Graph is not biconnected") - if self.n <= 2: + if self.n == 2: if self.m < 3: raise ValueError("Graph is not biconnected") comp = Component([], 0) @@ -2658,7 +2789,7 @@ class Triconnectivity: return # Triconnectivity algorithm - self.split_multi_egdes() + self.__split_multi_egdes() # Build adjacency list for e in self.graph_copy.edges(): @@ -2667,7 +2798,7 @@ class Triconnectivity: self.dfs_counter = 0 # Initialisation for dfs1() self.start_vertex = 0 # Initialisation for dfs1() - self.cut_vertex = self.dfs1(self.start_vertex, check=check) + self.cut_vertex = self.__dfs1(self.start_vertex, check=check) if check: # If graph is disconnected @@ -2688,23 +2819,23 @@ class Triconnectivity: # Add edge to the set reverse_edges self.reverse_edges.add(e) - self.build_acceptable_adj_struct() - self.dfs2() + self.__build_acceptable_adj_struct() + self.__dfs2() - self.path_search(self.start_vertex) + self.__path_search(self.start_vertex) # last split component c = Component([],0) while self.e_stack: - c.add_edge(self.estack_pop()) + c.add_edge(self.__estack_pop()) c.component_type = 2 if c.edge_list.get_length() > 4 else 1 self.components_list.append(c) c = None - self.assemble_triconnected_components() + self.__assemble_triconnected_components() self.print_triconnected_components() - def tstack_push(self, h, a, b): + def __tstack_push(self, h, a, b): """ Push `(h,a,b)` triple on Tstack """ @@ -2713,20 +2844,20 @@ class Triconnectivity: self.t_stack_a[self.t_stack_top] = a self.t_stack_b[self.t_stack_top] = b - def tstack_push_eos(self): + def __tstack_push_eos(self): """ Push end-of-stack marker on Tstack """ self.t_stack_top += 1 self.t_stack_a[self.t_stack_top] = -1 - def tstack_not_eos(self): + def __tstack_not_eos(self): """ Return true iff end-of-stack marker is not on top of Tstack """ return self.t_stack_a[self.t_stack_top] != -1 - def estack_pop(self): + def __estack_pop(self): """ Pop from estack and return the popped element """ @@ -2734,7 +2865,7 @@ class Triconnectivity: self.e_stack = self.e_stack[0:-1] return e - def new_component(self, edges=[], type_c=0): + def __new_component(self, edges=[], type_c=0): """ Create a new component, add `edges` to it. type_c = 0 for bond, 1 for polygon, 2 for triconnected component @@ -2743,7 +2874,7 @@ class Triconnectivity: self.components_list.append(c) return c - def high(self, v): + def __high(self, v): """ Return the high(v) value, which is the first value in highpt list of `v` """ @@ -2753,7 +2884,7 @@ class Triconnectivity: else: return head.data - def del_high(self, e): + def __del_high(self, e): if e in self.in_high: it = self.in_high[e] if it: @@ -2763,26 +2894,28 @@ class Triconnectivity: v = e[1] self.highpt[v].remove(it) - def split_multi_egdes(self): + def __split_multi_egdes(self): """ - This function will mark all the multiple edges present in graph_copy - as removed and append the multiple edges in component list. + Iterate through all the edges, and constructs bonds wherever + multiedges are present. - If there are `k` multiple edges between `u` and `v` then `k+1` - edges will be added to a component and edge_status will have k-1 edges - marked a 3(i.e edge removed). + If there are `k` multiple edges between `u` and `v`, then `k+1` + edges will be added to a new component (one of them is a virtual edge), + all the `k` edges are removed from the graph and a virtual edge is + between `u` and `v` is added to the graph. Instead of deleting edges + from the graph, they are instead marked as removed, i.e., 3 in + the dictionary `edge_status`. - It won't return anything but update the components_list and - graph_copy, which will become simple graph. + No return value. Update the `components_list` and `graph_copy`. + `graph_copy` will become simple graph after this function. """ - comp = [] if self.graph_copy.has_multiple_edges(): sorted_edges = sorted(self.graph_copy.edges()) for i in range(len(sorted_edges) - 1): - # It will add k - 1 multiple edges to comp + # Find multi edges and add to component and delete from graph if (sorted_edges[i][0] == sorted_edges[i + 1][0]) and \ (sorted_edges[i][1] == sorted_edges[i + 1][1]): self.edge_status[sorted_edges[i]] = 3 # edge removed @@ -2801,7 +2934,7 @@ class Triconnectivity: self.edge_status[newVEdge] = 0 comp.append(newVEdge) - self.new_component(comp) + self.__new_component(comp) comp = [] if comp: comp.append(sorted_edges[i+1]) @@ -2814,13 +2947,13 @@ class Triconnectivity: self.edge_status[newVEdge] = 0 comp.append(newVEdge) - self.new_component(comp) + self.__new_component(comp) - def dfs1(self, v, u=None, check=True): + def __dfs1(self, v, u=None, check=True): """ - This function builds the palm tree of the graph. + This function builds the palm tree of the graph using a dfs traversal. Also populates the lists lowpt1, lowpt2, nd, parent, and dfs_number. - It updates the dict edge_status to reflect palm tree arcs and fronds. + It updates the dict `edge_status` to reflect palm tree arcs and fronds. Input:: @@ -2835,59 +2968,8 @@ class Triconnectivity: Output:: - If ``check`` is set to True, and a cut vertex is found, the cut vertex - is returned. If no cut vertex is found, return value if None. + is returned. If no cut vertex is found, return value is None. If ``check`` is set to False, ``None`` is returned. - - Example:: - - An example to build the palm tree: - - sage: from sage.graphs.connectivity import Triconnectivity - sage: G = Graph() - sage: G.add_edges([(1,2),(1,13),(1,12),(1,8),(1,4),(2,13),(2,3),(3,13)]) - sage: G.add_edges([(3,4),(4,5),(4,7),(5,6),(5,7),(5,8),(6,7),(8,9),(8,12)]) - sage: G.add_edges([(8,11),(9,10),(9,12),(9,11),(10,11),(10,12)]) - sage: tric = Triconnectivity(G, check=False) - sage: tric.edge_status - {(0, 1, None): 1, - (0, 3, None): 2, - (0, 7, None): 2, - (0, 11, None): 2, - (0, 12, None): 2, - (1, 2, None): 1, - (1, 12, None): 2, - (2, 3, None): 1, - (2, 12, None): 1, - (3, 4, None): 1, - (3, 6, None): 2, - (4, 5, None): 1, - (4, 6, None): 2, - (4, 7, None): 1, - (5, 6, None): 1, - (7, 8, None): 1, - (7, 10, None): 2, - (7, 11, None): 2, - (8, 9, None): 1, - (8, 10, None): 2, - (8, 11, None): 2, - (9, 10, None): 1, - (9, 11, None): 1} - sage: tric.lowpt1 - [1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 8, 1, 1] - sage: tric.lowpt2 - [1, 2, 2, 4, 4, 5, 5, 8, 8, 8, 9, 8, 2] - sage: tric.parent - [None, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 9, 2] - - An example of a disconnected graph: - - sage: G = Graph() - sage: G.add_edges([(1,2),(1,3),(2,3),(3,4),(3,5),(4,5)]) - sage: tric = Triconnectivity(G, check=True) - sage: tric.is_biconnected - False - sage: tric.cut_vertex - 3 """ first_son = None # For testing biconnectivity s1 = None # Storing the cut vertex, if there is one @@ -2907,13 +2989,19 @@ class Triconnectivity: if first_son is None: first_son = w self.tree_arc[w] = e - s1 = self.dfs1(w, v, check) + s1 = self.__dfs1(w, v, check) if check: - # check for cut vertex + # Check for cut vertex. + # The situation in which there is no path from w to an + # ancestor of v : we have identified a cut vertex if (self.lowpt1[w] >= self.dfs_number[v]) and (w != first_son or u != None): s1 = v + # Calculate the `lowpt1` and `lowpt2` values. + # `lowpt1` is the smallest vertex (the vertex x with smallest + # dfs_number[x]) that can be reached from v. + # `lowpt2` is the next smallest vertex that can be reached from v. if self.lowpt1[w] < self.lowpt1[v]: self.lowpt2[v] = min(self.lowpt1[v], self.lowpt2[w]) self.lowpt1[v] = self.lowpt1[w] @@ -2934,22 +3022,26 @@ class Triconnectivity: elif self.dfs_number[w] > self.lowpt1[v]: self.lowpt2[v] = min(self.lowpt2[v], self.dfs_number[w]) - return s1 + return s1 # s1 is None is graph does not have a cut vertex - def build_acceptable_adj_struct(self): + def __build_acceptable_adj_struct(self): """ Builds the adjacency lists for each vertex with certain properties of the ordering, using the ``lowpt1`` and ``lowpt2`` values. The list ``adj`` and the dictionary ``in_adj`` are populated. + + `phi` values of each edge are calculated using the `lowpt` values of + incident vertices. The edges are then sorted by the `phi` values and + added to adjacency list. """ max = 3*self.n + 2 bucket = [[] for i in range(max+1)] for e in self.graph_copy.edges(): edge_type = self.edge_status[e] - if edge_type == 3: + if edge_type == 3: # If edge status is `removed`, go to next edge continue # compute phi value @@ -2973,6 +3065,7 @@ class Triconnectivity: bucket[phi].append(e) + # Populate `adj` and `in_adj` with the sorted edges for i in range(1,max+1): for e in bucket[i]: node = LinkedListNode() @@ -2984,9 +3077,10 @@ class Triconnectivity: self.adj[e[0]].append(node) self.in_adj[e] = node - def path_finder(self, v): + def __path_finder(self, v): """ This function is a helper function for `dfs2` function. + Calculate `newnum[v]` and identify the edges which start a new path. """ self.newnum[v] = self.dfs_counter - self.nd[v] + 1 e_node = self.adj[v].get_head() @@ -2998,9 +3092,10 @@ class Triconnectivity: self.new_path = False self.starts_path[e] = True if self.edge_status[e] == 1: # tree arc - self.path_finder(w) + self.__path_finder(w) self.dfs_counter -= 1 else: + # Identified a new frond that enters `w`. Add to `highpt[w]`. highpt_node = LinkedListNode() highpt_node.set_data(self.newnum[v]) self.highpt[w].append(highpt_node) @@ -3008,7 +3103,7 @@ class Triconnectivity: self.new_path = True - def dfs2(self): + def __dfs2(self): """ Update the values of lowpt1 and lowpt2 lists with the help of new numbering obtained from `Path Finder` funciton. @@ -3022,20 +3117,24 @@ class Triconnectivity: self.new_path = True # We call the pathFinder function with the start vertex - self.path_finder(self.start_vertex) + self.__path_finder(self.start_vertex) + # Update `old_to_new` values with the calculated `newnum` values for v in self.graph_copy.vertices(): self.old_to_new[self.dfs_number[v]] = self.newnum[v] - # Update lowpt values. + # Update lowpt values according to `newnum` values. for v in self.graph_copy.vertices(): self.node_at[self.newnum[v]] = v self.lowpt1[v] = self.old_to_new[self.lowpt1[v]] self.lowpt2[v] = self.old_to_new[self.lowpt2[v]] - def path_search(self, v): + def __path_search(self, v): """ - Function to find the separation pairs. + Find the separation pairs and construct the split components. + Check for type-1 and type-2 separation pairs, and construct + the split components while also creating new virtual edges wherever + required. """ y = 0 vnum = self.newnum[v] @@ -3050,21 +3149,22 @@ class Triconnectivity: else: w = e[1] wnum = self.newnum[w] - if self.edge_status[e] == 1: # tree arc - if self.starts_path[e]: + if self.edge_status[e] == 1: # e is a tree arc + if self.starts_path[e]: # if a new path starts at edge e y = 0 + # Pop all (h,a,b) from tstack where a > lowpt1[w] if self.t_stack_a[self.t_stack_top] > self.lowpt1[w]: while self.t_stack_a[self.t_stack_top] > self.lowpt1[w]: y = max(y, self.t_stack_h[self.t_stack_top]) b = self.t_stack_b[self.t_stack_top] self.t_stack_top -= 1 - self.tstack_push(y, self.lowpt1[w], b) + self.__tstack_push(y, self.lowpt1[w], b) else: - self.tstack_push(wnum + self.nd[w] - 1, self.lowpt1[w], vnum) - self.tstack_push_eos() + self.__tstack_push(wnum + self.nd[w] - 1, self.lowpt1[w], vnum) + self.__tstack_push_eos() - self.path_search(w) + self.__path_search(w) self.e_stack.append(self.tree_arc[w]) @@ -3074,7 +3174,9 @@ class Triconnectivity: temp_target = temp[0] else: temp_target = temp[1] - # while vnum is not the start_vertex + + # Type-2 separation pair check + # while v is not the start_vertex while vnum != 1 and ((self.t_stack_a[self.t_stack_top] == vnum) or \ (self.degree[w] == 2 and self.newnum[temp_target] > wnum)): @@ -3088,14 +3190,14 @@ class Triconnectivity: e_ab = None if self.degree[w] == 2 and self.newnum[temp_target] > wnum: # found type-2 separation pair - (v, temp_target) - e1 = self.estack_pop() - e2 = self.estack_pop() + e1 = self.__estack_pop() + e2 = self.__estack_pop() self.adj[w].remove(self.in_adj[e2]) if e2 in self.reverse_edges: - x = e2[0] + x = e2[0] # target else: - x = e2[1] + x = e2[1] # target e_virt = tuple([v, x, "newVEdge"+str(self.virtual_edge_num)]) self.graph_copy.add_edge(e_virt) @@ -3104,10 +3206,10 @@ class Triconnectivity: self.degree[x] -= 1 if e2 in self.reverse_edges: - e2_source = e2[1] + e2_source = e2[1] # target else: e2_source = e2[0] - if e2_source != w: # OGDF_ASSERT + if e2_source != w: raise ValueError("Graph is not biconnected") comp = Component([e1, e2, e_virt], 1) @@ -3118,14 +3220,14 @@ class Triconnectivity: e1 = self.e_stack[-1] if e1 in self.reverse_edges: if e1[1] == x and e1[0] == v: - e_ab = self.estack_pop() + e_ab = self.__estack_pop() self.adj[x].remove(self.in_adj[e_ab]) - self.del_high(e_ab) + self.__del_high(e_ab) else: if e1[0] == x and e1[1] == v: - e_ab = self.estack_pop() + e_ab = self.__estack_pop() self.adj[x].remove(self.in_adj[e_ab]) - self.del_high(e_ab) + self.__del_high(e_ab) else: # found type-2 separation pair - (self.node_at[a], self.node_at[b]) h = self.t_stack_h[self.t_stack_top] @@ -3145,23 +3247,23 @@ class Triconnectivity: break if (self.newnum[x] == a and self.newnum[xy_target] == b) or \ (self.newnum[xy_target] == a and self.newnum[x] == b): - e_ab = self.estack_pop() + e_ab = self.__estack_pop() if e_ab in self.reverse_edges: - e_ab_source = e_ab[1] + e_ab_source = e_ab[1] # source else: - e_ab_source = e_ab[0] + e_ab_source = e_ab[0] # source self.adj[e_ab_source].remove(self.in_adj[e_ab]) - self.del_high(e_ab) + self.__del_high(e_ab) else: - eh = self.estack_pop() + eh = self.__estack_pop() if eh in self.reverse_edges: eh_source = eh[1] else: eh_source = eh[0] if it != self.in_adj[eh]: self.adj[eh_source].remove(self.in_adj[eh]) - self.del_high(eh) + self.__del_high(eh) comp.add_edge(eh) self.degree[x] -= 1 @@ -3206,8 +3308,10 @@ class Triconnectivity: if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair - (self.node_at[self.lowpt1[w]], v) + + # Create a new component and add edges to it comp = Component([],0) - if not self.e_stack: # OGDF_ASSERT + if not self.e_stack: raise ValueError("stack is empty") while self.e_stack: xy = self.e_stack[-1] @@ -3222,22 +3326,22 @@ class Triconnectivity: (wnum <= y and y < wnum + self.nd[w])): break - comp.add_edge(self.estack_pop()) - self.del_high(xy) + comp.add_edge(self.__estack_pop()) + self.__del_high(xy) self.degree[self.node_at[xx]] -= 1 self.degree[self.node_at[y]] -= 1 e_virt = tuple([v, self.node_at[self.lowpt1[w]], "newVEdge"+str(self.virtual_edge_num)]) - self.graph_copy.add_edge(e_virt) + self.graph_copy.add_edge(e_virt) # Add virtual edge to graph self.virtual_edge_num += 1 - comp.finish_tric_or_poly(e_virt) + comp.finish_tric_or_poly(e_virt) # Add virtual edge to component self.components_list.append(comp) comp = None if (xx == vnum and y == self.lowpt1[w]) or \ (y == vnum and xx == self.lowpt1[w]): - comp_bond = Component([],type_c = 0) - eh = self.estack_pop() + comp_bond = Component([],type_c = 0) # new triple bond + eh = self.__estack_pop() if self.in_adj[eh] != it: if eh in self.reverse_edges: self.adj[eh[1]].remove(self.in_adj[eh]) @@ -3266,7 +3370,7 @@ class Triconnectivity: it = e_virt_node self.in_adj[e_virt] = it - if not e_virt in self.in_high and self.high(self.node_at[self.lowpt1[w]]) < vnum: + if not e_virt in self.in_high and self.__high(self.node_at[self.lowpt1[w]]) < vnum: vnum_node = LinkedListNode() vnum_node.set_data(vnum) self.highpt[self.node_at[self.lowpt1[w]]].push_front(vnum_node) @@ -3296,42 +3400,45 @@ class Triconnectivity: e_virt_node.set_data(e_virt) self.in_adj[eh] = e_virt_node # end type-1 search + + # if an path starts at edge e, empty the tstack. if self.starts_path[e]: - while self.tstack_not_eos(): + while self.__tstack_not_eos(): self.t_stack_top -= 1 self.t_stack_top -= 1 - while self.tstack_not_eos() and self.t_stack_b[self.t_stack_top] != vnum \ - and self.high(v) > self.t_stack_h[self.t_stack_top]: + while self.__tstack_not_eos() and self.t_stack_b[self.t_stack_top] != vnum \ + and self.__high(v) > self.t_stack_h[self.t_stack_top]: self.t_stack_top -= 1 outv -= 1 - else: #frond + else: # e is a frond if self.starts_path[e]: y = 0 + # pop all (h,a,b) from tstack where a > w if self.t_stack_a[self.t_stack_top] > wnum: while self.t_stack_a[self.t_stack_top] > wnum: y = max(y, self.t_stack_h[self.t_stack_top]) b = self.t_stack_b[self.t_stack_top] self.t_stack_top -= 1 - self.tstack_push(y, wnum, b) + self.__tstack_push(y, wnum, b) else: - self.tstack_push(vnum, wnum, vnum) + self.__tstack_push(vnum, wnum, vnum) self.e_stack.append(e) # add (v,w) to ESTACK - # Go to next node in adjacency list + # Go to next edge in adjacency list e_node = e_node.next - def assemble_triconnected_components(self): + def __assemble_triconnected_components(self): """ - Iterates through all the components built by `path_finder` and merges - the components wherever possible for contructing the final - triconnected components. - Formats the triconnected components into original vertices and edges. - The triconnected components are stored in `self.comp_list_new` and - `self.comp_type`. + Iterate through all the split components built by `path_finder` and + merges two bonds or two polygons that share an edge for contructing the + final triconnected components. + Subsequently, convert the edges in triconnected components into original + vertices and edges. The triconnected components are stored in + `self.comp_list_new` and `self.comp_type`. """ comp1 = {} # The index of first component that an edge belongs to comp2 = {} # The index of second component that an edge belongs to @@ -3354,6 +3461,8 @@ class Triconnectivity: e_node = e_node.next + # For each edge in a component, if the edge is a virtual edge, merge + # the two components the edge belongs to for i in range(num_components): c1 = self.components_list[i] c1_type = c1.component_type @@ -3365,10 +3474,11 @@ class Triconnectivity: if c1_type == 0 or c1_type == 1: e_node = self.components_list[i].edge_list.get_head() + # Iterate through each edge in the component while e_node: e = e_node.get_data() e_node_next = e_node.next - # the label of virtual edges is a string + # The label of a virtual edge is a string if not isinstance(e[2], str): e_node = e_node_next continue @@ -3384,18 +3494,24 @@ class Triconnectivity: e_node2 = item1[e] c2 = self.components_list[j] + + # If the two components are not the same type, do not merge if (c1_type != c2.component_type): - e_node = e_node_next + e_node = e_node_next # Go to next edge continue visited[j] = True l2 = c2.edge_list + # Remove the corresponding virtual edges in both the components + # and merge the components l2.remove(e_node2) l1.concatenate(l2) + # if `e_node_next` was empty, after merging two components, + # more edges are added to the component. if not e_node_next: - e_node_next = e_node.next + e_node_next = e_node.next # Go to next edge l1.remove(e_node) From b52f8482bbe77aab4116928a9f0e359ddb99fa8a Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Sat, 28 Jul 2018 00:12:11 +0530 Subject: [PATCH 064/264] Bug fix. --- src/sage/graphs/connectivity.pyx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index e7fa4f0704b..13b32be6155 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2861,9 +2861,7 @@ class Triconnectivity: """ Pop from estack and return the popped element """ - e = self.e_stack[-1] - self.e_stack = self.e_stack[0:-1] - return e + return self.e_stack.pop() def __new_component(self, edges=[], type_c=0): """ @@ -3308,7 +3306,6 @@ class Triconnectivity: if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair - (self.node_at[self.lowpt1[w]], v) - # Create a new component and add edges to it comp = Component([],0) if not self.e_stack: @@ -3354,7 +3351,8 @@ class Triconnectivity: self.graph_copy.add_edge(e_virt) self.virtual_edge_num += 1 comp_bond.add_edge(e_virt) - self.in_high[e_virt] = self.in_high[eh] + if eh in self.in_high: + self.in_high[e_virt] = self.in_high[eh] self.degree[v] -= 1 self.degree[self.node_at[self.lowpt1[w]]] -= 1 @@ -3395,7 +3393,8 @@ class Triconnectivity: self.tree_arc[v] = e_virt self.edge_status[e_virt] = 1 - self.in_adj[e_virt] = self.in_adj[eh] + if eh in self.in_adj: + self.in_adj[e_virt] = self.in_adj[eh] e_virt_node = LinkedListNode() e_virt_node.set_data(e_virt) self.in_adj[eh] = e_virt_node From d02146d03ca4e8ef1edce0c91077713358e620fb Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Wed, 1 Aug 2018 23:47:49 +0530 Subject: [PATCH 065/264] Deleting edges instead of setting edge_status to removed. --- src/sage/graphs/connectivity.pyx | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 13b32be6155..7275d609ba7 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2714,7 +2714,7 @@ class Triconnectivity: self.n = self.graph_copy.order() # number of vertices self.m = self.graph_copy.size() # number of edges - # status of each edge: unseen=0, tree=1, frond=2, removed=3 + # status of each edge: unseen=0, tree=1, frond=2 self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) # Edges of the graph which are in the reverse direction in palm tree @@ -2899,10 +2899,8 @@ class Triconnectivity: If there are `k` multiple edges between `u` and `v`, then `k+1` edges will be added to a new component (one of them is a virtual edge), - all the `k` edges are removed from the graph and a virtual edge is - between `u` and `v` is added to the graph. Instead of deleting edges - from the graph, they are instead marked as removed, i.e., 3 in - the dictionary `edge_status`. + all the `k` edges are deleted from the graph and a virtual edge is + between `u` and `v` is added to the graph. No return value. Update the `components_list` and `graph_copy`. `graph_copy` will become simple graph after this function. @@ -2916,12 +2914,12 @@ class Triconnectivity: # Find multi edges and add to component and delete from graph if (sorted_edges[i][0] == sorted_edges[i + 1][0]) and \ (sorted_edges[i][1] == sorted_edges[i + 1][1]): - self.edge_status[sorted_edges[i]] = 3 # edge removed + self.graph_copy.delete_edge(sorted_edges[i]) comp.append(sorted_edges[i]) else: if comp: comp.append(sorted_edges[i]) - self.edge_status[sorted_edges[i]] = 3 # edge removed + self.graph_copy.delete_edge(sorted_edges[i]) # Add virtual edge to graph_copy newVEdge = tuple([sorted_edges[i][0], sorted_edges[i][1], "newVEdge"+str(self.virtual_edge_num)]) @@ -2936,7 +2934,7 @@ class Triconnectivity: comp = [] if comp: comp.append(sorted_edges[i+1]) - self.edge_status[sorted_edges[i+1]] = 3 # edge removed + self.graph_copy.delete_edge(sorted_edges[i+1]) # Add virtual edge to graph_copy newVEdge = tuple([sorted_edges[i+1][0], sorted_edges[i+1][1], "newVEdge"+str(self.virtual_edge_num)]) @@ -3039,8 +3037,6 @@ class Triconnectivity: for e in self.graph_copy.edges(): edge_type = self.edge_status[e] - if edge_type == 3: # If edge status is `removed`, go to next edge - continue # compute phi value # bucket sort adjacency list by phi values From 035e2be115e6330db364e916613bb087ecda1638 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Sat, 4 Aug 2018 01:08:48 +0530 Subject: [PATCH 066/264] Fixed a bug related to the case of directed grapg input. --- src/sage/graphs/connectivity.pyx | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 7275d609ba7..efb38a6080b 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2578,15 +2578,12 @@ class Triconnectivity: of the $i^{th}$ component - 1 for bond, 2 for polygon, 3 for triconnected component. The output can be accessed through these variables. - ALGORITHM: - - We implement the algorithm proposed by Tarjan in [Tarjan72]_. The - original version is recursive. We emulate the recursion using a stack. - ALGORITHM:: We implement the algorithm proposed by Hopcroft and Tarjan in [Hopcroft1973]_ and later corrected by Gutwenger and Mutzel in [Gut2001]_. + In the case of a digraph, the computation is done on the underlying + graph. .. SEEALSO:: @@ -2662,6 +2659,15 @@ class Triconnectivity: sage: tric2 = Triconnectivity(G2) Triconnected: [('a', 'b', None), ('b', 'c', None), ('a', 'c', None), ('c', 'd', None), ('b', 'd', None), ('a', 'd', None)] + An example of a directed graph with multi-edges: + + sage: G3 = DiGraph() + sage: G3.allow_multiple_edges(True) + sage: G3.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(5,1)]) + sage: tric2 = Triconnectivity(G3) + Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] + Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, None)] + TESTS:: A disconnected graph: @@ -2684,7 +2690,7 @@ class Triconnectivity: """ def __init__(self, G, check=True): - from sage.graphs.graph import Graph + from sage.graphs.graph import Graph, DiGraph # graph_copy is a SparseGraph of the input graph `G` # We relabel the edges with increasing numbers to be able to # distinguish between multi-edges @@ -3161,7 +3167,6 @@ class Triconnectivity: self.__path_search(w) self.e_stack.append(self.tree_arc[w]) - temp_node = self.adj[w].get_head() temp = temp_node.get_data() if temp in self.reverse_edges: @@ -3526,7 +3531,14 @@ class Triconnectivity: if isinstance(e[2], str): label = e[2] else: - label = self.edge_label_dict[(source, target,e[2])][2] + # If the original input graph was a directed graph, the + # source and target can in reversed in the edge. + # Hence, we check for both. Since we use unique edge + # labels, this does not fetch a wrong edge. + if (source, target, e[2]) in self.edge_label_dict: + label = self.edge_label_dict[(source, target, e[2])][2] + else: + label = self.edge_label_dict[(target, source, e[2])][2] e_list_new.append(tuple([source, target, label])) # Add the component data to `comp_list_new` and `comp_type` self.comp_type.append(self.components_list[i].component_type) From f398a524a6de4374061c63e9ada4d059742795fb Mon Sep 17 00:00:00 2001 From: saiharsh Date: Mon, 6 Aug 2018 18:23:15 +0530 Subject: [PATCH 067/264] Added staticSPQRTree --- src/sage/graphs/connectivity.pyx | 126 ++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index efb38a6080b..30900f4e39c 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2599,6 +2599,7 @@ class Triconnectivity: sage: G.add_edges([(3,13),(4,5),(4,7),(5,6),(5,7),(5,8),(6,7),(8,9),(8,11)]) sage: G.add_edges([(8,12),(9,10),(9,11),(9,12),(10,11),(10,12)]) sage: tric = Triconnectivity(G) + sage: tric.print_triconnected_components() Triconnected: [(8, 9, None), (9, 10, None), (10, 11, None), (9, 11, None), (8, 11, None), (10, 12, None), (9, 12, None), (8, 12, 'newVEdge0')] Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] Polygon: [(8, 12, 'newVEdge1'), (1, 12, None), (8, 1, 'newVEdge2')] @@ -2619,6 +2620,7 @@ class Triconnectivity: sage: G.add_edges([(5,14),(6,8),(7,14),(8,9),(8,10),(8,11),(8,12),(9,10),(10,13)]) sage: G.add_edges([(10,14),(10,15),(10,16),(11,12),(11,13),(12,13),(14,15),(14,16),(15,16)]) sage: tric = Triconnectivity(G) + sage: tric.print_triconnected_components() Polygon: [(6, 8, None), (4, 6, None), (5, 8, 'newVEdge12'), (5, 4, 'newVEdge13')] Polygon: [(8, 9, None), (9, 10, None), (8, 10, 'newVEdge1')] Bond: [(8, 10, 'newVEdge1'), (8, 10, None), (8, 10, 'newVEdge4'), (10, 8, 'newVEdge5')] @@ -2640,6 +2642,7 @@ class Triconnectivity: sage: G.allow_multiple_edges(True) sage: G.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(1,5),(2,3)]) sage: tric = Triconnectivity(G) + sage: tric.print_triconnected_components() Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] Bond: [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')] Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, @@ -2656,7 +2659,8 @@ class Triconnectivity: sage: G2 = Graph() sage: G2.allow_multiple_edges(True) sage: G2.add_edges([('a','b'),('a','c'),('a','d'),('b','c'),('b','d'),('c','d')]) - sage: tric2 = Triconnectivity(G2) + sage: tric = Triconnectivity(G2) + sage: tric.print_triconnected_components() Triconnected: [('a', 'b', None), ('b', 'c', None), ('a', 'c', None), ('c', 'd', None), ('b', 'd', None), ('a', 'd', None)] An example of a directed graph with multi-edges: @@ -2664,7 +2668,8 @@ class Triconnectivity: sage: G3 = DiGraph() sage: G3.allow_multiple_edges(True) sage: G3.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(5,1)]) - sage: tric2 = Triconnectivity(G3) + sage: tric = Triconnectivity(G3) + sage: tric.print_triconnected_components() Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, None)] @@ -2839,7 +2844,7 @@ class Triconnectivity: c = None self.__assemble_triconnected_components() - self.print_triconnected_components() + #self.print_triconnected_components() def __tstack_push(self, h, a, b): """ @@ -3558,3 +3563,118 @@ class Triconnectivity: print "Triconnected: ", print self.comp_list_new[i] +def staticSPQRTree(G): + r""" + Construct a static spqr tree using the output generated by triconnectivity + which gives bond, polygon and triconnected components present in graph `G`. + Iterate over all the components and create respective Tree nodes, based on + component edges and graph `G` edges construct edges between two tree nodes. + First construct the tree with 0..n-1 labeled vertices then replace them + with original vertex labels. + + EXAMPLES:: + + sage: from sage.graphs.connectivity import staticSPQRTree, spqr_tree_to_graph + sage: G = Graph(2) + sage: for i in range(3): + ....: G.add_clique([0, 1, G.add_vertex(), G.add_vertex()]) + sage: Tree = staticSPQRTree(G) + sage: K4 = graphs.CompleteGraph(4) + sage: all(u[1].is_isomorphic(K4) for u in Tree.vertices() if u[0] == 'R') + True + sage: from sage.graphs.connectivity import spqr_tree_to_graph + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + + sage: G = Graph(2) + sage: for i in range(3): + ....: G.add_path([0, G.add_vertex(), G.add_vertex(), 1]) + sage: Tree = staticSPQRTree(G) + sage: C4 = graphs.CycleGraph(4) + sage: all(u[1].is_isomorphic(C4) for u in Tree.vertices() if u[0] == 'S') + True + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + + sage: G.allow_multiple_edges(True) + sage: G.add_edges(G.edges()) + sage: Tree = staticSPQRTree(G) + sage: all(u[1].is_isomorphic(C4) for u in Tree.vertices() if u[0] == 'S') + True + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + + sage: G = graphs.CycleGraph(6) + sage: Tree = staticSPQRTree(G) + sage: Tree.order() + 1 + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + sage: G.add_edge(0, 3) + sage: Tree = staticSPQRTree(G) + sage: Tree.order() + 3 + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + """ + from sage.graphs.graph import Graph + tric = Triconnectivity(G) + int_to_treeVertex = [] + Tree = Graph(multiedges=False) + partnerNode = {e:None for e in tric.graph_copy.edges()} + + for i in range(len(tric.comp_list_new)): + + # Do this case exist? + if not len(tric.comp_list_new[i]): + continue + + # Create a vertex in tree + treeVertex = Tree.add_vertex() + + if tric.comp_type[i] == 0: + int_to_treeVertex.append(("P", Graph(tric.comp_list_new[i], immutable=True, multiedges=True))) + + elif tric.comp_type[i] == 1: + int_to_treeVertex.append(("S", Graph(tric.comp_list_new[i], immutable=True, multiedges=True))) + + else: + int_to_treeVertex.append(("R", Graph(tric.comp_list_new[i], immutable=True, multiedges=True))) + + for originalEdge in tric.comp_list_new[i]: + + # (u, v) are vertices of graph_copy, construction on graph_copy + # is available in Triconnectivity + u, v = tric.vertex_to_int[originalEdge[0]], tric.vertex_to_int[originalEdge[1]] + label = originalEdge[2] + + # e is an edge of graph_copy + e = tuple([u, v, label]) + + # check if edge e is an original graph G. + # if yes assign graph_edge as original form of e. + # if not assign graph_edge as None + if e[2] and "newVEdge" in e[2]: + graph_edge = None + else: + if e in tric.reverse_edges: + graph_edge = tuple([originalEdge[1], originalEdge[0]]) + else: + graph_edge = tuple([originalEdge[0], originalEdge[1]]) + + if not graph_edge: + try: + if partnerNode[e] == None: + partnerNode[e] = treeVertex + else: + Tree.add_edge(partnerNode[e], treeVertex) + except: + new_e = tuple([v, u, label]) + if partnerNode[new_e] == None: + partnerNode[new_e] = treeVertex + else: + Tree.add_edge(partnerNode[new_e], treeVertex) + + Tree.relabel(int_to_treeVertex) + return Tree + From 04a8748ce7aebf5f213228bdfb4af4af40b77c4f Mon Sep 17 00:00:00 2001 From: David Coudert Date: Mon, 6 Aug 2018 18:43:38 +0200 Subject: [PATCH 068/264] trac #25598: review modifications --- src/sage/graphs/connectivity.pyx | 165 ++++++++++++++++++------------- 1 file changed, 99 insertions(+), 66 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 214b069604c..75fb8a150ef 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2359,37 +2359,54 @@ def spqr_tree_to_graph(T): return G + class LinkedListNode: """ - Helper class for ``Triconnectivity``. - Node in a linked list. - Has pointers to its previous node and next node. - If this node is the `head` of the linked list, reference to the linked list - object is stored in `listobj`. + Node in a ``LinkedList``. + + This class is a helper class for ``Triconnectivity``. + + This class implements a node of a (doubly) linked list and so has pointers + to previous and next nodes in the list. If this node is the ``head`` of the + linked list, reference to the linked list object is stored in ``listobj``. """ - prev = None - next = None - data = None #edge or int - listobj = None - def set_data(self, e): - self.data = e + + def __init__(self, data=None): + """ + Initialize this ``LinkedListNode``. + + INPUT: + + - ``data`` -- (default: ``None``) either an edge, or an integer. + """ + self.prev = None + self.next = None + self.set_data(data) + self.listobj = None + + def set_data(self, data): + self.data = data + def get_data(self): return self.data + def set_obj(self, l): self.listobj = l + def clear_obj(self): self.listobj = None + def replace(self, node): """ Replace self node with ``node`` in the corresponding linked list. """ - if self.prev == None and self.next == None: + if self.prev is None and self.next is None: self.listobj.set_head(node) - elif self.prev == None: + elif self.prev is None: self.listobj.head = node node.next = self.next node.listobj = self.listobj - elif self.next == None: + elif self.next is None: self.prev.next = node node.prev = self.prev else: @@ -2400,30 +2417,39 @@ class LinkedListNode: class LinkedList: """ - A helper class for ``Triconnectivity``. - A linked list with a head and a tail pointer + A doubly linked list with head and tail pointers. + + This is a helper class for ``Triconnectivity``. + + This class implements a doubly linked list of ``LinkedListNode``. """ - head = None - tail = None - length = 0 + def __init__(self): + """ + Initialize this ``LinkedList``. + """ + self.head = None + self.tail = None + self.length = 0 + def remove(self, node): """ Remove the node ``node`` from the linked list. """ - if node.prev == None and node.next == None: + if node.prev is None and node.next is None: self.head = None self.tail = None - elif node.prev == None: # node is head + elif node.prev is None: # node is head self.head = node.next node.next.prev = None node.next.set_obj(self) - elif node.next == None: #node is tail + elif node.next is None: #node is tail node.prev.next = None self.tail = node.prev else: node.prev.next = node.next node.next.prev = node.prev self.length -= 1 + def set_head(self, h): """ Set the node ``h`` as the head of the linked list. @@ -2432,33 +2458,37 @@ class LinkedList: self.tail = h self.length = 1 h.set_obj(self) + def append(self, node): """ Append the node ``node`` to the linked list. """ - if self.head == None: + if self.head is None: self.set_head(node) else: self.tail.next = node node.prev = self.tail self.tail = node self.length += 1 + def get_head(self): return self.head + def get_length(self): return self.length + def replace(self, node1, node2): """ Replace the node ``node1`` with ``node2`` in the linked list. """ - if node1.prev == None and node1.next == None: + if node1.prev is None and node1.next is None: self.head = node2 self.tail = node2 - elif node1.prev == None: # head has to be replaced + elif node1.prev is None: # head has to be replaced node1.next.prev = node2 node2.next = node1.next self.head = node2 - elif node1.next == None: # tail has to be replaced + elif node1.next is None: # tail has to be replaced node1.prev.next = node2 node2.prev = node1.prev self.tail = node2 @@ -2467,31 +2497,36 @@ class LinkedList: node1.next.prev = node2 node2.prev = node1.prev node2.next = node1.next + def push_front(self, node): """ Add node ``node`` to the beginning of the linked list. """ - if self.head == None: - self.head = node - self.tail = node - node.set_obj(self) + if self.head is None: + self.set_head(node) else: self.head.clear_obj() self.head.prev = node node.next = self.head self.head = node node.set_obj(self) - self.length += 1 + self.length += 1 + def to_string(self): + """ + Return a string representation of self. + """ temp = self.head s = "" while temp: s += " " + str(temp.get_data()) temp = temp.next return s + def concatenate(self, lst2): """ Concatenates lst2 to self. + Makes lst2 empty. """ self.tail.next = lst2.head @@ -2503,47 +2538,50 @@ class LinkedList: class Component: """ - A helper class for ``Triconnectivity``. - A connected component. - `edge_list` contains the list of edges belonging to the component. - `component_type` stores the type of the component. + Connected component class. + + This is a helper class for ``Triconnectivity``. + + This class is used to store a connected component. It contains: + - ``edge_list`` -- list of edges belonging to the component, stored as a ``LinkedList``. + - ``component_type`` -- the type of the component. - 0 if bond. - 1 if polygon. - 2 is triconnected component. """ - edge_list = LinkedList() - component_type = 0 #bond = 0, polygon = 1, triconnected = 2 def __init__(self, edge_list, type_c): """ - `edge_list` is a list of edges to be added to the component. - `type_c` is the type of the component. + Initialize this component. + + INPUT: + + - ``edge_list`` -- list of edges to be added to the component. + + - `type_c` -- type of the component (0, 1, or 2). """ self.edge_list = LinkedList() for e in edge_list: - e_node = LinkedListNode() - e_node.set_data(e) - self.edge_list.append(e_node) + self.edge_list.append(LinkedListNode(e)) self.component_type = type_c + def add_edge(self, e): - e_node = LinkedListNode() - e_node.set_data(e) - self.edge_list.append(e_node) + self.edge_list.append(LinkedListNode(e)) + def finish_tric_or_poly(self, e): """ Edge `e` is the last edge to be added to the component. Classify the component as a polygon or triconnected component depending on the number of edges belonging to it. """ - e_node = LinkedListNode() - e_node.set_data(e) - self.edge_list.append(e_node) + self.add_edge(e) if self.edge_list.get_length() >= 4: self.component_type = 2 else: self.component_type = 1 + def __str__(self): """ - Function for printing the component. + Return a string representation of the component. """ if self.component_type == 0: type_str = "Bond: " @@ -2552,11 +2590,12 @@ class Component: else: type_str = "Triconnected: " return type_str + self.edge_list.to_string() + def get_edge_list(self): """ - Return a list of edges belonging to the component. + Return the list of edges belonging to the component. """ - e_list = [] + cdef list e_list = [] e_node = self.edge_list.get_head() while e_node: e_list.append(e_node.get_data()) @@ -2897,11 +2936,11 @@ class Triconnectivity: """ Return the high(v) value, which is the first value in highpt list of `v` """ - head = self.highpt[v].head - if head == None: + head = self.highpt[v].get_head() + if head is None: return 0 else: - return head.data + return head.get_data() def __del_high(self, e): if e in self.in_high: @@ -3083,8 +3122,7 @@ class Triconnectivity: # Populate `adj` and `in_adj` with the sorted edges for i in range(1,max+1): for e in bucket[i]: - node = LinkedListNode() - node.set_data(e) + node = LinkedListNode(e) if e in self.reverse_edges: self.adj[e[1]].append(node) self.in_adj[e] = node @@ -3111,8 +3149,7 @@ class Triconnectivity: self.dfs_counter -= 1 else: # Identified a new frond that enters `w`. Add to `highpt[w]`. - highpt_node = LinkedListNode() - highpt_node.set_data(self.newnum[v]) + highpt_node = LinkedListNode(self.newnum[v]) self.highpt[w].append(highpt_node) self.in_high[e] = highpt_node self.new_path = True @@ -3303,8 +3340,7 @@ class Triconnectivity: comp = None self.e_stack.append(e_virt) - e_virt_node = LinkedListNode() - e_virt_node.set_data(e_virt) + e_virt_node = LinkedListNode(e_virt) # Replace `it` node with `e_virt_node` it.replace(e_virt_node) it = e_virt_node @@ -3377,16 +3413,14 @@ class Triconnectivity: if self.node_at[self.lowpt1[w]] != self.parent[v]: self.e_stack.append(e_virt) - e_virt_node = LinkedListNode() - e_virt_node.set_data(e_virt) + e_virt_node = LinkedListNode(e_virt) # replace `it` node with `e_virt_node` it.replace(e_virt_node) it = e_virt_node self.in_adj[e_virt] = it if not e_virt in self.in_high and self.__high(self.node_at[self.lowpt1[w]]) < vnum: - vnum_node = LinkedListNode() - vnum_node.set_data(vnum) + vnum_node = LinkedListNode(vnum) self.highpt[self.node_at[self.lowpt1[w]]].push_front(vnum_node) self.in_high[e_virt] = vnum_node @@ -3411,8 +3445,7 @@ class Triconnectivity: self.edge_status[e_virt] = 1 if eh in self.in_adj: self.in_adj[e_virt] = self.in_adj[eh] - e_virt_node = LinkedListNode() - e_virt_node.set_data(e_virt) + e_virt_node = LinkedListNode(e_virt) self.in_adj[eh] = e_virt_node # end type-1 search From e3aca08194df85767dbd1934b199f376059d86b6 Mon Sep 17 00:00:00 2001 From: saiharsh Date: Tue, 7 Aug 2018 02:42:17 +0530 Subject: [PATCH 069/264] Updated staticSPQRTree --- src/sage/graphs/connectivity.pyx | 40 +++++++++++--------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 75fb8a150ef..a4af721a396 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -3664,8 +3664,12 @@ def staticSPQRTree(G): tric = Triconnectivity(G) int_to_treeVertex = [] Tree = Graph(multiedges=False) - partnerNode = {e:None for e in tric.graph_copy.edges()} - + + partnerNode = {} + for i in range(len(tric.comp_list_new)): + for e in tric.comp_list_new[i]: + partnerNode[e] = None + for i in range(len(tric.comp_list_new)): # Do this case exist? @@ -3684,39 +3688,21 @@ def staticSPQRTree(G): else: int_to_treeVertex.append(("R", Graph(tric.comp_list_new[i], immutable=True, multiedges=True))) - for originalEdge in tric.comp_list_new[i]: - - # (u, v) are vertices of graph_copy, construction on graph_copy - # is available in Triconnectivity - u, v = tric.vertex_to_int[originalEdge[0]], tric.vertex_to_int[originalEdge[1]] - label = originalEdge[2] - - # e is an edge of graph_copy - e = tuple([u, v, label]) + for e in tric.comp_list_new[i]: # check if edge e is an original graph G. # if yes assign graph_edge as original form of e. # if not assign graph_edge as None if e[2] and "newVEdge" in e[2]: - graph_edge = None + graph_edge = False else: - if e in tric.reverse_edges: - graph_edge = tuple([originalEdge[1], originalEdge[0]]) - else: - graph_edge = tuple([originalEdge[0], originalEdge[1]]) + graph_edge = True if not graph_edge: - try: - if partnerNode[e] == None: - partnerNode[e] = treeVertex - else: - Tree.add_edge(partnerNode[e], treeVertex) - except: - new_e = tuple([v, u, label]) - if partnerNode[new_e] == None: - partnerNode[new_e] = treeVertex - else: - Tree.add_edge(partnerNode[new_e], treeVertex) + if partnerNode[e] == None: + partnerNode[e] = treeVertex + else: + Tree.add_edge(partnerNode[e], treeVertex) Tree.relabel(int_to_treeVertex) return Tree From f16db0518f142c8864be9e762eb43d4b289b9ba7 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Tue, 7 Aug 2018 15:45:45 +0200 Subject: [PATCH 070/264] trac #25598: review modifications on staticSPQRtree --- src/sage/graphs/connectivity.pyx | 101 +++++++++++++++---------------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index a4af721a396..66c9119116e 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -3594,30 +3594,47 @@ class Triconnectivity: def print_triconnected_components(self): """ - Print all the triconnected components along with the type of - each component. + Print the type and list of edges of each component. + + The types are ``{0: "Bond", 1: "Polygon", 2: "Triconnected"}``. """ + prefix = ["Bond", "Polygon", "Triconnected"] for i in range(len(self.comp_list_new)): - if self.comp_type[i] == 0: - print "Bond: ", - elif self.comp_type[i] == 1: - print "Polygon: ", - else: - print "Triconnected: ", - print self.comp_list_new[i] + print("{}: {}".format(prefix[self.comp_type[i]], self.comp_list_new[i])) def staticSPQRTree(G): r""" - Construct a static spqr tree using the output generated by triconnectivity - which gives bond, polygon and triconnected components present in graph `G`. - Iterate over all the components and create respective Tree nodes, based on - component edges and graph `G` edges construct edges between two tree nodes. - First construct the tree with 0..n-1 labeled vertices then replace them - with original vertex labels. + Return an SPQR-tree representing the triconnected components of the graph. + + An SPQR-tree is a tree data structure used to represent the triconnected + components of a biconnected (multi)graph and the 2-vertex cuts separating + them. A node of a SPQR-tree, and the graph associated with it, can be one of + the following four types: + + - ``S`` -- the associated graph is a cycle with at least three vertices. + ``S`` stands for ``series``. + + - ``P`` -- the associated graph is a dipole graph, a multigraph with two + vertices and three or more edges. ``P`` stands for ``parallel``. + + - ``Q`` -- the associated graph has a single real edge. This trivial case is + necessary to handle the graph that has only one edge. + + - ``R`` -- the associated graph is a 3-connected graph that is not a cycle + or dipole. ``R`` stands for ``rigid``. + + This method constructs a static spqr-tree using the decomposition of a + biconnected graph into cycles, cocycles, and 3-connected blocks generated by + :class:`sage.graphs.connectivity.Triconnectivity`. + See :wikipedia:`SPQR_tree`. + + OUTPUT: ``SPQR-tree`` a tree whose vertices are labeled with the block's type + and the subgraph of three-blocks in the decomposition. + EXAMPLES:: - sage: from sage.graphs.connectivity import staticSPQRTree, spqr_tree_to_graph + sage: from sage.graphs.connectivity import staticSPQRTree sage: G = Graph(2) sage: for i in range(3): ....: G.add_clique([0, 1, G.add_vertex(), G.add_vertex()]) @@ -3661,49 +3678,31 @@ def staticSPQRTree(G): True """ from sage.graphs.graph import Graph + # Types of components 0: "P", 1: "S", 2: "R" + component_type = ["P", "S", "R"] + tric = Triconnectivity(G) - int_to_treeVertex = [] - Tree = Graph(multiedges=False) - partnerNode = {} - for i in range(len(tric.comp_list_new)): - for e in tric.comp_list_new[i]: - partnerNode[e] = None + Tree = Graph(multiedges=False) + int_to_vertex = [] + partner_nodes = {} for i in range(len(tric.comp_list_new)): + # Create a new tree vertex + u = (component_type[tric.comp_type[i]], + Graph(tric.comp_list_new[i], immutable=True, multiedges=True)) + Tree.add_vertex(u) + int_to_vertex.append(u) - # Do this case exist? - if not len(tric.comp_list_new[i]): - continue - - # Create a vertex in tree - treeVertex = Tree.add_vertex() - - if tric.comp_type[i] == 0: - int_to_treeVertex.append(("P", Graph(tric.comp_list_new[i], immutable=True, multiedges=True))) - - elif tric.comp_type[i] == 1: - int_to_treeVertex.append(("S", Graph(tric.comp_list_new[i], immutable=True, multiedges=True))) - - else: - int_to_treeVertex.append(("R", Graph(tric.comp_list_new[i], immutable=True, multiedges=True))) - + # Add an edge to each node containing the same virtual edge for e in tric.comp_list_new[i]: - - # check if edge e is an original graph G. - # if yes assign graph_edge as original form of e. - # if not assign graph_edge as None if e[2] and "newVEdge" in e[2]: - graph_edge = False - else: - graph_edge = True - - if not graph_edge: - if partnerNode[e] == None: - partnerNode[e] = treeVertex + if e in partner_nodes: + for j in partner_nodes[e]: + Tree.add_edge(int_to_vertex[i], int_to_vertex[j]) + partner_nodes[e].append(i) else: - Tree.add_edge(partnerNode[e], treeVertex) + partner_nodes[e] = [i] - Tree.relabel(int_to_treeVertex) return Tree From 30054dfbd5a34febcc73b336a97b97b65fc86647 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Tue, 7 Aug 2018 20:05:23 +0530 Subject: [PATCH 071/264] Removed `replace` and `list_obj` from `LinkedListNode` and `LinkedList` --- src/sage/graphs/connectivity.pyx | 63 ++------------------------------ 1 file changed, 4 insertions(+), 59 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 66c9119116e..a79fa6e7b66 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2382,7 +2382,6 @@ class LinkedListNode: self.prev = None self.next = None self.set_data(data) - self.listobj = None def set_data(self, data): self.data = data @@ -2390,31 +2389,6 @@ class LinkedListNode: def get_data(self): return self.data - def set_obj(self, l): - self.listobj = l - - def clear_obj(self): - self.listobj = None - - def replace(self, node): - """ - Replace self node with ``node`` in the corresponding linked list. - """ - if self.prev is None and self.next is None: - self.listobj.set_head(node) - elif self.prev is None: - self.listobj.head = node - node.next = self.next - node.listobj = self.listobj - elif self.next is None: - self.prev.next = node - node.prev = self.prev - else: - self.prev.next = node - self.next.prev = node - node.prev = self.prev - node.next = self.next - class LinkedList: """ A doubly linked list with head and tail pointers. @@ -2441,7 +2415,6 @@ class LinkedList: elif node.prev is None: # node is head self.head = node.next node.next.prev = None - node.next.set_obj(self) elif node.next is None: #node is tail node.prev.next = None self.tail = node.prev @@ -2457,7 +2430,6 @@ class LinkedList: self.head = h self.tail = h self.length = 1 - h.set_obj(self) def append(self, node): """ @@ -2477,27 +2449,6 @@ class LinkedList: def get_length(self): return self.length - def replace(self, node1, node2): - """ - Replace the node ``node1`` with ``node2`` in the linked list. - """ - if node1.prev is None and node1.next is None: - self.head = node2 - self.tail = node2 - elif node1.prev is None: # head has to be replaced - node1.next.prev = node2 - node2.next = node1.next - self.head = node2 - elif node1.next is None: # tail has to be replaced - node1.prev.next = node2 - node2.prev = node1.prev - self.tail = node2 - else: - node1.prev.next = node2 - node1.next.prev = node2 - node2.prev = node1.prev - node2.next = node1.next - def push_front(self, node): """ Add node ``node`` to the beginning of the linked list. @@ -2505,11 +2456,9 @@ class LinkedList: if self.head is None: self.set_head(node) else: - self.head.clear_obj() self.head.prev = node node.next = self.head self.head = node - node.set_obj(self) self.length += 1 def to_string(self): @@ -3340,10 +3289,8 @@ class Triconnectivity: comp = None self.e_stack.append(e_virt) - e_virt_node = LinkedListNode(e_virt) - # Replace `it` node with `e_virt_node` - it.replace(e_virt_node) - it = e_virt_node + # Replace the edge `it` with `e_virt` + it.set_data(e_virt) self.in_adj[e_virt] = it self.degree[x] += 1 @@ -3413,10 +3360,8 @@ class Triconnectivity: if self.node_at[self.lowpt1[w]] != self.parent[v]: self.e_stack.append(e_virt) - e_virt_node = LinkedListNode(e_virt) - # replace `it` node with `e_virt_node` - it.replace(e_virt_node) - it = e_virt_node + # replace edge `it` with `e_virt` + it.set_data(e_virt) self.in_adj[e_virt] = it if not e_virt in self.in_high and self.__high(self.node_at[self.lowpt1[w]]) < vnum: From f3355a83b0da42e0092eb2917a0de47ccfed9245 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Tue, 7 Aug 2018 17:49:19 +0200 Subject: [PATCH 072/264] trac #25598: simplifies initilization --- src/sage/graphs/connectivity.pyx | 49 ++++++++++++-------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index a79fa6e7b66..21875272c9c 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2551,7 +2551,6 @@ class Component: e_node = e_node.next return e_list -from sage.graphs.base.sparse_graph cimport SparseGraph class Triconnectivity: """ @@ -2693,38 +2692,26 @@ class Triconnectivity: """ def __init__(self, G, check=True): - from sage.graphs.graph import Graph, DiGraph - # graph_copy is a SparseGraph of the input graph `G` - # We relabel the edges with increasing numbers to be able to - # distinguish between multi-edges - self.graph_copy = Graph(multiedges=True) - - # Add all the vertices first - # there is a possibility of isolated vertices - for v in G.vertex_iterator(): - self.graph_copy.add_vertex(v) - - edges = G.edges() - # dict to map new edges with the old edges + """ + """ + from sage.graphs.graph import Graph + + # Make a copy of the input graph G in which + # - vertices are relabeled as integers in [0..n-1] + # - edges are relabeled with distinct labels in order to distinguish + # between multi-edges + self.n = G.order() + self.m = G.size() + self.int_to_vertex = G.vertices() + self.vertex_to_int = {u:i for i,u in enumerate(self.int_to_vertex)} self.edge_label_dict = {} - for i in range(len(edges)): - newEdge = tuple([edges[i][0], edges[i][1], i]) - self.graph_copy.add_edge(newEdge) - self.edge_label_dict[newEdge] = edges[i] - - # type SparseGraph - self.graph_copy = self.graph_copy.copy(implementation='c_graph') - - # mapping of vertices to integers in c_graph - self.vertex_to_int = self.graph_copy.relabel(inplace=True, return_map=True) - - # mapping of integers to original vertices - self.int_to_vertex = dict([(v,k) for k,v in self.vertex_to_int.items()]) - self.n = self.graph_copy.order() # number of vertices - self.m = self.graph_copy.size() # number of edges + self.graph_copy = Graph(self.n, multiedges=True) + for i,e in enumerate(G.edge_iterator()): + self.graph_copy.add_edge(self.vertex_to_int[e[0]], self.vertex_to_int[e[1]], i) + self.edge_label_dict[e[0], e[1], i] = e # status of each edge: unseen=0, tree=1, frond=2 - self.edge_status = dict((e, 0) for e in self.graph_copy.edges()) + self.edge_status = {e: 0 for e in self.graph_copy.edges()} # Edges of the graph which are in the reverse direction in palm tree self.reverse_edges = set() @@ -2762,7 +2749,7 @@ class Triconnectivity: self.graph_copy_adjacency = [[] for i in range(self.n)] # Stores adjacency list # Dictionary of (e, True/False) to denote if edge e starts a path - self.starts_path = dict((e, False) for e in self.graph_copy.edges()) + self.starts_path = {e:False for e in self.graph_copy.edges()} self.is_biconnected = True # Boolean to store if the graph is biconnected or not self.cut_vertex = None # If graph is not biconnected From 41c14e84e4e0fd1ca8f8452ad311af5b56e68b55 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Tue, 7 Aug 2018 17:54:47 +0200 Subject: [PATCH 073/264] trac #25598: use edge and vertex iteratos --- src/sage/graphs/connectivity.pyx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 21875272c9c..4addd2b857e 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2711,7 +2711,7 @@ class Triconnectivity: self.edge_label_dict[e[0], e[1], i] = e # status of each edge: unseen=0, tree=1, frond=2 - self.edge_status = {e: 0 for e in self.graph_copy.edges()} + self.edge_status = {e: 0 for e in self.graph_copy.edge_iterator()} # Edges of the graph which are in the reverse direction in palm tree self.reverse_edges = set() @@ -2722,7 +2722,7 @@ class Triconnectivity: # A dictionary whose key is an edge e, value is a pointer to element in # self.highpt containing the edge e. Used in the `path_search` function. - self.in_high = dict((e, None) for e in self.graph_copy.edges()) + self.in_high = {e:None for e in self.graph_copy.edge_iterator()} # Translates DFS number of a vertex to its new number self.old_to_new = [0 for i in range(self.n+1)] @@ -2749,7 +2749,7 @@ class Triconnectivity: self.graph_copy_adjacency = [[] for i in range(self.n)] # Stores adjacency list # Dictionary of (e, True/False) to denote if edge e starts a path - self.starts_path = {e:False for e in self.graph_copy.edges()} + self.starts_path = {e:False for e in self.graph_copy.edge_iterator()} self.is_biconnected = True # Boolean to store if the graph is biconnected or not self.cut_vertex = None # If graph is not biconnected @@ -2779,7 +2779,7 @@ class Triconnectivity: if self.m < 3: raise ValueError("Graph is not biconnected") comp = Component([], 0) - for e in self.graph_copy.edges(): + for e in self.graph_copy.edge_iterator(): comp.add_edge(e) self.components_list.append(comp) return @@ -2788,7 +2788,7 @@ class Triconnectivity: self.__split_multi_egdes() # Build adjacency list - for e in self.graph_copy.edges(): + for e in self.graph_copy.edge_iterator(): self.graph_copy_adjacency[e[0]].append(e) self.graph_copy_adjacency[e[1]].append(e) @@ -2809,7 +2809,7 @@ class Triconnectivity: raise ValueError("Graph has a cut vertex") # Identify reversed edges to reflect the palm tree arcs and fronds - for e in self.graph_copy.edges(): + for e in self.graph_copy.edge_iterator(): up = (self.dfs_number[e[1]] - self.dfs_number[e[0]]) > 0 if (up and self.edge_status[e]==2) or (not up and self.edge_status[e]==1): # Add edge to the set reverse_edges @@ -2904,7 +2904,7 @@ class Triconnectivity: """ comp = [] if self.graph_copy.has_multiple_edges(): - sorted_edges = sorted(self.graph_copy.edges()) + sorted_edges = sorted(self.graph_copy.edge_iterator()) for i in range(len(sorted_edges) - 1): # Find multi edges and add to component and delete from graph @@ -3031,7 +3031,7 @@ class Triconnectivity: max = 3*self.n + 2 bucket = [[] for i in range(max+1)] - for e in self.graph_copy.edges(): + for e in self.graph_copy.edge_iterator(): edge_type = self.edge_status[e] # compute phi value @@ -3097,10 +3097,10 @@ class Triconnectivity: new numbering obtained from `Path Finder` funciton. Populate `highpt` values. """ - self.in_high = dict((e, None) for e in self.graph_copy.edges()) + self.in_high = {e:None for e in self.graph_copy.edge_iterator()} self.dfs_counter = self.n self.newnum = [0 for i in range(self.n)] - self.starts_path = dict((e, False) for e in self.graph_copy.edges()) + self.starts_path = {e:False for e in self.graph_copy.edge_iterator()} self.new_path = True @@ -3108,11 +3108,11 @@ class Triconnectivity: self.__path_finder(self.start_vertex) # Update `old_to_new` values with the calculated `newnum` values - for v in self.graph_copy.vertices(): + for v in self.graph_copy.vertex_iterator(): self.old_to_new[self.dfs_number[v]] = self.newnum[v] # Update lowpt values according to `newnum` values. - for v in self.graph_copy.vertices(): + for v in self.graph_copy.vertex_iterator(): self.node_at[self.newnum[v]] = v self.lowpt1[v] = self.old_to_new[self.lowpt1[v]] self.lowpt2[v] = self.old_to_new[self.lowpt2[v]] From 3d441a80a7519720e155fb6231dd73e0b62ebd8b Mon Sep 17 00:00:00 2001 From: David Coudert Date: Tue, 7 Aug 2018 18:10:38 +0200 Subject: [PATCH 074/264] trac #25598: change edge_label_dict to list and rename it --- src/sage/graphs/connectivity.pyx | 37 +++++++++++++------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 4addd2b857e..5e5b1059806 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2704,11 +2704,11 @@ class Triconnectivity: self.m = G.size() self.int_to_vertex = G.vertices() self.vertex_to_int = {u:i for i,u in enumerate(self.int_to_vertex)} - self.edge_label_dict = {} + self.int_to_original_edge_label = [] # to associate original edge label self.graph_copy = Graph(self.n, multiedges=True) - for i,e in enumerate(G.edge_iterator()): - self.graph_copy.add_edge(self.vertex_to_int[e[0]], self.vertex_to_int[e[1]], i) - self.edge_label_dict[e[0], e[1], i] = e + for i,(u, v, l) in enumerate(G.edge_iterator()): + self.graph_copy.add_edge(self.vertex_to_int[u], self.vertex_to_int[v], i) + self.int_to_original_edge_label.append(l) # status of each edge: unseen=0, tree=1, frond=2 self.edge_status = {e: 0 for e in self.graph_copy.edge_iterator()} @@ -3500,28 +3500,21 @@ class Triconnectivity: # Convert connected components into original graph vertices and edges self.comp_list_new = [] self.comp_type = [] - for i in range(len(self.components_list)): - if self.components_list[i].edge_list.get_length() > 0: - e_list = self.components_list[i].get_edge_list() + for comp in self.components_list: + if comp.edge_list.get_length() > 0: + e_list = comp.get_edge_list() e_list_new = [] # For each edge, get the original source, target and label - for e in e_list: - source = self.int_to_vertex[e[0]] - target = self.int_to_vertex[e[1]] - if isinstance(e[2], str): - label = e[2] + for u,v,l in e_list: + source = self.int_to_vertex[u] + target = self.int_to_vertex[v] + if isinstance(l, str): + label = l else: - # If the original input graph was a directed graph, the - # source and target can in reversed in the edge. - # Hence, we check for both. Since we use unique edge - # labels, this does not fetch a wrong edge. - if (source, target, e[2]) in self.edge_label_dict: - label = self.edge_label_dict[(source, target, e[2])][2] - else: - label = self.edge_label_dict[(target, source, e[2])][2] - e_list_new.append(tuple([source, target, label])) + label = self.int_to_original_edge_label[l] + e_list_new.append((source, target, label)) # Add the component data to `comp_list_new` and `comp_type` - self.comp_type.append(self.components_list[i].component_type) + self.comp_type.append(comp.component_type) self.comp_list_new.append(e_list_new) def print_triconnected_components(self): From c3444f703a3aeada4cb3172da45e2075fd4e993a Mon Sep 17 00:00:00 2001 From: David Coudert Date: Tue, 7 Aug 2018 19:09:59 +0200 Subject: [PATCH 075/264] trac #25598: better trivial cases --- src/sage/graphs/connectivity.pyx | 68 ++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 5e5b1059806..571b1b98a5c 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2586,9 +2586,9 @@ class Triconnectivity: - :meth:`~Graph.is_biconnected` - EXAMPLES:: + EXAMPLES: - An example from [Hopcroft1973]_: + An example from [Hopcroft1973]_:: sage: from sage.graphs.connectivity import Triconnectivity sage: G = Graph() @@ -2610,7 +2610,7 @@ class Triconnectivity: Polygon: [(3, 4, None), (4, 1, 'newVEdge10'), (3, 1, 'newVEdge11')] Triconnected: [(1, 2, None), (2, 3, None), (3, 1, 'newVEdge11'), (3, 13, None), (2, 13, None), (1, 13, None)] - An example from [Gut2001]_ + An example from [Gut2001]_:: sage: G = Graph() sage: G.add_edges([(1,2),(1,4),(2,3),(2,5),(3,4),(3,5),(4,5),(4,6),(5,7),(5,8)]) @@ -2633,7 +2633,7 @@ class Triconnectivity: Triconnected: [(2, 3, None), (3, 4, None), (4, 5, 'newVEdge14'), (3, 5, None), (2, 5, None), (2, 4, 'newVEdge15')] Polygon: [(1, 2, None), (2, 4, 'newVEdge15'), (1, 4, None)] - An example with multi-edges and accessing the triconnected components: + An example with multi-edges and accessing the triconnected components:: sage: G = Graph() sage: G.allow_multiple_edges(True) @@ -2651,7 +2651,7 @@ class Triconnectivity: sage: tric.comp_type [0, 0, 1] - An example of a triconnected graph: + An example of a triconnected graph:: sage: G2 = Graph() sage: G2.allow_multiple_edges(True) @@ -2660,7 +2660,7 @@ class Triconnectivity: sage: tric.print_triconnected_components() Triconnected: [('a', 'b', None), ('b', 'c', None), ('a', 'c', None), ('c', 'd', None), ('b', 'd', None), ('a', 'd', None)] - An example of a directed graph with multi-edges: + An example of a directed graph with multi-edges:: sage: G3 = DiGraph() sage: G3.allow_multiple_edges(True) @@ -2670,18 +2670,18 @@ class Triconnectivity: Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, None)] - TESTS:: + TESTS: - A disconnected graph: + A disconnected graph:: sage: from sage.graphs.connectivity import Triconnectivity sage: G = Graph([(1,2),(3,5)]) sage: tric = Triconnectivity(G) Traceback (most recent call last): ... - ValueError: Graph is disconnected + ValueError: Graph is not connected - A graph with a cut vertex: + A graph with a cut vertex:: sage: from sage.graphs.connectivity import Triconnectivity sage: G = Graph([(1,2),(1,3),(2,3),(3,4),(3,5),(4,5)]) @@ -2689,19 +2689,34 @@ class Triconnectivity: Traceback (most recent call last): ... ValueError: Graph has a cut vertex - """ def __init__(self, G, check=True): """ """ + self.n = G.order() + self.m = G.size() + + # Trivial cases + if self.n < 2: + raise ValueError("Graph is not biconnected") + elif self.n == 2 and self.m: + # a P block with at least 1 edge + self.comp_list_new = [G.edges()] + self.comp_type = [0] + return + elif self.m < self.n -1: + # less edges than a tree + raise ValueError("Graph is not connected") + elif self.m < self.n: + # less edges than a cycle + raise ValueError("Graph is not biconnected") + from sage.graphs.graph import Graph # Make a copy of the input graph G in which # - vertices are relabeled as integers in [0..n-1] # - edges are relabeled with distinct labels in order to distinguish # between multi-edges - self.n = G.order() - self.m = G.size() self.int_to_vertex = G.vertices() self.vertex_to_int = {u:i for i,u in enumerate(self.int_to_vertex)} self.int_to_original_edge_label = [] # to associate original edge label @@ -2710,11 +2725,16 @@ class Triconnectivity: self.graph_copy.add_edge(self.vertex_to_int[u], self.vertex_to_int[v], i) self.int_to_original_edge_label.append(l) + # + # Initialize data structures needed for the algorithm + # + # status of each edge: unseen=0, tree=1, frond=2 self.edge_status = {e: 0 for e in self.graph_copy.edge_iterator()} # Edges of the graph which are in the reverse direction in palm tree self.reverse_edges = set() + self.dfs_number = [0 for i in range(self.n)] # DFS number of vertex i # Linked list of fronds entering vertex i in the order they are visited @@ -2771,20 +2791,11 @@ class Triconnectivity: self.comp_list_new = [] # i^th entry is list of edges in i^th component self.comp_type = [] # i^th entry is type of i^th component - - # Trivial cases - if self.n < 2: - raise ValueError("Graph is not biconnected") - if self.n == 2: - if self.m < 3: - raise ValueError("Graph is not biconnected") - comp = Component([], 0) - for e in self.graph_copy.edge_iterator(): - comp.add_edge(e) - self.components_list.append(comp) - return - + # # Triconnectivity algorithm + # + + # Deal with multiple edges self.__split_multi_egdes() # Build adjacency list @@ -2799,13 +2810,10 @@ class Triconnectivity: if check: # If graph is disconnected if self.dfs_counter < self.n: - self.is_biconnected = False - raise ValueError("Graph is disconnected") + raise ValueError("Graph is not connected") # If graph has a cut vertex if self.cut_vertex != None: - self.cut_vertex = self.int_to_vertex[self.cut_vertex] - self.is_biconnected = False raise ValueError("Graph has a cut vertex") # Identify reversed edges to reflect the palm tree arcs and fronds From 982e0773a7d24cc04c2ec867ae930e273e852f25 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Tue, 7 Aug 2018 19:16:09 +0200 Subject: [PATCH 076/264] trac #25598: minor changes in split_multi_egdes --- src/sage/graphs/connectivity.pyx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 571b1b98a5c..34e4d528265 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2898,17 +2898,16 @@ class Triconnectivity: def __split_multi_egdes(self): """ - Iterate through all the edges, and constructs bonds wherever - multiedges are present. + Iterate through all the edges, and constructs bonds wherever multiedges + are present. - If there are `k` multiple edges between `u` and `v`, then `k+1` - edges will be added to a new component (one of them is a virtual edge), - all the `k` edges are deleted from the graph and a virtual edge is - between `u` and `v` is added to the graph. + If there are `k` multiple edges between `u` and `v`, then `k+1` edges + will be added to a new component (one of them is a virtual edge), all + the `k` edges are deleted from the graph and a virtual edge is between + `u` and `v` is added to the graph. No return value. Update the `components_list` and `graph_copy`. - `graph_copy` will become simple graph after this function. - + `graph_copy` will become a simple graph after this function. """ comp = [] if self.graph_copy.has_multiple_edges(): @@ -2926,7 +2925,7 @@ class Triconnectivity: self.graph_copy.delete_edge(sorted_edges[i]) # Add virtual edge to graph_copy - newVEdge = tuple([sorted_edges[i][0], sorted_edges[i][1], "newVEdge"+str(self.virtual_edge_num)]) + newVEdge = (sorted_edges[i][0], sorted_edges[i][1], "newVEdge"+str(self.virtual_edge_num)) self.graph_copy.add_edge(newVEdge) self.virtual_edge_num += 1 @@ -2941,7 +2940,7 @@ class Triconnectivity: self.graph_copy.delete_edge(sorted_edges[i+1]) # Add virtual edge to graph_copy - newVEdge = tuple([sorted_edges[i+1][0], sorted_edges[i+1][1], "newVEdge"+str(self.virtual_edge_num)]) + newVEdge = (sorted_edges[i+1][0], sorted_edges[i+1][1], "newVEdge"+str(self.virtual_edge_num)) self.graph_copy.add_edge(newVEdge) self.virtual_edge_num += 1 self.edge_status[newVEdge] = 0 From 7aa98b62b741ce79b0463f05578945142c6eb2c3 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Tue, 7 Aug 2018 19:20:00 +0200 Subject: [PATCH 077/264] trac #25598: minor changes in dfs1 --- src/sage/graphs/connectivity.pyx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 34e4d528265..3513d61dd97 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2954,7 +2954,7 @@ class Triconnectivity: Also populates the lists lowpt1, lowpt2, nd, parent, and dfs_number. It updates the dict `edge_status` to reflect palm tree arcs and fronds. - Input:: + INPUT: - ``v`` -- The start vertex for DFS. @@ -2964,11 +2964,11 @@ class Triconnectivity: graph has a cut vertex, the cut vertex is returned. If set to False, the graph is assumed to be biconnected, function returns None. - Output:: + OUTPUT: - If ``check`` is set to True, and a cut vertex is found, the cut vertex - is returned. If no cut vertex is found, return value is None. - If ``check`` is set to False, ``None`` is returned. + is returned. If no cut vertex is found, return None. + - If ``check`` is set to False, ``None`` is returned. """ first_son = None # For testing biconnectivity s1 = None # Storing the cut vertex, if there is one @@ -3002,8 +3002,8 @@ class Triconnectivity: # dfs_number[x]) that can be reached from v. # `lowpt2` is the next smallest vertex that can be reached from v. if self.lowpt1[w] < self.lowpt1[v]: - self.lowpt2[v] = min(self.lowpt1[v], self.lowpt2[w]) - self.lowpt1[v] = self.lowpt1[w] + self.lowpt2[v] = min(self.lowpt1[v], self.lowpt2[w]) + self.lowpt1[v] = self.lowpt1[w] elif self.lowpt1[w] == self.lowpt1[v]: self.lowpt2[v] = min(self.lowpt2[v], self.lowpt2[w]) @@ -3014,14 +3014,14 @@ class Triconnectivity: self.nd[v] += self.nd[w] else: - self.edge_status[e] = 2 #frond + self.edge_status[e] = 2 # frond if self.dfs_number[w] < self.lowpt1[v]: self.lowpt2[v] = self.lowpt1[v] self.lowpt1[v] = self.dfs_number[w] elif self.dfs_number[w] > self.lowpt1[v]: self.lowpt2[v] = min(self.lowpt2[v], self.dfs_number[w]) - return s1 # s1 is None is graph does not have a cut vertex + return s1 # s1 is None if graph does not have a cut vertex def __build_acceptable_adj_struct(self): From 685da776c8a621825a76fbd03ca212a8960dfb43 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Tue, 7 Aug 2018 19:23:25 +0200 Subject: [PATCH 078/264] trac #25598: minor changes in build_acceptable_adj_struct --- src/sage/graphs/connectivity.pyx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 3513d61dd97..12944663d88 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -3026,8 +3026,8 @@ class Triconnectivity: def __build_acceptable_adj_struct(self): """ - Builds the adjacency lists for each vertex with certain properties - of the ordering, using the ``lowpt1`` and ``lowpt2`` values. + Builds the adjacency lists for each vertex with certain properties of + the ordering, using the ``lowpt1`` and ``lowpt2`` values. The list ``adj`` and the dictionary ``in_adj`` are populated. @@ -3035,8 +3035,8 @@ class Triconnectivity: incident vertices. The edges are then sorted by the `phi` values and added to adjacency list. """ - max = 3*self.n + 2 - bucket = [[] for i in range(max+1)] + max_size = 3*self.n + 2 + bucket = [[] for _ in range(max_size + 1)] for e in self.graph_copy.edge_iterator(): edge_type = self.edge_status[e] @@ -3044,7 +3044,7 @@ class Triconnectivity: # compute phi value # bucket sort adjacency list by phi values if e in self.reverse_edges: - if edge_type==1: # tree arc + if edge_type == 1: # tree arc if self.lowpt2[e[0]] < self.dfs_number[e[1]]: phi = 3*self.lowpt1[e[0]] else: @@ -3052,7 +3052,7 @@ class Triconnectivity: else: # tree frond phi = 3*self.dfs_number[e[0]]+1 else: - if edge_type==1: # tree arc + if edge_type == 1: # tree arc if self.lowpt2[e[1]] < self.dfs_number[e[0]]: phi = 3*self.lowpt1[e[1]] else: @@ -3063,7 +3063,7 @@ class Triconnectivity: bucket[phi].append(e) # Populate `adj` and `in_adj` with the sorted edges - for i in range(1,max+1): + for i in range(1, max_size + 1): for e in bucket[i]: node = LinkedListNode(e) if e in self.reverse_edges: From 9d8f426e19c2e033a69269faa7eeb76c5a53d40c Mon Sep 17 00:00:00 2001 From: David Coudert Date: Tue, 7 Aug 2018 19:34:49 +0200 Subject: [PATCH 079/264] trac #25598: minor changes --- src/sage/graphs/connectivity.pyx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 12944663d88..9861c5f4b0d 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -3101,7 +3101,7 @@ class Triconnectivity: def __dfs2(self): """ Update the values of lowpt1 and lowpt2 lists with the help of - new numbering obtained from `Path Finder` funciton. + new numbering obtained from `Path Finder` function. Populate `highpt` values. """ self.in_high = {e:None for e in self.graph_copy.edge_iterator()} @@ -3127,9 +3127,8 @@ class Triconnectivity: def __path_search(self, v): """ Find the separation pairs and construct the split components. - Check for type-1 and type-2 separation pairs, and construct - the split components while also creating new virtual edges wherever - required. + Check for type-1 and type-2 separation pairs, and construct the split + components while also creating new virtual edges wherever required. """ y = 0 vnum = self.newnum[v] @@ -3300,7 +3299,7 @@ class Triconnectivity: (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair - (self.node_at[self.lowpt1[w]], v) # Create a new component and add edges to it - comp = Component([],0) + comp = Component([], 0) if not self.e_stack: raise ValueError("stack is empty") while self.e_stack: @@ -3330,7 +3329,7 @@ class Triconnectivity: if (xx == vnum and y == self.lowpt1[w]) or \ (y == vnum and xx == self.lowpt1[w]): - comp_bond = Component([],type_c = 0) # new triple bond + comp_bond = Component([], type_c=0) # new triple bond eh = self.__estack_pop() if self.in_adj[eh] != it: if eh in self.reverse_edges: @@ -3340,7 +3339,7 @@ class Triconnectivity: comp_bond.add_edge(eh) comp_bond.add_edge(e_virt) - e_virt = tuple([v, self.node_at[self.lowpt1[w]], "newVEdge"+str(self.virtual_edge_num)]) + e_virt = (v, self.node_at[self.lowpt1[w]], "newVEdge"+str(self.virtual_edge_num)) self.graph_copy.add_edge(e_virt) self.virtual_edge_num += 1 comp_bond.add_edge(e_virt) @@ -3351,6 +3350,7 @@ class Triconnectivity: self.components_list.append(comp_bond) comp_bond = None + if self.node_at[self.lowpt1[w]] != self.parent[v]: self.e_stack.append(e_virt) @@ -3369,7 +3369,7 @@ class Triconnectivity: else: self.adj[v].remove(it) comp_bond = Component([e_virt], type_c=0) - e_virt = tuple([self.node_at[self.lowpt1[w]], v, "newVEdge"+str(self.virtual_edge_num)]) + e_virt = (self.node_at[self.lowpt1[w]], v, "newVEdge"+str(self.virtual_edge_num)) self.graph_copy.add_edge(e_virt) self.virtual_edge_num += 1 comp_bond.add_edge(e_virt) From 7ef3eadb65b0ef9c2132958159823a49cb3b49f4 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Wed, 8 Aug 2018 22:22:35 +0530 Subject: [PATCH 080/264] Moved `staticSPQRtree` to connectivity, added `algorithm` parameter in `spqr_tree`. --- src/sage/graphs/connectivity.pyx | 288 +++++++++++++++++++------------ 1 file changed, 173 insertions(+), 115 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 9861c5f4b0d..85edae647d0 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2066,7 +2066,7 @@ def cleave(G, cut_vertices=None, virtual_edges=True): return cut_sides, cocycles, virtual_cut_graph -def spqr_tree(G): +def spqr_tree(G, algorithm="Hopcroft_tarjan"): r""" Return an SPQR-tree representing the triconnected components of the graph. @@ -2095,6 +2095,18 @@ def spqr_tree(G): vertices and one additional (virtual) edge per connected component resulting from deletion of the vertices in the cut. See :wikipedia:`SPQR_tree`. + INPUT: + + - ``G`` - the input graph. + + - ``algorithm`` -- The algorithm to use in computing the SPQR tree of ``G``. + The following algorithms are supported: + + - ``"Hopcroft_tarjan"`` (default) -- Hopcroft and Tarjan's algorithm. + Refer to [Hopcroft1973]_. + + - ``"Cleave"`` -- Using the ``cleave`` function. + OUTPUT: ``SPQR-tree`` a tree whose vertices are labeled with the block's type and the subgraph of three-blocks in the decomposition. @@ -2155,14 +2167,26 @@ def spqr_tree(G): sage: spqr_tree(G) Traceback (most recent call last): ... - ValueError: generation of SPQR-trees is only implemented for 2-connected graphs + ValueError: Graph is not biconnected sage: G = Graph([(0, 0)], loops=True) sage: spqr_tree(G) Traceback (most recent call last): ... - ValueError: generation of SPQR-trees is only implemented for graphs without loops + ValueError: Graph is not biconnected """ + from sage.graphs.generic_graph import GenericGraph + if not isinstance(G, GenericGraph): + raise TypeError("the input must be a Sage graph") + + if algorithm == "Hopcroft_tarjan": + from sage.graphs.connectivity import TriconnectivitySPQR + tric = TriconnectivitySPQR(G) + return tric.get_spqr_tree() + + if algorithm != "Cleave": + raise NotImplementedError("SPQR tree algorithm '%s' is not implemented." % algorithm) + from sage.graphs.graph import Graph from collections import Counter @@ -2364,7 +2388,7 @@ class LinkedListNode: """ Node in a ``LinkedList``. - This class is a helper class for ``Triconnectivity``. + This class is a helper class for ``TriconnectivitySPQR``. This class implements a node of a (doubly) linked list and so has pointers to previous and next nodes in the list. If this node is the ``head`` of the @@ -2393,7 +2417,7 @@ class LinkedList: """ A doubly linked list with head and tail pointers. - This is a helper class for ``Triconnectivity``. + This is a helper class for ``TriconnectivitySPQR``. This class implements a doubly linked list of ``LinkedListNode``. """ @@ -2489,7 +2513,7 @@ class Component: """ Connected component class. - This is a helper class for ``Triconnectivity``. + This is a helper class for ``TriconnectivitySPQR``. This class is used to store a connected component. It contains: - ``edge_list`` -- list of edges belonging to the component, stored as a ``LinkedList``. @@ -2552,10 +2576,10 @@ class Component: return e_list -class Triconnectivity: +class TriconnectivitySPQR: """ This module implements the algorithm for finding the triconnected - components of a biconnected graph. + components of a biconnected graph and constructing the SPQR tree. A biconnected graph is a graph where deletion of any one vertex does not disconnect the graph. @@ -2590,12 +2614,12 @@ class Triconnectivity: An example from [Hopcroft1973]_:: - sage: from sage.graphs.connectivity import Triconnectivity + sage: from sage.graphs.connectivity import TriconnectivitySPQR sage: G = Graph() sage: G.add_edges([(1,2),(1,4),(1,8),(1,12),(1,13),(2,3),(2,13),(3,4)]) sage: G.add_edges([(3,13),(4,5),(4,7),(5,6),(5,7),(5,8),(6,7),(8,9),(8,11)]) sage: G.add_edges([(8,12),(9,10),(9,11),(9,12),(10,11),(10,12)]) - sage: tric = Triconnectivity(G) + sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() Triconnected: [(8, 9, None), (9, 10, None), (10, 11, None), (9, 11, None), (8, 11, None), (10, 12, None), (9, 12, None), (8, 12, 'newVEdge0')] Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] @@ -2616,7 +2640,7 @@ class Triconnectivity: sage: G.add_edges([(1,2),(1,4),(2,3),(2,5),(3,4),(3,5),(4,5),(4,6),(5,7),(5,8)]) sage: G.add_edges([(5,14),(6,8),(7,14),(8,9),(8,10),(8,11),(8,12),(9,10),(10,13)]) sage: G.add_edges([(10,14),(10,15),(10,16),(11,12),(11,13),(12,13),(14,15),(14,16),(15,16)]) - sage: tric = Triconnectivity(G) + sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() Polygon: [(6, 8, None), (4, 6, None), (5, 8, 'newVEdge12'), (5, 4, 'newVEdge13')] Polygon: [(8, 9, None), (9, 10, None), (8, 10, 'newVEdge1')] @@ -2638,7 +2662,7 @@ class Triconnectivity: sage: G = Graph() sage: G.allow_multiple_edges(True) sage: G.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(1,5),(2,3)]) - sage: tric = Triconnectivity(G) + sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] Bond: [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')] @@ -2656,7 +2680,7 @@ class Triconnectivity: sage: G2 = Graph() sage: G2.allow_multiple_edges(True) sage: G2.add_edges([('a','b'),('a','c'),('a','d'),('b','c'),('b','d'),('c','d')]) - sage: tric = Triconnectivity(G2) + sage: tric = TriconnectivitySPQR(G2) sage: tric.print_triconnected_components() Triconnected: [('a', 'b', None), ('b', 'c', None), ('a', 'c', None), ('c', 'd', None), ('b', 'd', None), ('a', 'd', None)] @@ -2665,7 +2689,7 @@ class Triconnectivity: sage: G3 = DiGraph() sage: G3.allow_multiple_edges(True) sage: G3.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(5,1)]) - sage: tric = Triconnectivity(G3) + sage: tric = TriconnectivitySPQR(G3) sage: tric.print_triconnected_components() Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, None)] @@ -2674,18 +2698,18 @@ class Triconnectivity: A disconnected graph:: - sage: from sage.graphs.connectivity import Triconnectivity + sage: from sage.graphs.connectivity import TriconnectivitySPQR sage: G = Graph([(1,2),(3,5)]) - sage: tric = Triconnectivity(G) + sage: tric = TriconnectivitySPQR(G) Traceback (most recent call last): ... ValueError: Graph is not connected A graph with a cut vertex:: - sage: from sage.graphs.connectivity import Triconnectivity + sage: from sage.graphs.connectivity import TriconnectivitySPQR sage: G = Graph([(1,2),(1,3),(2,3),(3,4),(3,5),(4,5)]) - sage: tric = Triconnectivity(G) + sage: tric = TriconnectivitySPQR(G) Traceback (most recent call last): ... ValueError: Graph has a cut vertex @@ -2837,7 +2861,6 @@ class Triconnectivity: c = None self.__assemble_triconnected_components() - #self.print_triconnected_components() def __tstack_push(self, h, a, b): """ @@ -3534,107 +3557,142 @@ class Triconnectivity: for i in range(len(self.comp_list_new)): print("{}: {}".format(prefix[self.comp_type[i]], self.comp_list_new[i])) -def staticSPQRTree(G): - r""" - Return an SPQR-tree representing the triconnected components of the graph. - - An SPQR-tree is a tree data structure used to represent the triconnected - components of a biconnected (multi)graph and the 2-vertex cuts separating - them. A node of a SPQR-tree, and the graph associated with it, can be one of - the following four types: - - - ``S`` -- the associated graph is a cycle with at least three vertices. - ``S`` stands for ``series``. - - - ``P`` -- the associated graph is a dipole graph, a multigraph with two - vertices and three or more edges. ``P`` stands for ``parallel``. - - - ``Q`` -- the associated graph has a single real edge. This trivial case is - necessary to handle the graph that has only one edge. - - - ``R`` -- the associated graph is a 3-connected graph that is not a cycle - or dipole. ``R`` stands for ``rigid``. - - This method constructs a static spqr-tree using the decomposition of a - biconnected graph into cycles, cocycles, and 3-connected blocks generated by - :class:`sage.graphs.connectivity.Triconnectivity`. - See :wikipedia:`SPQR_tree`. - - OUTPUT: ``SPQR-tree`` a tree whose vertices are labeled with the block's type - and the subgraph of three-blocks in the decomposition. + def __staticSPQRTree(self): + """ + Constructs the SPQR tree using the triconnected components. + """ + from sage.graphs.graph import Graph + # Types of components 0: "P", 1: "S", 2: "R" + component_type = ["P", "S", "R"] + Tree = Graph(multiedges=False) + int_to_vertex = [] + partner_nodes = {} - EXAMPLES:: + for i in range(len(self.comp_list_new)): + # Create a new tree vertex + u = (component_type[self.comp_type[i]], + Graph(self.comp_list_new[i], immutable=True, multiedges=True)) + Tree.add_vertex(u) + int_to_vertex.append(u) + + # Add an edge to each node containing the same virtual edge + for e in self.comp_list_new[i]: + if e[2] and "newVEdge" in e[2]: + if e in partner_nodes: + for j in partner_nodes[e]: + Tree.add_edge(int_to_vertex[i], int_to_vertex[j]) + partner_nodes[e].append(i) + else: + partner_nodes[e] = [i] - sage: from sage.graphs.connectivity import staticSPQRTree - sage: G = Graph(2) - sage: for i in range(3): - ....: G.add_clique([0, 1, G.add_vertex(), G.add_vertex()]) - sage: Tree = staticSPQRTree(G) - sage: K4 = graphs.CompleteGraph(4) - sage: all(u[1].is_isomorphic(K4) for u in Tree.vertices() if u[0] == 'R') - True - sage: from sage.graphs.connectivity import spqr_tree_to_graph - sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) - True + return Tree - sage: G = Graph(2) - sage: for i in range(3): - ....: G.add_path([0, G.add_vertex(), G.add_vertex(), 1]) - sage: Tree = staticSPQRTree(G) - sage: C4 = graphs.CycleGraph(4) - sage: all(u[1].is_isomorphic(C4) for u in Tree.vertices() if u[0] == 'S') - True - sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) - True + def get_triconnected_components(self): + r""" + Return the triconnected components as a list of tuples. - sage: G.allow_multiple_edges(True) - sage: G.add_edges(G.edges()) - sage: Tree = staticSPQRTree(G) - sage: all(u[1].is_isomorphic(C4) for u in Tree.vertices() if u[0] == 'S') - True - sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) - True + Each component is represented as a tuple of the type of the component + and the list of edges of the component. - sage: G = graphs.CycleGraph(6) - sage: Tree = staticSPQRTree(G) - sage: Tree.order() - 1 - sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) - True - sage: G.add_edge(0, 3) - sage: Tree = staticSPQRTree(G) - sage: Tree.order() - 3 - sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) - True - """ - from sage.graphs.graph import Graph - # Types of components 0: "P", 1: "S", 2: "R" - component_type = ["P", "S", "R"] - - tric = Triconnectivity(G) - - Tree = Graph(multiedges=False) - int_to_vertex = [] - partner_nodes = {} - - for i in range(len(tric.comp_list_new)): - # Create a new tree vertex - u = (component_type[tric.comp_type[i]], - Graph(tric.comp_list_new[i], immutable=True, multiedges=True)) - Tree.add_vertex(u) - int_to_vertex.append(u) - - # Add an edge to each node containing the same virtual edge - for e in tric.comp_list_new[i]: - if e[2] and "newVEdge" in e[2]: - if e in partner_nodes: - for j in partner_nodes[e]: - Tree.add_edge(int_to_vertex[i], int_to_vertex[j]) - partner_nodes[e].append(i) - else: - partner_nodes[e] = [i] + EXAMPLES: - return Tree + sage: from sage.graphs.connectivity import TriconnectivitySPQR + sage: G = Graph(2) + sage: for i in range(3): + ....: G.add_path([0, G.add_vertex(), G.add_vertex(), 1]) + sage: tric = TriconnectivitySPQR(G) + sage: tric.get_triconnected_components() + [('Polygon', [(4, 5, None), (0, 4, None), (1, 5, None), (1, 0, 'newVEdge1')]), + ('Polygon', [(6, 7, None), (0, 6, None), (1, 7, None), (1, 0, 'newVEdge3')]), + ('Bond', [(1, 0, 'newVEdge1'), (1, 0, 'newVEdge3'), (1, 0, 'newVEdge4')]), + ('Polygon', [(1, 3, None), (1, 0, 'newVEdge4'), (0, 2, None), (2, 3, None)])] + """ + comps = [] + prefix = ["Bond", "Polygon", "Triconnected"] + for i in range(len(self.comp_list_new)): + comps.append((prefix[self.comp_type[i]], self.comp_list_new[i])) + return comps + + def get_spqr_tree(self): + r""" + Return an SPQR-tree representing the triconnected components of the graph. + + An SPQR-tree is a tree data structure used to represent the triconnected + components of a biconnected (multi)graph and the 2-vertex cuts separating + them. A node of a SPQR-tree, and the graph associated with it, can be one of + the following four types: + + - ``S`` -- the associated graph is a cycle with at least three vertices. + ``S`` stands for ``series``. + + - ``P`` -- the associated graph is a dipole graph, a multigraph with two + vertices and three or more edges. ``P`` stands for ``parallel``. + + - ``Q`` -- the associated graph has a single real edge. This trivial case is + necessary to handle the graph that has only one edge. + + - ``R`` -- the associated graph is a 3-connected graph that is not a cycle + or dipole. ``R`` stands for ``rigid``. + + This method constructs a static spqr-tree using the decomposition of a + biconnected graph into cycles, cocycles, and 3-connected blocks generated by + :class:`sage.graphs.connectivity.TriconnectivitySPQR`. + See :wikipedia:`SPQR_tree`. + + OUTPUT: ``SPQR-tree`` a tree whose vertices are labeled with the block's type + and the subgraph of three-blocks in the decomposition. + + + EXAMPLES:: + + sage: from sage.graphs.connectivity import TriconnectivitySPQR + sage: G = Graph(2) + sage: for i in range(3): + ....: G.add_clique([0, 1, G.add_vertex(), G.add_vertex()]) + sage: tric = TriconnectivitySPQR(G) + sage: Tree = tric.get_spqr_tree() + sage: K4 = graphs.CompleteGraph(4) + sage: all(u[1].is_isomorphic(K4) for u in Tree.vertices() if u[0] == 'R') + True + sage: from sage.graphs.connectivity import spqr_tree_to_graph + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + + sage: G = Graph(2) + sage: for i in range(3): + ....: G.add_path([0, G.add_vertex(), G.add_vertex(), 1]) + sage: tric = TriconnectivitySPQR(G) + sage: Tree = tric.get_spqr_tree() + sage: C4 = graphs.CycleGraph(4) + sage: all(u[1].is_isomorphic(C4) for u in Tree.vertices() if u[0] == 'S') + True + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + + sage: G.allow_multiple_edges(True) + sage: G.add_edges(G.edges()) + sage: tric = TriconnectivitySPQR(G) + sage: Tree = tric.get_spqr_tree() + sage: all(u[1].is_isomorphic(C4) for u in Tree.vertices() if u[0] == 'S') + True + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + + sage: G = graphs.CycleGraph(6) + sage: tric = TriconnectivitySPQR(G) + sage: Tree = tric.get_spqr_tree() + sage: Tree.order() + 1 + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + sage: G.add_edge(0, 3) + sage: tric = TriconnectivitySPQR(G) + sage: Tree = tric.get_spqr_tree() + sage: Tree.order() + 3 + sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) + True + """ + return self.__staticSPQRTree() From a31c96464964a42bf17bc5a6dd46ba49c94f3958 Mon Sep 17 00:00:00 2001 From: Ben Hutz Date: Wed, 8 Aug 2018 16:00:04 -0500 Subject: [PATCH 081/264] 25952 : minor code clean-up and an error fix --- .../endPN_minimal_model.py | 31 +++++++------------ .../arithmetic_dynamics/projective_ds.py | 15 ++++----- .../rings/polynomial/binary_form_reduce.py | 3 -- 3 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py b/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py index 2229911ba99..8f03a9a86d0 100644 --- a/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py +++ b/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py @@ -1012,6 +1012,8 @@ def smallest_dynamical(f, dynatomic=True, start_n=1, prec=53, emb=None, algorith properties of the map are utilized to choose how to compute minimal orbit representatives + - ``check_minimal`` -- (default: True), boolean, whether to check + if this map is a minimal model OUTPUT: pair [dynamical system, matrix] @@ -1053,15 +1055,12 @@ def insert_item(pts, item, index): def coshdelta(z): # The cosh of the hyperbolic distance from z = t+uj to j return (z.norm() + 1)/(2*z.imag()) - #g,M = f.minimal_model(return_transformation=True) + # can't be smaller if height 0 f.normalize_coordinates() if f.global_height(prec=prec) == 0: return [f, matrix(ZZ,2,2,[1,0,0,1])] all_min = f.all_minimal_models(return_transformation=True, algorithm=algorithm, check_minimal=check_minimal) - # make conjugation current - #for i in range(len(all_min)): - # all_min[i][1] = M*all_min[i][1] current_min = None current_size = None @@ -1092,22 +1091,14 @@ def coshdelta(z): assert(n<=4), "n > 4, failed to find usable poly" R = get_bound_dynamical(pts_poly, g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) - try: - G,MG = pts_poly.reduced_form(prec=prec, emb=emb, smallest_coeffs=False) - if G != pts_poly: - red_g = f.conjugate(M*MG) - R2 = get_bound_dynamical(G, red_g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) - if R2 < R: - R = R2 - else: - #use pts_poly since the search space is smaller - G = pts_poly - MG = matrix(ZZ,2,2,[1,0,0,1]) - except: # any failure -> skip - G = pts_poly - MG = matrix(ZZ,2,2,[1,0,0,1]) - + # search starts in fundamental domain + G,MG = pts_poly.reduced_form(prec=prec, emb=emb, smallest_coeffs=False) red_g = f.conjugate(M*MG) + if G != pts_poly: + R2 = get_bound_dynamical(G, red_g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) + if R2 < R: + # use the better bound + R = R2 red_g.normalize_coordinates() if red_g.global_height(prec=prec) == 0: return [red_g, M*MG] @@ -1141,6 +1132,8 @@ def coshdelta(z): if new_size < current_size: current_min = [G ,g, v, rep, M, coshdelta(v)] current_size = new_size + if new_size == 1: # early exit + return [current_min[1], current_min[4]] new_R = get_bound_dynamical(G, g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) if new_R < R: R = new_R diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index 60483edebc2..5c80953f607 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -3826,7 +3826,8 @@ def reduced_form(self, **kwds): * ``'BM'`` -- Bruin-Molnar algorithm [BM2012]_ * ``'HS'`` -- Hutz-Stoll algorithm [HS2018]_ - - ``check_minimal`` + - ``check_minimal`` -- (default: True), boolean, whether to check + if this map is a minimal model - ``smallest_coeffs`` -- (default: True), boolean, whether to find the model with smallest coefficients @@ -3987,11 +3988,11 @@ def reduced_form(self, **kwds): ( Dynamical System of Projective Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (x : y) to - (-2*x^2 - y^2 : 2*x^2 - 2*y^2) + (-2*x^2 + 2*y^2 : x^2 + 2*y^2) , - [2 2] - [0 3] + [ 2 -2] + [ 3 0] ) :: @@ -4032,11 +4033,11 @@ def reduced_form(self, **kwds): ( Dynamical System of Projective Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (x : y) to - (x^2 + x*y : y^2) + (x^2 - x*y + y^2 : y^2) , - [2 1] - [0 2] + [ 2 -1] + [ 0 2] ) """ if self.domain().ambient_space().dimension_relative() != 1: diff --git a/src/sage/rings/polynomial/binary_form_reduce.py b/src/sage/rings/polynomial/binary_form_reduce.py index a7976ab7363..4f5c4b7d0b9 100644 --- a/src/sage/rings/polynomial/binary_form_reduce.py +++ b/src/sage/rings/polynomial/binary_form_reduce.py @@ -436,13 +436,11 @@ def coshdelta(z): #euclidean norm squared normF = (sum([abs(i)**2 for i in compF.coefficients()])) target = (2**(n-1))*normF/thetaF - #print(normF,target) elif norm_type == 'height': hF = e**max([c.global_height(prec=prec) for c in F.coefficients()]) #height target = (2**(n-1))*(n+1)*(hF**2)/thetaF else: raise ValueError('type must be norm or height') - #print(epsinv(F, target, prec=prec)) return cosh(epsinv(F, target, prec=prec)) @@ -528,7 +526,6 @@ def insert_item(pts, item, index): def coshdelta(z): #The cosh of the hyperbolic distance from z = t+uj to j return (z.norm() + 1)/(2*z.imag())#reduce in the sense of Cremona-Stoll - #G,MG = F.reduced_form(prec=prec) #F \circ MG = G G = F MG = matrix(ZZ,2,2,[1,0,0,1]) x,y = G.parent().gens() From 5c88a35a669e6dcd25ab99b84e70f9ceaa408782 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 9 Aug 2018 10:53:06 +0200 Subject: [PATCH 082/264] trac #25598: correction of method spqr_tree_to_graph --- src/sage/graphs/connectivity.pyx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 85edae647d0..0161f97ada9 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2366,18 +2366,21 @@ def spqr_tree_to_graph(T): from collections import Counter count_G = Counter() - for t,g in T.vertex_iterator(): - if not t == 'P': - count_G.update(g.edge_iterator(labels=False)) - + count_P = Counter() for t,g in T.vertex_iterator(): if t == 'P': - count_g = Counter(g.edge_iterator(labels=False)) - for e,num in count_g.items(): - count_G[e] = abs(count_g[e] - count_G[e]) + count_P.update(g.edge_iterator()) + else: + count_G.update(g.edge_iterator()) G = Graph(multiedges=True) for e,num in count_G.items(): + if e in count_P: + num = abs(count_P[e] - count_G[e]) + elif num == 2: + # Case of 2 S or R blocks separated by a 2-cut without edge. + # No P-block was created as a P-block has at least 3 edges. + continue for _ in range(num): G.add_edge(e) From febf420ffe539e79d9b792e06a2fe9c4a0727c6a Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 9 Aug 2018 12:14:53 +0200 Subject: [PATCH 083/264] trac #25598: fix spqr_tree and spqr_tree_to_graph --- src/sage/graphs/connectivity.pyx | 47 +++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 0161f97ada9..30a3f7f9c59 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2066,7 +2066,7 @@ def cleave(G, cut_vertices=None, virtual_edges=True): return cut_sides, cocycles, virtual_cut_graph -def spqr_tree(G, algorithm="Hopcroft_tarjan"): +def spqr_tree(G, algorithm="Hopcroft_Tarjan"): r""" Return an SPQR-tree representing the triconnected components of the graph. @@ -2102,10 +2102,11 @@ def spqr_tree(G, algorithm="Hopcroft_tarjan"): - ``algorithm`` -- The algorithm to use in computing the SPQR tree of ``G``. The following algorithms are supported: - - ``"Hopcroft_tarjan"`` (default) -- Hopcroft and Tarjan's algorithm. - Refer to [Hopcroft1973]_. + - ``"Hopcroft_Tarjan"`` (default) -- Use the algorithm proposed by + Hopcroft and Tarjan in [Hopcroft1973]_ and later corrected by Gutwenger + and Mutzel in [Gut2001]_. - - ``"Cleave"`` -- Using the ``cleave`` function. + - ``"cleave"`` -- Using method :meth:`sage.graphs.connectivity.cleave`. OUTPUT: ``SPQR-tree`` a tree whose vertices are labeled with the block's type and the subgraph of three-blocks in the decomposition. @@ -2161,6 +2162,14 @@ def spqr_tree(G, algorithm="Hopcroft_tarjan"): sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) True + sage: G = Graph('LlCG{O@?GBoMw?') + sage: T = spqr_tree(G, algorithm="Hopcroft_Tarjan") + sage: G.is_isomorphic(spqr_tree_to_graph(T)) + True + sage: T2 = spqr_tree(G, algorithm='cleave') + sage: G.is_isomorphic(spqr_tree_to_graph(T2)) + True + TESTS:: sage: G = graphs.PathGraph(4) @@ -2174,18 +2183,23 @@ def spqr_tree(G, algorithm="Hopcroft_tarjan"): Traceback (most recent call last): ... ValueError: Graph is not biconnected + + sage: spqr_tree(Graph(), algorithm="easy") + Traceback (most recent call last): + ... + NotImplementedError: SPQR tree algorithm 'easy' is not implemented """ from sage.graphs.generic_graph import GenericGraph if not isinstance(G, GenericGraph): raise TypeError("the input must be a Sage graph") - if algorithm == "Hopcroft_tarjan": + if algorithm == "Hopcroft_Tarjan": from sage.graphs.connectivity import TriconnectivitySPQR tric = TriconnectivitySPQR(G) return tric.get_spqr_tree() - if algorithm != "Cleave": - raise NotImplementedError("SPQR tree algorithm '%s' is not implemented." % algorithm) + if algorithm != "cleave": + raise NotImplementedError("SPQR tree algorithm '{}' is not implemented".format(algorithm)) from sage.graphs.graph import Graph from collections import Counter @@ -2290,6 +2304,7 @@ def spqr_tree(G, algorithm="Hopcroft_tarjan"): Tree = Graph(name='SPQR tree of {}'.format(G.name())) SR_blocks = S_blocks + R_blocks Tree.add_vertices(SR_blocks) + P2 = [] for e,num in cocycles_count.items(): if num: P_block = ('P', Graph([e] * (num + max(0, counter_multiedges[e] - 1)), multiedges=True, immutable=True)) @@ -2302,6 +2317,17 @@ def spqr_tree(G, algorithm="Hopcroft_tarjan"): Tree.add_edge(block, P_block) except: continue + if num == 2: + # When 2 S or R blocks are separated by a 2-cut without edge, we + # have added a P block with only 2 edges. We must remove them + # and connect neighbors by an edge. So we record these blocks + P2.append(P_block) + + # We now remove the P blocks with only 2 edges. + for P_block in P2: + u, v = Tree.neighbors(P_block) + Tree.add_edge(u, v) + Tree.delete_vertex(P_block) # We finally add P blocks to account for multiple edges of the input graph # that are not involved in any separator of the graph @@ -2384,6 +2410,13 @@ def spqr_tree_to_graph(T): for _ in range(num): G.add_edge(e) + # Some edges might only be in P_blocks. Such edges are true edges of the + # graph. This happen when virtual edges have distinct labels. + for e,num in count_P.items(): + if e not in count_G: + for _ in range(num): + G.add_edge(e) + return G From 695a1d5cca97edd13cbab592356b6db7a9869f5b Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Fri, 10 Aug 2018 00:07:22 +0530 Subject: [PATCH 084/264] Moved `spqrtree` function to one place. --- src/sage/graphs/connectivity.pyx | 58 ++++++++++++++------------------ 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 30a3f7f9c59..7618f397d6e 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -3593,37 +3593,6 @@ class TriconnectivitySPQR: for i in range(len(self.comp_list_new)): print("{}: {}".format(prefix[self.comp_type[i]], self.comp_list_new[i])) - def __staticSPQRTree(self): - """ - Constructs the SPQR tree using the triconnected components. - """ - from sage.graphs.graph import Graph - # Types of components 0: "P", 1: "S", 2: "R" - component_type = ["P", "S", "R"] - - Tree = Graph(multiedges=False) - int_to_vertex = [] - partner_nodes = {} - - for i in range(len(self.comp_list_new)): - # Create a new tree vertex - u = (component_type[self.comp_type[i]], - Graph(self.comp_list_new[i], immutable=True, multiedges=True)) - Tree.add_vertex(u) - int_to_vertex.append(u) - - # Add an edge to each node containing the same virtual edge - for e in self.comp_list_new[i]: - if e[2] and "newVEdge" in e[2]: - if e in partner_nodes: - for j in partner_nodes[e]: - Tree.add_edge(int_to_vertex[i], int_to_vertex[j]) - partner_nodes[e].append(i) - else: - partner_nodes[e] = [i] - - return Tree - def get_triconnected_components(self): r""" Return the triconnected components as a list of tuples. @@ -3730,5 +3699,30 @@ class TriconnectivitySPQR: sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) True """ - return self.__staticSPQRTree() + from sage.graphs.graph import Graph + # Types of components 0: "P", 1: "S", 2: "R" + component_type = ["P", "S", "R"] + + Tree = Graph(multiedges=False) + int_to_vertex = [] + partner_nodes = {} + + for i in range(len(self.comp_list_new)): + # Create a new tree vertex + u = (component_type[self.comp_type[i]], + Graph(self.comp_list_new[i], immutable=True, multiedges=True)) + Tree.add_vertex(u) + int_to_vertex.append(u) + + # Add an edge to each node containing the same virtual edge + for e in self.comp_list_new[i]: + if e[2] and "newVEdge" in e[2]: + if e in partner_nodes: + for j in partner_nodes[e]: + Tree.add_edge(int_to_vertex[i], int_to_vertex[j]) + partner_nodes[e].append(i) + else: + partner_nodes[e] = [i] + + return Tree From 1b30d48f8f4680df9c8061af0f156a4c96e9711b Mon Sep 17 00:00:00 2001 From: David Coudert Date: Fri, 10 Aug 2018 10:00:13 +0200 Subject: [PATCH 085/264] trac #25598: add parameters solver and verbose for LP solvers --- src/sage/graphs/connectivity.pyx | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 7618f397d6e..6c847cd4447 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -1887,7 +1887,7 @@ def bridges(G, labels=True): # Methods for finding 3-vertex-connected components and building SPQR-tree # ============================================================================== -def cleave(G, cut_vertices=None, virtual_edges=True): +def cleave(G, cut_vertices=None, virtual_edges=True, solver=None, verbose=0): r""" Return the connected subgraphs separated by the input vertex cut. @@ -1907,6 +1907,15 @@ def cleave(G, cut_vertices=None, virtual_edges=True): edges to the sides of the cut or not. A virtual edge is an edge between a pair of vertices of the cut that are not connected by an edge in ``G``. + - ``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:`sage.numerical.mip.MixedIntegerLinearProgram.solve` of the class + :class:`sage.numerical.mip.MixedIntegerLinearProgram`. + + - ``verbose`` -- integer (default: ``0``). Sets the level of verbosity. Set + to 0 by default, which means quiet. + OUTPUT: A triple `(S, C, f)`, where - `S` is a list of the graphs that are sides of the vertex cut. @@ -2021,7 +2030,7 @@ def cleave(G, cut_vertices=None, virtual_edges=True): raise ValueError("vertex {} is not a vertex of the input graph".format(u)) cut_vertices = list(cut_vertices) else: - cut_size,cut_vertices = G.vertex_connectivity(value_only=False) + cut_size,cut_vertices = G.vertex_connectivity(value_only=False, solver=solver, verbose=verbose) if not cut_vertices: # Typical example is a clique raise ValueError("the input graph has no vertex cut") @@ -2066,7 +2075,7 @@ def cleave(G, cut_vertices=None, virtual_edges=True): return cut_sides, cocycles, virtual_cut_graph -def spqr_tree(G, algorithm="Hopcroft_Tarjan"): +def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): r""" Return an SPQR-tree representing the triconnected components of the graph. @@ -2108,6 +2117,15 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan"): - ``"cleave"`` -- Using method :meth:`sage.graphs.connectivity.cleave`. + - ``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:`sage.numerical.mip.MixedIntegerLinearProgram.solve` of the class + :class:`sage.numerical.mip.MixedIntegerLinearProgram`. + + - ``verbose`` -- integer (default: ``0``). Sets the level of verbosity. Set + to 0 by default, which means quiet. + OUTPUT: ``SPQR-tree`` a tree whose vertices are labeled with the block's type and the subgraph of three-blocks in the decomposition. @@ -2207,7 +2225,7 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan"): if G.has_loops(): raise ValueError("generation of SPQR-trees is only implemented for graphs without loops") - cut_size, cut_vertices = G.vertex_connectivity(value_only=False) + cut_size, cut_vertices = G.vertex_connectivity(value_only=False, solver=solver, verbose=verbose) if cut_size < 2: raise ValueError("generation of SPQR-trees is only implemented for 2-connected graphs") @@ -2252,7 +2270,7 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan"): # Add all the edges of K to cycles list. cycles_graph.add_edges(e for e in K.edge_iterator(labels=False)) else: - K_cut_size,K_cut_vertices = K.vertex_connectivity(value_only=False) + K_cut_size,K_cut_vertices = K.vertex_connectivity(value_only=False, solver=solver, verbose=verbose) if K_cut_size == 2: # The graph has a 2-vertex cut. We add it to the stack two_blocks.append((K, K_cut_vertices)) From 06e67b020870f2c15a7b33a6d1755abb4f2eca06 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Fri, 10 Aug 2018 10:25:01 +0200 Subject: [PATCH 086/264] trac #25598: case of cycles sharing a vertex --- src/sage/graphs/connectivity.pyx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 6c847cd4447..c1e92d18dab 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2296,13 +2296,14 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): S_blocks = [] if not cycles_graph: tmp = [] - elif cycles_graph.is_connected(): + elif cycles_graph.is_biconnected(): tmp = [cycles_graph] else: - tmp = list(cycles_graph.connected_components_subgraphs()) + B,C = cycles_graph.blocks_and_cut_vertices() + tmp = [cycles_graph.subgraph(b) for b in B] while tmp: block = tmp.pop() - if block.is_cycle(): + if block.order() > 2 and block.is_cycle(): S_blocks.append(('S', Graph(block, immutable=True))) elif block.has_multiple_edges(): cut = block.multiple_edges(labels=False)[0] From 400c97dda62ed388066fe0ff7f8db061de66974c Mon Sep 17 00:00:00 2001 From: David Coudert Date: Fri, 10 Aug 2018 19:50:07 +0200 Subject: [PATCH 087/264] trac #25598: small correction in cycles_graph edge removal --- src/sage/graphs/connectivity.pyx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index c1e92d18dab..be47f2411b9 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2286,11 +2286,12 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): # the number of S-blocks. We start removing edges of the triangulation. count = Counter([frozenset(e) for e in cycles_graph.multiple_edges(labels=False)]) for e,num in count.items(): - if num == 2 and e in virtual_edges: + if num == 2 and e in virtual_edges and cocycles_count[e] == 2: + # This virtual edge is only between 2 cycles virtual_edges.discard(e) - for _ in range(num): - cycles_graph.delete_edge(e) - cocycles_count[e] -= 1 + cycles_graph.delete_edge(e) + cycles_graph.delete_edge(e) + cocycles_count[e] -= 2 # We then extract cycles to form S_blocks S_blocks = [] From 04576805c27f838d1f486ad49b66fb05a8f70838 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Sat, 11 Aug 2018 12:50:46 +0200 Subject: [PATCH 088/264] trac #25598: fix manipulation of S_blocks in spqr_tree+cleave --- src/sage/graphs/connectivity.pyx | 72 ++++++++++++++------------------ 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index be47f2411b9..386fda1e9db 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2256,19 +2256,29 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): # We now search for 2-vertex cuts. If a cut is found, we split the graph and # check each side for S or R blocks or another 2-vertex cut R_blocks = [] - virtual_edges = set() two_blocks = [(SG, cut_vertices)] - cycles_graph = Graph(multiedges=True) cocycles_count = Counter() + cycles_list = [] + virtual_to_cycles = dict() while two_blocks: B,B_cut = two_blocks.pop() # B will be always simple graph. S, C, f = cleave(B, cut_vertices=B_cut) + # Store the number of edges of the cocycle (P block) + fe = frozenset(B_cut) + cocycles_count[fe] += C.size() + # Check the sides of the cut for K in S: if K.is_cycle(): - # Add all the edges of K to cycles list. - cycles_graph.add_edges(e for e in K.edge_iterator(labels=False)) + # Add this cycle to the list of cycles + cycles_list.append(K) + # We associate the index of K in cycle_list to the virtual edge + if f.size(): + if fe in virtual_to_cycles: + virtual_to_cycles[fe].append(len(cycles_list)-1) + else: + virtual_to_cycles[fe] = [len(cycles_list)-1] else: K_cut_size,K_cut_vertices = K.vertex_connectivity(value_only=False, solver=solver, verbose=verbose) if K_cut_size == 2: @@ -2277,47 +2287,29 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): else: # The graph is 3-vertex connected R_blocks.append(('R', Graph(K, immutable=True))) - # Store edges of cocycle (P block) and virtual edges (if any) - cocycles_count.update(frozenset(e) for e in C.edge_iterator(labels=False)) - virtual_edges.update(frozenset(e) for e in f.edge_iterator(labels=False)) - - # Cycles of order > 3 may have been triangulated; We undo this to reduce - # the number of S-blocks. We start removing edges of the triangulation. - count = Counter([frozenset(e) for e in cycles_graph.multiple_edges(labels=False)]) - for e,num in count.items(): - if num == 2 and e in virtual_edges and cocycles_count[e] == 2: + # Cycles of order > 3 may have been triangulated; We undo this to reduce the + # number of S-blocks. Two cycles can be merged if they share a virtual edge + # that is not shared by any other block, i.e., cocycles_count[e] == 2. We + # use a DisjointSet to form the groups of cycles to be merged. + from sage.sets.disjoint_set import DisjointSet + DS = DisjointSet(range(len(cycles_list))) + for e in virtual_to_cycles: + if cocycles_count[e] == 2 and len(virtual_to_cycles[e]) == 2: # This virtual edge is only between 2 cycles - virtual_edges.discard(e) - cycles_graph.delete_edge(e) - cycles_graph.delete_edge(e) + C1, C2 = virtual_to_cycles[e] + DS.union(C1, C2) + cycles_list[C1].delete_edge(e) + cycles_list[C2].delete_edge(e) cocycles_count[e] -= 2 - # We then extract cycles to form S_blocks + # We finalize the creation of S_blocks. S_blocks = [] - if not cycles_graph: - tmp = [] - elif cycles_graph.is_biconnected(): - tmp = [cycles_graph] - else: - B,C = cycles_graph.blocks_and_cut_vertices() - tmp = [cycles_graph.subgraph(b) for b in B] - while tmp: - block = tmp.pop() - if block.order() > 2 and block.is_cycle(): - S_blocks.append(('S', Graph(block, immutable=True))) - elif block.has_multiple_edges(): - cut = block.multiple_edges(labels=False)[0] - S,C,f = cleave(block, cut_vertices=cut) - # Remove the cut edge from `S block` components if present - for h in S: - while h.has_edge(cut): - h.delete_edge(cut) - h.add_edge(cut) - tmp.append(h) - else: - # This should never happen - raise ValueError("something goes wrong") + for root,indexes in DS.root_to_elements_dict().items(): + E = [] + for i in indexes: + E.extend(cycles_list[i].edge_iterator(labels=False)) + S_blocks.append(('S', Graph(E, immutable=True))) # We now build the SPQR tree From a5030c9d6eba8417dfe36ce54197aa31a3f4c1b8 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Sun, 12 Aug 2018 10:37:05 +0200 Subject: [PATCH 089/264] trac #25598: handle Q blocks --- src/sage/graphs/connectivity.pyx | 37 ++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 386fda1e9db..75f8a79c59d 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2188,6 +2188,19 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): sage: G.is_isomorphic(spqr_tree_to_graph(T2)) True + sage: G = Graph([(0, 1)], multiedges=True) + sage: T = spqr_tree(G, algorithm='cleave') + sage: T.vertices() + [('Q', Multi-graph on 2 vertices)] + sage: G.is_isomorphic(spqr_tree_to_graph(T)) + True + sage: T = spqr_tree(G, algorithm='Hopcroft_Tarjan') + sage: T.vertices() + [('Q', Multi-graph on 2 vertices)] + sage: G.add_edge(0, 1) + sage: spqr_tree(G, algorithm='cleave').vertices() + [('P', Multi-graph on 2 vertices)] + TESTS:: sage: G = graphs.PathGraph(4) @@ -2225,6 +2238,10 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): if G.has_loops(): raise ValueError("generation of SPQR-trees is only implemented for graphs without loops") + if G.order() == 2 and G.size(): + return Graph({('Q' if G.size() == 1 else 'P', Graph(G, immutable=True, multiedges=True)):[]}, + name='SPQR-tree of {}'.format(G.name())) + cut_size, cut_vertices = G.vertex_connectivity(value_only=False, solver=solver, verbose=verbose) if cut_size < 2: @@ -2406,7 +2423,7 @@ def spqr_tree_to_graph(T): count_G = Counter() count_P = Counter() for t,g in T.vertex_iterator(): - if t == 'P': + if t in ['P', 'Q']: count_P.update(g.edge_iterator()) else: count_G.update(g.edge_iterator()) @@ -2767,6 +2784,7 @@ class TriconnectivitySPQR: """ self.n = G.order() self.m = G.size() + self.graph_name = G.name() # Trivial cases if self.n < 2: @@ -3710,12 +3728,27 @@ class TriconnectivitySPQR: 3 sage: G.is_isomorphic(spqr_tree_to_graph(Tree)) True + + sage: G = Graph([(0, 1)], multiedges=True) + sage: Tree = TriconnectivitySPQR(G).get_spqr_tree() + sage: Tree.vertices() + [('Q', Multi-graph on 2 vertices)] + sage: G.add_edge(0, 1) + sage: Tree = TriconnectivitySPQR(G).get_spqr_tree() + sage: Tree.vertices() + [('P', Multi-graph on 2 vertices)] """ from sage.graphs.graph import Graph # Types of components 0: "P", 1: "S", 2: "R" component_type = ["P", "S", "R"] - Tree = Graph(multiedges=False) + Tree = Graph(multiedges=False, name='SPQR-tree of {}'.format(self.graph_name)) + + if len(self.comp_list_new) == 1 and self.comp_type[0] == 0: + Tree.add_vertex(('Q' if len(self.comp_list_new[0]) == 1 else 'P', + Graph(self.comp_list_new[0], immutable=True, multiedges=True))) + return Tree + int_to_vertex = [] partner_nodes = {} From b8fb124c76c5e708d8129f2cfad0b5dada1dd5a8 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Sun, 12 Aug 2018 11:08:20 +0200 Subject: [PATCH 090/264] trac #25598: review modification in examples --- src/sage/graphs/connectivity.pyx | 48 ++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 75f8a79c59d..6d7ced9357a 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2643,7 +2643,7 @@ class Component: class TriconnectivitySPQR: """ - This module implements the algorithm for finding the triconnected + This class implements the algorithm for finding the triconnected components of a biconnected graph and constructing the SPQR tree. A biconnected graph is a graph where deletion of any one vertex does not disconnect the graph. @@ -2673,17 +2673,29 @@ class TriconnectivitySPQR: .. SEEALSO:: + - :meth:`sage.graphs.connectivity.spqr_tree` - :meth:`~Graph.is_biconnected` EXAMPLES: - An example from [Hopcroft1973]_:: + :wikipedia:`SPQR_tree` reference paper example:: sage: from sage.graphs.connectivity import TriconnectivitySPQR - sage: G = Graph() - sage: G.add_edges([(1,2),(1,4),(1,8),(1,12),(1,13),(2,3),(2,13),(3,4)]) - sage: G.add_edges([(3,13),(4,5),(4,7),(5,6),(5,7),(5,8),(6,7),(8,9),(8,11)]) - sage: G.add_edges([(8,12),(9,10),(9,11),(9,12),(10,11),(10,12)]) + sage: from sage.graphs.connectivity import spqr_tree_to_graph + sage: G = Graph([(1, 2), (1, 4), (1, 8), (1, 12), (3, 4), (2, 3), + ....: (2, 13), (3, 13), (4, 5), (4, 7), (5, 6), (5, 8), (5, 7), (6, 7), + ....: (8, 11), (8, 9), (8, 12), (9, 10), (9, 11), (9, 12), (10, 12)]) + sage: tric = TriconnectivitySPQR(G) + sage: T = tric.get_spqr_tree() + sage: G.is_isomorphic(spqr_tree_to_graph(T)) + True + + An example from [Hopcroft1973]_:: + + sage: G = Graph([(1, 2), (1, 4), (1, 8), (1, 12), (1, 13), (2, 3), + ....: (2, 13), (3, 4), (3, 13), (4, 5), (4, 7), (5, 6), (5, 7), (5, 8), + ....: (6, 7), (8, 9), (8, 11), (8, 12), (9, 10), (9, 11), (9, 12), + ....: (10, 11), (10, 12)]) sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() Triconnected: [(8, 9, None), (9, 10, None), (10, 11, None), (9, 11, None), (8, 11, None), (10, 12, None), (9, 12, None), (8, 12, 'newVEdge0')] @@ -3618,6 +3630,30 @@ class TriconnectivitySPQR: Print the type and list of edges of each component. The types are ``{0: "Bond", 1: "Polygon", 2: "Triconnected"}``. + + EXAMPLES: + + An example from [Hopcroft1973]_:: + + sage: from sage.graphs.connectivity import TriconnectivitySPQR + sage: G = Graph([(1, 2), (1, 4), (1, 8), (1, 12), (1, 13), (2, 3), + ....: (2, 13), (3, 4), (3, 13), (4, 5), (4, 7), (5, 6), (5, 7), (5, 8), + ....: (6, 7), (8, 9), (8, 11), (8, 12), (9, 10), (9, 11), (9, 12), + ....: (10, 11), (10, 12)]) + sage: tric = TriconnectivitySPQR(G) + sage: tric.print_triconnected_components() + Triconnected: [(8, 9, None), (9, 10, None), (10, 11, None), (9, 11, None), (8, 11, None), (10, 12, None), (9, 12, None), (8, 12, 'newVEdge0')] + Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] + Polygon: [(8, 12, 'newVEdge1'), (1, 12, None), (8, 1, 'newVEdge2')] + Bond: [(1, 8, None), (8, 1, 'newVEdge2'), (8, 1, 'newVEdge3')] + Polygon: [(5, 8, None), (8, 1, 'newVEdge3'), (4, 5, 'newVEdge8'), (4, 1, 'newVEdge9')] + Polygon: [(5, 6, None), (6, 7, None), (5, 7, 'newVEdge5')] + Bond: [(5, 7, None), (5, 7, 'newVEdge5'), (5, 7, 'newVEdge6')] + Polygon: [(5, 7, 'newVEdge6'), (4, 7, None), (5, 4, 'newVEdge7')] + Bond: [(5, 4, 'newVEdge7'), (4, 5, 'newVEdge8'), (4, 5, None)] + Bond: [(1, 4, None), (4, 1, 'newVEdge9'), (4, 1, 'newVEdge10')] + Polygon: [(3, 4, None), (4, 1, 'newVEdge10'), (3, 1, 'newVEdge11')] + Triconnected: [(1, 2, None), (2, 3, None), (3, 1, 'newVEdge11'), (3, 13, None), (2, 13, None), (1, 13, None)] """ prefix = ["Bond", "Polygon", "Triconnected"] for i in range(len(self.comp_list_new)): From 93daa437094e752623830a73006544f12590502c Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Sun, 12 Aug 2018 21:47:43 +0530 Subject: [PATCH 091/264] Made the helper classes private. --- src/sage/graphs/connectivity.pyx | 42 +++++++++++++++++--------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 6d7ced9357a..774d70fe12c 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -27,6 +27,8 @@ Here is what the module can do: :meth:`is_cut_vertex` | Return True if the input vertex is a cut-vertex. :meth:`edge_connectivity` | Return the edge connectivity of the graph. :meth:`vertex_connectivity` | Return the vertex connectivity of the graph. + :meth:`~TriconnectivitySPQR.get_triconnected_components` | Return the triconnected components of a biconnected graph. + :meth:`~TriconnectivitySPQR.get_spqr_tree` | Returns the SPQR tree of a biconnected graph constructed from its triconnected components. **For DiGraph:** @@ -2449,7 +2451,7 @@ def spqr_tree_to_graph(T): return G -class LinkedListNode: +class _LinkedListNode: """ Node in a ``LinkedList``. @@ -2478,7 +2480,7 @@ class LinkedListNode: def get_data(self): return self.data -class LinkedList: +class _LinkedList: """ A doubly linked list with head and tail pointers. @@ -2574,7 +2576,7 @@ class LinkedList: lst2.head = None lst2.length = 0 -class Component: +class _Component: """ Connected component class. @@ -2597,13 +2599,13 @@ class Component: - `type_c` -- type of the component (0, 1, or 2). """ - self.edge_list = LinkedList() + self.edge_list = _LinkedList() for e in edge_list: - self.edge_list.append(LinkedListNode(e)) + self.edge_list.append(_LinkedListNode(e)) self.component_type = type_c def add_edge(self, e): - self.edge_list.append(LinkedListNode(e)) + self.edge_list.append(_LinkedListNode(e)) def finish_tric_or_poly(self, e): """ @@ -2840,7 +2842,7 @@ class TriconnectivitySPQR: self.dfs_number = [0 for i in range(self.n)] # DFS number of vertex i # Linked list of fronds entering vertex i in the order they are visited - self.highpt = [LinkedList() for i in range(self.n)] + self.highpt = [_LinkedList() for i in range(self.n)] # A dictionary whose key is an edge e, value is a pointer to element in # self.highpt containing the edge e. Used in the `path_search` function. @@ -2854,7 +2856,7 @@ class TriconnectivitySPQR: self.lowpt2 = [None for i in range(self.n)] # lowpt2 number of vertex i # i^th value contains a LinkedList of incident edges of vertex i - self.adj = [LinkedList() for i in range(self.n)] + self.adj = [_LinkedList() for i in range(self.n)] # A dictionary whose key is an edge, value is a pointer to element in # self.adj containing the edge. Used in the `path_search` function. @@ -2931,7 +2933,7 @@ class TriconnectivitySPQR: self.__path_search(self.start_vertex) # last split component - c = Component([],0) + c = _Component([],0) while self.e_stack: c.add_edge(self.__estack_pop()) c.component_type = 2 if c.edge_list.get_length() > 4 else 1 @@ -2973,7 +2975,7 @@ class TriconnectivitySPQR: Create a new component, add `edges` to it. type_c = 0 for bond, 1 for polygon, 2 for triconnected component """ - c = Component(edges, type_c) + c = _Component(edges, type_c) self.components_list.append(c) return c @@ -3166,7 +3168,7 @@ class TriconnectivitySPQR: # Populate `adj` and `in_adj` with the sorted edges for i in range(1, max_size + 1): for e in bucket[i]: - node = LinkedListNode(e) + node = _LinkedListNode(e) if e in self.reverse_edges: self.adj[e[1]].append(node) self.in_adj[e] = node @@ -3193,7 +3195,7 @@ class TriconnectivitySPQR: self.dfs_counter -= 1 else: # Identified a new frond that enters `w`. Add to `highpt[w]`. - highpt_node = LinkedListNode(self.newnum[v]) + highpt_node = _LinkedListNode(self.newnum[v]) self.highpt[w].append(highpt_node) self.in_high[e] = highpt_node self.new_path = True @@ -3306,7 +3308,7 @@ class TriconnectivitySPQR: if e2_source != w: raise ValueError("Graph is not biconnected") - comp = Component([e1, e2, e_virt], 1) + comp = _Component([e1, e2, e_virt], 1) self.components_list.append(comp) comp = None @@ -3327,7 +3329,7 @@ class TriconnectivitySPQR: h = self.t_stack_h[self.t_stack_top] self.t_stack_top -= 1 - comp = Component([],0) + comp = _Component([],0) while True: xy = self.e_stack[-1] if xy in self.reverse_edges: @@ -3372,7 +3374,7 @@ class TriconnectivitySPQR: x = self.node_at[b] if e_ab is not None: - comp = Component([e_ab, e_virt], type_c=0) + comp = _Component([e_ab, e_virt], type_c=0) e_virt = tuple([v, x, "newVEdge"+str(self.virtual_edge_num)]) self.graph_copy.add_edge(e_virt) self.virtual_edge_num += 1 @@ -3400,7 +3402,7 @@ class TriconnectivitySPQR: (self.parent[v] != self.start_vertex or outv >= 2): # type-1 separation pair - (self.node_at[self.lowpt1[w]], v) # Create a new component and add edges to it - comp = Component([], 0) + comp = _Component([], 0) if not self.e_stack: raise ValueError("stack is empty") while self.e_stack: @@ -3430,7 +3432,7 @@ class TriconnectivitySPQR: if (xx == vnum and y == self.lowpt1[w]) or \ (y == vnum and xx == self.lowpt1[w]): - comp_bond = Component([], type_c=0) # new triple bond + comp_bond = _Component([], type_c=0) # new triple bond eh = self.__estack_pop() if self.in_adj[eh] != it: if eh in self.reverse_edges: @@ -3460,7 +3462,7 @@ class TriconnectivitySPQR: self.in_adj[e_virt] = it if not e_virt in self.in_high and self.__high(self.node_at[self.lowpt1[w]]) < vnum: - vnum_node = LinkedListNode(vnum) + vnum_node = _LinkedListNode(vnum) self.highpt[self.node_at[self.lowpt1[w]]].push_front(vnum_node) self.in_high[e_virt] = vnum_node @@ -3469,7 +3471,7 @@ class TriconnectivitySPQR: else: self.adj[v].remove(it) - comp_bond = Component([e_virt], type_c=0) + comp_bond = _Component([e_virt], type_c=0) e_virt = (self.node_at[self.lowpt1[w]], v, "newVEdge"+str(self.virtual_edge_num)) self.graph_copy.add_edge(e_virt) self.virtual_edge_num += 1 @@ -3485,7 +3487,7 @@ class TriconnectivitySPQR: self.edge_status[e_virt] = 1 if eh in self.in_adj: self.in_adj[e_virt] = self.in_adj[eh] - e_virt_node = LinkedListNode(e_virt) + e_virt_node = _LinkedListNode(e_virt) self.in_adj[eh] = e_virt_node # end type-1 search From 5a59bf8b4401fcc4a18f2b0e15941cf70cbe54f5 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Sun, 12 Aug 2018 22:58:49 +0530 Subject: [PATCH 092/264] Changed some documentation. --- src/sage/graphs/connectivity.pyx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 774d70fe12c..ea84a1495dd 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2660,9 +2660,9 @@ class TriconnectivitySPQR: OUTPUT: No output, the triconnected components are printed. - The triconnected components are stored in `comp_list_new and `comp_type`. - `comp_list_new` is a list of components, with `comp_list_new[i]` contains - the list of edges in the $i^{th}$ component. `comp_type[i]` stores the type + The triconnected components are stored in `comp\_list\_new` and `comp\_type`. + `comp\_list\_new` is a list of components, with `comp\_list\_new[i]` contains + the list of edges in the $i^{th}$ component. `comp\_type[i]` stores the type of the $i^{th}$ component - 1 for bond, 2 for polygon, 3 for triconnected component. The output can be accessed through these variables. @@ -3668,7 +3668,7 @@ class TriconnectivitySPQR: Each component is represented as a tuple of the type of the component and the list of edges of the component. - EXAMPLES: + EXAMPLES:: sage: from sage.graphs.connectivity import TriconnectivitySPQR sage: G = Graph(2) From 34efb5c0ba6d92518d71ec3c48ce3c1b0c889e76 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Mon, 13 Aug 2018 01:03:29 +0530 Subject: [PATCH 093/264] Moved spqrtree function to init. --- src/sage/graphs/connectivity.pyx | 188 ++++++++++++++++--------------- 1 file changed, 98 insertions(+), 90 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index ea84a1495dd..d2e28e348b7 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2700,18 +2700,18 @@ class TriconnectivitySPQR: ....: (10, 11), (10, 12)]) sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() - Triconnected: [(8, 9, None), (9, 10, None), (10, 11, None), (9, 11, None), (8, 11, None), (10, 12, None), (9, 12, None), (8, 12, 'newVEdge0')] - Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] - Polygon: [(8, 12, 'newVEdge1'), (1, 12, None), (8, 1, 'newVEdge2')] - Bond: [(1, 8, None), (8, 1, 'newVEdge2'), (8, 1, 'newVEdge3')] - Polygon: [(5, 8, None), (8, 1, 'newVEdge3'), (4, 5, 'newVEdge8'), (4, 1, 'newVEdge9')] - Polygon: [(5, 6, None), (6, 7, None), (5, 7, 'newVEdge5')] - Bond: [(5, 7, None), (5, 7, 'newVEdge5'), (5, 7, 'newVEdge6')] - Polygon: [(5, 7, 'newVEdge6'), (4, 7, None), (5, 4, 'newVEdge7')] - Bond: [(5, 4, 'newVEdge7'), (4, 5, 'newVEdge8'), (4, 5, None)] - Bond: [(1, 4, None), (4, 1, 'newVEdge9'), (4, 1, 'newVEdge10')] - Polygon: [(3, 4, None), (4, 1, 'newVEdge10'), (3, 1, 'newVEdge11')] - Triconnected: [(1, 2, None), (2, 3, None), (3, 1, 'newVEdge11'), (3, 13, None), (2, 13, None), (1, 13, None)] + Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] + Bond: [(1, 8, None), (1, 8, 'newVEdge2'), (1, 8, 'newVEdge3')] + Bond: [(5, 7, None), (5, 7, 'newVEdge5'), (5, 7, 'newVEdge6')] + Bond: [(4, 5, None), (4, 5, 'newVEdge7'), (4, 5, 'newVEdge8')] + Bond: [(1, 4, None), (1, 4, 'newVEdge10'), (1, 4, 'newVEdge9')] + Triconnected: [(8, 9, None), (8, 11, None), (8, 12, 'newVEdge0'), (9, 10, None), (9, 11, None), (9, 12, None), (10, 11, None), (10, 12, None)] + Triconnected: [(1, 2, None), (1, 3, 'newVEdge11'), (1, 13, None), (2, 3, None), (2, 13, None), (3, 13, None)] + Polygon: [(1, 8, 'newVEdge2'), (1, 12, None), (8, 12, 'newVEdge1')] + Polygon: [(1, 4, 'newVEdge9'), (1, 8, 'newVEdge3'), (4, 5, 'newVEdge8'), (5, 8, None)] + Polygon: [(5, 6, None), (5, 7, 'newVEdge5'), (6, 7, None)] + Polygon: [(4, 5, 'newVEdge7'), (4, 7, None), (5, 7, 'newVEdge6')] + Polygon: [(1, 3, 'newVEdge11'), (1, 4, 'newVEdge10'), (3, 4, None)] An example from [Gut2001]_:: @@ -2721,20 +2721,20 @@ class TriconnectivitySPQR: sage: G.add_edges([(10,14),(10,15),(10,16),(11,12),(11,13),(12,13),(14,15),(14,16),(15,16)]) sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() - Polygon: [(6, 8, None), (4, 6, None), (5, 8, 'newVEdge12'), (5, 4, 'newVEdge13')] - Polygon: [(8, 9, None), (9, 10, None), (8, 10, 'newVEdge1')] - Bond: [(8, 10, 'newVEdge1'), (8, 10, None), (8, 10, 'newVEdge4'), (10, 8, 'newVEdge5')] - Triconnected: [(8, 11, None), (11, 12, None), (8, 12, None), (12, 13, None), (11, 13, None), (8, 13, 'newVEdge3')] - Polygon: [(8, 13, 'newVEdge3'), (10, 13, None), (8, 10, 'newVEdge4')] - Triconnected: [(10, 15, None), (14, 15, None), (15, 16, None), (10, 16, None), (14, 16, None), (10, 14, 'newVEdge6')] - Bond: [(10, 14, 'newVEdge6'), (14, 10, 'newVEdge7'), (10, 14, None)] - Polygon: [(14, 10, 'newVEdge7'), (10, 8, 'newVEdge5'), (5, 14, 'newVEdge10'), (5, 8, 'newVEdge11')] - Polygon: [(5, 7, None), (7, 14, None), (5, 14, 'newVEdge9')] - Bond: [(5, 14, None), (5, 14, 'newVEdge9'), (5, 14, 'newVEdge10')] - Bond: [(5, 8, None), (5, 8, 'newVEdge11'), (5, 8, 'newVEdge12')] - Bond: [(5, 4, 'newVEdge13'), (4, 5, 'newVEdge14'), (4, 5, None)] - Triconnected: [(2, 3, None), (3, 4, None), (4, 5, 'newVEdge14'), (3, 5, None), (2, 5, None), (2, 4, 'newVEdge15')] - Polygon: [(1, 2, None), (2, 4, 'newVEdge15'), (1, 4, None)] + Bond: [(8, 10, None), (8, 10, 'newVEdge1'), (8, 10, 'newVEdge4'), (8, 10, 'newVEdge5')] + Bond: [(10, 14, None), (10, 14, 'newVEdge6'), (10, 14, 'newVEdge7')] + Bond: [(5, 14, None), (5, 14, 'newVEdge10'), (5, 14, 'newVEdge9')] + Bond: [(5, 8, None), (5, 8, 'newVEdge11'), (5, 8, 'newVEdge12')] + Bond: [(4, 5, None), (4, 5, 'newVEdge13'), (4, 5, 'newVEdge14')] + Triconnected: [(8, 11, None), (8, 12, None), (8, 13, 'newVEdge3'), (11, 12, None), (11, 13, None), (12, 13, None)] + Triconnected: [(10, 14, 'newVEdge6'), (10, 15, None), (10, 16, None), (14, 15, None), (14, 16, None), (15, 16, None)] + Triconnected: [(2, 3, None), (2, 4, 'newVEdge15'), (2, 5, None), (3, 4, None), (3, 5, None), (4, 5, 'newVEdge14')] + Polygon: [(4, 5, 'newVEdge13'), (4, 6, None), (5, 8, 'newVEdge12'), (6, 8, None)] + Polygon: [(8, 9, None), (8, 10, 'newVEdge1'), (9, 10, None)] + Polygon: [(8, 10, 'newVEdge4'), (8, 13, 'newVEdge3'), (10, 13, None)] + Polygon: [(5, 8, 'newVEdge11'), (5, 14, 'newVEdge10'), (8, 10, 'newVEdge5'), (10, 14, 'newVEdge7')] + Polygon: [(5, 7, None), (5, 14, 'newVEdge9'), (7, 14, None)] + Polygon: [(1, 2, None), (1, 4, None), (2, 4, 'newVEdge15')] An example with multi-edges and accessing the triconnected components:: @@ -2743,10 +2743,9 @@ class TriconnectivitySPQR: sage: G.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(1,5),(2,3)]) sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() - Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] - Bond: [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')] - Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, - None), (2, 3, 'newVEdge1')] + Bond: [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')] + Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] + Polygon: [(1, 2, None), (1, 5, 'newVEdge0'), (2, 3, 'newVEdge1'), (3, 4, None), (4, 5, None)] sage: tric.comp_list_new [[(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')], [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')], @@ -2761,7 +2760,7 @@ class TriconnectivitySPQR: sage: G2.add_edges([('a','b'),('a','c'),('a','d'),('b','c'),('b','d'),('c','d')]) sage: tric = TriconnectivitySPQR(G2) sage: tric.print_triconnected_components() - Triconnected: [('a', 'b', None), ('b', 'c', None), ('a', 'c', None), ('c', 'd', None), ('b', 'd', None), ('a', 'd', None)] + Triconnected: [('a', 'b', None), ('a', 'c', None), ('a', 'd', None), ('b', 'c', None), ('b', 'd', None), ('c', 'd', None)] An example of a directed graph with multi-edges:: @@ -2770,8 +2769,8 @@ class TriconnectivitySPQR: sage: G3.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(5,1)]) sage: tric = TriconnectivitySPQR(G3) sage: tric.print_triconnected_components() - Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] - Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, None)] + Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] + Polygon: [(1, 2, None), (1, 5, 'newVEdge0'), (2, 3, None), (3, 4, None), (4, 5, None)] TESTS: @@ -2807,6 +2806,7 @@ class TriconnectivitySPQR: # a P block with at least 1 edge self.comp_list_new = [G.edges()] self.comp_type = [0] + self.__build_spqr_tree() return elif self.m < self.n -1: # less edges than a tree @@ -2894,6 +2894,8 @@ class TriconnectivitySPQR: # The final triconnected components are stored self.comp_list_new = [] # i^th entry is list of edges in i^th component self.comp_type = [] # i^th entry is type of i^th component + # The final SPQR tree is stored + self.spqr_tree = None # Graph # # Triconnectivity algorithm @@ -2942,6 +2944,8 @@ class TriconnectivitySPQR: self.__assemble_triconnected_components() + self.__build_spqr_tree() + def __tstack_push(self, h, a, b): """ Push `(h,a,b)` triple on Tstack @@ -3528,7 +3532,7 @@ class TriconnectivitySPQR: final triconnected components. Subsequently, convert the edges in triconnected components into original vertices and edges. The triconnected components are stored in - `self.comp_list_new` and `self.comp_type`. + `self.comp\_list\_new` and `self.comp\_type`. """ comp1 = {} # The index of first component that an edge belongs to comp2 = {} # The index of second component that an edge belongs to @@ -3627,6 +3631,38 @@ class TriconnectivitySPQR: self.comp_type.append(comp.component_type) self.comp_list_new.append(e_list_new) + def __build_spqr_tree(self): + from sage.graphs.graph import Graph + # Types of components 0: "P", 1: "S", 2: "R" + component_type = ["P", "S", "R"] + + self.spqr_tree = Graph(multiedges=False, name='SPQR-tree of {}'.format(self.graph_name)) + + if len(self.comp_list_new) == 1 and self.comp_type[0] == 0: + self.spqr_tree.add_vertex(('Q' if len(self.comp_list_new[0]) == 1 else 'P', + Graph(self.comp_list_new[0], immutable=True, multiedges=True))) + return + + int_to_vertex = [] + partner_nodes = {} + + for i in range(len(self.comp_list_new)): + # Create a new tree vertex + u = (component_type[self.comp_type[i]], + Graph(self.comp_list_new[i], immutable=True, multiedges=True)) + self.spqr_tree.add_vertex(u) + int_to_vertex.append(u) + + # Add an edge to each node containing the same virtual edge + for e in self.comp_list_new[i]: + if e[2] and "newVEdge" in e[2]: + if e in partner_nodes: + for j in partner_nodes[e]: + self.spqr_tree.add_edge(int_to_vertex[i], int_to_vertex[j]) + partner_nodes[e].append(i) + else: + partner_nodes[e] = [i] + def print_triconnected_components(self): """ Print the type and list of edges of each component. @@ -3644,22 +3680,25 @@ class TriconnectivitySPQR: ....: (10, 11), (10, 12)]) sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() - Triconnected: [(8, 9, None), (9, 10, None), (10, 11, None), (9, 11, None), (8, 11, None), (10, 12, None), (9, 12, None), (8, 12, 'newVEdge0')] - Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] - Polygon: [(8, 12, 'newVEdge1'), (1, 12, None), (8, 1, 'newVEdge2')] - Bond: [(1, 8, None), (8, 1, 'newVEdge2'), (8, 1, 'newVEdge3')] - Polygon: [(5, 8, None), (8, 1, 'newVEdge3'), (4, 5, 'newVEdge8'), (4, 1, 'newVEdge9')] - Polygon: [(5, 6, None), (6, 7, None), (5, 7, 'newVEdge5')] - Bond: [(5, 7, None), (5, 7, 'newVEdge5'), (5, 7, 'newVEdge6')] - Polygon: [(5, 7, 'newVEdge6'), (4, 7, None), (5, 4, 'newVEdge7')] - Bond: [(5, 4, 'newVEdge7'), (4, 5, 'newVEdge8'), (4, 5, None)] - Bond: [(1, 4, None), (4, 1, 'newVEdge9'), (4, 1, 'newVEdge10')] - Polygon: [(3, 4, None), (4, 1, 'newVEdge10'), (3, 1, 'newVEdge11')] - Triconnected: [(1, 2, None), (2, 3, None), (3, 1, 'newVEdge11'), (3, 13, None), (2, 13, None), (1, 13, None)] + Bond: [(5, 7, None), (5, 7, 'newVEdge5'), (5, 7, 'newVEdge6')] + Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] + Bond: [(1, 8, None), (1, 8, 'newVEdge2'), (1, 8, 'newVEdge3')] + Bond: [(1, 4, None), (1, 4, 'newVEdge10'), (1, 4, 'newVEdge9')] + Bond: [(4, 5, None), (4, 5, 'newVEdge7'), (4, 5, 'newVEdge8')] + Triconnected: [(1, 2, None), (1, 3, 'newVEdge11'), (1, 13, None), (2, 3, None), (2, 13, None), (3, 13, None)] + Triconnected: [(8, 9, None), (8, 11, None), (8, 12, 'newVEdge0'), (9, 10, None), (9, 11, None), (9, 12, None), (10, 11, None), (10, 12, None)] + Polygon: [(1, 4, 'newVEdge9'), (1, 8, 'newVEdge3'), (4, 5, 'newVEdge8'), (5, 8, None)] + Polygon: [(1, 8, 'newVEdge2'), (1, 12, None), (8, 12, 'newVEdge1')] + Polygon: [(4, 5, 'newVEdge7'), (4, 7, None), (5, 7, 'newVEdge6')] + Polygon: [(1, 3, 'newVEdge11'), (1, 4, 'newVEdge10'), (3, 4, None)] + Polygon: [(5, 6, None), (5, 7, 'newVEdge5'), (6, 7, None)] """ - prefix = ["Bond", "Polygon", "Triconnected"] - for i in range(len(self.comp_list_new)): - print("{}: {}".format(prefix[self.comp_type[i]], self.comp_list_new[i])) + #prefix = ["Bond", "Polygon", "Triconnected"] + #for i in range(len(self.comp_list_new)): + # print("{}: {}".format(prefix[self.comp_type[i]], self.comp_list_new[i])) + prefix = {'P': "Bond", 'S': "Polygon", 'R': "Triconnected"} + for node in self.spqr_tree.vertices(): + print("{}: {}".format(prefix[node[0]], node[1].edges())) def get_triconnected_components(self): r""" @@ -3676,15 +3715,15 @@ class TriconnectivitySPQR: ....: G.add_path([0, G.add_vertex(), G.add_vertex(), 1]) sage: tric = TriconnectivitySPQR(G) sage: tric.get_triconnected_components() - [('Polygon', [(4, 5, None), (0, 4, None), (1, 5, None), (1, 0, 'newVEdge1')]), - ('Polygon', [(6, 7, None), (0, 6, None), (1, 7, None), (1, 0, 'newVEdge3')]), - ('Bond', [(1, 0, 'newVEdge1'), (1, 0, 'newVEdge3'), (1, 0, 'newVEdge4')]), - ('Polygon', [(1, 3, None), (1, 0, 'newVEdge4'), (0, 2, None), (2, 3, None)])] + [('Bond', [(0, 1, 'newVEdge1'), (0, 1, 'newVEdge3'), (0, 1, 'newVEdge4')]), + ('Polygon', [(0, 1, 'newVEdge1'), (0, 4, None), (1, 5, None), (4, 5, None)]), + ('Polygon', [(0, 1, 'newVEdge4'), (0, 2, None), (1, 3, None), (2, 3, None)]), + ('Polygon', [(0, 1, 'newVEdge3'), (0, 6, None), (1, 7, None), (6, 7, None)])] """ comps = [] - prefix = ["Bond", "Polygon", "Triconnected"] - for i in range(len(self.comp_list_new)): - comps.append((prefix[self.comp_type[i]], self.comp_list_new[i])) + prefix = {'P': "Bond", 'S': "Polygon", 'R': "Triconnected"} + for node in self.spqr_tree.vertices(): + comps.append((prefix[node[0]], node[1].edges())) return comps def get_spqr_tree(self): @@ -3768,7 +3807,8 @@ class TriconnectivitySPQR: True sage: G = Graph([(0, 1)], multiedges=True) - sage: Tree = TriconnectivitySPQR(G).get_spqr_tree() + sage: tric = TriconnectivitySPQR(G) + sage: Tree = tric.get_spqr_tree() sage: Tree.vertices() [('Q', Multi-graph on 2 vertices)] sage: G.add_edge(0, 1) @@ -3776,36 +3816,4 @@ class TriconnectivitySPQR: sage: Tree.vertices() [('P', Multi-graph on 2 vertices)] """ - from sage.graphs.graph import Graph - # Types of components 0: "P", 1: "S", 2: "R" - component_type = ["P", "S", "R"] - - Tree = Graph(multiedges=False, name='SPQR-tree of {}'.format(self.graph_name)) - - if len(self.comp_list_new) == 1 and self.comp_type[0] == 0: - Tree.add_vertex(('Q' if len(self.comp_list_new[0]) == 1 else 'P', - Graph(self.comp_list_new[0], immutable=True, multiedges=True))) - return Tree - - int_to_vertex = [] - partner_nodes = {} - - for i in range(len(self.comp_list_new)): - # Create a new tree vertex - u = (component_type[self.comp_type[i]], - Graph(self.comp_list_new[i], immutable=True, multiedges=True)) - Tree.add_vertex(u) - int_to_vertex.append(u) - - # Add an edge to each node containing the same virtual edge - for e in self.comp_list_new[i]: - if e[2] and "newVEdge" in e[2]: - if e in partner_nodes: - for j in partner_nodes[e]: - Tree.add_edge(int_to_vertex[i], int_to_vertex[j]) - partner_nodes[e].append(i) - else: - partner_nodes[e] = [i] - - return Tree - + return self.spqr_tree From 76f8792ccf85223702b67d572ed36338f58c3fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 13 Aug 2018 11:21:56 +0200 Subject: [PATCH 094/264] py3 fix for wigner.py --- src/sage/functions/wigner.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/functions/wigner.py b/src/sage/functions/wigner.py index adb0404a119..9e1e5fd6459 100644 --- a/src/sage/functions/wigner.py +++ b/src/sage/functions/wigner.py @@ -187,8 +187,8 @@ def wigner_3j(j_1, j_2, j_3, m_1, m_2, m_3, prec=None): if isinstance(ressqrt, ComplexNumber): ressqrt = ressqrt.real() - imin = max(-j_3 + j_1 + m_2, -j_3 + j_2 - m_1, 0) - imax = min(j_2 + m_2, j_1 - m_1, j_1 + j_2 - j_3) + imin = int(max(-j_3 + j_1 + m_2, -j_3 + j_2 - m_1, 0)) + imax = int(min(j_2 + m_2, j_1 - m_1, j_1 + j_2 - j_3)) sumres = 0 for ii in range(imin, imax + 1): den = _Factlist[ii] * \ @@ -360,8 +360,8 @@ def racah(aa, bb, cc, dd, ee, ff, prec=None): _big_delta_coeff(bb, dd, ff, prec) if prefac == 0: return 0 - imin = max(aa + bb + ee, cc + dd + ee, aa + cc + ff, bb + dd + ff) - imax = min(aa + bb + cc + dd, aa + dd + ee + ff, bb + cc + ee + ff) + imin = int(max(aa + bb + ee, cc + dd + ee, aa + cc + ff, bb + dd + ff)) + imax = int(min(aa + bb + cc + dd, aa + dd + ee + ff, bb + cc + ee + ff)) maxfact = max(imax + 1, aa + bb + cc + dd, aa + dd + ee + ff, \ bb + cc + ee + ff) @@ -547,7 +547,7 @@ def wigner_9j(j_1, j_2, j_3, j_4, j_5, j_6, j_7, j_8, j_9, prec=None): algebra system [RH2003]_. """ imin = 0 - imax = min(j_1 + j_9, j_2 + j_6, j_4 + j_8) + imax = int(min(j_1 + j_9, j_2 + j_6, j_4 + j_8)) sumres = 0 for kk in range(imin, imax + 1): From ea07a31104cd12f861af8839a8bc04542e341279 Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Mon, 13 Aug 2018 23:50:06 +0530 Subject: [PATCH 095/264] Reverted the changes made to `get/print_connected_components` function. --- src/sage/graphs/connectivity.pyx | 121 ++++++++++++++++--------------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index d2e28e348b7..dde68d64f00 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2700,41 +2700,41 @@ class TriconnectivitySPQR: ....: (10, 11), (10, 12)]) sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() - Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] - Bond: [(1, 8, None), (1, 8, 'newVEdge2'), (1, 8, 'newVEdge3')] - Bond: [(5, 7, None), (5, 7, 'newVEdge5'), (5, 7, 'newVEdge6')] - Bond: [(4, 5, None), (4, 5, 'newVEdge7'), (4, 5, 'newVEdge8')] - Bond: [(1, 4, None), (1, 4, 'newVEdge10'), (1, 4, 'newVEdge9')] - Triconnected: [(8, 9, None), (8, 11, None), (8, 12, 'newVEdge0'), (9, 10, None), (9, 11, None), (9, 12, None), (10, 11, None), (10, 12, None)] - Triconnected: [(1, 2, None), (1, 3, 'newVEdge11'), (1, 13, None), (2, 3, None), (2, 13, None), (3, 13, None)] - Polygon: [(1, 8, 'newVEdge2'), (1, 12, None), (8, 12, 'newVEdge1')] - Polygon: [(1, 4, 'newVEdge9'), (1, 8, 'newVEdge3'), (4, 5, 'newVEdge8'), (5, 8, None)] - Polygon: [(5, 6, None), (5, 7, 'newVEdge5'), (6, 7, None)] - Polygon: [(4, 5, 'newVEdge7'), (4, 7, None), (5, 7, 'newVEdge6')] - Polygon: [(1, 3, 'newVEdge11'), (1, 4, 'newVEdge10'), (3, 4, None)] + Triconnected: [(8, 9, None), (9, 10, None), (10, 11, None), (9, 11, None), (8, 11, None), (10, 12, None), (9, 12, None), (8, 12, 'newVEdge0')] + Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] + Polygon: [(8, 12, 'newVEdge1'), (1, 12, None), (8, 1, 'newVEdge2')] + Bond: [(1, 8, None), (8, 1, 'newVEdge2'), (8, 1, 'newVEdge3')] + Polygon: [(5, 8, None), (8, 1, 'newVEdge3'), (4, 5, 'newVEdge8'), (4, 1, 'newVEdge9')] + Polygon: [(5, 6, None), (6, 7, None), (5, 7, 'newVEdge5')] + Bond: [(5, 7, None), (5, 7, 'newVEdge5'), (5, 7, 'newVEdge6')] + Polygon: [(5, 7, 'newVEdge6'), (4, 7, None), (5, 4, 'newVEdge7')] + Bond: [(5, 4, 'newVEdge7'), (4, 5, 'newVEdge8'), (4, 5, None)] + Bond: [(1, 4, None), (4, 1, 'newVEdge9'), (4, 1, 'newVEdge10')] + Polygon: [(3, 4, None), (4, 1, 'newVEdge10'), (3, 1, 'newVEdge11')] + Triconnected: [(1, 2, None), (2, 3, None), (3, 1, 'newVEdge11'), (3, 13, None), (2, 13, None), (1, 13, None)] An example from [Gut2001]_:: sage: G = Graph() - sage: G.add_edges([(1,2),(1,4),(2,3),(2,5),(3,4),(3,5),(4,5),(4,6),(5,7),(5,8)]) - sage: G.add_edges([(5,14),(6,8),(7,14),(8,9),(8,10),(8,11),(8,12),(9,10),(10,13)]) - sage: G.add_edges([(10,14),(10,15),(10,16),(11,12),(11,13),(12,13),(14,15),(14,16),(15,16)]) + sage: G.add_edges([(1,2),(1,4),(2,3),(2,5),(3,4),(3,5),(4,5),(4,6),(5,7),(5,8), + ....: (5,14),(6,8),(7,14),(8,9),(8,10),(8,11),(8,12),(9,10),(10,13), + ....: (10,14),(10,15),(10,16),(11,12),(11,13),(12,13),(14,15),(14,16),(15,16)]) sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() - Bond: [(8, 10, None), (8, 10, 'newVEdge1'), (8, 10, 'newVEdge4'), (8, 10, 'newVEdge5')] - Bond: [(10, 14, None), (10, 14, 'newVEdge6'), (10, 14, 'newVEdge7')] - Bond: [(5, 14, None), (5, 14, 'newVEdge10'), (5, 14, 'newVEdge9')] - Bond: [(5, 8, None), (5, 8, 'newVEdge11'), (5, 8, 'newVEdge12')] - Bond: [(4, 5, None), (4, 5, 'newVEdge13'), (4, 5, 'newVEdge14')] - Triconnected: [(8, 11, None), (8, 12, None), (8, 13, 'newVEdge3'), (11, 12, None), (11, 13, None), (12, 13, None)] - Triconnected: [(10, 14, 'newVEdge6'), (10, 15, None), (10, 16, None), (14, 15, None), (14, 16, None), (15, 16, None)] - Triconnected: [(2, 3, None), (2, 4, 'newVEdge15'), (2, 5, None), (3, 4, None), (3, 5, None), (4, 5, 'newVEdge14')] - Polygon: [(4, 5, 'newVEdge13'), (4, 6, None), (5, 8, 'newVEdge12'), (6, 8, None)] - Polygon: [(8, 9, None), (8, 10, 'newVEdge1'), (9, 10, None)] - Polygon: [(8, 10, 'newVEdge4'), (8, 13, 'newVEdge3'), (10, 13, None)] - Polygon: [(5, 8, 'newVEdge11'), (5, 14, 'newVEdge10'), (8, 10, 'newVEdge5'), (10, 14, 'newVEdge7')] - Polygon: [(5, 7, None), (5, 14, 'newVEdge9'), (7, 14, None)] - Polygon: [(1, 2, None), (1, 4, None), (2, 4, 'newVEdge15')] + Polygon: [(6, 8, None), (4, 6, None), (5, 8, 'newVEdge12'), (5, 4, 'newVEdge13')] + Polygon: [(8, 9, None), (9, 10, None), (8, 10, 'newVEdge1')] + Bond: [(8, 10, 'newVEdge1'), (8, 10, None), (8, 10, 'newVEdge4'), (10, 8, 'newVEdge5')] + Triconnected: [(8, 11, None), (11, 12, None), (8, 12, None), (12, 13, None), (11, 13, None), (8, 13, 'newVEdge3')] + Polygon: [(8, 13, 'newVEdge3'), (10, 13, None), (8, 10, 'newVEdge4')] + Triconnected: [(10, 15, None), (14, 15, None), (15, 16, None), (10, 16, None), (14, 16, None), (10, 14, 'newVEdge6')] + Bond: [(10, 14, 'newVEdge6'), (14, 10, 'newVEdge7'), (10, 14, None)] + Polygon: [(14, 10, 'newVEdge7'), (10, 8, 'newVEdge5'), (5, 14, 'newVEdge10'), (5, 8, 'newVEdge11')] + Polygon: [(5, 7, None), (7, 14, None), (5, 14, 'newVEdge9')] + Bond: [(5, 14, None), (5, 14, 'newVEdge9'), (5, 14, 'newVEdge10')] + Bond: [(5, 8, None), (5, 8, 'newVEdge11'), (5, 8, 'newVEdge12')] + Bond: [(5, 4, 'newVEdge13'), (4, 5, 'newVEdge14'), (4, 5, None)] + Triconnected: [(2, 3, None), (3, 4, None), (4, 5, 'newVEdge14'), (3, 5, None), (2, 5, None), (2, 4, 'newVEdge15')] + Polygon: [(1, 2, None), (2, 4, 'newVEdge15'), (1, 4, None)] An example with multi-edges and accessing the triconnected components:: @@ -2743,9 +2743,9 @@ class TriconnectivitySPQR: sage: G.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(1,5),(2,3)]) sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() - Bond: [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')] - Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] - Polygon: [(1, 2, None), (1, 5, 'newVEdge0'), (2, 3, 'newVEdge1'), (3, 4, None), (4, 5, None)] + Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] + Bond: [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')] + Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, 'newVEdge1')] sage: tric.comp_list_new [[(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')], [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')], @@ -2760,7 +2760,7 @@ class TriconnectivitySPQR: sage: G2.add_edges([('a','b'),('a','c'),('a','d'),('b','c'),('b','d'),('c','d')]) sage: tric = TriconnectivitySPQR(G2) sage: tric.print_triconnected_components() - Triconnected: [('a', 'b', None), ('a', 'c', None), ('a', 'd', None), ('b', 'c', None), ('b', 'd', None), ('c', 'd', None)] + Triconnected: [('a', 'b', None), ('b', 'c', None), ('a', 'c', None), ('c', 'd', None), ('b', 'd', None), ('a', 'd', None)] An example of a directed graph with multi-edges:: @@ -2769,8 +2769,8 @@ class TriconnectivitySPQR: sage: G3.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(5,1)]) sage: tric = TriconnectivitySPQR(G3) sage: tric.print_triconnected_components() - Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] - Polygon: [(1, 2, None), (1, 5, 'newVEdge0'), (2, 3, None), (3, 4, None), (4, 5, None)] + Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] + Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, None)] TESTS: @@ -3632,6 +3632,10 @@ class TriconnectivitySPQR: self.comp_list_new.append(e_list_new) def __build_spqr_tree(self): + """ + Constructs the SPQR tree of the graph and stores the tree in the variable + `self.spqr_tree`. Refer to function `~TriconnectivitySPQR.get_spqr_tree`. + """ from sage.graphs.graph import Graph # Types of components 0: "P", 1: "S", 2: "R" component_type = ["P", "S", "R"] @@ -3680,25 +3684,22 @@ class TriconnectivitySPQR: ....: (10, 11), (10, 12)]) sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() - Bond: [(5, 7, None), (5, 7, 'newVEdge5'), (5, 7, 'newVEdge6')] - Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] - Bond: [(1, 8, None), (1, 8, 'newVEdge2'), (1, 8, 'newVEdge3')] - Bond: [(1, 4, None), (1, 4, 'newVEdge10'), (1, 4, 'newVEdge9')] - Bond: [(4, 5, None), (4, 5, 'newVEdge7'), (4, 5, 'newVEdge8')] - Triconnected: [(1, 2, None), (1, 3, 'newVEdge11'), (1, 13, None), (2, 3, None), (2, 13, None), (3, 13, None)] - Triconnected: [(8, 9, None), (8, 11, None), (8, 12, 'newVEdge0'), (9, 10, None), (9, 11, None), (9, 12, None), (10, 11, None), (10, 12, None)] - Polygon: [(1, 4, 'newVEdge9'), (1, 8, 'newVEdge3'), (4, 5, 'newVEdge8'), (5, 8, None)] - Polygon: [(1, 8, 'newVEdge2'), (1, 12, None), (8, 12, 'newVEdge1')] - Polygon: [(4, 5, 'newVEdge7'), (4, 7, None), (5, 7, 'newVEdge6')] - Polygon: [(1, 3, 'newVEdge11'), (1, 4, 'newVEdge10'), (3, 4, None)] - Polygon: [(5, 6, None), (5, 7, 'newVEdge5'), (6, 7, None)] + Triconnected: [(8, 9, None), (9, 10, None), (10, 11, None), (9, 11, None), (8, 11, None), (10, 12, None), (9, 12, None), (8, 12, 'newVEdge0')] + Bond: [(8, 12, None), (8, 12, 'newVEdge0'), (8, 12, 'newVEdge1')] + Polygon: [(8, 12, 'newVEdge1'), (1, 12, None), (8, 1, 'newVEdge2')] + Bond: [(1, 8, None), (8, 1, 'newVEdge2'), (8, 1, 'newVEdge3')] + Polygon: [(5, 8, None), (8, 1, 'newVEdge3'), (4, 5, 'newVEdge8'), (4, 1, 'newVEdge9')] + Polygon: [(5, 6, None), (6, 7, None), (5, 7, 'newVEdge5')] + Bond: [(5, 7, None), (5, 7, 'newVEdge5'), (5, 7, 'newVEdge6')] + Polygon: [(5, 7, 'newVEdge6'), (4, 7, None), (5, 4, 'newVEdge7')] + Bond: [(5, 4, 'newVEdge7'), (4, 5, 'newVEdge8'), (4, 5, None)] + Bond: [(1, 4, None), (4, 1, 'newVEdge9'), (4, 1, 'newVEdge10')] + Polygon: [(3, 4, None), (4, 1, 'newVEdge10'), (3, 1, 'newVEdge11')] + Triconnected: [(1, 2, None), (2, 3, None), (3, 1, 'newVEdge11'), (3, 13, None), (2, 13, None), (1, 13, None)] """ - #prefix = ["Bond", "Polygon", "Triconnected"] - #for i in range(len(self.comp_list_new)): - # print("{}: {}".format(prefix[self.comp_type[i]], self.comp_list_new[i])) - prefix = {'P': "Bond", 'S': "Polygon", 'R': "Triconnected"} - for node in self.spqr_tree.vertices(): - print("{}: {}".format(prefix[node[0]], node[1].edges())) + prefix = ["Bond", "Polygon", "Triconnected"] + for i in range(len(self.comp_list_new)): + print("{}: {}".format(prefix[self.comp_type[i]], self.comp_list_new[i])) def get_triconnected_components(self): r""" @@ -3715,15 +3716,15 @@ class TriconnectivitySPQR: ....: G.add_path([0, G.add_vertex(), G.add_vertex(), 1]) sage: tric = TriconnectivitySPQR(G) sage: tric.get_triconnected_components() - [('Bond', [(0, 1, 'newVEdge1'), (0, 1, 'newVEdge3'), (0, 1, 'newVEdge4')]), - ('Polygon', [(0, 1, 'newVEdge1'), (0, 4, None), (1, 5, None), (4, 5, None)]), - ('Polygon', [(0, 1, 'newVEdge4'), (0, 2, None), (1, 3, None), (2, 3, None)]), - ('Polygon', [(0, 1, 'newVEdge3'), (0, 6, None), (1, 7, None), (6, 7, None)])] + [('Polygon', [(4, 5, None), (0, 4, None), (1, 5, None), (1, 0, 'newVEdge1')]), + ('Polygon', [(6, 7, None), (0, 6, None), (1, 7, None), (1, 0, 'newVEdge3')]), + ('Bond', [(1, 0, 'newVEdge1'), (1, 0, 'newVEdge3'), (1, 0, 'newVEdge4')]), + ('Polygon', [(1, 3, None), (1, 0, 'newVEdge4'), (0, 2, None), (2, 3, None)])] """ comps = [] - prefix = {'P': "Bond", 'S': "Polygon", 'R': "Triconnected"} - for node in self.spqr_tree.vertices(): - comps.append((prefix[node[0]], node[1].edges())) + prefix = ["Bond", "Polygon", "Triconnected"] + for i in range(len(self.comp_list_new)): + comps.append((prefix[self.comp_type[i]], self.comp_list_new[i])) return comps def get_spqr_tree(self): From c4cbcfb124c50067bbbd47dfea3808cd16ad9eb8 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Tue, 14 Aug 2018 12:51:48 +0200 Subject: [PATCH 096/264] trac #25598: improvement of documentation and tests --- src/sage/graphs/connectivity.pyx | 192 +++++++++++++++---------------- 1 file changed, 94 insertions(+), 98 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index dde68d64f00..cc00e038b68 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2644,39 +2644,47 @@ class _Component: class TriconnectivitySPQR: - """ - This class implements the algorithm for finding the triconnected - components of a biconnected graph and constructing the SPQR tree. - A biconnected graph is a graph where deletion of any one vertex does - not disconnect the graph. + r""" + Decompose a graph into triconnected components and build SPQR-tree. - INPUT: + This class implements the algorithm proposed by Hopcroft and Tarjan in + [Hopcroft1973]_, and later corrected by Gutwenger and Mutzel in [Gut2001]_, + for finding the triconnected components of a biconnected graph. It then + organizes these components into a SPQR-tree (See :wikipedia:`SPQR_tree`). - - ``G`` -- The input graph. + A SPQR-tree is a tree data structure used to represent the triconnected + components of a biconnected (multi)graph and the 2-vertex cuts separating + them. A node of a SPQR-tree, and the graph associated with it, can be one of + the following four types: - - ``check`` (default: ``True``) -- Boolean to indicate whether ``G`` - needs to be tested for biconnectivity. + - ``"S"`` -- the associated graph is a cycle with at least three vertices. + ``"S"`` stands for ``series`` and is also called a ``polygon``. - OUTPUT: + - ``"P"`` -- the associated graph is a dipole graph, a multigraph with two + vertices and three or more edges. ``"P"`` stands for ``parallel`` and the + node is called a ``bond``. - No output, the triconnected components are printed. - The triconnected components are stored in `comp\_list\_new` and `comp\_type`. - `comp\_list\_new` is a list of components, with `comp\_list\_new[i]` contains - the list of edges in the $i^{th}$ component. `comp\_type[i]` stores the type - of the $i^{th}$ component - 1 for bond, 2 for polygon, 3 for triconnected - component. The output can be accessed through these variables. - - ALGORITHM:: - We implement the algorithm proposed by Hopcroft and Tarjan in - [Hopcroft1973]_ and later corrected by Gutwenger and Mutzel in - [Gut2001]_. - In the case of a digraph, the computation is done on the underlying - graph. + - ``"Q"`` -- the associated graph has a single real edge. This trivial case + is necessary to handle the graph that has only one edge. + + - ``"R"`` -- the associated graph is a 3-vertex-connected graph that is not + a cycle or dipole. ``"R"`` stands for ``rigid``. + + The edges of the tree indicate the 2-vertex cuts of the graph. + + INPUT: + + - ``G`` -- The input graph. If ``G`` is a DiGraph, the computation is done + on the underlying Graph (i.e., ignoring edge orientation). + + - ``check`` (default: ``True``) -- Boolean to indicate whether ``G`` needs + to be tested for biconnectivity. .. SEEALSO:: - :meth:`sage.graphs.connectivity.spqr_tree` - :meth:`~Graph.is_biconnected` + - :wikipedia:`SPQR_tree` EXAMPLES: @@ -2715,63 +2723,50 @@ class TriconnectivitySPQR: An example from [Gut2001]_:: - sage: G = Graph() - sage: G.add_edges([(1,2),(1,4),(2,3),(2,5),(3,4),(3,5),(4,5),(4,6),(5,7),(5,8), - ....: (5,14),(6,8),(7,14),(8,9),(8,10),(8,11),(8,12),(9,10),(10,13), - ....: (10,14),(10,15),(10,16),(11,12),(11,13),(12,13),(14,15),(14,16),(15,16)]) - sage: tric = TriconnectivitySPQR(G) - sage: tric.print_triconnected_components() - Polygon: [(6, 8, None), (4, 6, None), (5, 8, 'newVEdge12'), (5, 4, 'newVEdge13')] - Polygon: [(8, 9, None), (9, 10, None), (8, 10, 'newVEdge1')] - Bond: [(8, 10, 'newVEdge1'), (8, 10, None), (8, 10, 'newVEdge4'), (10, 8, 'newVEdge5')] - Triconnected: [(8, 11, None), (11, 12, None), (8, 12, None), (12, 13, None), (11, 13, None), (8, 13, 'newVEdge3')] - Polygon: [(8, 13, 'newVEdge3'), (10, 13, None), (8, 10, 'newVEdge4')] - Triconnected: [(10, 15, None), (14, 15, None), (15, 16, None), (10, 16, None), (14, 16, None), (10, 14, 'newVEdge6')] - Bond: [(10, 14, 'newVEdge6'), (14, 10, 'newVEdge7'), (10, 14, None)] - Polygon: [(14, 10, 'newVEdge7'), (10, 8, 'newVEdge5'), (5, 14, 'newVEdge10'), (5, 8, 'newVEdge11')] - Polygon: [(5, 7, None), (7, 14, None), (5, 14, 'newVEdge9')] - Bond: [(5, 14, None), (5, 14, 'newVEdge9'), (5, 14, 'newVEdge10')] - Bond: [(5, 8, None), (5, 8, 'newVEdge11'), (5, 8, 'newVEdge12')] - Bond: [(5, 4, 'newVEdge13'), (4, 5, 'newVEdge14'), (4, 5, None)] - Triconnected: [(2, 3, None), (3, 4, None), (4, 5, 'newVEdge14'), (3, 5, None), (2, 5, None), (2, 4, 'newVEdge15')] - Polygon: [(1, 2, None), (2, 4, 'newVEdge15'), (1, 4, None)] + sage: G = Graph([(1, 2), (1, 4), (2, 3), (2, 5), (3, 4), (3, 5), (4, 5), + ....: (4, 6), (5, 7), (5, 8), (5, 14), (6, 8), (7, 14), (8, 9), (8, 10), + ....: (8, 11), (8, 12), (9, 10), (10, 13), (10, 14), (10, 15), (10, 16), + ....: (11, 12), (11, 13), (12, 13), (14, 15), (14, 16), (15, 16)]) + sage: T = TriconnectivitySPQR(G).get_spqr_tree() + sage: G.is_isomorphic(spqr_tree_to_graph(T)) + True An example with multi-edges and accessing the triconnected components:: - sage: G = Graph() - sage: G.allow_multiple_edges(True) - sage: G.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(1,5),(2,3)]) + sage: G = Graph([(1, 2), (1, 5), (1, 5), (2, 3), (2, 3), (3, 4), (4, 5)], multiedges=True) sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] Bond: [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')] Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, 'newVEdge1')] - sage: tric.comp_list_new - [[(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')], - [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')], - [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, 'newVEdge1')]] - sage: tric.comp_type - [0, 0, 1] An example of a triconnected graph:: - sage: G2 = Graph() - sage: G2.allow_multiple_edges(True) - sage: G2.add_edges([('a','b'),('a','c'),('a','d'),('b','c'),('b','d'),('c','d')]) - sage: tric = TriconnectivitySPQR(G2) - sage: tric.print_triconnected_components() - Triconnected: [('a', 'b', None), ('b', 'c', None), ('a', 'c', None), ('c', 'd', None), ('b', 'd', None), ('a', 'd', None)] + sage: G = Graph([('a', 'b'), ('a', 'c'), ('a', 'd'), ('b', 'c'), ('b', 'd'), ('c', 'd')]) + sage: T = TriconnectivitySPQR(G).get_spqr_tree() + sage: print(T.vertices()) + [('R', Multi-graph on 4 vertices)] + sage: G.is_isomorphic(spqr_tree_to_graph(T)) + True An example of a directed graph with multi-edges:: - sage: G3 = DiGraph() - sage: G3.allow_multiple_edges(True) - sage: G3.add_edges([(1,2),(2,3),(3,4),(4,5),(1,5),(5,1)]) - sage: tric = TriconnectivitySPQR(G3) + sage: G = DiGraph([(1, 2), (2, 3), (3, 4), (4, 5), (1, 5), (5, 1)]) + sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, None)] + Edge labels are preserved by the construction:: + + sage: G = Graph([(0, 1, '01'), (0, 4, '04'), (1, 2, '12'), (1, 5, '15'), + ....: (2, 3, '23'), (2, 6, '26'), (3, 7, '37'), (4, 5, '45'), + ....: (5, 6, '56'), (6, 7, '67')]) + sage: T = TriconnectivitySPQR(G).get_spqr_tree() + sage: H = spqr_tree_to_graph(T) + sage: set(G.edges()) == set(H.edges()) + True + TESTS: A disconnected graph:: @@ -2902,7 +2897,7 @@ class TriconnectivitySPQR: # # Deal with multiple edges - self.__split_multi_egdes() + self.__split_multiple_edges() # Build adjacency list for e in self.graph_copy.edge_iterator(): @@ -2948,7 +2943,7 @@ class TriconnectivitySPQR: def __tstack_push(self, h, a, b): """ - Push `(h,a,b)` triple on Tstack + Push ``(h, a, b)`` triple on Tstack """ self.t_stack_top += 1 self.t_stack_h[self.t_stack_top] = h @@ -2976,7 +2971,7 @@ class TriconnectivitySPQR: def __new_component(self, edges=[], type_c=0): """ - Create a new component, add `edges` to it. + Create a new component and add `edges` to it. type_c = 0 for bond, 1 for polygon, 2 for triconnected component """ c = _Component(edges, type_c) @@ -2985,7 +2980,7 @@ class TriconnectivitySPQR: def __high(self, v): """ - Return the high(v) value, which is the first value in highpt list of `v` + Return the high(v) value, which is the first value in highpt list of v. """ head = self.highpt[v].get_head() if head is None: @@ -2994,6 +2989,9 @@ class TriconnectivitySPQR: return head.get_data() def __del_high(self, e): + """ + Delete edge e from the highpt list of the endpoint v it belongs to. + """ if e in self.in_high: it = self.in_high[e] if it: @@ -3003,15 +3001,14 @@ class TriconnectivitySPQR: v = e[1] self.highpt[v].remove(it) - def __split_multi_egdes(self): + def __split_multiple_edges(self): """ - Iterate through all the edges, and constructs bonds wherever multiedges - are present. - - If there are `k` multiple edges between `u` and `v`, then `k+1` edges - will be added to a new component (one of them is a virtual edge), all - the `k` edges are deleted from the graph and a virtual edge is between - `u` and `v` is added to the graph. + Make the graph simple and build bonds recording multiple edges. + + If there are `k` multiple edges between `u` and `v`, then a new + component (a bond) with `k+1` edges (one of them is a virtual edge) will + be created, all the `k` edges are deleted from the graph and the virtual + edge between `u` and `v` is added to the graph. No return value. Update the `components_list` and `graph_copy`. `graph_copy` will become a simple graph after this function. @@ -3057,9 +3054,11 @@ class TriconnectivitySPQR: def __dfs1(self, v, u=None, check=True): """ - This function builds the palm tree of the graph using a dfs traversal. - Also populates the lists lowpt1, lowpt2, nd, parent, and dfs_number. - It updates the dict `edge_status` to reflect palm tree arcs and fronds. + This function builds the palm-tree of the graph using a dfs traversal. + + Also populates the lists ``lowpt1``, ``lowpt2``, ``nd``, ``parent``, and + ``dfs_number``. It updates the dict ``edge_status`` to reflect palm + tree arcs and fronds. INPUT: @@ -3067,15 +3066,16 @@ class TriconnectivitySPQR: - ``u`` -- The parent vertex of ``v`` in the palm tree. - - ``check`` -- if True, the graph is tested for biconnectivity. If the - graph has a cut vertex, the cut vertex is returned. If set to False, - the graph is assumed to be biconnected, function returns None. + - ``check`` -- if ``True``, the graph is tested for biconnectivity. If + the graph has a cut vertex, the cut vertex is returned. If set to + ``False``, the graph is assumed to be biconnected, function returns + ``None``. OUTPUT: - - If ``check`` is set to True, and a cut vertex is found, the cut vertex - is returned. If no cut vertex is found, return None. - - If ``check`` is set to False, ``None`` is returned. + - If ``check`` is set to ``True``` and a cut vertex is found, the cut + vertex is returned. If no cut vertex is found, return ``None``. + - If ``check`` is set to ``False``, ``None`` is returned. """ first_son = None # For testing biconnectivity s1 = None # Storing the cut vertex, if there is one @@ -3138,8 +3138,8 @@ class TriconnectivitySPQR: The list ``adj`` and the dictionary ``in_adj`` are populated. - `phi` values of each edge are calculated using the `lowpt` values of - incident vertices. The edges are then sorted by the `phi` values and + ``phi`` values of each edge are calculated using the ``lowpt`` values of + incident vertices. The edges are then sorted by the ``phi`` values and added to adjacency list. """ max_size = 3*self.n + 2 @@ -3182,8 +3182,8 @@ class TriconnectivitySPQR: def __path_finder(self, v): """ - This function is a helper function for `dfs2` function. - Calculate `newnum[v]` and identify the edges which start a new path. + This function is a helper function for `__dfs2` function. + Calculate ``newnum[v]`` and identify the edges which start a new path. """ self.newnum[v] = self.dfs_counter - self.nd[v] + 1 e_node = self.adj[v].get_head() @@ -3207,9 +3207,9 @@ class TriconnectivitySPQR: def __dfs2(self): """ - Update the values of lowpt1 and lowpt2 lists with the help of - new numbering obtained from `Path Finder` function. - Populate `highpt` values. + Update the values of ``lowpt1`` and ``lowpt2`` lists with the help of + new numbering obtained from ``__path_finder`` function. + Populate ``highpt`` values. """ self.in_high = {e:None for e in self.graph_copy.edge_iterator()} self.dfs_counter = self.n @@ -3527,7 +3527,7 @@ class TriconnectivitySPQR: def __assemble_triconnected_components(self): """ - Iterate through all the split components built by `path_finder` and + Iterate through all the split components built by ``__path_finder`` and merges two bonds or two polygons that share an edge for contructing the final triconnected components. Subsequently, convert the edges in triconnected components into original @@ -3633,8 +3633,8 @@ class TriconnectivitySPQR: def __build_spqr_tree(self): """ - Constructs the SPQR tree of the graph and stores the tree in the variable - `self.spqr_tree`. Refer to function `~TriconnectivitySPQR.get_spqr_tree`. + Build the SPQR-tree of the graph and store it in variable + ``self.spqr_tree``. See :meth:`~TriconnectivitySPQR.get_spqr_tree`. """ from sage.graphs.graph import Graph # Types of components 0: "P", 1: "S", 2: "R" @@ -3748,15 +3748,11 @@ class TriconnectivitySPQR: - ``R`` -- the associated graph is a 3-connected graph that is not a cycle or dipole. ``R`` stands for ``rigid``. - This method constructs a static spqr-tree using the decomposition of a - biconnected graph into cycles, cocycles, and 3-connected blocks generated by - :class:`sage.graphs.connectivity.TriconnectivitySPQR`. - See :wikipedia:`SPQR_tree`. + The edges of the tree indicate the 2-vertex cuts of the graph. OUTPUT: ``SPQR-tree`` a tree whose vertices are labeled with the block's type and the subgraph of three-blocks in the decomposition. - EXAMPLES:: sage: from sage.graphs.connectivity import TriconnectivitySPQR From 9f685f3db724a69dcc0e469949a196e858cb168e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 19 Aug 2018 09:07:16 +0200 Subject: [PATCH 097/264] py3: adding hash for projective spaces --- .../schemes/projective/projective_space.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/sage/schemes/projective/projective_space.py b/src/sage/schemes/projective/projective_space.py index 8459b31dc37..4c1c8e41d1b 100644 --- a/src/sage/schemes/projective/projective_space.py +++ b/src/sage/schemes/projective/projective_space.py @@ -444,6 +444,24 @@ def __ne__(self, other): """ return not (self == other) + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: hash(ProjectiveSpace(QQ, 3, 'a')) == hash(ProjectiveSpace(ZZ, 3, 'a')) + False + sage: hash(ProjectiveSpace(ZZ, 1, 'a')) == hash(ProjectiveSpace(ZZ, 0, 'a')) + False + sage: hash(ProjectiveSpace(ZZ, 2, 'a')) == hash(AffineSpace(ZZ, 2, 'a')) + False + sage: P = ProjectiveSpace(ZZ, 1, 'x') + sage: hash(loads(P.dumps())) == hash(P) + True + """ + return hash((self.dimension_relative(), self.coordinate_ring())) + def __pow__(self, m): """ Return the Cartesian power of this space. From 2caa7ae8d0ae8996eceb7cea1d6b539345c4f1e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 19 Aug 2018 11:42:15 +0200 Subject: [PATCH 098/264] fixing one 64bits hash --- src/sage/schemes/projective/projective_point.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index c51739c9236..4bc8872c6ea 100644 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -401,7 +401,7 @@ def __hash__(self): sage: P. = ProjectiveSpace(Zmod(10), 1) sage: hash(P([2, 5])) -479010389 # 32-bit - 4677413289753502123 # 64-bit + 3339499987291935084 # 64-bit """ R = self.codomain().base_ring() #if there is a fraction field normalize the point so that From eafa8b6e66fad59630d023ebdf5dc61af5f73dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 19 Aug 2018 17:23:56 +0200 Subject: [PATCH 099/264] fix hash for 32bit --- src/sage/schemes/projective/projective_point.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index 4bc8872c6ea..e2fb888accc 100644 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -364,7 +364,7 @@ def _richcmp_(self, right, op): def __hash__(self): """ - Computes the hash value of this point. + Compute the hash value of this point. If the base ring has a fraction field, normalize the point in the fraction field and then hash so that equal points have @@ -400,7 +400,7 @@ def __hash__(self): sage: P. = ProjectiveSpace(Zmod(10), 1) sage: hash(P([2, 5])) - -479010389 # 32-bit + -2047536788 # 32-bit 3339499987291935084 # 64-bit """ R = self.codomain().base_ring() @@ -414,7 +414,7 @@ def __hash__(self): #a constant value return hash(self.codomain()) - def scale_by(self,t): + def scale_by(self, t): """ Scale the coordinates of the point by ``t``. From 3281d9eb951a50e3fac7fa57e570c83737630e06 Mon Sep 17 00:00:00 2001 From: SaiHarsh Date: Sun, 19 Aug 2018 22:15:05 +0530 Subject: [PATCH 100/264] Added bucket sort and example output updated --- src/sage/graphs/connectivity.pyx | 120 +++++++++++++++++++++++++------ 1 file changed, 98 insertions(+), 22 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index cc00e038b68..5c4f58e465a 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2736,9 +2736,9 @@ class TriconnectivitySPQR: sage: G = Graph([(1, 2), (1, 5), (1, 5), (2, 3), (2, 3), (3, 4), (4, 5)], multiedges=True) sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() - Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] - Bond: [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge1')] - Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, 'newVEdge1')] + Bond: [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge0')] + Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge1')] + Polygon: [(4, 5, None), (1, 5, 'newVEdge1'), (3, 4, None), (1, 2, None), (2, 3, 'newVEdge0')] An example of a triconnected graph:: @@ -3001,35 +3001,110 @@ class TriconnectivitySPQR: v = e[1] self.highpt[v].remove(it) + def bucketSort(self, bucket, edge_list): + """ + Use radix sort to sort the buckets + """ + # if only one edge is present + if len(bucket) == 1: + return + + # Create n bucket linked lists + bucket_list = [] + for i in range(self.n): + bucket_list.append(_LinkedList()) + + # Get the head pointer of the edge list + e_node = edge_list.head + + # Link the n buckets w.r.t bucketId + while e_node: + bucketId = bucket[e_node.get_data()] + if bucket_list[bucketId].get_head(): + bucket_list[bucketId].tail.next = e_node + bucket_list[bucketId].tail = bucket_list[bucketId].tail.next + else: + bucket_list[bucketId].set_head(e_node) + e_node = e_node.next + + # Using bucket list rearrange the edge_list + new_tail = None + for i in range(self.n): + new_head = bucket_list[i].get_head() + if(new_head): + if new_tail: + new_tail.next = new_head + else: + edge_list.set_head(new_head) + new_tail = bucket_list[i].tail + + edge_list.tail = new_tail + new_tail.next = None + + def sort_edges(self): + """ + A helper function for Split_multiple_edges to sort the edges of + graph_copy. + + It won't take any input instead creates a linked list of edges, which + are in graph_copy and returns head pointer of the sorted edge list. + + This function is an implementation of the sorting algorithm given in + [Hopcroft1973]_ + """ + # Create a linkedlist of edges + edge_list = _LinkedList() + for e in self.graph_copy.edges(sort=False): + edge_list.append(_LinkedListNode(e)) + + bucketMin = {} # Contains a lower index of edge end point + bucketMax = {} # Contains a higher index of edge end point + + # As all the vertices and thier indexes are same + # and in all edge (u, v), u < v. + # for all edges bucket min will be first u and + # bucket max will be v + for e in self.graph_copy.edge_iterator(): + bucketMin[e] = e[0] + bucketMax[e] = e[1] + + # Sort according to the endpoint with lower index + self.bucketSort(bucketMin, edge_list) + # Sort according to the endpoint with higher index + self.bucketSort(bucketMax, edge_list) + + # Return sorted list head pointer + return edge_list.get_head() + def __split_multiple_edges(self): """ - Make the graph simple and build bonds recording multiple edges. - - If there are `k` multiple edges between `u` and `v`, then a new - component (a bond) with `k+1` edges (one of them is a virtual edge) will - be created, all the `k` edges are deleted from the graph and the virtual - edge between `u` and `v` is added to the graph. + Iterate through all the edges, and constructs bonds wherever multiedges + are present. + + If there are `k` multiple edges between `u` and `v`, then `k+1` edges + will be added to a new component (one of them is a virtual edge), all + the `k` edges are deleted from the graph and a virtual edge is between + `u` and `v` is added to the graph. No return value. Update the `components_list` and `graph_copy`. `graph_copy` will become a simple graph after this function. """ comp = [] if self.graph_copy.has_multiple_edges(): - sorted_edges = sorted(self.graph_copy.edge_iterator()) - for i in range(len(sorted_edges) - 1): - + sorted_edges = self.sort_edges() + while sorted_edges.next: # Find multi edges and add to component and delete from graph - if (sorted_edges[i][0] == sorted_edges[i + 1][0]) and \ - (sorted_edges[i][1] == sorted_edges[i + 1][1]): - self.graph_copy.delete_edge(sorted_edges[i]) - comp.append(sorted_edges[i]) + if (sorted_edges.get_data()[0] == sorted_edges.next.get_data()[0]) and \ + (sorted_edges.get_data()[1] == sorted_edges.next.get_data()[1]): + self.graph_copy.delete_edge(sorted_edges.get_data()) + comp.append(sorted_edges.get_data()) else: if comp: - comp.append(sorted_edges[i]) - self.graph_copy.delete_edge(sorted_edges[i]) + comp.append(sorted_edges.get_data()) + self.graph_copy.delete_edge(sorted_edges.get_data()) # Add virtual edge to graph_copy - newVEdge = (sorted_edges[i][0], sorted_edges[i][1], "newVEdge"+str(self.virtual_edge_num)) + newVEdge = (sorted_edges.get_data()[0], sorted_edges.get_data()[1], "newVEdge"+str(self.virtual_edge_num)) self.graph_copy.add_edge(newVEdge) self.virtual_edge_num += 1 @@ -3039,12 +3114,13 @@ class TriconnectivitySPQR: comp.append(newVEdge) self.__new_component(comp) comp = [] + sorted_edges = sorted_edges.next if comp: - comp.append(sorted_edges[i+1]) - self.graph_copy.delete_edge(sorted_edges[i+1]) + comp.append(sorted_edges.get_data()) + self.graph_copy.delete_edge(sorted_edges.get_data()) # Add virtual edge to graph_copy - newVEdge = (sorted_edges[i+1][0], sorted_edges[i+1][1], "newVEdge"+str(self.virtual_edge_num)) + newVEdge = (sorted_edges.get_data()[0], sorted_edges.get_data()[1], "newVEdge"+str(self.virtual_edge_num)) self.graph_copy.add_edge(newVEdge) self.virtual_edge_num += 1 self.edge_status[newVEdge] = 0 From f2e9fc206814859df6a0820022b78f66b1f9fc9b Mon Sep 17 00:00:00 2001 From: SaiHarsh Date: Sun, 19 Aug 2018 23:26:08 +0530 Subject: [PATCH 101/264] Accidentally removed the split_multiple_edges comment --- src/sage/graphs/connectivity.pyx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 5c4f58e465a..e2e50c509f4 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -3078,16 +3078,12 @@ class TriconnectivitySPQR: def __split_multiple_edges(self): """ - Iterate through all the edges, and constructs bonds wherever multiedges - are present. + Make the graph simple and build bonds recording multiple edges. - If there are `k` multiple edges between `u` and `v`, then `k+1` edges - will be added to a new component (one of them is a virtual edge), all - the `k` edges are deleted from the graph and a virtual edge is between - `u` and `v` is added to the graph. - - No return value. Update the `components_list` and `graph_copy`. - `graph_copy` will become a simple graph after this function. + If there are `k` multiple edges between `u` and `v`, then a new + component (a bond) with `k+1` edges (one of them is a virtual edge) will + be created, all the `k` edges are deleted from the graph and the virtual + edge between `u` and `v` is added to the graph. """ comp = [] if self.graph_copy.has_multiple_edges(): From 804499fecae3d674f9f1be443eead4890a25186a Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Tue, 21 Aug 2018 22:44:58 +0530 Subject: [PATCH 102/264] Minor changes in comments. --- src/sage/graphs/connectivity.pyx | 35 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index e2e50c509f4..343413a4c67 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -3001,7 +3001,7 @@ class TriconnectivitySPQR: v = e[1] self.highpt[v].remove(it) - def bucketSort(self, bucket, edge_list): + def __bucket_sort(self, bucket, edge_list): """ Use radix sort to sort the buckets """ @@ -3027,11 +3027,11 @@ class TriconnectivitySPQR: bucket_list[bucketId].set_head(e_node) e_node = e_node.next - # Using bucket list rearrange the edge_list + # Rearrange the `edge_list` Using bucket list new_tail = None for i in range(self.n): new_head = bucket_list[i].get_head() - if(new_head): + if new_head: if new_tail: new_tail.next = new_head else: @@ -3041,39 +3041,38 @@ class TriconnectivitySPQR: edge_list.tail = new_tail new_tail.next = None - def sort_edges(self): + def __sort_edges(self): """ - A helper function for Split_multiple_edges to sort the edges of + A helper function for `split_multiple_edges` to sort the edges of graph_copy. - It won't take any input instead creates a linked list of edges, which - are in graph_copy and returns head pointer of the sorted edge list. + Sorts the edges of `graph_copy` and stores the sorted edges in a linked + list. The head pointer of the linked list is returned. This function is an implementation of the sorting algorithm given in - [Hopcroft1973]_ + [Hopcroft1973]_. """ # Create a linkedlist of edges edge_list = _LinkedList() for e in self.graph_copy.edges(sort=False): edge_list.append(_LinkedListNode(e)) - bucketMin = {} # Contains a lower index of edge end point - bucketMax = {} # Contains a higher index of edge end point + bucketMin = {} # Contains the lower index of edge end point + bucketMax = {} # Contains the higher index of edge end point - # As all the vertices and thier indexes are same - # and in all edge (u, v), u < v. - # for all edges bucket min will be first u and - # bucket max will be v + # In `graph_copy`, every edge `(u, v)` is such that `u < v`. + # Hence, `bucketMin` of an edge `(u, v)` will be `u` + # and `bucketMax` will be `v`. for e in self.graph_copy.edge_iterator(): bucketMin[e] = e[0] bucketMax[e] = e[1] # Sort according to the endpoint with lower index - self.bucketSort(bucketMin, edge_list) + self.__bucket_sort(bucketMin, edge_list) # Sort according to the endpoint with higher index - self.bucketSort(bucketMax, edge_list) + self.__bucket_sort(bucketMax, edge_list) - # Return sorted list head pointer + # Return the head pointer to the sorted edge list return edge_list.get_head() def __split_multiple_edges(self): @@ -3087,7 +3086,7 @@ class TriconnectivitySPQR: """ comp = [] if self.graph_copy.has_multiple_edges(): - sorted_edges = self.sort_edges() + sorted_edges = self.__sort_edges() while sorted_edges.next: # Find multi edges and add to component and delete from graph if (sorted_edges.get_data()[0] == sorted_edges.next.get_data()[0]) and \ From a3bddad41a19d480475cdc761403d4b0b9d796b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 22 Aug 2018 08:05:17 +0200 Subject: [PATCH 103/264] detail in #26091 (hash doctest) --- src/sage/schemes/projective/projective_point.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index e2fb888accc..a558d4090fd 100644 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -399,9 +399,10 @@ def __hash__(self): :: sage: P. = ProjectiveSpace(Zmod(10), 1) - sage: hash(P([2, 5])) - -2047536788 # 32-bit - 3339499987291935084 # 64-bit + sage: hash(P([2, 5])) == hash(P([2,5])) + True + sage: hash(P([3, 7])) == hash(P([2,5])) + True """ R = self.codomain().base_ring() #if there is a fraction field normalize the point so that From e224c68299700b348574809bb0254f670473f0d3 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Wed, 22 Aug 2018 15:01:35 +0200 Subject: [PATCH 104/264] trac #25598: reviewer improvement of documentation --- src/sage/graphs/connectivity.pyx | 37 +++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 343413a4c67..24c0f9a568a 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2453,18 +2453,16 @@ def spqr_tree_to_graph(T): class _LinkedListNode: """ - Node in a ``LinkedList``. + Node in a ``_LinkedList``. This class is a helper class for ``TriconnectivitySPQR``. This class implements a node of a (doubly) linked list and so has pointers - to previous and next nodes in the list. If this node is the ``head`` of the - linked list, reference to the linked list object is stored in ``listobj``. + to previous and next nodes in the list. """ - def __init__(self, data=None): """ - Initialize this ``LinkedListNode``. + Initialize this ``_LinkedListNode``. INPUT: @@ -2486,11 +2484,11 @@ class _LinkedList: This is a helper class for ``TriconnectivitySPQR``. - This class implements a doubly linked list of ``LinkedListNode``. + This class implements a doubly linked list of ``_LinkedListNode``. """ def __init__(self): """ - Initialize this ``LinkedList``. + Initialize this ``_LinkedList``. """ self.head = None self.tail = None @@ -2565,7 +2563,7 @@ class _LinkedList: def concatenate(self, lst2): """ - Concatenates lst2 to self. + Concatenate lst2 to self. Makes lst2 empty. """ @@ -2583,7 +2581,9 @@ class _Component: This is a helper class for ``TriconnectivitySPQR``. This class is used to store a connected component. It contains: - - ``edge_list`` -- list of edges belonging to the component, stored as a ``LinkedList``. + - ``edge_list`` -- list of edges belonging to the component, stored as a + ``_LinkedList``. + - ``component_type`` -- the type of the component. - 0 if bond. - 1 if polygon. @@ -2608,7 +2608,9 @@ class _Component: self.edge_list.append(_LinkedListNode(e)) def finish_tric_or_poly(self, e): - """ + r""" + Finalize the component by adding edge `e`. + Edge `e` is the last edge to be added to the component. Classify the component as a polygon or triconnected component depending on the number of edges belonging to it. @@ -2789,6 +2791,15 @@ class TriconnectivitySPQR: """ def __init__(self, G, check=True): """ + Initialize this object, decompose the graph and build SPQR-tree. + + INPUT: + + - ``G`` -- The input graph. If ``G`` is a DiGraph, the computation is + done on the underlying Graph (i.e., ignoring edge orientation). + + - ``check`` (default: ``True``) -- Boolean to indicate whether ``G`` + needs to be tested for biconnectivity. """ self.n = G.order() self.m = G.size() @@ -3099,7 +3110,8 @@ class TriconnectivitySPQR: self.graph_copy.delete_edge(sorted_edges.get_data()) # Add virtual edge to graph_copy - newVEdge = (sorted_edges.get_data()[0], sorted_edges.get_data()[1], "newVEdge"+str(self.virtual_edge_num)) + newVEdge = (sorted_edges.get_data()[0], sorted_edges.get_data()[1], + "newVEdge"+str(self.virtual_edge_num)) self.graph_copy.add_edge(newVEdge) self.virtual_edge_num += 1 @@ -3115,7 +3127,8 @@ class TriconnectivitySPQR: self.graph_copy.delete_edge(sorted_edges.get_data()) # Add virtual edge to graph_copy - newVEdge = (sorted_edges.get_data()[0], sorted_edges.get_data()[1], "newVEdge"+str(self.virtual_edge_num)) + newVEdge = (sorted_edges.get_data()[0], sorted_edges.get_data()[1], + "newVEdge"+str(self.virtual_edge_num)) self.graph_copy.add_edge(newVEdge) self.virtual_edge_num += 1 self.edge_status[newVEdge] = 0 From 2b86b7a342e1cbf058c4bcac771fefb1effb5088 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Wed, 22 Aug 2018 15:25:56 +0200 Subject: [PATCH 105/264] trac #25598: addition of a TODO block --- src/sage/graphs/connectivity.pyx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 24c0f9a568a..7a21e9ac5f9 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2788,6 +2788,17 @@ class TriconnectivitySPQR: Traceback (most recent call last): ... ValueError: Graph has a cut vertex + + .. TODO:: + + - Remove recursion in methods ``__dfs1``, ``__dfs2``, ``__path_finder`` + and ``__path_search``. This currently restricts the size of graphs as + maximum recusion depth might be exceeded. + + - Cythonize the code for more efficiency. Many data structures can be + turned into integer arrays. More care is needed for the doubly linked + list and for the lists of lists. Note that the internal graph copy + must allow edge addition due to the insertion of virtual edges. """ def __init__(self, G, check=True): """ From f1ba21810dea251d1454e55ba9809404fcbfef5e Mon Sep 17 00:00:00 2001 From: David Coudert Date: Wed, 22 Aug 2018 16:04:53 +0200 Subject: [PATCH 106/264] trac #25598: improve html output --- src/sage/graphs/connectivity.pyx | 64 ++++++++++++++++---------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 7a21e9ac5f9..e26d37f0abf 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2086,17 +2086,17 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): them. A node of a SPQR-tree, and the graph associated with it, can be one of the following four types: - - ``S`` -- the associated graph is a cycle with at least three vertices. - ``S`` stands for ``series``. + - ``"S"`` -- the associated graph is a cycle with at least three vertices. + ``"S"`` stands for ``series``. - - ``P`` -- the associated graph is a dipole graph, a multigraph with two - vertices and three or more edges. ``P`` stands for ``parallel``. + - ``"P"`` -- the associated graph is a dipole graph, a multigraph with two + vertices and three or more edges. ``"P"`` stands for ``parallel``. - - ``Q`` -- the associated graph has a single real edge. This trivial case is - necessary to handle the graph that has only one edge. + - ``"Q"`` -- the associated graph has a single real edge. This trivial case + is necessary to handle the graph that has only one edge. - - ``R`` -- the associated graph is a 3-connected graph that is not a cycle - or dipole. ``R`` stands for ``rigid``. + - ``"R"`` -- the associated graph is a 3-connected graph that is not a cycle + or dipole. ``"R"`` stands for ``rigid``. This method decomposes a biconnected graph into cycles, cocycles, and 3-connected blocks summed over cocycles, and arranges them as a SPQR-tree. @@ -2115,9 +2115,10 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): - ``"Hopcroft_Tarjan"`` (default) -- Use the algorithm proposed by Hopcroft and Tarjan in [Hopcroft1973]_ and later corrected by Gutwenger - and Mutzel in [Gut2001]_. + and Mutzel in [Gut2001]_. See + :class:`~sage.graphs.connectivity.TriconnectivitySPQR`. - - ``"cleave"`` -- Using method :meth:`sage.graphs.connectivity.cleave`. + - ``"cleave"`` -- Using method :meth:`~sage.graphs.connectivity.cleave`. - ``solver`` -- (default: ``None``) Specify a Linear Program (LP) solver to be used. If set to ``None``, the default one is used. For more information @@ -2676,11 +2677,11 @@ class TriconnectivitySPQR: INPUT: - - ``G`` -- The input graph. If ``G`` is a DiGraph, the computation is done + - ``G`` -- the input graph. If ``G`` is a DiGraph, the computation is done on the underlying Graph (i.e., ignoring edge orientation). - - ``check`` (default: ``True``) -- Boolean to indicate whether ``G`` needs - to be tested for biconnectivity. + - ``check`` -- boolean (default: ``True``); indicates whether ``G`` needs to + be tested for biconnectivity. .. SEEALSO:: @@ -2806,10 +2807,10 @@ class TriconnectivitySPQR: INPUT: - - ``G`` -- The input graph. If ``G`` is a DiGraph, the computation is + - ``G`` -- the input graph. If ``G`` is a DiGraph, the computation is done on the underlying Graph (i.e., ignoring edge orientation). - - ``check`` (default: ``True``) -- Boolean to indicate whether ``G`` + - ``check`` -- boolean (default: ``True``); indicates whether ``G`` needs to be tested for biconnectivity. """ self.n = G.order() @@ -3766,8 +3767,6 @@ class TriconnectivitySPQR: """ Print the type and list of edges of each component. - The types are ``{0: "Bond", 1: "Polygon", 2: "Triconnected"}``. - EXAMPLES: An example from [Hopcroft1973]_:: @@ -3792,6 +3791,7 @@ class TriconnectivitySPQR: Polygon: [(3, 4, None), (4, 1, 'newVEdge10'), (3, 1, 'newVEdge11')] Triconnected: [(1, 2, None), (2, 3, None), (3, 1, 'newVEdge11'), (3, 13, None), (2, 13, None), (1, 13, None)] """ + # The types are {0: "Bond", 1: "Polygon", 2: "Triconnected"} prefix = ["Bond", "Polygon", "Triconnected"] for i in range(len(self.comp_list_new)): print("{}: {}".format(prefix[self.comp_type[i]], self.comp_list_new[i])) @@ -3817,6 +3817,7 @@ class TriconnectivitySPQR: ('Polygon', [(1, 3, None), (1, 0, 'newVEdge4'), (0, 2, None), (2, 3, None)])] """ comps = [] + # The types are {0: "Bond", 1: "Polygon", 2: "Triconnected"} prefix = ["Bond", "Polygon", "Triconnected"] for i in range(len(self.comp_list_new)): comps.append((prefix[self.comp_type[i]], self.comp_list_new[i])) @@ -3824,29 +3825,30 @@ class TriconnectivitySPQR: def get_spqr_tree(self): r""" - Return an SPQR-tree representing the triconnected components of the graph. + Return an SPQR-tree representing the triconnected components of the + graph. An SPQR-tree is a tree data structure used to represent the triconnected - components of a biconnected (multi)graph and the 2-vertex cuts separating - them. A node of a SPQR-tree, and the graph associated with it, can be one of - the following four types: + components of a biconnected (multi)graph and the 2-vertex cuts + separating them. A node of a SPQR-tree, and the graph associated with + it, can be one of the following four types: - - ``S`` -- the associated graph is a cycle with at least three vertices. - ``S`` stands for ``series``. + - ``"S"`` -- the associated graph is a cycle with at least three vertices. + ``"S"`` stands for ``series``. - - ``P`` -- the associated graph is a dipole graph, a multigraph with two - vertices and three or more edges. ``P`` stands for ``parallel``. + - ``"P"`` -- the associated graph is a dipole graph, a multigraph with + two vertices and three or more edges. ``"P"`` stands for ``parallel``. - - ``Q`` -- the associated graph has a single real edge. This trivial case is - necessary to handle the graph that has only one edge. + - ``"Q"`` -- the associated graph has a single real edge. This trivial + case is necessary to handle the graph that has only one edge. - - ``R`` -- the associated graph is a 3-connected graph that is not a cycle - or dipole. ``R`` stands for ``rigid``. + - ``"R"`` -- the associated graph is a 3-connected graph that is not a + cycle or dipole. ``"R"`` stands for ``rigid``. The edges of the tree indicate the 2-vertex cuts of the graph. - OUTPUT: ``SPQR-tree`` a tree whose vertices are labeled with the block's type - and the subgraph of three-blocks in the decomposition. + OUTPUT: ``SPQR-tree`` a tree whose vertices are labeled with the block's + type and the subgraph of three-blocks in the decomposition. EXAMPLES:: From 6eee0fe425259ac061b392492597e363eefb5ae2 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Tue, 28 Aug 2018 18:22:21 +0200 Subject: [PATCH 107/264] trac #25598: expose spqr_tree in Graph --- src/sage/graphs/connectivity.pyx | 2 -- src/sage/graphs/graph.py | 7 +++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index e26d37f0abf..92b5a2e3375 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -27,8 +27,6 @@ Here is what the module can do: :meth:`is_cut_vertex` | Return True if the input vertex is a cut-vertex. :meth:`edge_connectivity` | Return the edge connectivity of the graph. :meth:`vertex_connectivity` | Return the vertex connectivity of the graph. - :meth:`~TriconnectivitySPQR.get_triconnected_components` | Return the triconnected components of a biconnected graph. - :meth:`~TriconnectivitySPQR.get_spqr_tree` | Returns the SPQR tree of a biconnected graph constructed from its triconnected components. **For DiGraph:** diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 942b4db1ccf..062d99f37e2 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -8318,7 +8318,7 @@ def has_perfect_matching(self, algorithm="Edmonds", solver=None, verbose=0): from sage.graphs.lovasz_theta import lovasz_theta from sage.graphs.partial_cube import is_partial_cube from sage.graphs.orientations import strong_orientations_iterator, random_orientation - from sage.graphs.connectivity import bridges + from sage.graphs.connectivity import bridges, cleave, spqr_tree _additional_categories = { @@ -8341,7 +8341,10 @@ def has_perfect_matching(self, algorithm="Edmonds", solver=None, verbose=0): "tutte_polynomial" : "Algorithmically hard stuff", "lovasz_theta" : "Leftovers", "strong_orientations_iterator" : "Connectivity, orientations, trees", - "random_orientation" : "Connectivity, orientations, trees" + "random_orientation" : "Connectivity, orientations, trees", + "bridges" : "Connectivity, orientations, trees", + "cleave" : "Connectivity, orientations, trees", + "spqr_tree" : "Connectivity, orientations, trees" } __doc__ = __doc__.replace("{INDEX_OF_METHODS}",gen_thematic_rest_table_index(Graph,_additional_categories)) From b084469ecb30d258925ae31581e98ac67240c2ee Mon Sep 17 00:00:00 2001 From: David Coudert Date: Tue, 28 Aug 2018 18:57:26 +0200 Subject: [PATCH 108/264] trac #25598: fix small issue in documentation of graph.py --- src/sage/graphs/graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 062d99f37e2..98406e4db82 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -8330,7 +8330,7 @@ def has_perfect_matching(self, algorithm="Edmonds", solver=None, verbose=0): "rank_decomposition" : "Algorithmically hard stuff", "pathwidth" : "Algorithmically hard stuff", "matching_polynomial" : "Algorithmically hard stuff", - "all_max_cliques" : "Clique-related methods", + "all_max_clique" : "Clique-related methods", "cliques_maximum" : "Clique-related methods", "random_spanning_tree" : "Connectivity, orientations, trees", "is_cartesian_product" : "Graph properties", From a5a660048419c04e01a4218f680628cadd74dd09 Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Wed, 29 Aug 2018 07:32:03 +0300 Subject: [PATCH 109/264] Initial implementation of finite dim subalgebras and ideals --- .../en/reference/algebras/lie_algebras.rst | 2 + src/sage/algebras/lie_algebras/ideal.py | 280 ++++++++ src/sage/algebras/lie_algebras/subalgebra.py | 627 ++++++++++++++++++ ...ite_dimensional_lie_algebras_with_basis.py | 44 ++ 4 files changed, 953 insertions(+) create mode 100644 src/sage/algebras/lie_algebras/ideal.py create mode 100644 src/sage/algebras/lie_algebras/subalgebra.py diff --git a/src/doc/en/reference/algebras/lie_algebras.rst b/src/doc/en/reference/algebras/lie_algebras.rst index e4dc016916e..08690abbdf4 100644 --- a/src/doc/en/reference/algebras/lie_algebras.rst +++ b/src/doc/en/reference/algebras/lie_algebras.rst @@ -10,6 +10,7 @@ Lie Algebras sage/algebras/lie_algebras/examples sage/algebras/lie_algebras/free_lie_algebra sage/algebras/lie_algebras/heisenberg + sage/algebras/lie_algebras/ideal sage/algebras/lie_algebras/lie_algebra sage/algebras/lie_algebras/lie_algebra_element sage/algebras/lie_algebras/morphism @@ -17,6 +18,7 @@ Lie Algebras sage/algebras/lie_algebras/onsager sage/algebras/lie_algebras/poincare_birkhoff_witt sage/algebras/lie_algebras/structure_coefficients + sage/algebras/lie_algebras/subalgebra sage/algebras/lie_algebras/verma_module sage/algebras/lie_algebras/virasoro diff --git a/src/sage/algebras/lie_algebras/ideal.py b/src/sage/algebras/lie_algebras/ideal.py new file mode 100644 index 00000000000..6c0f2d64ffc --- /dev/null +++ b/src/sage/algebras/lie_algebras/ideal.py @@ -0,0 +1,280 @@ +r""" +Ideals of Lie algebras + +AUTHORS: + +- Eero Hakavuori (2018-08-29): initial version +""" + +# **************************************************************************** +# Copyright (C) 2018 Eero Hakavuori +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.algebras.lie_algebras.subalgebra import LieSubalgebra_finite_dimensional_with_basis +from sage.categories.lie_algebras import LieAlgebras +from sage.misc.cachefunc import cached_method +from sage.modules.free_module_element import vector +from sage.sets.family import Family + + +class LieIdeal_finite_dimensional_with_basis(LieSubalgebra_finite_dimensional_with_basis): + r""" + An ideal of a finite dimensional Lie algebra with basis. + + INPUT: + + - ``ambient`` -- the Lie algebra containing the ideal + - ``gens`` -- a list of generators of the ideal + - ``category`` -- (optional) a subcategory of subobjects of finite + dimensional Lie algebras with basis + + EXAMPLES: + + An ideal is defined by giving a list of generators:: + + sage: L = lie_algebras.Heisenberg(QQ, 1) + sage: X, Y, Z = L.basis() + sage: I = L.ideal([X, Z]); I + Ideal (p1, z) of Heisenberg algebra of rank 1 over Rational Field + sage: I.basis() + Family (p1, z) + + Different generating sets can lead to the same basis:: + + sage: I2 = L.ideal(X); I2 + Ideal (p1) of Heisenberg algebra of rank 1 over Rational Field + sage: I2.basis() + Family (p1, z) + + Elements of the ambient Lie algebra can be reduced modulo the ideal:: + + sage: I.reduce(X + 2*Y + 3*Z) + 2*q1 + + The reduction gives elements in a fixed complementary subspace to the ideal. + The complementary subspace is spanned by those basis elements which are not + leading supports of the basis of the ideal:: + + sage: L. = LieAlgebra(SR, {('X','Y'): {'Z': 1}}) + sage: I = L.ideal(X + Y) + sage: I.basis() + Family (X + Y, Z) + sage: el = var('x')*X + var('y')*Y + var('z')*Z; el + x*X + y*Y + z*Z + sage: I.reduce(el) + (x-y)*X + + It is possible to compute with ideals of ideals:: + + sage: sc = {('X','Y'): {'Z': 1}, ('X','Z'): {'W': 1}} + sage: L. = LieAlgebra(QQ, sc) + sage: I = L.ideal(Z); I + Ideal (Z) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field + sage: z, w = I.basis() + sage: J = I.ideal(w); J + Ideal (W) of Ideal (Z) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field + sage: J.basis() + Family (W,) + sage: J.reduce(z + w) + Z + + The zero ideal can be created by giving 0 as a generator or with an empty + list of generators:: + + sage: L. = LieAlgebra(QQ, {('X','Y'): {'Z': 1}}) + sage: I1 = L.ideal(0) + sage: I2 = L.ideal([]) + sage: I1 is I2 + True + sage: I1.basis() + Family () + + TESTS: + + A test suite:: + + sage: I = L.ideal(X + Y) + sage: TestSuite(I).run() + """ + + @staticmethod + def __classcall_private__(cls, ambient, gens, category=None): + """ + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('X','Y'): {'X': 1}}) + sage: I1 = L.ideal(X) + sage: I2 = L.ideal((X,)) + sage: I3 = L.ideal([X]) + sage: I1 is I2 and I2 is I3 + True + """ + if not isinstance(gens, (list, tuple)): + gens = [gens] + gens = tuple(ambient(gen) for gen in gens) + + gens = tuple(gens) + if len(gens) == 0: + gens = (ambient.zero(),) + + cat = LieAlgebras(ambient.base_ring()).FiniteDimensional().WithBasis() + category = cat.Subobjects().or_subcategory(category) + + sup = super(LieIdeal_finite_dimensional_with_basis, cls) + return sup.__classcall__(cls, ambient, gens, category) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: L.ideal([X, Y]) + Ideal (X, Y) of Abelian Lie algebra on 2 generators (X, Y) over Rational Field + """ + gens = self.gens() + if len(gens) == 1: + gens = "(%s)" % gens[0] + return "Ideal %s of %s" % (gens, self.ambient()) + + # for submodule computations, the order of the basis is reversed so that + # the pivot elements in the echelon form are the leading terms + def _to_m(self, X): + r""" + Return the reversed vector of an element of the ambient Lie algebra. + + INPUT: + + - ``X`` -- an element of the ambient Lie algebra + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) + sage: I = L.ideal([x, z]) + sage: el = x + 2*y + 3*z + sage: el.to_vector() + (1, 2, 3) + sage: I._to_m(el) + (3, 2, 1) + """ + return vector(self.ambient().base_ring(), reversed(X.to_vector())) + + def _from_m(self, v): + r""" + Return the element of the ambient Lie algebra from a reversed vector. + + INPUT: + + - ``v`` -- a vector + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) + sage: I = L.ideal([x, z]) + sage: I._from_m([3, 2, 1]) + x + 2*y + 3*z + """ + R = self.ambient().base_ring() + return self.ambient().from_vector(vector(R, reversed(v))) + + @cached_method + def basis(self): + r""" + Return a basis of ``self``. + + EXAMPLES: + + sage: sc = {('x','y'): {'z': 1}, ('x','z'): {'w': 1}} + sage: L. = LieAlgebra(QQ, sc) + sage: L.ideal([x + y + z + w]).basis() + Family (x + y, z, w) + """ + L = self.ambient() + m = L.module() + B = m.basis() + + sm = m.submodule([self._to_m(X) for X in self.gens()]) + d = 0 + + while sm.dimension() > d: + d = sm.dimension() + SB = sm.basis() + sm = m.submodule(sm.basis() + + [self._to_m(L.bracket(v, self._from_m(w))) + for v in B for w in SB]) + + return Family(reversed([self.element_class(self, self._from_m(v)) + for v in sm.echelonized_basis()])) + + def reduce(self, X): + r""" + Reduce an element of the ambient Lie algebra modulo ``self``. + + INPUT: + + - ``X`` -- an element of the ambient Lie algebra + + OUTPUT: + + An element `Y` of the ambient Lie algebra that is contained in a fixed + complementary submodule `V` to ``self`` such that `X = Y` mod ``self``. + + When the base ring of ``self`` is a field, the complementary submodule + `V` is spanned by the elements of the basis that are not the leading + supports of the basis of ``self``. + + EXAMPLES: + + An example reduction in a 6 dimensional Lie algebra:: + + sage: sc = {('a','b'): {'d': 1}, ('a','c'): {'e': 1}, + ....: ('b','c'): {'f': 1}} + sage: L. = LieAlgebra(QQ, sc) + sage: I = L.ideal(c) + sage: I.reduce(a + b + c + d + e + f) + a + b + d + + The reduction of an element is zero if and only if the element belongs + to the ideal:: + + sage: I.reduce(c + e) + 0 + sage: c + e in I + True + + Over non-fields, the complementary submodule may not be spanned by + a subset of the basis of the ambient Lie algebra:: + + sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) + sage: I = L.ideal(Y) + sage: I.basis() + Family (Y, 3*Z) + sage: I.reduce(3*Z) + 0 + sage: I.reduce(Y + 14*Z) + 2*Z + """ + R = self.base_ring() + for Y in self.basis(): + Y = Y.value + k, c = Y.leading_item() + + if R.is_field(): + X = X - X[k] / c * Y + else: + try: + q, r = X[k].quo_rem(c) + X = X - q * Y + except AttributeError: + pass + + return X diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py new file mode 100644 index 00000000000..368028880c2 --- /dev/null +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -0,0 +1,627 @@ +r""" +Subalgebras of Lie algebras + +AUTHORS: + +- Eero Hakavuori (2018-08-29): initial version +""" + +# **************************************************************************** +# Copyright (C) 2018 Eero Hakavuori +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.algebras.lie_algebras.lie_algebra_element import LieAlgebraElementWrapper +from sage.categories.lie_algebras import LieAlgebras +from sage.categories.homset import Hom +from sage.categories.morphism import SetMorphism +from sage.categories.sets_cat import Sets +from sage.misc.cachefunc import cached_method +from sage.modules.free_module_element import vector +from sage.sets.family import Family +from sage.structure.element import coercion_model, have_same_parent +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation + + +class LieSubset(Parent, UniqueRepresentation): + r""" + An abstract base class for a subset of a Lie algebra. + + INPUT: + + - ``ambient`` -- the Lie algebra containing the subset + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset + sage: L. = LieAlgebra(QQ) + sage: S = LieSubset(L); S + Abstract subset of Free Lie algebra generated by (X, Y) over Rational Field + sage: S.category() + Category of subobjects of sets + + TESTS:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset + sage: L. = LieAlgebra(QQ, abelian=True) + sage: K. = LieAlgebra(QQ, {}) + sage: LieSubset(L) == LieSubset(K) + True + """ + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset + sage: L. = LieAlgebra(QQ) + sage: LieSubset(L) + Abstract subset of Free Lie algebra generated by (X, Y) over Rational Field + """ + return "Abstract subset of %s" % self.ambient() + + def __init__(self, ambient, category=None): + r""" + Initialize ``self``. + + TESTS:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset + sage: L. = LieAlgebra(QQ) + sage: S = LieSubset(L) + sage: TestSuite(S).run() + """ + self._ambient = ambient + cat = Sets().Subobjects() + category = cat.or_subcategory(category) + super(LieSubset, self).__init__(ambient.base_ring(), category=category) + + # register a coercion to the ambient Lie algebra + H = Hom(self, ambient) + f = SetMorphism(H, self.lift) + ambient.register_coercion(f) + + def __getitem__(self, x): + r""" + If `x` is a pair `(a, b)`, return the Lie bracket `[a, b]`. + Otherwise try to return the `x`-th element of ``self``. + + This replicates the convenience syntax for Lie brackets of Lie algebras. + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset + sage: L. = LieAlgebra(QQ, representation="polynomial") + sage: S = LieSubset(L) + sage: a = S(x); b = S(y) + sage: S[a, b] + x*y - y*x + sage: S[a, a + S[a,b]] + x^2*y - 2*x*y*x + y*x^2 + """ + if isinstance(x, tuple) and len(x) == 2: + return self(x[0])._bracket_(self(x[1])) + return super(LieSubset, self).__getitem__(x) + + def _an_element_(self): + r""" + Return an element of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset + sage: L. = LieAlgebra(QQ) + sage: S = LieSubset(L) + sage: S._an_element_() + X + Y + """ + return self.element_class(self, self.ambient()._an_element_()) + + def _element_constructor_(self, x): + r""" + Convert ``x`` into ``self``. + + EXAMPLES: + + Elements of subsets are created directly from elements + of the ambient Lie algebra:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset + sage: L. = LieAlgebra(ZZ, {('x','y'): {'x': 1}}) + sage: S = LieSubset(L) + sage: S(x) + x + sage: S(x).parent() + Abstract subset of Lie algebra on 2 generators (x, y) over Integer Ring + + A list of 2 elements is interpreted as a Lie bracket:: + + sage: S([S(x), S(y)]) + x + sage: S([S(x), S(y)]) == S(L[x, y]) + True + """ + if isinstance(x, list) and len(x) == 2: + return self(x[0])._bracket_(self(x[1])) + + try: + P = x.parent() +# if P is self: +# return x + if x.parent() == self.ambient(): + return self.element_class(self, x) + except AttributeError: + pass + + return super(LieSubset, self)._element_constructor_(x) + + @cached_method + def zero(self): + r""" + Return the element `0`. + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset + sage: L. = LieAlgebra(QQ, representation="polynomial") + sage: S = LieSubset(L) + sage: S.zero() + 0 + sage: S.zero() == S(L.zero()) + True + """ + return self.element_class(self, self.ambient().zero()) + + def ambient(self): + r""" + Return the ambient Lie algebra of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset + sage: L. = LieAlgebra(QQ, abelian=True) + sage: S = LieSubset(L) + sage: S.ambient() is L + True + """ + return self._ambient + + def lift(self, X): + r""" + Coerce an element ``X`` of ``self`` into the ambient Lie algebra. + + INPUT: + + - ``X`` -- an element of ``self`` + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset + sage: L. = LieAlgebra(QQ, abelian=True) + sage: S = LieSubset(L) + sage: sx = S(x); sx + x + sage: sx.parent() + Abstract subset of Abelian Lie algebra on 2 generators (x, y) over Rational Field + sage: a = S.lift(sx); a + x + sage: a.parent() + Abelian Lie algebra on 2 generators (x, y) over Rational Field + """ + return X.value + + def retract(self, x): + r""" + Retract ``x`` to ``self``. + + INPUT: + + - ``x`` -- an element of the ambient Lie algebra + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset + sage: L. = LieAlgebra(QQ, abelian=True) + sage: S = LieSubset(L) + sage: S.retract(x) + x + sage: S.retract(x).parent() + Abstract subset of Abelian Lie algebra on 2 generators (x, y) over Rational Field + """ + return self.element_class(self, x) + + class Element(LieAlgebraElementWrapper): + r""" + Wrap an element of the ambient Lie algebra as an element. + """ + + def _bracket_(self, x): + """ + Return the Lie bracket ``[self, x]``. + + Assumes ``x`` and ``self`` have the same parent. + + INPUT: + + - ``x`` -- an element of the same Lie subalgebra as ``self`` + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset + sage: L. = LieAlgebra(QQ) + sage: S = LieSubset(L) + sage: S(x)._bracket_(S(y)) + [x, y] + """ + return type(self)(self.parent(), self.value.bracket(x.value)) + + +class LieSubset_with_gens(LieSubset): + r""" + An abstract base class for a subset of a Lie algebra with a generating set. + + INPUT: + + - ``ambient`` -- the Lie algebra containing the subset + - ``gens`` -- a tuple of generators from the ambient Lie algebra + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset_with_gens + sage: L. = LieAlgebra(QQ) + sage: L = L.Lyndon() + sage: LieSubset_with_gens(L, (X, L[X, Y])) + Abstract subset with generators (X, [X, Y]) of Free Lie algebra + generated by (X, Y) over Rational Field in the Lyndon basis + """ + + def __init__(self, ambient, gens, category=None): + r""" + Initialize ``self``. + + TESTS:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset_with_gens + sage: L. = LieAlgebra(QQ) + sage: S = LieSubset_with_gens(L, (X,)) + sage: TestSuite(S).run() + """ + self._gens = gens + super(LieSubset_with_gens, self).__init__(ambient, category=category) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset_with_gens + sage: L. = LieAlgebra(QQ) + sage: LieSubset_with_gens(L, (X,)) + Abstract subset with generators (X,) of Free Lie algebra generated by (X, Y) over Rational Field + """ + return "Abstract subset with generators %s of %s" % (self.gens(), self.ambient()) + + def _an_element_(self): + r""" + Return an element of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset_with_gens + sage: L. = LieAlgebra(QQ) + sage: S = LieSubset_with_gens(L, (X,)) + sage: S._an_element_() + X + """ + return self.element_class(self, self.gens()[0]) + + def gens(self): + r""" + Return the generating set of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.subalgebra import LieSubset_with_gens + sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) + sage: S = LieSubset_with_gens(L, (x,)) + sage: S.gens() + (x,) + """ + return self._gens + + +class LieSubalgebra_finite_dimensional_with_basis(LieSubset_with_gens): + r""" + A Lie subalgebra of a finite dimensional Lie algebra with basis. + + INPUT: + + - ``ambient`` -- the Lie algebra containing the subalgebra + - ``gens`` -- a list of generators of the subalgebra + - ``category`` -- (optional) a subcategory of subobjects of finite + dimensional Lie algebras with basis + + EXAMPLES: + + A subalgebra is defined by giving a list of generators:: + + sage: L = lie_algebras.Heisenberg(QQ, 1) + sage: X, Y, Z = L.basis() + sage: I = L.subalgebra([X, Z]); I + Subalgebra generated by (p1, z) of Heisenberg algebra of rank 1 over Rational Field + sage: I.basis() + Family (p1, z) + + A subalgebra of a subalgebra is a subalgebra of the original:: + + sage: sc = {('X','Y'): {'Z': 1}, ('X','Z'): {'W': 1}} + sage: L. = LieAlgebra(QQ, sc) + sage: S1 = L.subalgebra([Y, Z, W]); S1 + Subalgebra generated by (Y, Z, W) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field + sage: S2 = S1.subalgebra(S1.basis()[1:]); S2 + Subalgebra generated by (Z, W) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field + sage: S3 = S2.subalgebra(S2.basis()[1:]); S3 + Subalgebra generated by W of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field + + The zero dimensional subalgebra can be created by giving 0 as a generator + or with an empty list of generators:: + + sage: L. = LieAlgebra(QQ, {('X','Y'): {'Z': 1}}) + sage: S1 = L.subalgebra(0) + sage: S2 = L.subalgebra([]) + sage: S1 is S2 + True + sage: S1.basis() + Family () + + TESTS: + + A test suite:: + + sage: S = L.subalgebra(X + Y) + sage: TestSuite(S).run() + """ + + @staticmethod + def __classcall_private__(cls, ambient, gens, category=None): + """ + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('X','Y'): {'X': 1}}) + sage: S1 = L.subalgebra(X) + sage: S2 = L.subalgebra((X,)) + sage: S3 = L.subalgebra([X]) + sage: S1 is S2 and S2 is S3 + True + """ + if not isinstance(gens, (list, tuple)): + gens = [gens] + gens = tuple(ambient(gen) for gen in gens) + + gens = tuple(gens) + if len(gens) == 0: + gens = (ambient.zero(),) + + if isinstance(ambient, LieSubalgebra_finite_dimensional_with_basis): + # a nested subalgebra is a subalgebra + gens = tuple(ambient.lift(gen) for gen in gens) + ambient = ambient.ambient() + + cat = LieAlgebras(ambient.base_ring()).FiniteDimensional().WithBasis() + category = cat.Subobjects().or_subcategory(category) + + sup = super(LieSubalgebra_finite_dimensional_with_basis, cls) + return sup.__classcall__(cls, ambient, gens, category) + + def __contains__(self, x): + r""" + Return ``True`` if ``x`` is an element of ``self``. + + EXAMPLES: + + Elements of the ambient Lie algebra are contained in the subalgebra + if they are iterated brackets of the generators:: + + sage: sc = {('x','y'): {'z': 1}, ('x','z'): {'w': 1}} + sage: L. = LieAlgebra(QQ, sc) + sage: S = L.subalgebra([x, y]) + sage: z in S + True + sage: w in S + True + sage: u in S + False + + TESTS:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) + sage: I = L.subalgebra(x) + sage: I(x) in I + True + + """ + if x in self.ambient(): + x = self.ambient()(x) + return x.to_vector() in self.ambient_submodule() + return super(LieSubset, self).__contains__(x) + + def _element_constructor_(self, x): + """ + Convert ``x`` into ``self``. + + EXAMPLES: + + Elements of subalgebras are created directly from elements + of the ambient Lie algebra:: + + sage: L. = LieAlgebra(ZZ, {('x','y'): {'z': 1}}) + sage: S = L.subalgebra([x, y]) + sage: S(y) + y + sage: S(y).parent() + Subalgebra generated by (x, y) of Lie algebra on 4 generators (x, y, z, w) over Integer Ring + + A vector of length equal to the dimension of the subalgebra is + interpreted as a coordinate vector:: + + sage: S(vector(ZZ, [2,3,5])) + 2*x + 3*y + 5*z + """ + if x in self.module(): + return self.from_vector(x) + + return super(LieSubalgebra_finite_dimensional_with_basis, self)._element_constructor_(x) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: L.subalgebra([X, Y]) + Subalgebra generated by (X, Y) of Abelian Lie algebra on 2 generators (X, Y) over Rational Field + """ + gens = self.gens() + if len(gens) == 1: + gens = gens[0] + return "Subalgebra generated by %s of %s" % (gens, self.ambient()) + + @cached_method + def basis(self): + r""" + Return a basis of ``self``. + + EXAMPLES: + + sage: sc = {('x','y'): {'z': 1}, ('x','z'): {'w': 1}} + sage: L. = LieAlgebra(QQ, sc) + sage: L.subalgebra([x + y, z + w]).basis() + Family (x + y, z, w) + """ + L = self.ambient() + m = L.module() + sm = m.submodule([X.to_vector() for X in self.gens()]) + d = 0 + + while sm.dimension() > d: + d = sm.dimension() + SB = sm.basis() + sm = m.submodule(sm.basis() + + [L.bracket(v, w).to_vector() + for v in SB for w in SB]) + + return Family(self.element_class(self, L.from_vector(v)) + for v in sm.echelonized_basis()) + + def basis_matrix(self): + r""" + Return the basis matrix of ``self`` as a submodule + of the ambient Lie algebra. + + EXAMPLES:: + + sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) + sage: S1 = L.subalgebra([4*X + Y, Y]) + sage: S1.basis_matrix() + [ 4 0 0] + [ 0 1 0] + [ 0 0 12] + sage: K. = LieAlgebra(QQ, {('X','Y'): {'Z': 3}}) + sage: S2 = K.subalgebra([4*X + Y, Y]) + sage: S2.basis_matrix() + [1 0 0] + [0 1 0] + [0 0 1] + """ + return self.ambient_submodule().basis_matrix() + + def ambient_submodule(self): + r""" + Return the submodule of the ambient Lie algebra + corresponding to ``self``. + + EXAMPLES:: + + sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) + sage: S = L.subalgebra([X, Y]) + sage: S.ambient_submodule() + Sparse free module of degree 3 and rank 3 over Integer Ring + User basis matrix: + [1 0 0] + [0 1 0] + [0 0 3] + """ + m = self.ambient().module() + ambientbasis = [X.value.to_vector() for X in self.basis()] + return m.submodule_with_basis(ambientbasis) + + class Element(LieSubset.Element): + + def __getitem__(self, i): + r""" + Return the coefficient of ``self`` indexed by ``i``. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('X','Y'): {'Z': 1}}) + sage: S = L.subalgebra([X, Y]) + sage: el = S(2*Y + 9*Z) + sage: el[1] + 2 + sage: el[2] + 9 + """ + return self.monomial_coefficients()[i] + + def to_vector(self): + r""" + Return the vector in ``g.module()`` corresponding to the + element ``self`` of ``g`` (where ``g`` is the parent of + ``self``). + + EXAMPLES:: + + sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) + sage: S = L.subalgebra([X, Y]) + sage: S.basis() + Family (X, Y, 3*Z) + sage: S(2*Y + 9*Z).to_vector() + (0, 2, 3) + """ + sm = self.parent().ambient_submodule() + return sm.coordinate_vector(self.value.to_vector()) + + def monomial_coefficients(self, copy=True): + r""" + 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: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) + sage: S = L.subalgebra([X, Y]) + sage: S(2*Y + 9*Z).monomial_coefficients() + {0: 0, 1: 2, 2: 3} + """ + v = self.to_vector() + return dict(zip(range(len(v)), v)) diff --git a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py index b9bc161fb4b..6750d8c9176 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -568,6 +568,50 @@ def inner_derivations_basis(self): return tuple([matrix(R, N, N, list(b)) for b in IDer.row_module().basis()]) + def subalgebra(self, gens): + r""" + Return the subalgebra of ``self`` generated by ``gens``. + + INPUT: + + - ``gens`` -- a list of generators of the subalgebra + + EXAMPLES:: + + sage: H = lie_algebras.Heisenberg(QQ, 2) + sage: p1,p2,q1,q2,z = H.basis() + sage: S = H.subalgebra([p1, q1]) + sage: S.basis().list() + [p1, q1, z] + sage: S.basis_matrix() + [1 0 0 0 0] + [0 0 1 0 0] + [0 0 0 0 1] + """ + from sage.algebras.lie_algebras.subalgebra import LieSubalgebra_finite_dimensional_with_basis + return LieSubalgebra_finite_dimensional_with_basis(self, gens) + + def ideal(self, gens): + r""" + Return the ideal of ``self`` generated by ``gens``. + + INPUT: + + - ``gens`` -- a list of generators of the ideal + + EXAMPLES:: + + sage: H = lie_algebras.Heisenberg(QQ, 2) + sage: p1,p2,q1,q2,z = H.basis() + sage: I = H.ideal([p1-p2, q1-q2]) + sage: I.basis().list() + [-p1 + p2, -q1 + q2, z] + sage: I.reduce(p1 + p2 + q1 + q2 + z) + 2*p1 + 2*q1 + """ + from sage.algebras.lie_algebras.ideal import LieIdeal_finite_dimensional_with_basis + return LieIdeal_finite_dimensional_with_basis(self, gens) + @cached_method def is_ideal(self, A): """ From 69dfbfe2257f97f1ea5fec495f3a1909558214c8 Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Wed, 29 Aug 2018 08:07:43 +0300 Subject: [PATCH 110/264] Preserve nilpotent category for ideals and subalgebras of nilpotent Lie algebras --- ...ional_nilpotent_lie_algebras_with_basis.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py index af82ff37fc1..d7254e24c1c 100644 --- a/src/sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py @@ -21,6 +21,7 @@ from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.lie_algebras import LieAlgebras + class FiniteDimensionalNilpotentLieAlgebrasWithBasis(CategoryWithAxiom_over_base_ring): r""" Category of finite dimensional nilpotent Lie algebras with basis. @@ -48,6 +49,7 @@ class FiniteDimensionalNilpotentLieAlgebrasWithBasis(CategoryWithAxiom_over_base _base_category_class_and_axiom = (LieAlgebras.FiniteDimensional.WithBasis, "Nilpotent") class ParentMethods: + def _test_nilpotency(self, **options): r""" Tests that ``self`` is nilpotent and has the correct step. @@ -115,3 +117,55 @@ def is_nilpotent(self): """ return True + def subalgebra(self, gens): + r""" + Return the subalgebra of ``self`` generated by ``gens``. + + INPUT: + + - ``gens`` -- a list of generators of the subalgebra + + EXAMPLES: + + A subalgebra of a nilpotent Lie algebra is nilpotent:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: S = L.subalgebra([X, Y]) + sage: S.basis() + Family (X, Y) + sage: S.category() + Category of subobjects of finite dimensional nilpotent lie algebras with basis over Rational Field + """ + from sage.algebras.lie_algebras.subalgebra import LieSubalgebra_finite_dimensional_with_basis + C = LieAlgebras(self.base_ring()).FiniteDimensional().WithBasis() + C = C.Nilpotent().Subobjects() + return LieSubalgebra_finite_dimensional_with_basis(self, gens, + category=C) + + def ideal(self, gens): + r""" + Return the ideal of ``self`` generated by ``gens``. + + INPUT: + + - ``gens`` -- a list of generators of the ideal + + EXAMPLES: + + An ideal of a nilpotent Lie algebra is nilpotent:: + + sage: L = LieAlgebra(QQ, 3, step=3) + sage: x,y,z = L.homogeneous_component_basis(1) + sage: I = L.ideal(z) + sage: L.basis().list() + [X_1, X_2, X_3, X_12, X_13, X_23, X_112, X_113, X_122, X_123, X_132, X_133, X_223, X_233] + sage: I.basis().list() + [X_3, X_13, X_23, X_113, X_123, X_132, X_133, X_223, X_233] + sage: I.category() + Category of subobjects of finite dimensional nilpotent lie algebras with basis over Rational Field + """ + from sage.algebras.lie_algebras.ideal import LieIdeal_finite_dimensional_with_basis + C = LieAlgebras(self.base_ring()).FiniteDimensional().WithBasis() + C = C.Nilpotent().Subobjects() + return LieIdeal_finite_dimensional_with_basis(self, gens, + category=C) From a0d60e1f775af58e648f1c062d6128a2f164217d Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Wed, 29 Aug 2018 08:28:30 +0300 Subject: [PATCH 111/264] Refactored subobject computations to use lift and retract instead of value --- src/sage/algebras/lie_algebras/ideal.py | 2 +- src/sage/algebras/lie_algebras/subalgebra.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/lie_algebras/ideal.py b/src/sage/algebras/lie_algebras/ideal.py index 6c0f2d64ffc..47333473988 100644 --- a/src/sage/algebras/lie_algebras/ideal.py +++ b/src/sage/algebras/lie_algebras/ideal.py @@ -265,7 +265,7 @@ def reduce(self, X): """ R = self.base_ring() for Y in self.basis(): - Y = Y.value + Y = self.lift(Y) k, c = Y.leading_item() if R.is_field(): diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index 368028880c2..0ede91d1c73 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -261,7 +261,10 @@ def _bracket_(self, x): sage: S(x)._bracket_(S(y)) [x, y] """ - return type(self)(self.parent(), self.value.bracket(x.value)) + P = self.parent() + self_lift = P.lift(self) + x_lift = P.lift(x) + return P.retract(P.ambient().bracket(self_lift, x_lift)) class LieSubset_with_gens(LieSubset): @@ -564,7 +567,7 @@ def ambient_submodule(self): [0 0 3] """ m = self.ambient().module() - ambientbasis = [X.value.to_vector() for X in self.basis()] + ambientbasis = [self.lift(X).to_vector() for X in self.basis()] return m.submodule_with_basis(ambientbasis) class Element(LieSubset.Element): @@ -601,7 +604,7 @@ def to_vector(self): (0, 2, 3) """ sm = self.parent().ambient_submodule() - return sm.coordinate_vector(self.value.to_vector()) + return sm.coordinate_vector(self.parent().lift(self).to_vector()) def monomial_coefficients(self, copy=True): r""" From 59576970729d4378562773cc04af617b183b2127 Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Wed, 29 Aug 2018 09:11:49 +0300 Subject: [PATCH 112/264] Fix for lower central series computation of subalgebras: 'from_vector' now checks if the vector is given in the ambient or intrinsic form --- src/sage/algebras/lie_algebras/subalgebra.py | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index 0ede91d1c73..dcacac27fcb 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -528,6 +528,38 @@ def basis(self): return Family(self.element_class(self, L.from_vector(v)) for v in sm.echelonized_basis()) + def from_vector(self, v): + r""" + Return the element of ``self`` corresponding to the vector ``v`` + + INPUT: + + - ``v`` -- a vector in ``self.module()`` or ``self.ambient().module()`` + + EXAMPLES: + + An element from a vector of the intrinsic module:: + + sage: L. = LieAlgebra(ZZ, abelian=True) + sage: L.dimension() + 3 + sage: S = L.subalgebra([X, Y]) + sage: S.dimension() + 2 + sage: S.from_vector([1, 2]) + X + 2*Y + + An element from a vector of the ambient module + + sage: S.from_vector([1, 2, 3]) + X + 2*Y + 3*Z + """ + if len(v) == self.ambient().dimension(): + return self.ambient().from_vector(v) + + sup = super(LieSubalgebra_finite_dimensional_with_basis, self) + return sup.from_vector(v) + def basis_matrix(self): r""" Return the basis matrix of ``self`` as a submodule From e2cdaed522396d8154a9eed6246580a886b8f366 Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Wed, 29 Aug 2018 09:18:48 +0300 Subject: [PATCH 113/264] Added doctest for nilpotency step computation of ideals and subalgebras --- ...ional_nilpotent_lie_algebras_with_basis.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py index d7254e24c1c..227ddaa32a0 100644 --- a/src/sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py @@ -129,12 +129,17 @@ def subalgebra(self, gens): A subalgebra of a nilpotent Lie algebra is nilpotent:: - sage: L. = LieAlgebra(QQ, abelian=True) - sage: S = L.subalgebra([X, Y]) - sage: S.basis() - Family (X, Y) + sage: L = LieAlgebra(QQ, 3, step=4) + sage: x,y,z = L.homogeneous_component_basis(1) + sage: S = L.subalgebra([x, y]) + sage: L.dimension() + 32 + sage: S.dimension() + 8 sage: S.category() Category of subobjects of finite dimensional nilpotent lie algebras with basis over Rational Field + sage: S.step() + 4 """ from sage.algebras.lie_algebras.subalgebra import LieSubalgebra_finite_dimensional_with_basis C = LieAlgebras(self.base_ring()).FiniteDimensional().WithBasis() @@ -154,15 +159,17 @@ def ideal(self, gens): An ideal of a nilpotent Lie algebra is nilpotent:: - sage: L = LieAlgebra(QQ, 3, step=3) + sage: L = LieAlgebra(QQ, 3, step=4) sage: x,y,z = L.homogeneous_component_basis(1) sage: I = L.ideal(z) - sage: L.basis().list() - [X_1, X_2, X_3, X_12, X_13, X_23, X_112, X_113, X_122, X_123, X_132, X_133, X_223, X_233] - sage: I.basis().list() - [X_3, X_13, X_23, X_113, X_123, X_132, X_133, X_223, X_233] + sage: L.dimension() + 32 + sage: I.dimension() + 24 sage: I.category() Category of subobjects of finite dimensional nilpotent lie algebras with basis over Rational Field + sage: I.step() + 3 """ from sage.algebras.lie_algebras.ideal import LieIdeal_finite_dimensional_with_basis C = LieAlgebras(self.base_ring()).FiniteDimensional().WithBasis() From 45d0abbb22ad655ecfd080b6d905155f4cfe728c Mon Sep 17 00:00:00 2001 From: David Coudert Date: Wed, 29 Aug 2018 14:02:39 +0200 Subject: [PATCH 114/264] trac #25598: fix the merging of cycles with cleave --- src/sage/graphs/connectivity.pyx | 56 +++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 92b5a2e3375..a6de2ebc8e9 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2202,6 +2202,31 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): sage: spqr_tree(G, algorithm='cleave').vertices() [('P', Multi-graph on 2 vertices)] + sage: from collections import Counter + sage: G = graphs.PetersenGraph() + sage: T = G.spqr_tree(algorithm="Hopcroft_Tarjan") + sage: Counter(u[0] for u in T) + Counter({'R': 1}) + sage: T = G.spqr_tree(algorithm="cleave") + sage: Counter(u[0] for u in T) + Counter({'R': 1}) + sage: for u,v in G.edges(labels=False): + ....: G.add_path([u, G.add_vertex(), G.add_vertex(), v]) + sage: T = G.spqr_tree(algorithm="Hopcroft_Tarjan") + sage: Counter(u[0] for u in T) + Counter({'P': 15, 'S': 15, 'R': 1}) + sage: T = G.spqr_tree(algorithm="cleave") + sage: Counter(u[0] for u in T) + Counter({'P': 15, 'S': 15, 'R': 1}) + sage: for u,v in G.edges(labels=False): + ....: G.add_path([u, G.add_vertex(), G.add_vertex(), v]) + sage: T = G.spqr_tree(algorithm="Hopcroft_Tarjan") + sage: Counter(u[0] for u in T) + Counter({'S': 75, 'P': 60, 'R': 1}) + sage: T = G.spqr_tree(algorithm="cleave") # long time + sage: Counter(u[0] for u in T) # long time + Counter({'S': 75, 'P': 60, 'R': 1}) + TESTS:: sage: G = graphs.PathGraph(4) @@ -2277,7 +2302,7 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): two_blocks = [(SG, cut_vertices)] cocycles_count = Counter() cycles_list = [] - virtual_to_cycles = dict() + virtual_edge_to_cycles = dict() while two_blocks: B,B_cut = two_blocks.pop() @@ -2286,17 +2311,13 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): # Store the number of edges of the cocycle (P block) fe = frozenset(B_cut) cocycles_count[fe] += C.size() + if f.size(): + virtual_edge_to_cycles[fe] = [] # Check the sides of the cut for K in S: if K.is_cycle(): # Add this cycle to the list of cycles cycles_list.append(K) - # We associate the index of K in cycle_list to the virtual edge - if f.size(): - if fe in virtual_to_cycles: - virtual_to_cycles[fe].append(len(cycles_list)-1) - else: - virtual_to_cycles[fe] = [len(cycles_list)-1] else: K_cut_size,K_cut_vertices = K.vertex_connectivity(value_only=False, solver=solver, verbose=verbose) if K_cut_size == 2: @@ -2309,17 +2330,23 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): # Cycles of order > 3 may have been triangulated; We undo this to reduce the # number of S-blocks. Two cycles can be merged if they share a virtual edge # that is not shared by any other block, i.e., cocycles_count[e] == 2. We - # use a DisjointSet to form the groups of cycles to be merged. + # first associate cycles to virtual edges. Then, we use a DisjointSet to + # form the groups of cycles to be merged. + for K_index,K in enumerate(cycles_list): + for e in K.edge_iterator(labels=False): + fe = frozenset(e) + if fe in virtual_edge_to_cycles: + virtual_edge_to_cycles[fe].append(K_index) from sage.sets.disjoint_set import DisjointSet DS = DisjointSet(range(len(cycles_list))) - for e in virtual_to_cycles: - if cocycles_count[e] == 2 and len(virtual_to_cycles[e]) == 2: + for fe in virtual_edge_to_cycles: + if cocycles_count[fe] == 2 and len(virtual_edge_to_cycles[fe]) == 2: # This virtual edge is only between 2 cycles - C1, C2 = virtual_to_cycles[e] + C1, C2 = virtual_edge_to_cycles[fe] DS.union(C1, C2) - cycles_list[C1].delete_edge(e) - cycles_list[C2].delete_edge(e) - cocycles_count[e] -= 2 + cycles_list[C1].delete_edge(fe) + cycles_list[C2].delete_edge(fe) + cocycles_count[fe] -= 2 # We finalize the creation of S_blocks. S_blocks = [] @@ -2329,7 +2356,6 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): E.extend(cycles_list[i].edge_iterator(labels=False)) S_blocks.append(('S', Graph(E, immutable=True))) - # We now build the SPQR tree Tree = Graph(name='SPQR tree of {}'.format(G.name())) SR_blocks = S_blocks + R_blocks From 342ebf10c17809710fb830202c940acb6cc007e4 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Wed, 29 Aug 2018 19:42:21 +0200 Subject: [PATCH 115/264] trac #25598: fix Hopcroft_Tarjan --- src/sage/graphs/connectivity.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index a6de2ebc8e9..e22d80dabb3 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2641,7 +2641,7 @@ class _Component: depending on the number of edges belonging to it. """ self.add_edge(e) - if self.edge_list.get_length() >= 4: + if self.edge_list.get_length() > 4: self.component_type = 2 else: self.component_type = 1 From 48b0f1ce819cebdeda1b8c82789bc89ca37e81d8 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 30 Aug 2018 11:08:08 +0200 Subject: [PATCH 116/264] trac #25598: better fix of Hopcroft_Tarjan --- src/sage/graphs/connectivity.pyx | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index e22d80dabb3..f099502dcd4 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2625,12 +2625,15 @@ class _Component: - `type_c` -- type of the component (0, 1, or 2). """ self.edge_list = _LinkedList() + self.vertices = set() for e in edge_list: - self.edge_list.append(_LinkedListNode(e)) + self.add_edge(e) self.component_type = type_c def add_edge(self, e): self.edge_list.append(_LinkedListNode(e)) + self.vertices.add(e[0]) + self.vertices.add(e[1]) def finish_tric_or_poly(self, e): r""" @@ -2641,7 +2644,7 @@ class _Component: depending on the number of edges belonging to it. """ self.add_edge(e) - if self.edge_list.get_length() > 4: + if self.edge_list.get_length() > len(self.vertices): self.component_type = 2 else: self.component_type = 1 @@ -2746,7 +2749,7 @@ class TriconnectivitySPQR: Bond: [(5, 4, 'newVEdge7'), (4, 5, 'newVEdge8'), (4, 5, None)] Bond: [(1, 4, None), (4, 1, 'newVEdge9'), (4, 1, 'newVEdge10')] Polygon: [(3, 4, None), (4, 1, 'newVEdge10'), (3, 1, 'newVEdge11')] - Triconnected: [(1, 2, None), (2, 3, None), (3, 1, 'newVEdge11'), (3, 13, None), (2, 13, None), (1, 13, None)] + Triconnected: [(1, 13, None), (2, 13, None), (3, 13, None), (3, 1, 'newVEdge11'), (2, 3, None), (1, 2, None)] An example from [Gut2001]_:: @@ -2765,7 +2768,7 @@ class TriconnectivitySPQR: sage: tric.print_triconnected_components() Bond: [(2, 3, None), (2, 3, None), (2, 3, 'newVEdge0')] Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge1')] - Polygon: [(4, 5, None), (1, 5, 'newVEdge1'), (3, 4, None), (1, 2, None), (2, 3, 'newVEdge0')] + Polygon: [(4, 5, None), (1, 5, 'newVEdge1'), (3, 4, None), (2, 3, 'newVEdge0'), (1, 2, None)] An example of a triconnected graph:: @@ -2782,7 +2785,7 @@ class TriconnectivitySPQR: sage: tric = TriconnectivitySPQR(G) sage: tric.print_triconnected_components() Bond: [(1, 5, None), (1, 5, None), (1, 5, 'newVEdge0')] - Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (1, 2, None), (2, 3, None)] + Polygon: [(4, 5, None), (1, 5, 'newVEdge0'), (3, 4, None), (2, 3, None), (1, 2, None)] Edge labels are preserved by the construction:: @@ -2977,12 +2980,11 @@ class TriconnectivitySPQR: self.__path_search(self.start_vertex) # last split component - c = _Component([],0) - while self.e_stack: - c.add_edge(self.__estack_pop()) - c.component_type = 2 if c.edge_list.get_length() > 4 else 1 - self.components_list.append(c) - c = None + if self.e_stack: + e = self.__estack_pop() + c = _Component(self.e_stack, 0) + c.finish_tric_or_poly(e) + self.components_list.append(c) self.__assemble_triconnected_components() @@ -3813,7 +3815,7 @@ class TriconnectivitySPQR: Bond: [(5, 4, 'newVEdge7'), (4, 5, 'newVEdge8'), (4, 5, None)] Bond: [(1, 4, None), (4, 1, 'newVEdge9'), (4, 1, 'newVEdge10')] Polygon: [(3, 4, None), (4, 1, 'newVEdge10'), (3, 1, 'newVEdge11')] - Triconnected: [(1, 2, None), (2, 3, None), (3, 1, 'newVEdge11'), (3, 13, None), (2, 13, None), (1, 13, None)] + Triconnected: [(1, 13, None), (2, 13, None), (3, 13, None), (3, 1, 'newVEdge11'), (2, 3, None), (1, 2, None)] """ # The types are {0: "Bond", 1: "Polygon", 2: "Triconnected"} prefix = ["Bond", "Polygon", "Triconnected"] @@ -3838,7 +3840,7 @@ class TriconnectivitySPQR: [('Polygon', [(4, 5, None), (0, 4, None), (1, 5, None), (1, 0, 'newVEdge1')]), ('Polygon', [(6, 7, None), (0, 6, None), (1, 7, None), (1, 0, 'newVEdge3')]), ('Bond', [(1, 0, 'newVEdge1'), (1, 0, 'newVEdge3'), (1, 0, 'newVEdge4')]), - ('Polygon', [(1, 3, None), (1, 0, 'newVEdge4'), (0, 2, None), (2, 3, None)])] + ('Polygon', [(1, 3, None), (1, 0, 'newVEdge4'), (2, 3, None), (0, 2, None)])] """ comps = [] # The types are {0: "Bond", 1: "Polygon", 2: "Triconnected"} From 1733c509a044b6b484b8f0f2e0000588130fca61 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 30 Aug 2018 12:28:13 +0200 Subject: [PATCH 117/264] trac #25598: fix issue with edge labels --- src/sage/graphs/connectivity.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index f099502dcd4..37d39108c61 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2791,7 +2791,7 @@ class TriconnectivitySPQR: sage: G = Graph([(0, 1, '01'), (0, 4, '04'), (1, 2, '12'), (1, 5, '15'), ....: (2, 3, '23'), (2, 6, '26'), (3, 7, '37'), (4, 5, '45'), - ....: (5, 6, '56'), (6, 7, '67')]) + ....: (5, 6, '56'), (6, 7, 67)]) sage: T = TriconnectivitySPQR(G).get_spqr_tree() sage: H = spqr_tree_to_graph(T) sage: set(G.edges()) == set(H.edges()) @@ -3781,7 +3781,7 @@ class TriconnectivitySPQR: # Add an edge to each node containing the same virtual edge for e in self.comp_list_new[i]: - if e[2] and "newVEdge" in e[2]: + if e[2] and isinstance(e[2], str) and e[2].startswith("newVEdge"): if e in partner_nodes: for j in partner_nodes[e]: self.spqr_tree.add_edge(int_to_vertex[i], int_to_vertex[j]) From a99ba15128ea25c42eec0eee93c08293a64e5054 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 30 Aug 2018 13:32:23 +0200 Subject: [PATCH 118/264] trac #25598: clean manipulation of virtual edges --- src/sage/graphs/connectivity.pyx | 40 +++++++++++++++++--------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 37d39108c61..c257e0e2de0 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2925,6 +2925,8 @@ class TriconnectivitySPQR: # Label used for virtual edges, incremented at every new virtual edge self.virtual_edge_num = 0 + # Virtual edges are stored in a set + self.virtual_edges = set() self.new_path = False # Boolean used to store if new path is started @@ -3027,6 +3029,15 @@ class TriconnectivitySPQR: self.components_list.append(c) return c + def __new_virtual_edge(self, u, v): + """ + Return a new virtual edge between `u` and `v`. + """ + e = (u, v, "newVEdge"+str(self.virtual_edge_num)) + self.virtual_edge_num += 1 + self.virtual_edges.add(e) + return e + def __high(self, v): """ Return the high(v) value, which is the first value in highpt list of v. @@ -3148,10 +3159,9 @@ class TriconnectivitySPQR: self.graph_copy.delete_edge(sorted_edges.get_data()) # Add virtual edge to graph_copy - newVEdge = (sorted_edges.get_data()[0], sorted_edges.get_data()[1], - "newVEdge"+str(self.virtual_edge_num)) + newVEdge = self.__new_virtual_edge(sorted_edges.get_data()[0], + sorted_edges.get_data()[1]) self.graph_copy.add_edge(newVEdge) - self.virtual_edge_num += 1 # mark unseen for newVEdge self.edge_status[newVEdge] = 0 @@ -3165,10 +3175,8 @@ class TriconnectivitySPQR: self.graph_copy.delete_edge(sorted_edges.get_data()) # Add virtual edge to graph_copy - newVEdge = (sorted_edges.get_data()[0], sorted_edges.get_data()[1], - "newVEdge"+str(self.virtual_edge_num)) + newVEdge = self.__new_virtual_edge(sorted_edges.get_data()[0], sorted_edges.get_data()[1]) self.graph_copy.add_edge(newVEdge) - self.virtual_edge_num += 1 self.edge_status[newVEdge] = 0 comp.append(newVEdge) @@ -3421,9 +3429,8 @@ class TriconnectivitySPQR: else: x = e2[1] # target - e_virt = tuple([v, x, "newVEdge"+str(self.virtual_edge_num)]) + e_virt = self.__new_virtual_edge(v, x) self.graph_copy.add_edge(e_virt) - self.virtual_edge_num += 1 self.degree[v] -= 1 self.degree[x] -= 1 @@ -3491,9 +3498,8 @@ class TriconnectivitySPQR: self.degree[x] -= 1 self.degree[xy_target] -= 1 - e_virt = tuple([self.node_at[a], self.node_at[b], "newVEdge"+str(self.virtual_edge_num)]) + e_virt = self.__new_virtual_edge(self.node_at[a], self.node_at[b]) self.graph_copy.add_edge(e_virt) - self.virtual_edge_num += 1 comp.finish_tric_or_poly(e_virt) self.components_list.append(comp) comp = None @@ -3501,9 +3507,8 @@ class TriconnectivitySPQR: if e_ab is not None: comp = _Component([e_ab, e_virt], type_c=0) - e_virt = tuple([v, x, "newVEdge"+str(self.virtual_edge_num)]) + e_virt = self.__new_virtual_edge(v, x) self.graph_copy.add_edge(e_virt) - self.virtual_edge_num += 1 comp.add_edge(e_virt) self.degree[x] -= 1 self.degree[v] -= 1 @@ -3549,9 +3554,8 @@ class TriconnectivitySPQR: self.degree[self.node_at[xx]] -= 1 self.degree[self.node_at[y]] -= 1 - e_virt = tuple([v, self.node_at[self.lowpt1[w]], "newVEdge"+str(self.virtual_edge_num)]) + e_virt = self.__new_virtual_edge(v, self.node_at[self.lowpt1[w]]) self.graph_copy.add_edge(e_virt) # Add virtual edge to graph - self.virtual_edge_num += 1 comp.finish_tric_or_poly(e_virt) # Add virtual edge to component self.components_list.append(comp) comp = None @@ -3568,9 +3572,8 @@ class TriconnectivitySPQR: comp_bond.add_edge(eh) comp_bond.add_edge(e_virt) - e_virt = (v, self.node_at[self.lowpt1[w]], "newVEdge"+str(self.virtual_edge_num)) + e_virt = self.__new_virtual_edge(v, self.node_at[self.lowpt1[w]]) self.graph_copy.add_edge(e_virt) - self.virtual_edge_num += 1 comp_bond.add_edge(e_virt) if eh in self.in_high: self.in_high[e_virt] = self.in_high[eh] @@ -3598,9 +3601,8 @@ class TriconnectivitySPQR: else: self.adj[v].remove(it) comp_bond = _Component([e_virt], type_c=0) - e_virt = (self.node_at[self.lowpt1[w]], v, "newVEdge"+str(self.virtual_edge_num)) + e_virt = self.__new_virtual_edge(self.node_at[self.lowpt1[w]], v) self.graph_copy.add_edge(e_virt) - self.virtual_edge_num += 1 comp_bond.add_edge(e_virt) eh = self.tree_arc[v]; @@ -3781,7 +3783,7 @@ class TriconnectivitySPQR: # Add an edge to each node containing the same virtual edge for e in self.comp_list_new[i]: - if e[2] and isinstance(e[2], str) and e[2].startswith("newVEdge"): + if e in self.virtual_edges: if e in partner_nodes: for j in partner_nodes[e]: self.spqr_tree.add_edge(int_to_vertex[i], int_to_vertex[j]) From 7b6b7bb76fc64b504c37182ada08a39d6ad7c760 Mon Sep 17 00:00:00 2001 From: Ben Hutz Date: Thu, 30 Aug 2018 20:00:23 -0500 Subject: [PATCH 119/264] 25952: updates from review --- .../endPN_minimal_model.py | 28 ++--- .../rings/polynomial/binary_form_reduce.py | 107 +++++++++--------- .../rings/polynomial/multi_polynomial.pyx | 49 ++++---- 3 files changed, 91 insertions(+), 93 deletions(-) diff --git a/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py b/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py index 8f03a9a86d0..15bc7cafeac 100644 --- a/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py +++ b/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py @@ -990,7 +990,7 @@ def smallest_dynamical(f, dynatomic=True, start_n=1, prec=53, emb=None, algorith A binary form defining the periodic points is associated to ``f``. From this polynomial a bound on the search space can be determined. - ``f`` should already me a minimal model or finding the orbit + ``f`` should already be a minimal model or finding the orbit representatives may give wrong results. INPUT: @@ -1076,7 +1076,6 @@ def coshdelta(z): gn = g.nth_iterate_map(n) pts_poly = y*gn[0] - x*gn[1] d = ZZ(pts_poly.degree()) - # repeated_roots = (max([ex for p,ex in pts_poly.factor()]) > 1) max_mult = max([ex for p,ex in pts_poly.factor()]) while ((d < 3) or (max_mult >= d/2) and (n < 5)): n = n+1 @@ -1087,7 +1086,6 @@ def coshdelta(z): pts_poly = y*gn[0] - x*gn[1] d = ZZ(pts_poly.degree()) max_mult = max([ex for p,ex in pts_poly.factor()]) - # repeated_roots = (max([ex for p,ex in pts_poly.factor()]) > 1) assert(n<=4), "n > 4, failed to find usable poly" R = get_bound_dynamical(pts_poly, g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) @@ -1118,15 +1116,15 @@ def coshdelta(z): TI = matrix(ZZ,2,2,[1,-1,0,1]) count = 0 - pts = [[G, red_g, v0, rep, M*MG, coshdelta(v0), 0]] #label - 0:None, 1:S, 2:T, 3:T^(-1) + pts = [[G, red_g, v0, rep, M*MG, coshdelta(v0), 0]] # label - 0:None, 1:S, 2:T, 3:T^(-1) if current_min is None: current_min = [G, red_g, v0, rep, M*MG, coshdelta(v0)] while pts != []: G, g, v, rep, M, D, label = pts.pop() - #apply ST and keep z, Sz + # apply ST and keep z, Sz if D > R: break #all remaining pts are too far away - #check if it is smaller. If so, we can improve the bound + # check if it is smaller. If so, we can improve the bound count += 1 new_size = e**g.global_height(prec=prec) if new_size < current_size: @@ -1138,19 +1136,23 @@ def coshdelta(z): if new_R < R: R = new_R - #add new points to check - if label != 1 and min((rep+1).norm(), (rep-1).norm()) >= 1: #don't undo S - #do inversion if outside "bad" domain + # add new points to check + if label != 1 and min((rep+1).norm(), (rep-1).norm()) >= 1: # don't undo S + # the 2nd condition is equivalent to |\Re(-1/rep)| <= 1/2 + # this means that rep can have resulted from an inversion step in + # the shift-and-invert procedure, so don't invert + + # do inversion z = -1/v new_pt = [G.subs({x:-y, y:x}), g.conjugate(S), z, -1/rep, M*S, coshdelta(z), 1] pts = insert_item(pts, new_pt, 5) - if label != 3: #don't undo T on g - #do right shift + if label != 3: # don't undo T on g + # do right shift z = v-1 new_pt = [G.subs({x:x+y}), g.conjugate(TI), z, rep-1, M*TI, coshdelta(z), 2] pts = insert_item(pts, new_pt, 5) - if label != 2: #don't undo TI on g - #do left shift + if label != 2: # don't undo TI on g + # do left shift z = v+1 new_pt = [G.subs({x:x-y}), g.conjugate(T), z, rep+1, M*T, coshdelta(z), 3] pts = insert_item(pts, new_pt, 5) diff --git a/src/sage/rings/polynomial/binary_form_reduce.py b/src/sage/rings/polynomial/binary_form_reduce.py index 4f5c4b7d0b9..19af28b05ad 100644 --- a/src/sage/rings/polynomial/binary_form_reduce.py +++ b/src/sage/rings/polynomial/binary_form_reduce.py @@ -42,19 +42,19 @@ from sage.rings.real_mpfr import RealField from sage.symbolic.constants import e -def covariant_z0(F, z0_inv=False, prec=53, emb=None, error_limit = 0.000001): +def covariant_z0(F, z0_cov=False, prec=53, emb=None, error_limit=0.000001): """ - Return the `z_0` covariant and Julia invariant from Cremona-Stoll [CS2003]_. + Return the covariant and Julia invariant from Cremona-Stoll [CS2003]_. In [CS2003]_ and [HS2018]_ the Julia invariant is denoted as `\Theta(F)` or `R(F, z(F))`. Note that you may get faster convergence if you first move - `z_0(F)` to the fundamental domain before computing the true invariant + `z_0(F)` to the fundamental domain before computing the true covariant INPUT: - ``F`` -- binary form of degree at least 3 with no multiple roots - - ``z0_inv`` -- boolean, compute only the `z_0` invariant. Otherwise, solve + - ``z0_cov`` -- boolean, compute only the `z_0` invariant. Otherwise, solve the minimization problem - ``prec``-- positive integer. precision to use in CC @@ -72,7 +72,7 @@ def covariant_z0(F, z0_inv=False, prec=53, emb=None, error_limit = 0.000001): sage: R. = QQ[] sage: F = 19*x^8 - 262*x^7*y + 1507*x^6*y^2 - 4784*x^5*y^3 + 9202*x^4*y^4\ ....: - 10962*x^3*y^5 + 7844*x^2*y^6 - 3040*x*y^7 + 475*y^8 - sage: covariant_z0(F, prec=80, z0_inv=True) + sage: covariant_z0(F, prec=80, z0_cov=True) (1.3832330115323681438175 + 0.31233552177413614978744*I, 3358.4074848663492819259) sage: F = -x^8 + 6*x^7*y - 7*x^6*y^2 - 12*x^5*y^3 + 27*x^4*y^4\ @@ -84,17 +84,17 @@ def covariant_z0(F, z0_inv=False, prec=53, emb=None, error_limit = 0.000001): :: sage: R. = QQ[] - sage: covariant_z0(x^3 + 2*x^2*y - 3*x*y^2, z0_inv=True)[0] + sage: covariant_z0(x^3 + 2*x^2*y - 3*x*y^2, z0_cov=True)[0] 0.230769230769231 + 0.799408065031789*I - sage: -1/covariant_z0(-y^3 + 2*y^2*x + 3*y*x^2, z0_inv=True)[0] + sage: -1/covariant_z0(-y^3 + 2*y^2*x + 3*y*x^2, z0_cov=True)[0] 0.230769230769231 + 0.799408065031789*I :: sage: R. = QQ[] - sage: covariant_z0(2*x^2*y - 3*x*y^2, z0_inv=True)[0] + sage: covariant_z0(2*x^2*y - 3*x*y^2, z0_cov=True)[0] 0.750000000000000 + 1.29903810567666*I - sage: -1/covariant_z0(-x^3 - x^2*y + 2*x*y^2, z0_inv=True)[0] + 1 + sage: -1/covariant_z0(-x^3 - x^2*y + 2*x*y^2, z0_cov=True)[0] + 1 0.750000000000000 + 1.29903810567666*I :: @@ -111,7 +111,7 @@ def covariant_z0(F, z0_inv=False, prec=53, emb=None, error_limit = 0.000001): Traceback (most recent call last): ... ValueError: must be at least degree 3 - sage: covariant_z0((x+y)^3, z0_inv=True) + sage: covariant_z0((x+y)^3, z0_cov=True) Traceback (most recent call last): ... ValueError: cannot have multiple roots for z0 invariant @@ -160,7 +160,7 @@ def covariant_z0(F, z0_inv=False, prec=53, emb=None, error_limit = 0.000001): roots = f.roots() if (max([ex for p,ex in roots]) > 1)\ or (f.degree() < d-1): - if z0_inv: + if z0_cov: raise ValueError('cannot have multiple roots for z0 invariant') else: # just need a starting point for Newton's method @@ -175,13 +175,10 @@ def covariant_z0(F, z0_inv=False, prec=53, emb=None, error_limit = 0.000001): n = ZZ(f.degree()) PR = PolynomialRing(K,'x,y') x,y = PR.gens() - Q = [] # finds Stoll and Cremona's Q_0 - for j in range(len(roots)): - k = (1/(dF(roots[j]).abs()**(2/(n-2)))) * ((x-(roots[j]*y)) * (x-(roots[j].conjugate()*y))) - Q.append(k) + q = sum([(1/(dF(r).abs()**(2/(n-2)))) * ((x-(r*y)) * (x-(r.conjugate()*y)))\ + for r in roots]) # this is Q_0 , always positive def as long as F has distinct roots - q = sum([Q[i] for i in range(len(Q))]) A = q.monomial_coefficient(x**2) B = q.monomial_coefficient(x*y) C = q.monomial_coefficient(y**2) @@ -193,8 +190,8 @@ def covariant_z0(F, z0_inv=False, prec=53, emb=None, error_limit = 0.000001): if z.imag() < 0: z = (-B - ((B**2)-(4*A*C)).sqrt())/(2*A) - if z0_inv: - FM = f #for Julia's invariant + if z0_cov: + FM = f # for Julia's invariant else: # solve the minimization problem for 'true' covariant CF = ComplexIntervalField(prec=prec) # keeps trac of our precision error @@ -209,15 +206,15 @@ def covariant_z0(F, z0_inv=False, prec=53, emb=None, error_limit = 0.000001): for p,e in L1: if e >= d/2: raise ValueError('cannot have a root with multiplicity >= %s/2'%d) - for l in range(e): + for _ in range(e): L.append(p) RCF = PolynomialRing(CF, 'u,t') a = RCF(0) c = RCF(0) u,t = RCF.gens() - for j in range(len(L)): - a += u**2/((t-L[j]) * (t-L[j].conjugate()) + u**2) - c += (t-L[j].real())/((t-L[j]) * (t-L[j].conjugate()) + u**2) + for l in L: + a += u**2/((t-l) * (t-l.conjugate()) + u**2) + c += (t-l.real())/((t-l) * (t-l.conjugate()) + u**2) # Newton's Method, to find solutions. Error bound is less than diameter of our z err = z.diameter() zz = z.diameter() @@ -252,7 +249,7 @@ def covariant_z0(F, z0_inv=False, prec=53, emb=None, error_limit = 0.000001): uF = z.imag() th = FM.lc().abs()**2 for r,ex in FM.roots(): - for k in range(ex): + for _ in range(ex): th = th * ((((r-tF).abs())**2 + uF**2)/uF) # undo shift and invert (if needed) @@ -264,7 +261,7 @@ def covariant_z0(F, z0_inv=False, prec=53, emb=None, error_limit = 0.000001): #// compute inverse of eps_F #from Michael Stoll -def epsinv(F, target, prec=53, target_tol = 0.001, z = None, emb=None): +def epsinv(F, target, prec=53, target_tol=0.001, z=None, emb=None): """ Compute a bound on the hyperbolic distance. @@ -301,17 +298,18 @@ def coshdelta(z): return (z.norm() + 1)/(2*z.imag()) def RQ(delta): + # this is the quotient R(F_0,z)/R(F_0,z(F)) for a generic z + # at distance delta from j. See Lemma 4.2 in [HS2018]. cd = cosh(delta).n(prec=prec) sd = sinh(delta).n(prec=prec) return prod([cd + (cost*phi[0] + sint*phi[1])*sd for phi in phis]) def epsF(delta): - pol = RQ(delta); + pol = RQ(delta) #get R quotient in terms of z S = PolynomialRing(C, 'v') - g = S([(i-d)*pol[i-d] for i in range(2*d+1)]) + g = S([(i-d)*pol[i-d] for i in range(2*d+1)]) # take derivative drts = [e for e in g.roots(ring=C, multiplicities=False) if (e.norm()-1).abs() < 0.1] - #print(("A", pol)) - #print([pol(r/r.abs()).real() for r in drts]) + # find min return min([pol(r/r.abs()).real() for r in drts]) C = ComplexField(prec=prec) @@ -345,45 +343,46 @@ def epsF(delta): t = PR.gen(0) # compute phi_1, ..., phi_k # first find F_0 and its roots + # this change of variables on f moves z(f) to j, i.e. produces F_0 rts = f(z.imag()*t + z.real()).roots(ring=C) - phis = [] + phis = [] # stereographic projection of roots for r,e in rts: phis.extend([[2*r.real()/(r.norm()+1), (r.norm()-1)/(r.norm()+1)]]) - if d != f.degree(): + if d != f.degree(): # include roots at infinity phis.extend([(d-f.degree())*[0,1]]) + + # for writing RQ in terms of generic z to minimize LC = LaurentSeriesRing(C, 'u', default_prec=2*d+2) u = LC.gen(0) - cost = (u + u**(-1))/2; - sint = (u - u**(-1))/(2*C.gen(0)); - # first find an interval containing the desired value, then use regula falsi on log eps_F - #d -> delta value in interval [0,1]? - #v in value in interval [1,epsF(1)] - dl = R(0.0); vl = R(1.0); - du = R(1.0); vu = epsF(du); + cost = (u + u**(-1))/2 + sint = (u - u**(-1))/(2*C.gen(0)) + + # first find an interval containing the desired value + # then use regula falsi on log eps_F + # d -> delta value in interval [0,1] + # v in value in interval [1,epsF(1)] + dl = R(0.0); vl = R(1.0) + du = R(1.0); vu = epsF(du) while vu < target: - #compute the next value of epsF for delta = 2*delta - dl = du; vl = vu; - du *= 2; vu = epsF(du); + # compute the next value of epsF for delta = 2*delta + dl = du; vl = vu + du *= 2; vu = epsF(du) # now dl < delta <= du - #print(("B",dl,du,vl,vu)) logt = target.log() l2 = (vu.log() -logt).n(prec=prec) l1 = (vl.log()-logt).n(prec=prec) dn = (dl*l2 - du*l1)/(l2 - l1) - #print(("C",dn)) - vn = epsF(dn); - #print(("d",vn)) - dl = du; vl = vu; - du = dn; vu = vn; + vn = epsF(dn) + dl = du; vl = vu + du = dn; vu = vn while (du-dl).abs() >= target_tol or max(vl, vu) < target: - #print((dl,du,vl,vu)) l2 = (vu.log() -logt).n(prec=prec) l1 = (vl.log()-logt).n(prec=prec) dn = (dl*l2 - du*l1)/(l2 - l1) - vn = epsF(dn); - dl = du; vl = vu; - du = dn; vu = vn; - return max(dl, du); + vn = epsF(dn) + dl = du; vl = vu + du = dn; vu = vn + return max(dl, du) ################### def get_bound_poly(F, prec=53, norm_type='norm', emb=None): @@ -568,7 +567,11 @@ def coshdelta(z): #add new points to check if label != 1 and min((rep+1).norm(), (rep-1).norm()) >= 1: #don't undo S - #do inversion if outside "bad" domain + # the 2nd condition is equivalent to |\Re(-1/rep)| <= 1/2 + # this means that rep can have resulted from an inversion step in + # the shift-and-invert procedure, so don't invert + + # do inversion z = -1/v new_pt = [G.subs({x:-y, y:x}), z, -1/rep, M*S, coshdelta(z), 1] pts = insert_item(pts, new_pt, 4) diff --git a/src/sage/rings/polynomial/multi_polynomial.pyx b/src/sage/rings/polynomial/multi_polynomial.pyx index 8f69435e22c..828961d03ed 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -2073,10 +2073,11 @@ cdef class MPolynomial(CommutativeRingElement): A portion of the algorithm uses Newton's method to find a solution to a system of equations. If Newton's method fails to converge to a point - in the upper half plane, the function will use the less precise `Q_0` - covariant as defined in [CS2003]_. Additionally, if this polynomial has + in the upper half plane, the function will use the less precise `z_0` + covariant from the `Q_0` form as defined on page 7 of [CS2003]_. + Additionally, if this polynomial has a root with multiplicity at lease half the total degree of the polynomial, - then we must also use the `Q_0` covariant. See [CS2003]_ for details. + then we must also use the `z_0` covariant. See [CS2003]_ for details. Note that, if the covariant is within ``error_limit`` of the boundry but outside the fundamental domain, our function will erroneously move @@ -2256,37 +2257,33 @@ cdef class MPolynomial(CommutativeRingElement): error_limit = kwds.get('error_limit', 0.000001) emb = kwds.get('emb', None) - #getting a numerical approximation of the roots of our polynomial + # getting a numerical approximation of the roots of our polynomial CF = ComplexIntervalField(prec=prec) # keeps trac of our precision error RF = RealField(prec=prec) R = self.parent() x,y = R.gens() - #finding quadratic Q_0, gives us our convariant, z_0 + # finding quadratic Q_0, gives us our convariant, z_0 from sage.rings.polynomial.binary_form_reduce import covariant_z0 try: - z, th = covariant_z0(self, prec=prec, emb=emb, z0_inv=True) + z, th = covariant_z0(self, prec=prec, emb=emb, z0_cov=True) except ValueError:# multiple roots F = self.lc()*prod([p for p,e in self.factor()]) - z, th = covariant_z0(F, prec=prec, emb=emb, z0_inv=True) + z, th = covariant_z0(F, prec=prec, emb=emb, z0_cov=True) z = CF(z) - # this moves z to our fundamental domain using the three steps laid + # this moves z_0 to our fundamental domain using the three steps laid # out in the algorithim by [CS2003] # this is found in section 5 of their paper M = matrix(QQ, [[1,0], [0,1]]) # used to keep track of how our z is moved. zc = z.center() while zc.real() < RF(-0.5) or zc.real() >= RF(0.5) or (zc.real() <= RF(0) and zc.abs() < RF(1))\ or (zc.real() > RF(0) and zc.abs() <= RF(1)): - if zc.real() < RF(-0.5): # moves z into fundamental domain by m - m = zc.real().abs().round() # finds amount to move z's real part by + if (zc.real() < RF(-0.5)) or (zc.real() >= RF(0.5)): + # moves z into fundamental domain by m + m = zc.real().round() # finds amount to move z's real part by Qm = QQ(m) - M = M * matrix(QQ, [[1,-Qm], [0,1]]) # move - z += m # M.inverse()*z is supposed to move z by m - elif zc.real() >= RF(0.5): # moves z into fundamental domain by m - m = zc.real().round() - Qm = QQ(m) - M = M * matrix(QQ, [[1,Qm], [0,1]]) #move z - z -= m + M = M * matrix(QQ, [[1,Qm], [0,1]]) # move + z -= m # M.inverse()*z is supposed to move z by m elif (zc.real() <= RF(0) and zc.abs() < RF(1)) or (zc.real() > RF(0) and zc.abs() <= RF(1)): # flips z z = -1/z M = M * matrix(QQ, [[0,-1], [1,0]])# multiply on left because we are taking inverse matrices @@ -2307,19 +2304,15 @@ cdef class MPolynomial(CommutativeRingElement): # moves our z to fundamental domain as before while zc.real() < RF(-0.5) or zc.real() >= RF(0.5) or (zc.real() <= RF(0) and zc.abs() < RF(1))\ or (zc.real() > RF(0) and zc.abs() <= RF(1)): - if zc.real() < RF(-0.5): - m = zc.real().abs().round() - Qm = QQ(m) - M = M*matrix(QQ, [[1,-Qm], [0,1]]) - z += m # M.inverse()*Z is supposed to move z by m - elif zc.real() >= RF(0.5): #else if - m = zc.real().round() + if (zc.real() < RF(-0.5)) or (zc.real() >= RF(0.5)): + # moves z into fundamental domain by m + m = zc.real().round() # finds amount to move z's real part by Qm = QQ(m) - M = M * matrix(QQ, [[1,Qm], [0,1]]) - z -= m - elif (zc.real() <= RF(0) and zc.abs() < RF(1)) or (zc.real() > RF(0) and zc.abs() <= RF(1)): + M = M * matrix(QQ, [[1,Qm], [0,1]]) # move + z -= m # M.inverse()*z is supposed to move z by m + elif (zc.real() <= RF(0) and zc.abs() < RF(1)) or (zc.real() > RF(0) and zc.abs() <= RF(1)): # flips z z = -1/z - M = M * matrix(QQ, [[0,-1],[ 1,0]]) + M = M * matrix(QQ, [[0,-1], [1,0]])# multiply on left because we are taking inverse matrices zc = z.center() if return_conjugation: From 6e2d52d4276083347ca38d654fd11b0a3a128b29 Mon Sep 17 00:00:00 2001 From: Aaron Lauve Date: Fri, 31 Aug 2018 19:05:16 -0500 Subject: [PATCH 120/264] First draft of implementing Nakayama's Conjecture (now a theorem) to correct the errors in .cpi/s. * Added method to compute preimages of p-cores. * Overwrote inherited methods .central_orthogonal_idempotent/s. --- src/sage/combinat/symmetric_group_algebra.py | 254 ++++++++++++++----- 1 file changed, 186 insertions(+), 68 deletions(-) diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index dccfb25ed47..15cf7691bc8 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -11,13 +11,14 @@ from six.moves import range from sage.misc.cachefunc import cached_method -from .combinatorial_algebra import CombinatorialAlgebra -from .free_module import CombinatorialFreeModule +from sage.combinat.combinatorial_algebra import CombinatorialAlgebra +from sage.combinat.free_module import CombinatorialFreeModule from sage.algebras.group_algebra import GroupAlgebra_class from sage.categories.weyl_groups import WeylGroups +from sage.categories.algebras import Algebras from sage.combinat.permutation import Permutation, Permutations, from_permutation_group_element -from . import partition -from .tableau import Tableau, StandardTableaux_size, StandardTableaux_shape, StandardTableaux +from sage.combinat import partition +from sage.combinat.tableau import Tableau, StandardTableaux_size, StandardTableaux_shape, StandardTableaux from sage.interfaces.all import gap from sage.rings.all import QQ, PolynomialRing from sage.arith.all import factorial @@ -956,105 +957,156 @@ def retract_okounkov_vershik(self, f, m): def cpis(self): """ - Return a list of the centrally primitive idempotents of - ``self``. + Return the primitive central orthogonal idempotents for ``self``. + + TESTS:: + + sage: QS3 = SymmetricGroupAlgebra(QQ,3) + sage: QS3.cps() == QS3.central_orthogonal_idempotents() + True + """ + from sage.misc.superseded import deprecation + deprecation(25942, "This method (=cpis) is deprecated. Use central_orthogonal_idempotents instead.") + return central_orthogonal_idempotents(self) + + def central_orthogonal_idempotents(self): + r""" + Return the primitive central orthogonal idempotents for ``self``. This method supercedes the default implementation of central orthogonal idempotents found within :class:`sage.categories.finite_dimensional_semisimple_algebras_with_basis.FiniteDimensionalSemisimpleAlgebrasWithBasis`. + If ``self.base_ring()`` has characteristic `p > 0`, the idempotents + are indexed by the distinct `p`-cores `\mu` for partitions of `n`, + as described in Nakayama's Conjecture (now a theorem). The + corresponding idempotent is given by + + .. MATH:: + + f_\mu = \sum_{\lambda\vdash n} e_\lambda + + where `e_\lambda` is the classical idempotent (defined over `QQ`) and + the sum runs over all `\lambda` with `p`-core `\mu`. + + .. SEEALSO:: + + - :meth:`_block_ingredients` + - :meth:`sage.combinat.partition.Partition.core` + EXAMPLES:: sage: QS3 = SymmetricGroupAlgebra(QQ,3) - sage: a = QS3.cpis() + sage: a = QS3.central_orthogonal_idempotents() sage: a[0] # [3] 1/6*[1, 2, 3] + 1/6*[1, 3, 2] + 1/6*[2, 1, 3] + 1/6*[2, 3, 1] + 1/6*[3, 1, 2] + 1/6*[3, 2, 1] sage: a[1] # [2, 1] 2/3*[1, 2, 3] - 1/3*[2, 3, 1] - 1/3*[3, 1, 2] - sage: QS3.cpis == QS3.central_orthogonal_idempotents - True TESTS: Check this works with other indexing sets:: sage: G = SymmetricGroup(3).algebra(QQ) - sage: a = G.cpis() + sage: a = G.central_orthogonal_idempotents() sage: a[0] 1/6*() + 1/6*(2,3) + 1/6*(1,2) + 1/6*(1,2,3) + 1/6*(1,3,2) + 1/6*(1,3) sage: a[1] 2/3*() - 1/3*(1,2,3) - 1/3*(1,3,2) - """ - return tuple(self.cpi(p) for p in partition.Partitions_n(self.n)) - - central_orthogonal_idempotents = cpis - @cached_method - def cpi(self, p): + sage: G = SymmetricGroup(3).algebra(GF(2)) + sage: a = G.central_orthogonal_idempotents() + sage: a[0] + (1,2,3) + (1,3,2) + sage: a[1] + () + (1,2,3) + (1,3,2) + + Check this works in positive characteristic:: + + sage: def test_n_with_primes(n, primes): + ....: Sn = {p:SymmetricGroupAlgebra(GF(p), n) for p in primes} + ....: for p in primes: + ....: idems = Sn[p].central_orthogonal_idempotents() + ....: tst = [sum(idems)==Sn[p].one()] + ....: for i in range(len(idems)-1): + ....: e = idems[i] + ....: for j in range(i, len(idems)): + ....: f = idems[j] + ....: if i == j: + ....: tst.append(e*e == e) + ....: else: + ....: tst.append(e*f == 0) + ....: print "%s blocks for p=%s ... tests pass?"%( \ + ....: len(idems), p, all(tst) ) + sage: test_n_with_primes(5, [2,3,5,7]) # long time + 2 blocks for p=2 ... tests pass? True + 3 blocks for p=3 ... tests pass? True + 3 blocks for p=5 ... tests pass? True + 7 blocks for p=7 ... tests pass? True """ - Return the centrally primitive idempotent for the symmetric group - of order `n` corresponding to the irreducible representation - indexed by the partition ``p``. - - EXAMPLES:: - - sage: QS3 = SymmetricGroupAlgebra(QQ,3) - sage: QS3.cpi(Partition([2,1])) - 2/3*[1, 2, 3] - 1/3*[2, 3, 1] - 1/3*[3, 1, 2] - sage: QS3.cpi(Partition([3])) - 1/6*[1, 2, 3] + 1/6*[1, 3, 2] + 1/6*[2, 1, 3] + 1/6*[2, 3, 1] + 1/6*[3, 1, 2] + 1/6*[3, 2, 1] - sage: QS3.cpi(Partition([1,1,1])) - 1/6*[1, 2, 3] - 1/6*[1, 3, 2] - 1/6*[2, 1, 3] + 1/6*[2, 3, 1] + 1/6*[3, 1, 2] - 1/6*[3, 2, 1] + R = self.base_ring() + G = self._indices + blocks_partition = self._block_ingredients() + idems = [] + for block in blocks_partition: + idems.append( sum(cpi_over_QQ(la) for la in block) ) + denoms = set(R(c.denominator()) for e in idems for c in e.coefficients()) + if not all(denoms): + return None + else: + return [self.sum_of_terms((G(g), R(c)) for (g, c) in iter(e)) for e in idems] - sage: QS0 = SymmetricGroupAlgebra(QQ, 0) - sage: QS0.cpi(Partition([])) - [] + def central_orthogonal_idempotent(self, la): + r""" + Return the central idempotent for the symmetric group of + order `n` corresponding to the irreducible representation + (over `QQ`) indexed by the partition ``la``, if this + element is well-defined over ``self.base_ring()``. - TESTS:: + Otherwise, return ``None``. - sage: QS3.cpi(Partition([2,2])) - Traceback (most recent call last): - ... - TypeError: p (= [2, 2]) must be a partition of n (= 3) - sage: QS4_Z3 = SymmetricGroupAlgebra(GF(3), 4) - sage: QS4_Z3.cpi(Partition([1,3])) - Traceback (most recent call last): - ... - ValueError: [1, 3] is not an element of Partitions - sage: QS4_Z3.cpi([3,1]) - Traceback (most recent call last): - ... - TypeError: unhashable type: 'list' + .. SEEALSO:: - sage: QS4_Z3.cpi(Partition([2,2])) - Traceback (most recent call last): - ... - TypeError: Symmetric group algebra of order 4 - over Finite Field of size 3 is not a semisimple algebra + :meth:`~sage.combinat.symmetric_group_algebra.cpi_over_QQ` """ R = self.base_ring() - if p not in partition.Partitions_n(self.n): - raise TypeError("p (= {p}) must be a partition of n (= {n})".format(p=p, n=self.n)) + G = self._indices + cpi = cpi_over_QQ(la) + denoms = set(R(c.denominator()) for c in cpi.coefficients()) + if not all(denoms): + return None + else: + return self.sum_of_terms((G(g), R(c)) for (g, c) in iter(cpi)) - character_table = eval(gap.eval("Display(Irr(SymmetricGroup(%d)));"%self.n)) + def _block_ingredients(self): + r""" + Return the partitions of ``self.n``, themselves partitioned by + their distinct `p`-cores, where `p` is the characteristic of + ``self.base_ring()`` - np = partition.Partitions_n(self.n).list() - np.reverse() - p_index = np.index(p) + If this characteristic is zero, we take the `p`-core operation + to be the identity map on partitions. - big_coeff = character_table[p_index][0] / factorial(self.n) + These lists of partitions, say with common `p`-core `\lambda`, + are components of the central orthogonal idempotent + corresponding to `\lambda`. - character_row = character_table[p_index] - P = Permutations(self.n) - R = self.base_ring() - dct = {} - for g in P: - coeff = big_coeff * character_row[np.index(g.cycle_type())] - if not R(coeff.denominator()): - raise TypeError("%s is not a semisimple algebra"%self) + .. SEEALSO:: + + :meth:`central_orthogonal_idempotents` + """ + p = self.base_ring().characteristic() + if not p: + return [[la] for la in partition.Partitions(self.n)] + + blocks = {} + for la in partition.Partitions_n(self.n): + c = la.core(p) + if c in blocks: + blocks[c].append(la) else: - dct[self._indices(g)] = R(coeff) - return self._from_dict(dct) + blocks[c] = [la] + return [blocks[c] for c in sorted(blocks.keys(), reverse=True)] @cached_method def algebra_generators(self): @@ -1814,6 +1866,72 @@ def epsilon_ik(self, itab, ktab, star=0, mult='l2r'): else: return z.map_support(lambda x: x.inverse()) +@cached_method(key=lambda x: partition.Partitions()(x)) +def cpi_over_QQ(la): + """ + Return the primitive central idempotent for the symmetric group + algebra over `QQ` of order ``n`` corresponding to the + irreducible representation indexed by the partition ``la``. + + EXAMPLES:: + + sage: from sage.combinat.symmetric_group_algebra import cpi_over_QQ + sage: e = cpi_over_QQ([2,1]); e + 2/3*[1, 2, 3] - 1/3*[2, 3, 1] - 1/3*[3, 1, 2] + sage: cpi_over_QQ([3]) + 1/6*[1, 2, 3] + 1/6*[1, 3, 2] + 1/6*[2, 1, 3] + 1/6*[2, 3, 1] + 1/6*[3, 1, 2] + 1/6*[3, 2, 1] + sage: cpi_over_QQ([1,1,1]) + 1/6*[1, 2, 3] - 1/6*[1, 3, 2] - 1/6*[2, 1, 3] + 1/6*[2, 3, 1] + 1/6*[3, 1, 2] - 1/6*[3, 2, 1] + + sage: cpi_over_QQ(Partition([])) + [] + + TESTS:: + + sage: cpi_over_QQ([1,3]) + Traceback (most recent call last): + ... + ValueError: [1, 3] is not an element of Partitions of the integer 4 + sage: e = cpi_over_QQ([3,1]); e + 3/8*[1, 2, 3, 4] + 1/8*[1, 2, 4, 3] + 1/8*[1, 3, 2, 4] + + 1/8*[1, 4, 3, 2] + 1/8*[2, 1, 3, 4] - 1/8*[2, 1, 4, 3] + - 1/8*[2, 3, 4, 1] - 1/8*[2, 4, 1, 3] - 1/8*[3, 1, 4, 2] + + 1/8*[3, 2, 1, 4] - 1/8*[3, 4, 1, 2] - 1/8*[3, 4, 2, 1] + - 1/8*[4, 1, 2, 3] + 1/8*[4, 2, 3, 1] - 1/8*[4, 3, 1, 2] + - 1/8*[4, 3, 2, 1] + + sage: QS4_Z3 = SymmetricGroupAlgebra(GF(3), 4) + sage: e in QS4_Z3 + False + sage: QS4_Z3.sum_of_terms((g, GF(3)(c)) for (g, c) in iter(e)) + 2*[1, 2, 4, 3] + 2*[1, 3, 2, 4] + 2*[1, 4, 3, 2] + + 2*[2, 1, 3, 4] + [2, 1, 4, 3] + [2, 3, 4, 1] + + [2, 4, 1, 3] + [3, 1, 4, 2] + 2*[3, 2, 1, 4] + + [3, 4, 1, 2] + [3, 4, 2, 1] + [4, 1, 2, 3] + + 2*[4, 2, 3, 1] + [4, 3, 1, 2] + [4, 3, 2, 1] + """ + if la not in partition.Partitions(): + raise TypeError("lambda (= {0}) must be a partition of a nonnegative integer".format(la)) + else: + la = partition.Partitions()(la) + n = la.size() + + character_table = eval(gap.eval("Display(Irr(SymmetricGroup(%d)));"%n)) + + np = partition.Partitions_n(n).list() + np.reverse() + la_index = np.index(la) + + big_coeff = character_table[la_index][0] / factorial(n) + + character_row = character_table[la_index] + P = Permutations(n) + dct = {} + for g in P: + coeff = big_coeff * character_row[np.index(g.cycle_type())] + dct[P(g)] = coeff + return SymmetricGroupAlgebra(QQ, n)._from_dict(dct) + epsilon_ik_cache = {} def epsilon_ik(itab, ktab, star=0): From ff672fc23d93ec67f595348827cc69e2e07bcf12 Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Sun, 2 Sep 2018 13:28:19 +0300 Subject: [PATCH 121/264] retract to subalgebra now checks that the element is contained in the subalgebra --- src/sage/algebras/lie_algebras/subalgebra.py | 51 ++++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index dcacac27fcb..acca9b49079 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -501,6 +501,44 @@ def _repr_(self): gens = gens[0] return "Subalgebra generated by %s of %s" % (gens, self.ambient()) + def retract(self, X): + r""" + Retract ``X`` to ``self``. + + INPUT: + + - ``X`` -- an element of the ambient Lie algebra + + EXAMPLES: + + Retraction to a subalgebra of a free nilpotent Lie algebra:: + + sage: L = LieAlgebra(QQ, 3, step=2) + sage: L.inject_variables() + Defining X_1, X_2, X_3, X_12, X_13, X_23 + sage: S = L.subalgebra([X_1, X_2]) + sage: el = S.retract(2*X_1 + 3*X_2 + 5*X_12); el + 2*X_1 + 3*X_2 + 5*X_12 + sage: el.parent() + Subalgebra generated by (X_1, X_2) of Free Nilpotent Lie algebra on + 6 generators (X_1, X_2, X_3, X_12, X_13, X_23) over Rational Field + + Retraction raises an error if the element is not contained in the + subalgebra:: + + sage: S.retract(X_3) + Traceback (most recent call last): + ... + ValueError: the element X_3 is not in Subalgebra generated + by (X_1, X_2) of Free Nilpotent Lie algebra on 6 generators + (X_1, X_2, X_3, X_12, X_13, X_23) over Rational Field + """ + if X not in self: + raise ValueError("the element %s is not in %s" % (X, self)) + + sup = super(LieSubalgebra_finite_dimensional_with_basis, self) + return sup.retract(X) + @cached_method def basis(self): r""" @@ -546,16 +584,20 @@ def from_vector(self, v): sage: S = L.subalgebra([X, Y]) sage: S.dimension() 2 - sage: S.from_vector([1, 2]) + sage: el = S.from_vector([1, 2]); el X + 2*Y + sage: el.parent() == S + True An element from a vector of the ambient module - sage: S.from_vector([1, 2, 3]) - X + 2*Y + 3*Z + sage: el = S.from_vector([1, 2, 0]); el + X + 2*Y + sage: el.parent() == S + True """ if len(v) == self.ambient().dimension(): - return self.ambient().from_vector(v) + return self.retract(self.ambient().from_vector(v)) sup = super(LieSubalgebra_finite_dimensional_with_basis, self) return sup.from_vector(v) @@ -582,6 +624,7 @@ def basis_matrix(self): """ return self.ambient_submodule().basis_matrix() + @cached_method def ambient_submodule(self): r""" Return the submodule of the ambient Lie algebra From 2d7c00706e3bc93cc85d81ef7d2f77c8632ea86a Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Sun, 2 Sep 2018 14:02:26 +0300 Subject: [PATCH 122/264] Added repr_short method to ideals mimicking ring ideals --- src/sage/algebras/lie_algebras/ideal.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/lie_algebras/ideal.py b/src/sage/algebras/lie_algebras/ideal.py index 47333473988..9cb4e880578 100644 --- a/src/sage/algebras/lie_algebras/ideal.py +++ b/src/sage/algebras/lie_algebras/ideal.py @@ -141,10 +141,21 @@ def _repr_(self): sage: L.ideal([X, Y]) Ideal (X, Y) of Abelian Lie algebra on 2 generators (X, Y) over Rational Field """ - gens = self.gens() - if len(gens) == 1: - gens = "(%s)" % gens[0] - return "Ideal %s of %s" % (gens, self.ambient()) + return "Ideal %s of %s" % (self._repr_short(), self.ambient()) + + def _repr_short(self): + """ + Represent the list of generators. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: L.ideal([X, Y])._repr_short() + '(X, Y)' + sage: L.ideal(X)._repr_short() + '(X)' + """ + return '(%s)' % (', '.join(str(X) for X in self.gens())) # for submodule computations, the order of the basis is reversed so that # the pivot elements in the echelon form are the leading terms From bbef2071fbd1bf74d87a0bd3d7a9d36ca9f550fe Mon Sep 17 00:00:00 2001 From: David Coudert Date: Sun, 2 Sep 2018 14:11:38 +0200 Subject: [PATCH 123/264] trac #25598: fix wikipedia links --- src/sage/graphs/connectivity.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index b17ebadd04a..2127c25309a 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2688,7 +2688,7 @@ class TriconnectivitySPQR: This class implements the algorithm proposed by Hopcroft and Tarjan in [Hopcroft1973]_, and later corrected by Gutwenger and Mutzel in [Gut2001]_, for finding the triconnected components of a biconnected graph. It then - organizes these components into a SPQR-tree (See :wikipedia:`SPQR_tree`). + organizes these components into a SPQR-tree. See the:wikipedia:`SPQR_tree`. A SPQR-tree is a tree data structure used to represent the triconnected components of a biconnected (multi)graph and the 2-vertex cuts separating @@ -2726,7 +2726,7 @@ class TriconnectivitySPQR: EXAMPLES: - :wikipedia:`SPQR_tree` reference paper example:: + Example from the :wikipedia:`SPQR_tree`:: sage: from sage.graphs.connectivity import TriconnectivitySPQR sage: from sage.graphs.connectivity import spqr_tree_to_graph From 209c75541d4f6a4bfe7ba1c469b14a9ffe66a6e8 Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Sun, 2 Sep 2018 16:34:10 +0300 Subject: [PATCH 124/264] Fixed error in testing if a subalgebra or ideal is an ideal of a Lie algebra --- src/sage/algebras/lie_algebras/ideal.py | 20 ++++++++++++ src/sage/algebras/lie_algebras/subalgebra.py | 33 ++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/sage/algebras/lie_algebras/ideal.py b/src/sage/algebras/lie_algebras/ideal.py index 9cb4e880578..62800890e4e 100644 --- a/src/sage/algebras/lie_algebras/ideal.py +++ b/src/sage/algebras/lie_algebras/ideal.py @@ -289,3 +289,23 @@ def reduce(self, X): pass return X + + @cached_method + def is_ideal(self, A): + """ + Return if ``self`` is an ideal of ``A``. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'x': 1}}) + sage: I = L.ideal(x) + sage: I.is_ideal(L) + True + sage: I.is_ideal(I) + True + sage: L.is_ideal(I) + False + """ + if A == self.ambient(): + return True + return super(LieIdeal_finite_dimensional_with_basis, self).is_ideal(A) diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index acca9b49079..ab50a714f19 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -21,6 +21,7 @@ from sage.categories.homset import Hom from sage.categories.morphism import SetMorphism from sage.categories.sets_cat import Sets +from sage.matrix.constructor import matrix from sage.misc.cachefunc import cached_method from sage.modules.free_module_element import vector from sage.sets.family import Family @@ -645,6 +646,38 @@ def ambient_submodule(self): ambientbasis = [self.lift(X).to_vector() for X in self.basis()] return m.submodule_with_basis(ambientbasis) + @cached_method + def is_ideal(self, A): + """ + Return if ``self`` is an ideal of ``A``. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) + sage: S1 = L.subalgebra([x]) + sage: S1.is_ideal(L) + False + sage: S2 = L.subalgebra([x, y]) + sage: S2.is_ideal(L) + True + sage: S3 = L.subalgebra([y, z]) + sage: S3.is_ideal(L) + True + """ + if A == self: + return True + if A not in LieAlgebras(self.base_ring()).FiniteDimensional().WithBasis(): + raise NotImplementedError("A must be a finite dimensional" + " Lie algebra with basis") + B = self.basis() + AB = A.basis() + try: + b_mat = matrix(A.base_ring(), [A.bracket(b, ab).to_vector() + for b in B for ab in AB]) + except (ValueError, TypeError): + return False + return b_mat.row_space().is_submodule(self.ambient_submodule()) + class Element(LieSubset.Element): def __getitem__(self, i): From a441a596d549e18f35e810db6b24ad3882e3e37c Mon Sep 17 00:00:00 2001 From: Aaron Lauve Date: Sun, 2 Sep 2018 08:58:29 -0500 Subject: [PATCH 125/264] allow p-cores as input for central_orthogonal_idempotent --- src/sage/combinat/symmetric_group_algebra.py | 190 ++++++++++++++----- 1 file changed, 138 insertions(+), 52 deletions(-) diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index 15cf7691bc8..5069595860d 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -962,12 +962,15 @@ def cpis(self): TESTS:: sage: QS3 = SymmetricGroupAlgebra(QQ,3) - sage: QS3.cps() == QS3.central_orthogonal_idempotents() + sage: cpis = QS3.cpis() + doctest:...: DeprecationWarning: Method (cpis) is deprecated; use central_orthogonal_idempotents instead. + See http://trac.sagemath.org/25942 for details. + sage: cpis == QS3.central_orthogonal_idempotents() True """ from sage.misc.superseded import deprecation - deprecation(25942, "This method (=cpis) is deprecated. Use central_orthogonal_idempotents instead.") - return central_orthogonal_idempotents(self) + deprecation(25942, "DeprecationWarning: Method (cpis) is deprecated; use central_orthogonal_idempotents instead.") + return self.central_orthogonal_idempotents() def central_orthogonal_idempotents(self): r""" @@ -977,22 +980,9 @@ def central_orthogonal_idempotents(self): central orthogonal idempotents found within :class:`sage.categories.finite_dimensional_semisimple_algebras_with_basis.FiniteDimensionalSemisimpleAlgebrasWithBasis`. - If ``self.base_ring()`` has characteristic `p > 0`, the idempotents - are indexed by the distinct `p`-cores `\mu` for partitions of `n`, - as described in Nakayama's Conjecture (now a theorem). The - corresponding idempotent is given by - - .. MATH:: - - f_\mu = \sum_{\lambda\vdash n} e_\lambda - - where `e_\lambda` is the classical idempotent (defined over `QQ`) and - the sum runs over all `\lambda` with `p`-core `\mu`. - .. SEEALSO:: - - :meth:`_block_ingredients` - - :meth:`sage.combinat.partition.Partition.core` + - :meth:`central_orthogonal_idempotent` EXAMPLES:: @@ -1036,68 +1026,164 @@ def central_orthogonal_idempotents(self): ....: tst.append(e*e == e) ....: else: ....: tst.append(e*f == 0) - ....: print "%s blocks for p=%s ... tests pass?"%( \ - ....: len(idems), p, all(tst) ) + ....: print("{0} blocks for p={1} ... {2}".format( len(idems), p, all(tst) )) sage: test_n_with_primes(5, [2,3,5,7]) # long time - 2 blocks for p=2 ... tests pass? True - 3 blocks for p=3 ... tests pass? True - 3 blocks for p=5 ... tests pass? True - 7 blocks for p=7 ... tests pass? True + 2 blocks for p=2 ... True + 3 blocks for p=3 ... True + 3 blocks for p=5 ... True + 7 blocks for p=7 ... True """ - R = self.base_ring() - G = self._indices - blocks_partition = self._block_ingredients() - idems = [] - for block in blocks_partition: - idems.append( sum(cpi_over_QQ(la) for la in block) ) - denoms = set(R(c.denominator()) for e in idems for c in e.coefficients()) - if not all(denoms): - return None - else: - return [self.sum_of_terms((G(g), R(c)) for (g, c) in iter(e)) for e in idems] + out = [] + blocks = self._blocks_dictionary() + for key in sorted(blocks.keys(), reverse=True): + out.append(self.central_orthogonal_idempotent(key)) + return out - def central_orthogonal_idempotent(self, la): + def central_orthogonal_idempotent(self, la, block=True): r""" Return the central idempotent for the symmetric group of - order `n` corresponding to the irreducible representation - (over `QQ`) indexed by the partition ``la``, if this - element is well-defined over ``self.base_ring()``. + order `n` corresponding to the indecomposable block to which + the partition ``la`` is associated. + + If ``self.base_ring()`` contains `QQ`, this corresponds to + the classical central idempotent corresponding to the + irreducible representation indexed by ``la``. + + Alternatively, if ``self.base_ring()`` has characteristic + `p > 0`, then Nakayama's Conjecture provides that ``la`` is + associated to an idempotent `f_\mu` where `\mu` is the + `p`-core of ``la``. This `f_\mu` is a sum of classical + idempotents, + + .. MATH:: + + f_\mu = \sum_{core(\lambda)=\mu} e_\lambda, + + where the sum ranges over the partitions `\lambda` of `n` + with `p`-core equal to `\mu`. + + INPUT: + + - ``la`` -- A partition of ``self.n`` or a `p`-core of such + a partition, if ``self.base_ring().characteristic() == p``. + + - ``block`` -- boolean (default: ``True``). When this + optional variable is set to ``False``, the method attempts + to return the classical idempotent associated to ``la`` + (defined over `QQ`). If the corresponding coefficients are + not defined over ``self.base_ring()``, return ``None``. - Otherwise, return ``None``. + EXAMPLES: + + Asking for block idempotents in any characteristic, by + passing a partition of ``self.n``:: + + sage: S0 = SymmetricGroup(4).algebra(QQ) + sage: S2 = SymmetricGroup(4).algebra(GF(2)) + sage: S3 = SymmetricGroup(4).algebra(GF(3)) + sage: S0.central_orthogonal_idempotent([2,1,1]) + 3/8*() - 1/8*(3,4) - 1/8*(2,3) - 1/8*(2,4) - 1/8*(1,2) + - 1/8*(1,2)(3,4) + 1/8*(1,2,3,4) + 1/8*(1,2,4,3) + + 1/8*(1,3,4,2) - 1/8*(1,3) - 1/8*(1,3)(2,4) + + 1/8*(1,3,2,4) + 1/8*(1,4,3,2) - 1/8*(1,4) + + 1/8*(1,4,2,3) - 1/8*(1,4)(2,3) + sage: S2.central_orthogonal_idempotent([2,1,1]) + () + sage: S3.central_orthogonal_idempotent([4]) + () + (1,2)(3,4) + (1,3)(2,4) + (1,4)(2,3) + sage: _ == S3.central_orthogonal_idempotent([1,1,1,1]) + True + + Asking for block idempotents in any characteristic, by + passing `p`-cores:: + + sage: S0.central_orthogonal_idempotent([1,1]) + Traceback (most recent call last): + ... + ValueError: [1, 1] is not a partition of integer 4 + sage: S2.central_orthogonal_idempotent([]) + () + sage: S2.central_orthogonal_idempotent([1]) + Traceback (most recent call last): + ... + ValueError: [1].core(2) (= [1]) is not a 2-core of a partition of 4 + sage: S3.central_orthogonal_idempotent([1]) + () + (1,2)(3,4) + (1,3)(2,4) + (1,4)(2,3) + + Asking for classical idempotents (taking ``block=False``):: + + sage: S3.central_orthogonal_idempotent([2,2], block=False) is None + True + sage: S3.central_orthogonal_idempotent([2,1,1], block=False) + (3,4) + (2,3) + (2,4) + (1,2) + (1,2)(3,4) + 2*(1,2,3,4) + + 2*(1,2,4,3) + 2*(1,3,4,2) + (1,3) + (1,3)(2,4) + + 2*(1,3,2,4) + 2*(1,4,3,2) + (1,4) + 2*(1,4,2,3) + + (1,4)(2,3) .. SEEALSO:: - :meth:`~sage.combinat.symmetric_group_algebra.cpi_over_QQ` + - :meth:`sage.combinat.partition.Partition.core` + - :meth:`sage.combinat.symmetric_group_algebra.cpi_over_QQ` """ + if not la in partition.Partitions(): + raise ValueError("{0} is not a partition of a nonnegative integer".format(la)) + R = self.base_ring() + p = R.characteristic() G = self._indices - cpi = cpi_over_QQ(la) + + if not block or not p: + if sum(la) != self.n: + raise ValueError("{0} is not a partition of integer {1}".format(la, self.n)) + cpi = cpi_over_QQ(la) + else: + mu = partition.Partitions()(la).core(p) + try: + block = self._blocks_dictionary()[mu] + cpi = sum(cpi_over_QQ(lam) for lam in block) + except KeyError: + raise ValueError("{0}.core({1}) (= {2}) is not a {1}-core of a partition of {3}".format(la, p, mu, self.n)) + denoms = set(R(c.denominator()) for c in cpi.coefficients()) if not all(denoms): return None else: return self.sum_of_terms((G(g), R(c)) for (g, c) in iter(cpi)) - def _block_ingredients(self): + def _blocks_dictionary(self): r""" - Return the partitions of ``self.n``, themselves partitioned by - their distinct `p`-cores, where `p` is the characteristic of - ``self.base_ring()`` + Return the partitions of ``self.n``, themselves partitioned + by their distinct `p`-cores, where `p` is the characteristic + of ``self.base_ring()`` - If this characteristic is zero, we take the `p`-core operation + If the characteristic is zero, we take the `p`-core operation to be the identity map on partitions. - These lists of partitions, say with common `p`-core `\lambda`, + These lists of partitions, say with common `p`-core `\mu`, are components of the central orthogonal idempotent - corresponding to `\lambda`. + corresponding to `\mu`. + + TESTS:: + + sage: B2 = SymmetricGroupAlgebra(GF(2), 4)._blocks_dictionary() + sage: [tuple(B2[key]) for key in sorted(B2.keys())] + [([4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1])] + sage: B3 = SymmetricGroupAlgebra(GF(3), 4)._blocks_dictionary() + sage: [tuple(B3[key]) for key in sorted(B3.keys())] + [([4], [2, 2], [1, 1, 1, 1]), ([2, 1, 1],), ([3, 1],)] + sage: B5 = SymmetricGroupAlgebra(GF(5), 4)._blocks_dictionary() + sage: [tuple(B5[key]) for key in sorted(B5.keys())] + [([1, 1, 1, 1],), ([2, 1, 1],), ([2, 2],), ([3, 1],), ([4],)] + sage: B5 == SymmetricGroupAlgebra(QQ, 4)._blocks_dictionary() + True .. SEEALSO:: - :meth:`central_orthogonal_idempotents` + :meth:`central_orthogonal_idempotent` """ p = self.base_ring().characteristic() if not p: - return [[la] for la in partition.Partitions(self.n)] + return {la:[la] for la in partition.Partitions(self.n)} blocks = {} for la in partition.Partitions_n(self.n): @@ -1106,7 +1192,7 @@ def _block_ingredients(self): blocks[c].append(la) else: blocks[c] = [la] - return [blocks[c] for c in sorted(blocks.keys(), reverse=True)] + return blocks @cached_method def algebra_generators(self): @@ -1911,7 +1997,7 @@ def cpi_over_QQ(la): + 2*[4, 2, 3, 1] + [4, 3, 1, 2] + [4, 3, 2, 1] """ if la not in partition.Partitions(): - raise TypeError("lambda (= {0}) must be a partition of a nonnegative integer".format(la)) + raise ValueError("{0} is not an element of Partitions of the integer 4".format(la)) else: la = partition.Partitions()(la) n = la.size() From efa01f7a9237f179e1b7295060c4eb508d150503 Mon Sep 17 00:00:00 2001 From: Aaron Lauve Date: Sun, 2 Sep 2018 13:59:44 -0500 Subject: [PATCH 126/264] setup deprecated_function_alias --- src/sage/combinat/symmetric_group_algebra.py | 33 ++++++-------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index 5069595860d..c10308ebd0d 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -8,26 +8,27 @@ # http://www.gnu.org/licenses/ #***************************************************************************** from __future__ import print_function, absolute_import +import itertools +import six from six.moves import range from sage.misc.cachefunc import cached_method +from sage.misc.superseded import deprecated_function_alias from sage.combinat.combinatorial_algebra import CombinatorialAlgebra from sage.combinat.free_module import CombinatorialFreeModule -from sage.algebras.group_algebra import GroupAlgebra_class -from sage.categories.weyl_groups import WeylGroups -from sage.categories.algebras import Algebras from sage.combinat.permutation import Permutation, Permutations, from_permutation_group_element +from sage.combinat.permutation_cython import (left_action_same_n, right_action_same_n) from sage.combinat import partition from sage.combinat.tableau import Tableau, StandardTableaux_size, StandardTableaux_shape, StandardTableaux +from sage.algebras.group_algebra import GroupAlgebra_class +from sage.categories.weyl_groups import WeylGroups +from sage.categories.algebras import Algebras from sage.interfaces.all import gap from sage.rings.all import QQ, PolynomialRing from sage.arith.all import factorial from sage.matrix.all import matrix from sage.modules.all import vector from sage.groups.perm_gps.permgroup_element import PermutationGroupElement -import itertools -from sage.combinat.permutation_cython import (left_action_same_n, right_action_same_n) -import six # TODO: Remove this function and replace it with the class # TODO: Create parents for other bases (such as the seminormal basis) @@ -955,23 +956,6 @@ def retract_okounkov_vershik(self, f, m): dct[p_ret] += coeff return RSm._from_dict(dct) - def cpis(self): - """ - Return the primitive central orthogonal idempotents for ``self``. - - TESTS:: - - sage: QS3 = SymmetricGroupAlgebra(QQ,3) - sage: cpis = QS3.cpis() - doctest:...: DeprecationWarning: Method (cpis) is deprecated; use central_orthogonal_idempotents instead. - See http://trac.sagemath.org/25942 for details. - sage: cpis == QS3.central_orthogonal_idempotents() - True - """ - from sage.misc.superseded import deprecation - deprecation(25942, "DeprecationWarning: Method (cpis) is deprecated; use central_orthogonal_idempotents instead.") - return self.central_orthogonal_idempotents() - def central_orthogonal_idempotents(self): r""" Return the primitive central orthogonal idempotents for ``self``. @@ -1150,6 +1134,9 @@ def central_orthogonal_idempotent(self, la, block=True): else: return self.sum_of_terms((G(g), R(c)) for (g, c) in iter(cpi)) + cpis = deprecated_function_alias(25942, central_orthogonal_idempotents) + cpi = deprecated_function_alias(25942, central_orthogonal_idempotent) + def _blocks_dictionary(self): r""" Return the partitions of ``self.n``, themselves partitioned From c9471eebeafc03f7e5d8a764687143f5cde8902c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 2 Sep 2018 21:39:41 +0200 Subject: [PATCH 127/264] conversion of fricas Record type, expose fricas differential equation solver --- src/sage/calculus/desolvers.py | 52 ++++++++++++++++++++++++++++++++-- src/sage/interfaces/fricas.py | 4 +++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/sage/calculus/desolvers.py b/src/sage/calculus/desolvers.py index 24bc544690b..5639e4727c5 100644 --- a/src/sage/calculus/desolvers.py +++ b/src/sage/calculus/desolvers.py @@ -88,7 +88,49 @@ maxima = Maxima() -def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False): +def fricas_desolve(de, dvar, ivar, ics): + """ + Solve a ODE via FriCAS. + + TESTS: + + A 3rd order linear equation:: + + sage: y = function("y")(x) + sage: de = x^3*diff(y, x, 3) + x^2*diff(y, x, 2) - 2*x*diff(y, x) + 2*y - 2*x^4 + sage: Y = desolve(de, y, algorithm="fricas"); Y # optional - fricas + (2*x^3 - 3*x^2 + 1)*_C0/x + (x^3 - 1)*_C1/x + (x^3 - 3*x^2 - 1)*_C2/x + 1/15*(x^5 - 10*x^3 + 20*x^2 + 4)/x + sage: de.substitute_function(y.operator(), lambda x: Y).simplify_full() # optional - fricas + 0 + + With initial conditions:: + + sage: Y = desolve(de, y, ics=[1,3,7], algorithm="fricas"); Y # optional - fricas + 1/15*(x^5 + 15*x^3 + 50*x^2 - 21)/x + + A non-linear equation:: + + sage: de = diff(y, x) == y/(x+y*log(y)) + sage: Y = desolve(de, y, algorithm="fricas"); Y # optional - fricas + 1/2*(log(y(x))^2*y(x) - 2*x)/y(x) + + """ + from sage.interfaces.fricas import fricas + if ics is None: + y = fricas(de).solve(dvar.operator(), ivar).sage() + else: + eq = fricas.equation(ivar, ics[0]) + y = fricas(de).solve(dvar.operator(), eq, ics[1:]).sage() + + if isinstance(y, dict): + basis = y["basis"] + particular = y["particular"] + return particular + sum(var("_C"+str(i))*v for i, v in enumerate(basis)) + else: + return y + + +def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, algorithm=None): r""" Solves a 1st or 2nd order linear ODE via Maxima, including IVP and BVP. @@ -452,6 +494,12 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) if len(ivars) != 1: raise ValueError("Unable to determine independent variable, please specify.") ivar = ivars[0] + if algorithm == "fricas": + return fricas_desolve(de, dvar, ivar, ics) + + elif algorithm is not None and algorithm != "maxima": + raise ValueError("Unknown algorithm: %s" % algorithm) + de00 = de._maxima_() P = de00.parent() dvar_str=P(dvar.operator()).str() @@ -1767,5 +1815,3 @@ def desolve_tides_mpfr(f, ics, initial, final, delta, tolrel=1e-16, tolabs=1e-1 res[i] = [RealField(ceil(digits*log(10,2)))(_) for _ in res[i].split(' ') if len(_) > 2] shutil.rmtree(tempdir) return res - - diff --git a/src/sage/interfaces/fricas.py b/src/sage/interfaces/fricas.py index 51eb5f5046b..1a1b3a992c5 100644 --- a/src/sage/interfaces/fricas.py +++ b/src/sage/interfaces/fricas.py @@ -1428,6 +1428,10 @@ def _sage_(self): # now translate domains which cannot be coerced to InputForm, # or where we do not need it. head = str(domain.car()) + if head == "Record": + fields = fricas("[string symbol(e.2) for e in rest destruct %s]"%domain._name).sage() + return {field: self.elt(field).sage() for field in fields} + if head == "List": n = P.get_integer('#(%s)' %self._name) return [P.new('elt(%s,%s)' %(self._name, k)).sage() for k in range(1, n+1)] From a67ee28641a0c4c06c184c732cf1a32ae1d22e52 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 3 Sep 2018 10:07:10 +0200 Subject: [PATCH 128/264] add fricas system solver, adapt doctests --- src/sage/calculus/desolvers.py | 115 ++++++++++++++++++++++++++------- src/sage/interfaces/fricas.py | 5 ++ 2 files changed, 95 insertions(+), 25 deletions(-) diff --git a/src/sage/calculus/desolvers.py b/src/sage/calculus/desolvers.py index 5639e4727c5..687cfeaf9de 100644 --- a/src/sage/calculus/desolvers.py +++ b/src/sage/calculus/desolvers.py @@ -88,34 +88,31 @@ maxima = Maxima() -def fricas_desolve(de, dvar, ivar, ics): +def fricas_desolve(de, dvar, ics, ivar): """ - Solve a ODE via FriCAS. + Solve an ODE using FriCAS. - TESTS: - - A 3rd order linear equation:: + EXAMPLES:: - sage: y = function("y")(x) - sage: de = x^3*diff(y, x, 3) + x^2*diff(y, x, 2) - 2*x*diff(y, x) + 2*y - 2*x^4 - sage: Y = desolve(de, y, algorithm="fricas"); Y # optional - fricas - (2*x^3 - 3*x^2 + 1)*_C0/x + (x^3 - 1)*_C1/x + (x^3 - 3*x^2 - 1)*_C2/x + 1/15*(x^5 - 10*x^3 + 20*x^2 + 4)/x - sage: de.substitute_function(y.operator(), lambda x: Y).simplify_full() # optional - fricas - 0 + sage: x = var('x') + sage: y = function('y')(x) + sage: desolve(diff(y,x) + y - 1, y, algorithm="fricas") # optional - fricas + _C0*e^(-x) + 1 - With initial conditions:: + sage: desolve(diff(y, x) + y == y^3*sin(x), y, algorithm="fricas") # optional - fricas + -1/5*(2*cos(x)*y(x)^2 + 4*sin(x)*y(x)^2 - 5)*e^(-2*x)/y(x)^2 - sage: Y = desolve(de, y, ics=[1,3,7], algorithm="fricas"); Y # optional - fricas - 1/15*(x^5 + 15*x^3 + 50*x^2 - 21)/x + TESTS:: - A non-linear equation:: + sage: from sage.calculus.desolvers import fricas_desolve + sage: Y = fricas_desolve(diff(y,x) + y - 1, y, [42,1783], x) # optional - fricas + sage: Y.subs(x=42) # optional - fricas + 1783 - sage: de = diff(y, x) == y/(x+y*log(y)) - sage: Y = desolve(de, y, algorithm="fricas"); Y # optional - fricas - 1/2*(log(y(x))^2*y(x) - 2*x)/y(x) """ from sage.interfaces.fricas import fricas + from sage.symbolic.ring import SR if ics is None: y = fricas(de).solve(dvar.operator(), ivar).sage() else: @@ -125,14 +122,40 @@ def fricas_desolve(de, dvar, ivar, ics): if isinstance(y, dict): basis = y["basis"] particular = y["particular"] - return particular + sum(var("_C"+str(i))*v for i, v in enumerate(basis)) + return particular + sum(SR.var("_C"+str(i))*v for i, v in enumerate(basis)) else: return y +def fricas_desolve_system(des, dvars, ics, ivar): + """ + Solve a system of ODEs using FriCAS. + + TESTS:: + + sage: from sage.calculus.desolvers import fricas_desolve_system + sage: t = var('t') + sage: x = function('x')(t) + sage: y = function('y')(t) + sage: de1 = diff(x,t) + y - 1 == 0 + sage: de2 = diff(y,t) - x + 1 == 0 + sage: fricas_desolve_system([de1, de2], [x, y], None, t) + {'basis': [(cos(t), sin(t)), (sin(t), -cos(t))], + 'particular': (cos(t)^2 + sin(t)^2, 1)} + + """ + from sage.interfaces.fricas import fricas + from sage.symbolic.ring import SR + dvars = [dvar.operator() for dvar in dvars] + if ics is None: + y = fricas(des).solve(dvars, ivar).sage() + else: + raise NotImplementedError("Solving systems of differential equations with initial conditions using FriCAS is not yet implemented") + + return y def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, algorithm=None): r""" - Solves a 1st or 2nd order linear ODE via Maxima, including IVP and BVP. + Solve a 1st or 2nd order linear ODE, including IVP and BVP. INPUT: @@ -173,6 +196,12 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, can be used only if the result is one SymbolicEquation (does not contain a singular solution, for example). + - ``algorithm`` - (optional, default None) -- one of + + - 'maxima' - use maxima (the default) + + - 'fricas' - use FriCAS (the optional fricas spkg has to be installed) + OUTPUT: In most cases return a SymbolicEquation which defines the solution @@ -209,7 +238,6 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, sage: desolve(de, y) _K2*e^(-x) + _K1*e^x - x - :: sage: f = desolve(de, y, [10,2,1]); f @@ -416,6 +444,26 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, sage: desolve(diff(y,x,2)+2*diff(y,x)+y == 0,y,[0,3,pi/2,2],show_method=True) [(2*x*(2*e^(1/2*pi) - 3)/pi + 3)*e^(-x), 'constcoeff'] + Using algorithm='fricas' we can invoke FriCAS' differential + equation solver. For example, it can solve higher order linear + equations:: + + sage: de = x^3*diff(y, x, 3) + x^2*diff(y, x, 2) - 2*x*diff(y, x) + 2*y - 2*x^4 + sage: Y = desolve(de, y, algorithm="fricas"); Y # optional - fricas + (2*x^3 - 3*x^2 + 1)*_C0/x + (x^3 - 1)*_C1/x + (x^3 - 3*x^2 - 1)*_C2/x + 1/15*(x^5 - 10*x^3 + 20*x^2 + 4)/x + + The initial conditions are then interpreted as `[x_0, y(x_0), + y'(x_0), \dots, y^(n)(x_0)]`:: + + sage: Y = desolve(de, y, ics=[1,3,7], algorithm="fricas"); Y # optional - fricas + 1/15*(x^5 + 15*x^3 + 50*x^2 - 21)/x + + FriCAS can also solve some non-linear equations:: + + sage: de = diff(y, x) == y/(x+y*log(y)) + sage: Y = desolve(de, y, algorithm="fricas"); Y # optional - fricas + 1/2*(log(y(x))^2*y(x) - 2*x)/y(x) + TESTS: :trac:`9961` fixed (allow assumptions on the dependent variable in desolve):: @@ -495,8 +543,7 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, raise ValueError("Unable to determine independent variable, please specify.") ivar = ivars[0] if algorithm == "fricas": - return fricas_desolve(de, dvar, ivar, ics) - + return fricas_desolve(de, dvar, ics, ivar) elif algorithm is not None and algorithm != "maxima": raise ValueError("Unknown algorithm: %s" % algorithm) @@ -760,7 +807,7 @@ def sanitize_var(exprs): # 'y(x) -> y(x) return soln -def desolve_system(des, vars, ics=None, ivar=None): +def desolve_system(des, vars, ics=None, ivar=None, algorithm=None): """ Solve a system of any size of 1st order ODEs. Initial conditions are optional. @@ -780,6 +827,12 @@ def desolve_system(des, vars, ics=None, ivar=None): specified if there is more than one independent variable in the equation. + - ``algorithm`` - (optional, default None) -- one of + + - 'maxima' - use maxima (the default) + + - 'fricas' - use FriCAS (the optional fricas spkg has to be installed) + EXAMPLES:: sage: t = var('t') @@ -791,6 +844,12 @@ def desolve_system(des, vars, ics=None, ivar=None): [x(t) == (x(0) - 1)*cos(t) - (y(0) - 1)*sin(t) + 1, y(t) == (y(0) - 1)*cos(t) + (x(0) - 1)*sin(t) + 1] + The same system solved using FriCAS:: + + sage: desolve_system([de1, de2], [x,y], algorithm='fricas') # optional - fricas + {'basis': [(cos(t), sin(t)), (sin(t), -cos(t))], + 'particular': (cos(t)^2 + sin(t)^2, 1)} + Now we give some initial conditions:: sage: sol = desolve_system([de1, de2], [x,y], ics=[0,1,2]); sol @@ -863,7 +922,7 @@ def desolve_system(des, vars, ics=None, ivar=None): if len(ics) != (len(vars) + 1): raise ValueError("Initial conditions aren't complete: number of vars is different from number of dependent variables. Got ics = {0}, vars = {1}".format(ics, vars)) - if len(des)==1: + if len(des)==1 and algorithm is None: return desolve_laplace(des[0], vars[0], ics=ics, ivar=ivar) ivars = set([]) for i, de in enumerate(des): @@ -875,6 +934,12 @@ def desolve_system(des, vars, ics=None, ivar=None): if len(ivars) != 1: raise ValueError("Unable to determine independent variable, please specify.") ivar = list(ivars)[0] + + if algorithm == "fricas": + return fricas_desolve_system(des, vars, ics, ivar) + elif algorithm is not None and algorithm != "maxima": + raise ValueError("Unknown algorithm: %s" % algorithm) + dvars = [v._maxima_() for v in vars] if ics is not None: ivar_ic = ics[0] diff --git a/src/sage/interfaces/fricas.py b/src/sage/interfaces/fricas.py index 1a1b3a992c5..6886397c894 100644 --- a/src/sage/interfaces/fricas.py +++ b/src/sage/interfaces/fricas.py @@ -1408,6 +1408,7 @@ def _sage_(self): from sage.symbolic.ring import SR from sage.symbolic.all import I from sage.matrix.constructor import matrix + from sage.modules.free_module_element import vector from sage.structure.factorization import Factorization from sage.misc.sage_eval import sage_eval @@ -1436,6 +1437,10 @@ def _sage_(self): n = P.get_integer('#(%s)' %self._name) return [P.new('elt(%s,%s)' %(self._name, k)).sage() for k in range(1, n+1)] + if head == "Vector": + n = P.get_integer('#(%s)' %self._name) + return vector([P.new('elt(%s,%s)' %(self._name, k)).sage() for k in range(1, n+1)]) + if head == "Matrix": base_ring = self._get_sage_type(domain[1]) rows = P.new('listOfLists(%s)' %self._name).sage() From 75bd25902623e741755f92639b93e288c6144266 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 3 Sep 2018 22:19:33 +0200 Subject: [PATCH 129/264] adapt output format, add ability to use initial conditions for system solver --- src/sage/calculus/desolvers.py | 45 ++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/sage/calculus/desolvers.py b/src/sage/calculus/desolvers.py index 687cfeaf9de..cf3d7eb64ea 100644 --- a/src/sage/calculus/desolvers.py +++ b/src/sage/calculus/desolvers.py @@ -128,30 +128,55 @@ def fricas_desolve(de, dvar, ics, ivar): def fricas_desolve_system(des, dvars, ics, ivar): """ - Solve a system of ODEs using FriCAS. + Solve a system of first order ODEs using FriCAS. + + EXAMPLES:: + + sage: t = var('t') + sage: x = function('x')(t) + sage: y = function('y')(t) + sage: de1 = diff(x,t) + y - 1 == 0 + sage: de2 = diff(y,t) - x + 1 == 0 + sage: desolve_system([de1, de2], [x, y], algorithm="fricas") # optional - fricas + [x(t) == _C0*cos(t) + cos(t)^2 + _C1*sin(t) + sin(t)^2, + y(t) == -_C1*cos(t) + _C0*sin(t) + 1] + + sage: desolve_system([de1, de2], [x,y], [0,1,2], algorithm="fricas") # optional - fricas + [x(t) == cos(t)^2 + sin(t)^2 - sin(t), y(t) == cos(t) + 1] TESTS:: sage: from sage.calculus.desolvers import fricas_desolve_system sage: t = var('t') sage: x = function('x')(t) + sage: fricas_desolve_system([diff(x,t) + 1 == 0], [x], None, t) # optional - fricas + [x(t) == _C0 - t] + sage: y = function('y')(t) sage: de1 = diff(x,t) + y - 1 == 0 sage: de2 = diff(y,t) - x + 1 == 0 - sage: fricas_desolve_system([de1, de2], [x, y], None, t) - {'basis': [(cos(t), sin(t)), (sin(t), -cos(t))], - 'particular': (cos(t)^2 + sin(t)^2, 1)} + sage: sol = fricas_desolve_system([de1,de2], [x,y], [0,1,-1], t); sol # optional - fricas + [x(t) == cos(t)^2 + sin(t)^2 + 2*sin(t), y(t) == -2*cos(t) + 1] """ from sage.interfaces.fricas import fricas from sage.symbolic.ring import SR - dvars = [dvar.operator() for dvar in dvars] + ops = [dvar.operator() for dvar in dvars] + y = fricas(des).solve(ops, ivar).sage() + basis = y["basis"] + particular = y["particular"] + pars = [SR.var("_C"+str(i)) for i in range(len(basis))] + solv = particular + sum(p*v for p, v in zip(pars, basis)) + if ics is None: - y = fricas(des).solve(dvars, ivar).sage() + sols = solv else: - raise NotImplementedError("Solving systems of differential equations with initial conditions using FriCAS is not yet implemented") + ics0 = ics[0] + eqs = [val == sol.subs({ivar: ics0}) for val, sol in zip(ics[1:], solv)] + pars_values = solve(eqs, pars, solution_dict=True) + sols = [sol.subs(pars_values[0]) for sol in solv] - return y + return [dvar == sol for dvar, sol in zip(dvars, sols)] def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, algorithm=None): r""" @@ -847,8 +872,8 @@ def desolve_system(des, vars, ics=None, ivar=None, algorithm=None): The same system solved using FriCAS:: sage: desolve_system([de1, de2], [x,y], algorithm='fricas') # optional - fricas - {'basis': [(cos(t), sin(t)), (sin(t), -cos(t))], - 'particular': (cos(t)^2 + sin(t)^2, 1)} + [x(t) == _C0*cos(t) + cos(t)^2 + _C1*sin(t) + sin(t)^2, + y(t) == -_C1*cos(t) + _C0*sin(t) + 1] Now we give some initial conditions:: From a4e5412826d97db6d104ee3af25207654bb06bea Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 3 Sep 2018 22:26:43 +0200 Subject: [PATCH 130/264] forgot import --- src/sage/calculus/desolvers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/calculus/desolvers.py b/src/sage/calculus/desolvers.py index cf3d7eb64ea..8fbd4ab2de4 100644 --- a/src/sage/calculus/desolvers.py +++ b/src/sage/calculus/desolvers.py @@ -161,6 +161,7 @@ def fricas_desolve_system(des, dvars, ics, ivar): """ from sage.interfaces.fricas import fricas from sage.symbolic.ring import SR + from sage.symbolic.relation import solve ops = [dvar.operator() for dvar in dvars] y = fricas(des).solve(ops, ivar).sage() basis = y["basis"] From 78527b4895c743fa2b771b33dbbc5ec2de1c7827 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 3 Sep 2018 22:29:57 +0200 Subject: [PATCH 131/264] pyflakes: remove import --- src/sage/calculus/desolvers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/calculus/desolvers.py b/src/sage/calculus/desolvers.py index 8fbd4ab2de4..a90f91b7c2b 100644 --- a/src/sage/calculus/desolvers.py +++ b/src/sage/calculus/desolvers.py @@ -80,7 +80,6 @@ from sage.calculus.functional import diff from sage.misc.functional import N from sage.misc.decorators import rename_keyword -from tempfile import mkdtemp import shutil import os from sage.rings.real_mpfr import RealField From da86caf41a9836d0bd961bed82badc4ead159adf Mon Sep 17 00:00:00 2001 From: Ben Hutz Date: Tue, 4 Sep 2018 17:06:43 -0500 Subject: [PATCH 132/264] 25952: fix doctest --- src/sage/schemes/projective/projective_morphism.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/schemes/projective/projective_morphism.py b/src/sage/schemes/projective/projective_morphism.py index 96d77807daa..f38fb0457b7 100644 --- a/src/sage/schemes/projective/projective_morphism.py +++ b/src/sage/schemes/projective/projective_morphism.py @@ -1717,16 +1717,16 @@ def reduced_form(self, prec=300, return_conjugation=True, error_limit=0.000001): ( Dynamical System of Projective Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (x : y) to - (x^3 : 3*x^2*y + y^3) - , + (x^3 + 3*x*y^2 : y^3) , - [ -1 0] - [221 -1] + [ 0 -1] + [ 1 221] ) """ from sage.misc.superseded import deprecation deprecation(23479, "use sage.dynamics.arithmetic_dynamics.projective_ds.reduced_form instead") - return self.as_dynamical_system().reduced_form(prec, return_conjugation, error_limit) + return self.as_dynamical_system().reduced_form(prec=prec,\ + return_conjugation=return_conjugation, error_limit=error_limit) class SchemeMorphism_polynomial_projective_space_field(SchemeMorphism_polynomial_projective_space): From fbe27e85e925f6433767f55aa4c6dd3c8ab5c651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 5 Sep 2018 09:43:02 +0200 Subject: [PATCH 133/264] some care for magma interface --- src/sage/interfaces/magma.py | 25 ++----------------- .../polynomial/multi_polynomial_ideal.py | 2 +- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/sage/interfaces/magma.py b/src/sage/interfaces/magma.py index 7237ea40ba7..ed50de476c6 100644 --- a/src/sage/interfaces/magma.py +++ b/src/sage/interfaces/magma.py @@ -2777,29 +2777,6 @@ def magma_console(): console('sage-native-execute magma') -def magma_version(): - """ - Return the version of Magma that you have in your PATH on your - computer. - - OUTPUT: - - - - ``numbers`` - 3-tuple: major, minor, etc. - - - ``string`` - version as a string - - - EXAMPLES:: - - sage: magma_version() # random, optional - magma - ((2, 14, 9), 'V2.14-9') - """ - from sage.misc.superseded import deprecation - deprecation(20388, 'This function has been deprecated. Use magma.version() instead.') - return magma.version() - - class MagmaGBLogPrettyPrinter: """ A device which filters Magma Groebner basis computation logs. @@ -2883,6 +2860,8 @@ def __init__(self, verbosity=1, style='magma'): Highest degree reached during computation: 3. """ self.verbosity = verbosity + if style not in ['sage', 'magma']: + raise ValueError('style must be sage or magma') self.style = style self.curr_deg = 0 # current degree diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal.py b/src/sage/rings/polynomial/multi_polynomial_ideal.py index 9baf6b0ea2b..f6dddf6ba2e 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ideal.py +++ b/src/sage/rings/polynomial/multi_polynomial_ideal.py @@ -389,7 +389,7 @@ def _groebner_basis_magma(self, deg_bound=None, prot=False, magma=magma_default) sage: I = sage.rings.ideal.Cyclic(R,6) sage: gb = I.groebner_basis('magma:GroebnerBasis', deg_bound=4) # indirect doctest; optional - magma sage: len(gb) # optional - magma - 7 + 5 """ R = self.ring() if not deg_bound: From d351b1b0d4ad3a2fe74168cf947372243132e7dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 5 Sep 2018 10:03:05 +0200 Subject: [PATCH 134/264] py3: hash for construction functors --- src/sage/categories/pushout.py | 90 ++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 20 deletions(-) diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index bd274e8fbf9..a478916f239 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -18,6 +18,7 @@ # TODO, think through the rankings, and override pushout where necessary. + class ConstructionFunctor(Functor): """ Base class for construction functors. @@ -104,10 +105,10 @@ def __mul__(self, other): Compose ``self`` and ``other`` to a composite construction functor, unless one of them is the identity. - NOTE: + .. NOTE:: - The product is in functorial notation, i.e., when applying the - product to an object, the second factor is applied first. + The product is in functorial notation, i.e., when applying the + product to an object, the second factor is applied first. TESTS:: @@ -139,11 +140,11 @@ def pushout(self, other): """ Composition of two construction functors, ordered by their ranks. - NOTE: + .. NOTE:: - - This method seems not to be used in the coercion model. + - This method seems not to be used in the coercion model. - - By default, the functor with smaller rank is applied first. + - By default, the functor with smaller rank is applied first. TESTS:: @@ -196,12 +197,28 @@ def __ne__(self, other): """ return not (self == other) + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: from sage.categories.pushout import IdentityConstructionFunctor + sage: I = IdentityConstructionFunctor() + sage: F = QQ.construction()[0] + sage: hash(I) == hash(F) + False + sage: hash(I) == hash(I) + True + """ + return hash(repr(self)) + def _repr_(self): """ - NOTE: + .. NOTE:: - By default, it returns the name of the construction functor's class. - Usually, this method will be overloaded. + By default, it returns the name of the construction + functor's class. Usually, this method will be overloaded. TESTS:: @@ -221,10 +238,10 @@ def merge(self, other): """ Merge ``self`` with another construction functor, or return None. - NOTE: + .. NOTE:: - The default is to merge only if the two functors coincide. But this - may be overloaded for subclasses, such as the quotient functor. + The default is to merge only if the two functors coincide. But this + may be overloaded for subclasses, such as the quotient functor. EXAMPLES:: @@ -494,14 +511,16 @@ def __ne__(self, other): """ return not (self == other) + __hash__ = ConstructionFunctor.__hash__ + def __mul__(self, other): """ Compose construction functors to a composit construction functor, unless one of them is the identity. - NOTE: + .. NOTE:: - The product is in functorial notation, i.e., when applying the product to an object - then the second factor is applied first. + The product is in functorial notation, i.e., when applying the product to an object + then the second factor is applied first. EXAMPLES:: @@ -648,6 +667,8 @@ def __ne__(self, other): """ return not (self == other) + __hash__ = ConstructionFunctor.__hash__ + def __mul__(self, other): """ Compose construction functors to a composit construction functor, unless one of them is the identity. @@ -897,6 +918,8 @@ def __ne__(self, other): """ return not (self == other) + __hash__ = ConstructionFunctor.__hash__ + def merge(self, other): """ Merge ``self`` with another construction functor, or return None. @@ -938,6 +961,7 @@ def _repr_(self): """ return "Poly[%s]" % self.var + class MultiPolynomialFunctor(ConstructionFunctor): """ A constructor for multivariate polynomial rings. @@ -1039,6 +1063,8 @@ def __ne__(self, other): """ return not (self == other) + __hash__ = ConstructionFunctor.__hash__ + def __mul__(self, other): """ If two MPoly functors are given in a row, form a single MPoly functor @@ -1127,7 +1153,6 @@ def _repr_(self): return "MPoly[%s]" % ','.join(self.vars) - class InfinitePolynomialFunctor(ConstructionFunctor): """ A Construction Functor for Infinite Polynomial Rings (see :mod:`~sage.rings.polynomial.infinite_polynomial_ring`). @@ -1308,6 +1333,8 @@ def __ne__(self, other): """ return not(self == other) + __hash__ = ConstructionFunctor.__hash__ + def __mul__(self, other): """ Compose construction functors to a composite construction functor, unless one of them is the identity. @@ -1491,10 +1518,10 @@ def expand(self): True """ - if len(self._gens)==1: + if len(self._gens) == 1: return [self] - return [InfinitePolynomialFunctor((x,), self._order, self._imple) for x in reversed(self._gens)] - + return [InfinitePolynomialFunctor((x,), self._order, self._imple) + for x in reversed(self._gens)] class MatrixFunctor(ConstructionFunctor): @@ -1603,6 +1630,8 @@ def __ne__(self, other): """ return not (self == other) + __hash__ = ConstructionFunctor.__hash__ + def merge(self, other): """ Merging is only happening if both functors are matrix functors of the same dimension. @@ -1632,6 +1661,7 @@ def merge(self, other): else: return MatrixFunctor(self.nrows, self.ncols, self.is_sparse and other.is_sparse) + class LaurentPolynomialFunctor(ConstructionFunctor): """ Construction functor for Laurent polynomial rings. @@ -1757,6 +1787,8 @@ def __ne__(self, other): """ return not (self == other) + __hash__ = ConstructionFunctor.__hash__ + def merge(self, other): """ Two Laurent polynomial construction functors merge if the variable names coincide. @@ -1918,6 +1950,8 @@ def __ne__(self, other): """ return not (self == other) + __hash__ = ConstructionFunctor.__hash__ + def merge(self, other): """ Two constructors of free modules merge, if the module ranks and the inner products coincide. If both @@ -1982,6 +2016,7 @@ def merge(self, other): else: return VectorFunctor(self.n, self.is_sparse and other.is_sparse, self.inner_product_matrix) + class SubspaceFunctor(ConstructionFunctor): """ Constructing a subspace of an ambient free module, given by a basis. @@ -2157,6 +2192,8 @@ def __ne__(self, other): """ return not (self == other) + __hash__ = ConstructionFunctor.__hash__ + def merge(self, other): """ Two Subspace Functors are merged into a construction functor of the sum of two subspaces. @@ -2217,6 +2254,7 @@ def merge(self, other): else: return None + class FractionField(ConstructionFunctor): """ Construction functor for fraction fields. @@ -2472,6 +2510,8 @@ def __ne__(self, other): """ return not (self == other) + __hash__ = ConstructionFunctor.__hash__ + def merge(self, other): """ Two Completion functors are merged, if they are equal. If the precisions of @@ -2611,7 +2651,8 @@ def commutes(self,other): ... CoercionException: Don't know how to apply Completion[+Infinity, prec=+Infinity] to 7-adic Ring with capped relative precision 20 """ - return isinstance(other,FractionField) + return isinstance(other, FractionField) + class QuotientFunctor(ConstructionFunctor): """ @@ -2770,6 +2811,8 @@ def __ne__(self, other): """ return not (self == other) + __hash__ = ConstructionFunctor.__hash__ + def merge(self, other): """ Two quotient functors with coinciding names are merged by taking the gcd of their moduli. @@ -2813,6 +2856,7 @@ def merge(self, other): # yield a field if either self or other yields a field. return QuotientFunctor(gcd, names=self.names, as_field=self.as_field or other.as_field) + class AlgebraicExtensionFunctor(ConstructionFunctor): """ Algebraic extension (univariate polynomial ring modulo principal ideal). @@ -3049,6 +3093,8 @@ def __ne__(self, other): """ return not (self == other) + __hash__ = ConstructionFunctor.__hash__ + def merge(self,other): """ Merging with another :class:`AlgebraicExtensionFunctor`. @@ -3322,6 +3368,7 @@ def merge(self, other): # if isinstance(other,AlgebraicExtensionFunctor): # return self + class PermutationGroupFunctor(ConstructionFunctor): rank = 10 @@ -3395,6 +3442,7 @@ def merge(self, other): return PermutationGroupFunctor(self.gens() + other.gens(), new_domain) + class BlackBoxConstructionFunctor(ConstructionFunctor): """ Construction functor obtained from any callable object. @@ -3489,6 +3537,8 @@ def __ne__(self, other): """ return not (self == other) + __hash__ = ConstructionFunctor.__hash__ + def pushout(R, S): r""" From efee7d84ac60015037d924dfa25d9dea35870c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 5 Sep 2018 10:38:43 +0200 Subject: [PATCH 135/264] py3: hash for the example of Module --- src/sage/modules/module.pyx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/modules/module.pyx b/src/sage/modules/module.pyx index 5dbe40399e5..cd2aa74aee6 100644 --- a/src/sage/modules/module.pyx +++ b/src/sage/modules/module.pyx @@ -34,6 +34,8 @@ A minimal example of a module:: ....: def __eq__(self, other): ....: if not isinstance(other, MyModule): return False ....: return self.base_ring() == other.base_ring() + ....: def __hash__(self): + ....: return hash(self.base_ring()) sage: M = MyModule(QQ) sage: M(1) From 15c313928def0dfaa909ad698d4f06dd7f8203b0 Mon Sep 17 00:00:00 2001 From: Aaron Lauve Date: Wed, 5 Sep 2018 13:34:59 -0500 Subject: [PATCH 136/264] addressing reviewer comments --- ...ensional_semisimple_algebras_with_basis.py | 10 +- src/sage/combinat/symmetric_group_algebra.py | 147 ++++++++++-------- 2 files changed, 91 insertions(+), 66 deletions(-) diff --git a/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py b/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py index a8f0b4dccf1..4b052ea940d 100644 --- a/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py @@ -86,10 +86,14 @@ def central_orthogonal_idempotents(self): acts on `V_i` as multiplication by the `i`th power of a cube root of unity:: - sage: A3 = AlternatingGroup(3).algebra(QQbar) + sage: P. = QQ[] + sage: QP = QuotientRing(P, P.ideal(x^2+x+1)) + sage: A3 = AlternatingGroup(3).algebra(QP) sage: idempotents = A3.central_orthogonal_idempotents() - sage: idempotents[0] - 1/3*() + 1/3*(1,2,3) + 1/3*(1,3,2) + sage: idempotents + (1/3*() + 1/3*(1,2,3) + 1/3*(1,3,2), + 1/3*() + (-1/3*xbar-1/3)*(1,2,3) + 1/3*xbar*(1,3,2), + 1/3*() + 1/3*xbar*(1,2,3) + (-1/3*xbar-1/3)*(1,3,2)) sage: A3.is_identity_decomposition_into_orthogonal_idempotents(idempotents) True diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index c10308ebd0d..ee7906a760d 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -12,13 +12,13 @@ import six from six.moves import range -from sage.misc.cachefunc import cached_method +from sage.misc.cachefunc import cached_method, cached_function from sage.misc.superseded import deprecated_function_alias from sage.combinat.combinatorial_algebra import CombinatorialAlgebra from sage.combinat.free_module import CombinatorialFreeModule from sage.combinat.permutation import Permutation, Permutations, from_permutation_group_element from sage.combinat.permutation_cython import (left_action_same_n, right_action_same_n) -from sage.combinat import partition +from sage.combinat.partition import _Partitions, Partitions_n from sage.combinat.tableau import Tableau, StandardTableaux_size, StandardTableaux_shape, StandardTableaux from sage.algebras.group_algebra import GroupAlgebra_class from sage.categories.weyl_groups import WeylGroups @@ -754,7 +754,6 @@ def cell_module(self, la, **kwds): sage: M is N True """ - from sage.combinat.partition import _Partitions la = _Partitions(la) kwds['bracket'] = kwds.get('bracket', False) return super(SymmetricGroupAlgebra_n, self).cell_module(la, **kwds) @@ -958,22 +957,19 @@ def retract_okounkov_vershik(self, f, m): def central_orthogonal_idempotents(self): r""" - Return the primitive central orthogonal idempotents for ``self``. + Return a maximal list of central orthogonal idempotents for ``self``. - This method supercedes the default implementation of - central orthogonal idempotents found within - :class:`sage.categories.finite_dimensional_semisimple_algebras_with_basis.FiniteDimensionalSemisimpleAlgebrasWithBasis`. - - .. SEEALSO:: - - - :meth:`central_orthogonal_idempotent` + This method does not require that ``self`` be semisimple, relying + on Nakayama's Conjecture whenever ``self.base_ring()`` has + positive characteristic. EXAMPLES:: sage: QS3 = SymmetricGroupAlgebra(QQ,3) sage: a = QS3.central_orthogonal_idempotents() sage: a[0] # [3] - 1/6*[1, 2, 3] + 1/6*[1, 3, 2] + 1/6*[2, 1, 3] + 1/6*[2, 3, 1] + 1/6*[3, 1, 2] + 1/6*[3, 2, 1] + 1/6*[1, 2, 3] + 1/6*[1, 3, 2] + 1/6*[2, 1, 3] + 1/6*[2, 3, 1] + + 1/6*[3, 1, 2] + 1/6*[3, 2, 1] sage: a[1] # [2, 1] 2/3*[1, 2, 3] - 1/3*[2, 3, 1] - 1/3*[3, 1, 2] @@ -1016,46 +1012,59 @@ def central_orthogonal_idempotents(self): 3 blocks for p=3 ... True 3 blocks for p=5 ... True 7 blocks for p=7 ... True + + .. SEEALSO:: + + - :meth:`central_orthogonal_idempotent` """ out = [] blocks = self._blocks_dictionary() - for key in sorted(blocks.keys(), reverse=True): + for key in sorted(blocks, reverse=True): out.append(self.central_orthogonal_idempotent(key)) return out def central_orthogonal_idempotent(self, la, block=True): r""" - Return the central idempotent for the symmetric group of - order `n` corresponding to the indecomposable block to which - the partition ``la`` is associated. + Return the central idempotent for the symmetric group of order `n` + corresponding to the indecomposable block to which the partition + ``la`` is associated. - If ``self.base_ring()`` contains `QQ`, this corresponds to - the classical central idempotent corresponding to the - irreducible representation indexed by ``la``. + If ``self.base_ring()`` contains `QQ`, this corresponds to the + classical central idempotent corresponding to the irreducible + representation indexed by ``la``. - Alternatively, if ``self.base_ring()`` has characteristic - `p > 0`, then Nakayama's Conjecture provides that ``la`` is - associated to an idempotent `f_\mu` where `\mu` is the - `p`-core of ``la``. This `f_\mu` is a sum of classical - idempotents, + Alternatively, if ``self.base_ring()`` has characteristic `p > 0`, + then [Mur1983]_, Theorem 2.8 provides that ``la`` is associated to + an idempotent `f_\mu` where `\mu` is the `p`-core of ``la``. + This `f_\mu` is a sum of classical idempotents, .. MATH:: - f_\mu = \sum_{core(\lambda)=\mu} e_\lambda, + f_\mu = \sum_{c(\lambda)=\mu} e_\lambda, - where the sum ranges over the partitions `\lambda` of `n` - with `p`-core equal to `\mu`. + where the sum ranges over the partitions `\lambda` of `n` with + `p`-core equal to `\mu`. + + REFERENCES: + + .. [Mur1983] G. E. Murphy. *The idempotents of the symmetric group + and Nakayama's conjecture*. J. Algebra **81** (1983). 258-265. INPUT: - - ``la`` -- A partition of ``self.n`` or a `p`-core of such - a partition, if ``self.base_ring().characteristic() == p``. + - ``la`` -- a partition of ``self.n`` or a + ``self.base_ring().characteristic()``-core of such + a partition + + - ``block`` -- boolean (default: ``True``); when ``False``, this + returns the classical idempotent associated to ``la`` + (defined over `\QQ`) + + OUTPUT: - - ``block`` -- boolean (default: ``True``). When this - optional variable is set to ``False``, the method attempts - to return the classical idempotent associated to ``la`` - (defined over `QQ`). If the corresponding coefficients are - not defined over ``self.base_ring()``, return ``None``. + If ``block=False`` and the corresponding coefficients are + not defined over ``self.base_ring()``, then return ``None``. + Otherwise return an element of ``self``. EXAMPLES: @@ -1090,11 +1099,11 @@ def central_orthogonal_idempotent(self, la, block=True): sage: S2.central_orthogonal_idempotent([1]) Traceback (most recent call last): ... - ValueError: [1].core(2) (= [1]) is not a 2-core of a partition of 4 + ValueError: the 2-core of [1] is not a 2-core of a partition of 4 sage: S3.central_orthogonal_idempotent([1]) () + (1,2)(3,4) + (1,3)(2,4) + (1,4)(2,3) - Asking for classical idempotents (taking ``block=False``):: + Asking for classical idempotents:: sage: S3.central_orthogonal_idempotent([2,2], block=False) is None True @@ -1109,7 +1118,7 @@ def central_orthogonal_idempotent(self, la, block=True): - :meth:`sage.combinat.partition.Partition.core` - :meth:`sage.combinat.symmetric_group_algebra.cpi_over_QQ` """ - if not la in partition.Partitions(): + if not la in _Partitions: raise ValueError("{0} is not a partition of a nonnegative integer".format(la)) R = self.base_ring() @@ -1121,12 +1130,11 @@ def central_orthogonal_idempotent(self, la, block=True): raise ValueError("{0} is not a partition of integer {1}".format(la, self.n)) cpi = cpi_over_QQ(la) else: - mu = partition.Partitions()(la).core(p) - try: - block = self._blocks_dictionary()[mu] - cpi = sum(cpi_over_QQ(lam) for lam in block) - except KeyError: - raise ValueError("{0}.core({1}) (= {2}) is not a {1}-core of a partition of {3}".format(la, p, mu, self.n)) + mu = _Partitions(la).core(p) + if mu in self._blocks_dictionary(): + cpi = sum(cpi_over_QQ(lam) for lam in self._blocks_dictionary()[mu]) + else: + raise ValueError("the {1}-core of {0} is not a {1}-core of a partition of {2}".format(la, p, self.n)) denoms = set(R(c.denominator()) for c in cpi.coefficients()) if not all(denoms): @@ -1153,13 +1161,13 @@ def _blocks_dictionary(self): TESTS:: sage: B2 = SymmetricGroupAlgebra(GF(2), 4)._blocks_dictionary() - sage: [tuple(B2[key]) for key in sorted(B2.keys())] + sage: [tuple(B2[key]) for key in sorted(B2)] [([4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1])] sage: B3 = SymmetricGroupAlgebra(GF(3), 4)._blocks_dictionary() - sage: [tuple(B3[key]) for key in sorted(B3.keys())] + sage: [tuple(B3[key]) for key in sorted(B3)] [([4], [2, 2], [1, 1, 1, 1]), ([2, 1, 1],), ([3, 1],)] sage: B5 = SymmetricGroupAlgebra(GF(5), 4)._blocks_dictionary() - sage: [tuple(B5[key]) for key in sorted(B5.keys())] + sage: [tuple(B5[key]) for key in sorted(B5)] [([1, 1, 1, 1],), ([2, 1, 1],), ([2, 2],), ([3, 1],), ([4],)] sage: B5 == SymmetricGroupAlgebra(QQ, 4)._blocks_dictionary() True @@ -1170,10 +1178,10 @@ def _blocks_dictionary(self): """ p = self.base_ring().characteristic() if not p: - return {la:[la] for la in partition.Partitions(self.n)} + return {la:[la] for la in Partitions_n(self.n)} blocks = {} - for la in partition.Partitions_n(self.n): + for la in Partitions_n(self.n): c = la.core(p) if c in blocks: blocks[c].append(la) @@ -1244,7 +1252,7 @@ def _conjugacy_classes_representatives_underlying_group(self): [(1,2,3), (1,2), ()] """ P = self.basis().keys() - return [P.element_in_conjugacy_classes(nu) for nu in partition.Partitions(self.n)] + return [P.element_in_conjugacy_classes(nu) for nu in Partitions_n(self.n)] def rsw_shuffling_element(self, k): r""" @@ -1716,7 +1724,7 @@ def seminormal_basis(self, mult='l2r'): http://www.ms.unimelb.edu.au/~ram/Publications/1997PLMSv75p99.pdf """ basis = [] - for part in partition.Partitions_n(self.n): + for part in Partitions_n(self.n): stp = StandardTableaux_shape(part) for t1 in stp: for t2 in stp: @@ -1939,7 +1947,7 @@ def epsilon_ik(self, itab, ktab, star=0, mult='l2r'): else: return z.map_support(lambda x: x.inverse()) -@cached_method(key=lambda x: partition.Partitions()(x)) +@cached_function(key=lambda x: _Partitions(x)) def cpi_over_QQ(la): """ Return the primitive central idempotent for the symmetric group @@ -1964,7 +1972,7 @@ def cpi_over_QQ(la): sage: cpi_over_QQ([1,3]) Traceback (most recent call last): ... - ValueError: [1, 3] is not an element of Partitions of the integer 4 + ValueError: [1, 3] is not an element of Partitions sage: e = cpi_over_QQ([3,1]); e 3/8*[1, 2, 3, 4] + 1/8*[1, 2, 4, 3] + 1/8*[1, 3, 2, 4] + 1/8*[1, 4, 3, 2] + 1/8*[2, 1, 3, 4] - 1/8*[2, 1, 4, 3] @@ -1983,27 +1991,40 @@ def cpi_over_QQ(la): + [3, 4, 1, 2] + [3, 4, 2, 1] + [4, 1, 2, 3] + 2*[4, 2, 3, 1] + [4, 3, 1, 2] + [4, 3, 2, 1] """ - if la not in partition.Partitions(): - raise ValueError("{0} is not an element of Partitions of the integer 4".format(la)) + if la not in _Partitions: + raise ValueError("{0} is not an element of Partitions".format(la)) else: - la = partition.Partitions()(la) + la = _Partitions(la) n = la.size() character_table = eval(gap.eval("Display(Irr(SymmetricGroup(%d)));"%n)) - np = partition.Partitions_n(n).list() - np.reverse() - la_index = np.index(la) + Pn = Partitions_n(n) + C = Pn.cardinality() + indices = {lam: C-1-i for i,lam in enumerate(Pn)} + la_index = indices[la] big_coeff = character_table[la_index][0] / factorial(n) character_row = character_table[la_index] - P = Permutations(n) dct = {} - for g in P: - coeff = big_coeff * character_row[np.index(g.cycle_type())] - dct[P(g)] = coeff + for g in Permutations(n): + coeff = big_coeff * character_row[indices[g.cycle_type()]] + dct[g] = coeff return SymmetricGroupAlgebra(QQ, n)._from_dict(dct) + #np = Partitions_n(n).list() + #np.reverse() + #la_index = np.index(la) + + #big_coeff = character_table[la_index][0] / factorial(n) + + #character_row = character_table[la_index] + #P = Permutations(n) + #dct = {} + #for g in P: + # coeff = big_coeff * character_row[np.index(g.cycle_type())] + # dct[P(g)] = coeff + #return SymmetricGroupAlgebra(QQ, n)._from_dict(dct) epsilon_ik_cache = {} @@ -2514,7 +2535,7 @@ def seminormal_test(n): sage: seminormal_test(3) True """ - for part in partition.Partitions_n(n): + for part in Partitions_n(n): for tab in StandardTableaux(part): #Theorem 3.1.10 if not e(tab)*(1/kappa(part)) - e_hat(tab) == 0: From 90a2093b0954fabba6ccc71008af9e2218fe8a0d Mon Sep 17 00:00:00 2001 From: Aaron Lauve Date: Wed, 5 Sep 2018 14:20:52 -0500 Subject: [PATCH 137/264] moving from gap to libgap --- src/sage/combinat/symmetric_group_algebra.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index ee7906a760d..00ccc6e5909 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -23,7 +23,7 @@ from sage.algebras.group_algebra import GroupAlgebra_class from sage.categories.weyl_groups import WeylGroups from sage.categories.algebras import Algebras -from sage.interfaces.all import gap +from sage.libs.gap.libgap import libgap from sage.rings.all import QQ, PolynomialRing from sage.arith.all import factorial from sage.matrix.all import matrix @@ -1997,7 +1997,7 @@ def cpi_over_QQ(la): la = _Partitions(la) n = la.size() - character_table = eval(gap.eval("Display(Irr(SymmetricGroup(%d)));"%n)) + character_table = [c.sage() for c in libgap.Irr(libgap.SymmetricGroup(n))] Pn = Partitions_n(n) C = Pn.cardinality() @@ -2012,19 +2012,6 @@ def cpi_over_QQ(la): coeff = big_coeff * character_row[indices[g.cycle_type()]] dct[g] = coeff return SymmetricGroupAlgebra(QQ, n)._from_dict(dct) - #np = Partitions_n(n).list() - #np.reverse() - #la_index = np.index(la) - - #big_coeff = character_table[la_index][0] / factorial(n) - - #character_row = character_table[la_index] - #P = Permutations(n) - #dct = {} - #for g in P: - # coeff = big_coeff * character_row[np.index(g.cycle_type())] - # dct[P(g)] = coeff - #return SymmetricGroupAlgebra(QQ, n)._from_dict(dct) epsilon_ik_cache = {} From 824d3fe320ee356320bc9d248e27c38cf09d3c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 5 Sep 2018 14:06:31 +0200 Subject: [PATCH 138/264] cleanup and deprecate imports for Eta products --- src/sage/modular/all.py | 10 +++++++++- src/sage/modular/etaproducts.py | 6 +++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/sage/modular/all.py b/src/sage/modular/all.py index d3cc256fa89..3e9089b7a40 100644 --- a/src/sage/modular/all.py +++ b/src/sage/modular/all.py @@ -1,4 +1,6 @@ from __future__ import absolute_import +from sage.misc.lazy_import import lazy_import + from .quatalg.all import * from .modsym.all import * @@ -27,7 +29,11 @@ from .buzzard import buzzard_tpslopes -from .etaproducts import * +from .etaproducts import (EtaGroup, EtaProduct, EtaGroupElement, + AllCusps, CuspFamily) +lazy_import("sage.modular.etaproducts", ['num_cusps_of_width', 'qexp_eta', + 'eta_poly_relations'], + deprecation=26196) from .overconvergent.all import * @@ -38,3 +44,5 @@ from .btquotients.all import * from .pollack_stevens.all import * + +del absolute_import diff --git a/src/sage/modular/etaproducts.py b/src/sage/modular/etaproducts.py index 16fa124e9e8..4c3cd8c56b5 100644 --- a/src/sage/modular/etaproducts.py +++ b/src/sage/modular/etaproducts.py @@ -680,6 +680,7 @@ def num_cusps_of_width(N, d): EXAMPLES:: + sage: from sage.modular.etaproducts import num_cusps_of_width sage: [num_cusps_of_width(18,d) for d in divisors(18)] [1, 1, 2, 2, 1, 1] sage: num_cusps_of_width(4,8) @@ -845,11 +846,12 @@ def qexp_eta(ps_ring, prec): EXAMPLES:: + sage: from sage.modular.etaproducts import qexp_eta sage: qexp_eta(ZZ[['q']], 100) 1 - q - q^2 + q^5 + q^7 - q^12 - q^15 + q^22 + q^26 - q^35 - q^40 + q^51 + q^57 - q^70 - q^77 + q^92 + O(q^100) """ prec = Integer(prec) - assert prec>0, "prec must be a positive integer" + assert prec > 0, "prec must be a positive integer" v = [Integer(0)] * prec pm = Integer(1) v[0] = pm @@ -864,6 +866,7 @@ def qexp_eta(ps_ring, prec): pass return ps_ring(v, prec=prec) + def eta_poly_relations(eta_elements, degree, labels=['x1','x2'], verbose=False): r""" Find polynomial relations between eta products. @@ -904,6 +907,7 @@ def eta_poly_relations(eta_elements, degree, labels=['x1','x2'], verbose=False): EXAMPLES:: + sage: from sage.modular.etaproducts import eta_poly_relations sage: t = EtaProduct(26, {2:2,13:2,26:-2,1:-2}) sage: u = EtaProduct(26, {2:4,13:2,26:-4,1:-2}) sage: eta_poly_relations([t, u], 3) From 8c57b113e61188a49e24ecc5204d1e2a7c8eb7d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 5 Sep 2018 21:44:47 +0200 Subject: [PATCH 139/264] fix : adding one missing import in doc --- src/sage/modules/module.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/modules/module.pyx b/src/sage/modules/module.pyx index 5dbe40399e5..14d6a4e56fd 100644 --- a/src/sage/modules/module.pyx +++ b/src/sage/modules/module.pyx @@ -11,6 +11,7 @@ EXAMPLES: A minimal example of a module:: + sage: from sage.structure.richcmp import richcmp sage: class MyElement(sage.structure.element.ModuleElement): ....: def __init__(self, parent, x): ....: self.x = x From 243b6c4596157ea9b3fc706829a48b5872307975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 5 Sep 2018 21:47:41 +0200 Subject: [PATCH 140/264] fix import --- src/sage/interfaces/all.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/interfaces/all.py b/src/sage/interfaces/all.py index 4d54e5236a4..185884aa23b 100644 --- a/src/sage/interfaces/all.py +++ b/src/sage/interfaces/all.py @@ -17,7 +17,7 @@ from .gnuplot import gnuplot from .kash import kash, kash_version, Kash from .lisp import lisp, Lisp -from .magma import magma, magma_version, Magma +from .magma import magma, Magma from .magma_free import magma_free from .macaulay2 import macaulay2, Macaulay2 from .maple import maple, Maple @@ -74,3 +74,5 @@ from .sage0 import sage0_console from .lie import lie_console from .r import r_console + +del absolute_import From 86bfd328d9d43d9c0cb958e31b6816aa64b9ec21 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Thu, 6 Sep 2018 10:05:21 +1000 Subject: [PATCH 141/264] Changing the caching of SGA central_orthogonal_idempotent(s). --- src/doc/en/reference/references/index.rst | 3 + src/sage/combinat/symmetric_group_algebra.py | 159 +++++++------------ 2 files changed, 62 insertions(+), 100 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index ae955621a6a..d33564fe68d 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2170,6 +2170,9 @@ REFERENCES: Number Theory" (ed. Y. Motohashi), London Math. Soc. Lecture Notes 247 (1997), 313-320, Cambridge Univ. Press. +.. [Mur1983] \G. E. Murphy. *The idempotents of the symmetric group + and Nakayama's conjecture*. J. Algebra **81** (1983). 258-265. + .. [MV2010] \D. Micciancio, P. Voulgaris. *A Deterministic Single Exponential Time Algorithm for Most Lattice Problems based on Voronoi Cell Computations*. Proceedings of the 42nd ACM diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index 00ccc6e5909..1c663f0e5b5 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -13,6 +13,7 @@ from six.moves import range from sage.misc.cachefunc import cached_method, cached_function +from sage.misc.lazy_attribute import lazy_attribute from sage.misc.superseded import deprecated_function_alias from sage.combinat.combinatorial_algebra import CombinatorialAlgebra from sage.combinat.free_module import CombinatorialFreeModule @@ -23,7 +24,6 @@ from sage.algebras.group_algebra import GroupAlgebra_class from sage.categories.weyl_groups import WeylGroups from sage.categories.algebras import Algebras -from sage.libs.gap.libgap import libgap from sage.rings.all import QQ, PolynomialRing from sage.arith.all import factorial from sage.matrix.all import matrix @@ -284,6 +284,7 @@ def __init__(self, R, W, category): self.n = W.degree() else: self.n = W.cartan_type().rank() + 1 + self._idempotent_cache = {} category = category.Unital().FiniteDimensional().WithBasis().Cellular() GroupAlgebra_class.__init__(self, R, W, prefix='', latex_prefix='', category=category) @@ -1018,8 +1019,7 @@ def central_orthogonal_idempotents(self): - :meth:`central_orthogonal_idempotent` """ out = [] - blocks = self._blocks_dictionary() - for key in sorted(blocks, reverse=True): + for key in sorted(self._blocks_dictionary, reverse=True): out.append(self.central_orthogonal_idempotent(key)) return out @@ -1029,13 +1029,13 @@ def central_orthogonal_idempotent(self, la, block=True): corresponding to the indecomposable block to which the partition ``la`` is associated. - If ``self.base_ring()`` contains `QQ`, this corresponds to the + If ``self.base_ring()`` contains `\QQ`, this corresponds to the classical central idempotent corresponding to the irreducible representation indexed by ``la``. Alternatively, if ``self.base_ring()`` has characteristic `p > 0`, - then [Mur1983]_, Theorem 2.8 provides that ``la`` is associated to - an idempotent `f_\mu` where `\mu` is the `p`-core of ``la``. + then Theorem 2.8 in [Mur1983]_ provides that ``la`` is associated + to an idempotent `f_\mu`, where `\mu` is the `p`-core of ``la``. This `f_\mu` is a sum of classical idempotents, .. MATH:: @@ -1045,19 +1045,14 @@ def central_orthogonal_idempotent(self, la, block=True): where the sum ranges over the partitions `\lambda` of `n` with `p`-core equal to `\mu`. - REFERENCES: - - .. [Mur1983] G. E. Murphy. *The idempotents of the symmetric group - and Nakayama's conjecture*. J. Algebra **81** (1983). 258-265. - INPUT: - ``la`` -- a partition of ``self.n`` or a ``self.base_ring().characteristic()``-core of such a partition - - ``block`` -- boolean (default: ``True``); when ``False``, this - returns the classical idempotent associated to ``la`` + - ``block`` -- boolean (default: ``True``); when ``False``, + this returns the classical idempotent associated to ``la`` (defined over `\QQ`) OUTPUT: @@ -1082,10 +1077,12 @@ def central_orthogonal_idempotent(self, la, block=True): + 1/8*(1,4,2,3) - 1/8*(1,4)(2,3) sage: S2.central_orthogonal_idempotent([2,1,1]) () - sage: S3.central_orthogonal_idempotent([4]) + sage: idem = S3.central_orthogonal_idempotent([4]); idem () + (1,2)(3,4) + (1,3)(2,4) + (1,4)(2,3) - sage: _ == S3.central_orthogonal_idempotent([1,1,1,1]) + sage: idem == S3.central_orthogonal_idempotent([1,1,1,1]) True + sage: S3.central_orthogonal_idempotent([2,2]) + () + (1,2)(3,4) + (1,3)(2,4) + (1,4)(2,3) Asking for block idempotents in any characteristic, by passing `p`-cores:: @@ -1102,6 +1099,8 @@ def central_orthogonal_idempotent(self, la, block=True): ValueError: the 2-core of [1] is not a 2-core of a partition of 4 sage: S3.central_orthogonal_idempotent([1]) () + (1,2)(3,4) + (1,3)(2,4) + (1,4)(2,3) + sage: S3.central_orthogonal_idempotent([7]) + () + (1,2)(3,4) + (1,3)(2,4) + (1,4)(2,3) Asking for classical idempotents:: @@ -1116,35 +1115,61 @@ def central_orthogonal_idempotent(self, la, block=True): .. SEEALSO:: - :meth:`sage.combinat.partition.Partition.core` - - :meth:`sage.combinat.symmetric_group_algebra.cpi_over_QQ` """ - if not la in _Partitions: - raise ValueError("{0} is not a partition of a nonnegative integer".format(la)) - + la = _Partitions(la) R = self.base_ring() p = R.characteristic() - G = self._indices if not block or not p: - if sum(la) != self.n: + if la in self._idempotent_cache: + return self._idempotent_cache[la] + if la.size() != self.n: raise ValueError("{0} is not a partition of integer {1}".format(la, self.n)) - cpi = cpi_over_QQ(la) else: - mu = _Partitions(la).core(p) - if mu in self._blocks_dictionary(): - cpi = sum(cpi_over_QQ(lam) for lam in self._blocks_dictionary()[mu]) - else: + mu = la.core(p) + if mu in self._idempotent_cache: + return self._idempotent_cache[mu] + if mu not in self._blocks_dictionary: raise ValueError("the {1}-core of {0} is not a {1}-core of a partition of {2}".format(la, p, self.n)) - denoms = set(R(c.denominator()) for c in cpi.coefficients()) - if not all(denoms): + from sage.libs.gap.libgap import libgap + from sage.data_structures.blas_dict import iaxpy + G = self._indices + character_table = [c.sage() for c in libgap.Irr(libgap.SymmetricGroup(self.n))] + Pn = Partitions_n(self.n) + C = Pn.cardinality() + # We get the indices of the partitions in the reverse lex order + # (i.e., reverse of the iteration order of partitions). + indices = {lam: C-1-i for i,lam in enumerate(Pn)} + + def cpi_over_QQ(lam): + lam_index = indices[lam] + big_coeff = character_table[lam_index][0] / factorial(self.n) + character_row = character_table[lam_index] + return {g: big_coeff * character_row[indices[g.cycle_type()]] + for g in G} + + if not block or not p: + cpi = cpi_over_QQ(la) + else: + cpi = {} + for lam in self._blocks_dictionary[mu]: + iaxpy(1, cpi_over_QQ(lam), cpi) + + if not all(R(cpi[g].denominator()) for g in cpi): return None + + ret = self.element_class(self, {g: R(cpi[g]) for g in cpi if R(cpi[g])}) + if not block or not p: + self._idempotent_cache[la] = ret else: - return self.sum_of_terms((G(g), R(c)) for (g, c) in iter(cpi)) + self._idempotent_cache[mu] = ret + return ret cpis = deprecated_function_alias(25942, central_orthogonal_idempotents) cpi = deprecated_function_alias(25942, central_orthogonal_idempotent) + @lazy_attribute def _blocks_dictionary(self): r""" Return the partitions of ``self.n``, themselves partitioned @@ -1160,16 +1185,16 @@ def _blocks_dictionary(self): TESTS:: - sage: B2 = SymmetricGroupAlgebra(GF(2), 4)._blocks_dictionary() + sage: B2 = SymmetricGroupAlgebra(GF(2), 4)._blocks_dictionary sage: [tuple(B2[key]) for key in sorted(B2)] [([4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1])] - sage: B3 = SymmetricGroupAlgebra(GF(3), 4)._blocks_dictionary() + sage: B3 = SymmetricGroupAlgebra(GF(3), 4)._blocks_dictionary sage: [tuple(B3[key]) for key in sorted(B3)] [([4], [2, 2], [1, 1, 1, 1]), ([2, 1, 1],), ([3, 1],)] - sage: B5 = SymmetricGroupAlgebra(GF(5), 4)._blocks_dictionary() + sage: B5 = SymmetricGroupAlgebra(GF(5), 4)._blocks_dictionary sage: [tuple(B5[key]) for key in sorted(B5)] [([1, 1, 1, 1],), ([2, 1, 1],), ([2, 2],), ([3, 1],), ([4],)] - sage: B5 == SymmetricGroupAlgebra(QQ, 4)._blocks_dictionary() + sage: B5 == SymmetricGroupAlgebra(QQ, 4)._blocks_dictionary True .. SEEALSO:: @@ -1178,7 +1203,7 @@ def _blocks_dictionary(self): """ p = self.base_ring().characteristic() if not p: - return {la:[la] for la in Partitions_n(self.n)} + return {la: [la] for la in Partitions_n(self.n)} blocks = {} for la in Partitions_n(self.n): @@ -1947,72 +1972,6 @@ def epsilon_ik(self, itab, ktab, star=0, mult='l2r'): else: return z.map_support(lambda x: x.inverse()) -@cached_function(key=lambda x: _Partitions(x)) -def cpi_over_QQ(la): - """ - Return the primitive central idempotent for the symmetric group - algebra over `QQ` of order ``n`` corresponding to the - irreducible representation indexed by the partition ``la``. - - EXAMPLES:: - - sage: from sage.combinat.symmetric_group_algebra import cpi_over_QQ - sage: e = cpi_over_QQ([2,1]); e - 2/3*[1, 2, 3] - 1/3*[2, 3, 1] - 1/3*[3, 1, 2] - sage: cpi_over_QQ([3]) - 1/6*[1, 2, 3] + 1/6*[1, 3, 2] + 1/6*[2, 1, 3] + 1/6*[2, 3, 1] + 1/6*[3, 1, 2] + 1/6*[3, 2, 1] - sage: cpi_over_QQ([1,1,1]) - 1/6*[1, 2, 3] - 1/6*[1, 3, 2] - 1/6*[2, 1, 3] + 1/6*[2, 3, 1] + 1/6*[3, 1, 2] - 1/6*[3, 2, 1] - - sage: cpi_over_QQ(Partition([])) - [] - - TESTS:: - - sage: cpi_over_QQ([1,3]) - Traceback (most recent call last): - ... - ValueError: [1, 3] is not an element of Partitions - sage: e = cpi_over_QQ([3,1]); e - 3/8*[1, 2, 3, 4] + 1/8*[1, 2, 4, 3] + 1/8*[1, 3, 2, 4] - + 1/8*[1, 4, 3, 2] + 1/8*[2, 1, 3, 4] - 1/8*[2, 1, 4, 3] - - 1/8*[2, 3, 4, 1] - 1/8*[2, 4, 1, 3] - 1/8*[3, 1, 4, 2] - + 1/8*[3, 2, 1, 4] - 1/8*[3, 4, 1, 2] - 1/8*[3, 4, 2, 1] - - 1/8*[4, 1, 2, 3] + 1/8*[4, 2, 3, 1] - 1/8*[4, 3, 1, 2] - - 1/8*[4, 3, 2, 1] - - sage: QS4_Z3 = SymmetricGroupAlgebra(GF(3), 4) - sage: e in QS4_Z3 - False - sage: QS4_Z3.sum_of_terms((g, GF(3)(c)) for (g, c) in iter(e)) - 2*[1, 2, 4, 3] + 2*[1, 3, 2, 4] + 2*[1, 4, 3, 2] - + 2*[2, 1, 3, 4] + [2, 1, 4, 3] + [2, 3, 4, 1] - + [2, 4, 1, 3] + [3, 1, 4, 2] + 2*[3, 2, 1, 4] - + [3, 4, 1, 2] + [3, 4, 2, 1] + [4, 1, 2, 3] - + 2*[4, 2, 3, 1] + [4, 3, 1, 2] + [4, 3, 2, 1] - """ - if la not in _Partitions: - raise ValueError("{0} is not an element of Partitions".format(la)) - else: - la = _Partitions(la) - n = la.size() - - character_table = [c.sage() for c in libgap.Irr(libgap.SymmetricGroup(n))] - - Pn = Partitions_n(n) - C = Pn.cardinality() - indices = {lam: C-1-i for i,lam in enumerate(Pn)} - la_index = indices[la] - - big_coeff = character_table[la_index][0] / factorial(n) - - character_row = character_table[la_index] - dct = {} - for g in Permutations(n): - coeff = big_coeff * character_row[indices[g.cycle_type()]] - dct[g] = coeff - return SymmetricGroupAlgebra(QQ, n)._from_dict(dct) - epsilon_ik_cache = {} def epsilon_ik(itab, ktab, star=0): From 5b752c4776b13d150f1fc6d60b38220171a7e781 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Thu, 6 Sep 2018 10:08:32 +1000 Subject: [PATCH 142/264] Some tweaks to the changed test in fin-dim semiseimple algebras w/ basis. --- ...imensional_semisimple_algebras_with_basis.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py b/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py index 4b052ea940d..563254e8a5a 100644 --- a/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_semisimple_algebras_with_basis.py @@ -68,32 +68,27 @@ def central_orthogonal_idempotents(self): idempotents of ``self``. *Central orthogonal idempotents* of an algebra `A` - are idempotents `(e_1, \dots, e_n)` in the center + are idempotents `(e_1, \ldots, e_n)` in the center of `A` such that `e_i e_j = 0` whenever `i \neq j`. With the maximality condition, they sum up to `1` and are uniquely determined (up to order). - INPUT: - - - ``self`` -- a semisimple algebra. - EXAMPLES: For the algebra of the (abelian) alternating group `A_3`, we recover three idempotents corresponding to the three one-dimensional representations `V_i` on which `(1,2,3)` - acts on `V_i` as multiplication by the `i`th power of a + acts on `V_i` as multiplication by the `i`-th power of a cube root of unity:: - sage: P. = QQ[] - sage: QP = QuotientRing(P, P.ideal(x^2+x+1)) - sage: A3 = AlternatingGroup(3).algebra(QP) + sage: R = CyclotomicField(3) + sage: A3 = AlternatingGroup(3).algebra(R) sage: idempotents = A3.central_orthogonal_idempotents() sage: idempotents (1/3*() + 1/3*(1,2,3) + 1/3*(1,3,2), - 1/3*() + (-1/3*xbar-1/3)*(1,2,3) + 1/3*xbar*(1,3,2), - 1/3*() + 1/3*xbar*(1,2,3) + (-1/3*xbar-1/3)*(1,3,2)) + 1/3*() - (1/3*zeta3+1/3)*(1,2,3) - (-1/3*zeta3)*(1,3,2), + 1/3*() - (-1/3*zeta3)*(1,2,3) - (1/3*zeta3+1/3)*(1,3,2)) sage: A3.is_identity_decomposition_into_orthogonal_idempotents(idempotents) True From 3223375b83ed77021a34ae452a4ac3fa2fc60ab9 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Thu, 6 Sep 2018 10:31:45 +1000 Subject: [PATCH 143/264] Further optimizations of idempotent computation in positive char. --- src/sage/combinat/symmetric_group_algebra.py | 29 ++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index 1c663f0e5b5..6e4d861ec79 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -1142,19 +1142,32 @@ def central_orthogonal_idempotent(self, la, block=True): # (i.e., reverse of the iteration order of partitions). indices = {lam: C-1-i for i,lam in enumerate(Pn)} - def cpi_over_QQ(lam): - lam_index = indices[lam] - big_coeff = character_table[lam_index][0] / factorial(self.n) + if not block or not p: + la_index = indices[la] + big_coeff = character_table[la_index][0] / factorial(self.n) character_row = character_table[lam_index] - return {g: big_coeff * character_row[indices[g.cycle_type()]] + cpi = {g: big_coeff * character_row[indices[g.cycle_type()]] for g in G} - - if not block or not p: - cpi = cpi_over_QQ(la) else: + # We compute the cycle types of the permutations + cycles = {} + for g in G: + ind = indices[g.cycle_type()] + if ind in cycles: + cycles[ind].append(g) + else: + cycles[ind] = [g] + + denom = factorial(self.n) cpi = {} for lam in self._blocks_dictionary[mu]: - iaxpy(1, cpi_over_QQ(lam), cpi) + lam_index = indices[lam] + big_coeff = character_table[lam_index][0] / denom + character_row = character_table[lam_index] + iaxpy(1, + {g: big_coeff * character_row[ind] + for ind in cycles for g in cycles[ind]}, + cpi) if not all(R(cpi[g].denominator()) for g in cpi): return None From 8bcc044995c5b344078d6e907ca739e3dee808da Mon Sep 17 00:00:00 2001 From: Nils Bruin Date: Thu, 6 Sep 2018 00:09:55 -0700 Subject: [PATCH 144/264] trac 26197: fix use of Baker's theorem for differentials --- .../riemann_surfaces/riemann_surface.py | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index 411aca5d329..85b8966cef0 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -246,7 +246,8 @@ def differential_basis_baker(f): r""" Compute a differential bases for a curve that is nonsingular outside (1:0:0),(0:1:0),(0:0:1) - Baker's theorem tells us that if a curve has its singularities at the coordinate vertices, + Baker's theorem tells us that if a curve has its singularities at the coordinate vertices and meets + some further easily tested genericity criteria, then we can read off a basis for the regular differentials from the interior of the Newton polygon spanned by the monomials. While this theorem only applies to special plane curves it is worth implementing because the analysis is relatively cheap and it applies to a lot of @@ -275,18 +276,38 @@ def differential_basis_baker(f): sage: differential_basis_baker(x^2+y^2-1) [] + TESTS:: + + sage: from sage.schemes.riemann_surfaces.riemann_surface import differential_basis_baker + sage: R.=QQ[] + sage: f = y^12 - x*(x - 1)^7 + sage: differential_basis_baker(f) is None + True + """ k = f.base_ring() R = PolynomialRing(k,3,"x,y,z") x,y,z = R.gens() F = f(x/z,y/z).numerator() W = [F] + [F.derivative(v) for v in R.gens()] + #we check that the singularities lie at (1:0:0),(0:1:0),(0:0:1) + #by checking that the eliminations of x, y, z result in (principal) ideals + #generated by a monomial. This is a sufficient condition, but not completely necessary. + #It's cheap to check, though. for c in R.gens(): B = GCD([W[i].resultant(W[j],c) for i in range(4) for j in range(i)]) if len(B.monomials()) > 1: return None from sage.geometry.polyhedron.constructor import Polyhedron - P = Polyhedron(f.dict().keys()) + D = { (k[0],k[1]): v for k,v in f.dict().iteritems()} + P = Polyhedron(D) + kT=k['t'] + #here we check the additional genericity conditions: that the polynomials + #along the edges of the newton polygon are square-free. + for e in P.bounded_edges(): + h=kT([D.get(tuple(c),0) for c in Polyhedron(e).integral_points()]) + if not h.is_squarefree(): + return None x,y = f.parent().gens() return [x**(a[0]-1)*y**(a[1]-1) for a in P.integral_points() if P.interior_contains(a)] From 42e9acf7d29fc92307fc558b373e6d299acdcf15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jori=20M=C3=A4ntysalo?= Date: Thu, 6 Sep 2018 11:47:14 +0300 Subject: [PATCH 145/264] Short-circuit algorithm. --- src/sage/combinat/posets/lattices.py | 36 +++++++++++++++++++++------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/sage/combinat/posets/lattices.py b/src/sage/combinat/posets/lattices.py index d0fd30aa31f..ef091c200af 100644 --- a/src/sage/combinat/posets/lattices.py +++ b/src/sage/combinat/posets/lattices.py @@ -4180,22 +4180,40 @@ def is_constructible_by_doublings(self, type): lattices. The same reference gives a test for being constructible by convex or by any subset. """ + from sage.combinat.set_partition import SetPartition + if type not in ['interval', 'lower', 'upper', 'convex', 'any']: raise ValueError("type must be one of 'interval', 'lower', 'upper', 'convex' or 'any'") if self.cardinality() < 5: return True - if type == 'interval': - return (len(self.join_irreducibles()) == - len(self.meet_irreducibles()) == - self._hasse_diagram.principal_congruences_poset()[0].cardinality()) + if (type == 'interval' and len(self.join_irreducibles()) != + len(self.meet_irreducibles())): + return False + + if type == 'upper' or type == 'interval': + H = self._hasse_diagram + found = set() + for v in H: + if H.out_degree(v) == 1: + S = SetPartition(H.congruence([[v, next(H.neighbor_out_iterator(v))]])) + if S in found: + return False + found.add(S) + return True + if type == 'lower': - return (len(self.join_irreducibles()) == - self._hasse_diagram.principal_congruences_poset()[0].cardinality()) - if type == 'upper': - return (len(self.meet_irreducibles()) == - self._hasse_diagram.principal_congruences_poset()[0].cardinality()) + H = self._hasse_diagram + found = set() + for v in H: + if H.in_degree(v) == 1: + S = SetPartition(H.congruence([[v, next(H.neighbor_in_iterator(v))]])) + if S in found: + return False + found.add(S) + return True + if type == 'convex': return self._hasse_diagram.is_congruence_normal() # type == 'any' From d6bd580f6c7c0404db1de0842a2f64e3ff7f10db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 6 Sep 2018 11:27:21 +0200 Subject: [PATCH 146/264] py3: hash for test_parent4 --- src/sage/misc/nested_class_test.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/sage/misc/nested_class_test.py b/src/sage/misc/nested_class_test.py index 6bbbbadacd4..21321cadcf9 100644 --- a/src/sage/misc/nested_class_test.py +++ b/src/sage/misc/nested_class_test.py @@ -47,7 +47,7 @@ from __future__ import print_function, absolute_import from six import add_metaclass -__all__ = [] # Don't document any parents +__all__ = [] # Don't document any parents from sage.structure.parent import Parent from sage.structure.element_wrapper import ElementWrapper @@ -55,6 +55,7 @@ from sage.misc.classcall_metaclass import ClasscallMetaclass from sage.misc.nested_class import NestedClassMetaclass + class TestParent1(Parent): def __init__(self): """ @@ -86,6 +87,7 @@ def __init__(self): class Element(ElementWrapper): pass + class TestParent3(UniqueRepresentation, Parent): def __init__(self): @@ -112,7 +114,7 @@ def __init__(self): """ from sage.categories.all import Sets - Parent.__init__(self, category = Sets()) + Parent.__init__(self, category=Sets()) def __eq__(self, other): """ @@ -134,6 +136,18 @@ def __ne__(self, other): """ return self.__class__ != other.__class__ + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: from sage.misc.nested_class_test import TestParent4 + sage: hash(TestParent4()) == hash(TestParent4()) + True + """ + return hash(8960522744683456048) + class Element(ElementWrapper): pass @@ -145,6 +159,7 @@ class B(object): """ pass + class ABB(object): class B(object): """ @@ -153,11 +168,13 @@ class B(object): """ pass + class ABL(object): """ There is no problem here. """ - B=B + B = B + class ALB(object): """ @@ -200,6 +217,7 @@ class CMeta(object): """ pass + CMeta = ALBMeta.CMeta From 3d528aeb02e2d7cea993aa5c86b71c3f3883b9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 26 Jun 2018 09:08:28 +0200 Subject: [PATCH 147/264] small cleanup of graph_list, removing use of isinstance(.., file) there --- src/sage/graphs/graph_list.py | 67 ++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/src/sage/graphs/graph_list.py b/src/sage/graphs/graph_list.py index e1229d553e5..8a17725d139 100644 --- a/src/sage/graphs/graph_list.py +++ b/src/sage/graphs/graph_list.py @@ -9,13 +9,15 @@ (to_graphics_array and show_graphs) """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 Robert L. Miller # and Emily A. Kirkman # # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** +from io import IOBase + def from_whatever(data): """ @@ -36,12 +38,12 @@ def from_whatever(data): [Graph on 15 vertices, Looped multi-graph on 17 vertices] """ from sage.graphs.graph import Graph - if isinstance(data, file): + if isinstance(data, IOBase): if data.name[data.name.rindex('.'):] == '.g6': return from_graph6(data) elif data.name[data.name.rindex('.'):] == '.s6': return from_sparse6(data) - else: # convert to list of lines, do each separately + else: # convert to list of lines, do each separately L = data.readlines() return from_whatever(L) if isinstance(data, list): @@ -69,6 +71,7 @@ def from_whatever(data): l.append(Graph(d, sparse=sparse)) return l + def from_graph6(data): """ Returns a list of Sage Graphs, given a list of graph6 data. @@ -87,20 +90,20 @@ def from_graph6(data): [Graph on 15 vertices, Graph on 25 vertices] """ from sage.graphs.graph import Graph - if isinstance(data,str): + if isinstance(data, str): data = data.split('\n') l = [] for d in data: if not d == '': - l.append(Graph(d, format = 'graph6')) + l.append(Graph(d, format='graph6')) return l - elif isinstance(data,list): + elif isinstance(data, list): l = [] for d in data: if isinstance(d, str): nn = d.rfind('\n') if nn == -1: - l.append(Graph(d,format='graph6')) + l.append(Graph(d, format='graph6')) elif len(d) == nn + 1: l.append(Graph(d[:nn], format='graph6')) else: @@ -108,13 +111,14 @@ def from_graph6(data): else: l.append(from_graph6(d)) return l - elif isinstance(data,file): + elif isinstance(data, IOBase): strlist = data.readlines() l = [] for s in strlist: l.append(Graph(s[:s.rfind('\n')], format='graph6')) return l + def from_sparse6(data): """ Returns a list of Sage Graphs, given a list of sparse6 data. @@ -133,14 +137,14 @@ def from_sparse6(data): [Looped multi-graph on 17 vertices, Looped multi-graph on 39 vertices] """ from sage.graphs.graph import Graph - if isinstance(data,str): + if isinstance(data, str): data = data.split('\n') l = [] for d in data: if not d == '': - l.append(Graph(d, format = 'sparse6', sparse=True)) + l.append(Graph(d, format='sparse6', sparse=True)) return l - elif isinstance(data,list): + elif isinstance(data, list): l = [] for d in data: if isinstance(d, str): @@ -154,14 +158,15 @@ def from_sparse6(data): else: l.append(from_sparse6(d)) return l - elif isinstance(data,file): + elif isinstance(data, IOBase): strlist = data.readlines() l = [] for s in strlist: l.append(Graph(s[:s.rfind('\n')], format='sparse6', sparse=True)) return l -def to_graph6(list, file = None, output_list=False): + +def to_graph6(list, file=None, output_list=False): r""" Converts a list of Sage graphs to a single string of graph6 graphs. If file is specified, then the string will be written quietly to @@ -192,7 +197,7 @@ def to_graph6(list, file = None, output_list=False): if file is None: if output_list: a = l.split('\n') - a = a[:len(a)-1] + a = a[:len(a) - 1] return a else: return l @@ -200,7 +205,8 @@ def to_graph6(list, file = None, output_list=False): file.write(l) file.flush() -def to_sparse6(list, file = None, output_list=False): + +def to_sparse6(list, file=None, output_list=False): r""" Converts a list of Sage graphs to a single string of sparse6 graphs. If file is specified, then the string will be written @@ -231,7 +237,7 @@ def to_sparse6(list, file = None, output_list=False): if file is None: if output_list: a = l.split('\n') - a = a[:len(a)-1] + a = a[:len(a) - 1] return a else: return l @@ -239,7 +245,7 @@ def to_sparse6(list, file = None, output_list=False): file.write(l) file.flush() - + def to_graphics_array(graph_list, **kwds): """ Draw all graphs in a graphics array @@ -248,7 +254,7 @@ def to_graphics_array(graph_list, **kwds): - ``graph_list`` - a list of Sage graphs - GRAPH PLOTTING: + GRAPH PLOTTING: Defaults to circular layout for graphs. This allows for a nicer display in a small area and takes much less time to @@ -275,9 +281,9 @@ def to_graphics_array(graph_list, **kwds): """ from sage.graphs import graph plist = [] - for i in range(len(graph_list)): - if isinstance(graph_list[i], graph.GenericGraph): - pos = graph_list[i].get_pos() + for graph_i in graph_list: + if isinstance(graph_i, graph.GenericGraph): + pos = graph_i.get_pos() if pos is None: if 'layout' not in kwds: kwds['layout'] = 'circular' @@ -286,9 +292,11 @@ def to_graphics_array(graph_list, **kwds): if 'vertex_labels' not in kwds: kwds['vertex_labels'] = False kwds['graph_border'] = True - plist.append(graph_list[i].plot(**kwds)) + plist.append(graph_i.plot(**kwds)) else: - plist.append(graph_list[i].plot(pos=pos, vertex_size=50, vertex_labels=False, graph_border=True)) + plist.append(graph_i.plot(pos=pos, vertex_size=50, + vertex_labels=False, + graph_border=True)) else: raise TypeError('param list must be a list of Sage (di)graphs.') from sage.plot.plot import graphics_array @@ -308,13 +316,11 @@ def show_graphs(graph_list, **kwds): INPUT: - - - ``list`` - a list of Sage graphs - + - ``list`` -- a list of Sage graphs GRAPH PLOTTING: Defaults to circular layout for graphs. This allows for a nicer display in a small area and takes much less time to - compute than the spring- layout algorithm for many graphs. + compute than the spring-layout algorithm for many graphs. EXAMPLES: Create a list of graphs:: @@ -362,8 +368,5 @@ def show_graphs(graph_list, **kwds): """ graph_list = list(graph_list) for i in range(len(graph_list) // 20 + 1): - graph_slice =graph_list[20*i:20*(i+1)] + graph_slice = graph_list[20 * i: 20 * (i + 1)] to_graphics_array(graph_slice, **kwds).show() - - - From 83ea91ba409bedd23cd1098a9d40fdfef75f3db0 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Tue, 26 Jun 2018 15:44:54 +0000 Subject: [PATCH 148/264] add a potentially useful little utility function try_read I had to put more things in sage.misc.misc, but short of creating a new module for file-related utilities I don't see a better place --- src/sage/misc/misc.py | 99 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index d4443db9d1a..f1bd45c24c4 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -116,6 +116,105 @@ def sage_makedirs(dir): print("Setting permissions of DOT_SAGE directory so only you " "can read and write it.") +def try_read(obj, splitlines=False): + """ + Determine if a given object is a readable file-like object and if so + read and return its contents. + + That is, the object has a callable method named ``read()`` which takes + no arguments (except ``self``) then the method is executed and the + contents are returned. + + Alternatively, if the ``splitlines=True`` is given, first ``splitlines()`` + is tried, then if that fails ``read().splitlines()``. + + If either method fails, ``None`` is returned. + + INPUT: + + - ``obj`` -- typically a `file` or `io.BaseIO` object, but any other + object with a ``read()`` method is accepted. + + - ``splitlines`` -- `bool`, optional; if True, return a list of lines + instead of a string. + + EXAMPLES:: + + sage: import io + sage: filename = tmp_filename() + sage: from sage.misc.misc import try_read + sage: with open(filename, 'w') as fobj: + ....: _ = fobj.write('a\nb\nc') + sage: with open(filename) as fobj: + ....: print(try_read(fobj)) + a + b + c + sage: with open(filename) as fobj: + ....: try_read(fobj, splitlines=True) + ['a\n', 'b\n', 'c'] + + The following example is identical to the above example on Python 3, + but different on Python 2 where ``open != io.open``:: + + sage: with io.open(filename) as fobj: + ....: print(try_read(fobj)) + a + b + c + + I/O buffers:: + + sage: buf = io.StringIO('a\nb\nc') + sage: print(try_read(buf)) + a + b + c + sage: _ = buf.seek(0); try_read(buf, splitlines=True) + ['a\n', 'b\n', 'c'] + sage: buf = io.BytesIO(b'a\nb\nc') + sage: try_read(buf) == b'a\nb\nc' + True + sage: _ = buf.seek(0) + sage: try_read(buf, splitlines=True) == [b'a\n', b'b\n', b'c'] + True + + Custom readable:: + + sage: class MyFile(object): + ....: def read(self): return 'Hello world!' + sage: try_read(MyFile()) + 'Hello world!' + sage: try_read(MyFile(), splitlines=True) + ['Hello world!'] + + Not readable:: + + sage: try_read(1) is None + True + """ + + if splitlines: + try: + return obj.readlines() + except (AttributeError, TypeError): + pass + + try: + data = obj.read() + except (AttributeError, TypeError): + return + + if splitlines: + try: + data = data.splitlines() + except (AttributeError, TypeError): + # Not a string?? + data = [data] + + return data + + ################################################# # Next we create the Sage temporary directory. From 440fc6b5033cbf56f14cf1739d897185a71e7f85 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Tue, 26 Jun 2018 15:47:10 +0000 Subject: [PATCH 149/264] refactor to reduce duplication and enhance flexibility from_whatever and friends now accept arbitrary iterables and file-like objects where applicable, and are less prone to returning nonsense for nonsense inputs --- src/sage/graphs/graph_list.py | 216 +++++++++++++++------------------- 1 file changed, 98 insertions(+), 118 deletions(-) diff --git a/src/sage/graphs/graph_list.py b/src/sage/graphs/graph_list.py index 8a17725d139..fed5ac4cd13 100644 --- a/src/sage/graphs/graph_list.py +++ b/src/sage/graphs/graph_list.py @@ -16,7 +16,9 @@ # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ # **************************************************************************** -from io import IOBase + + +from sage.misc.misc import try_read def from_whatever(data): @@ -27,8 +29,8 @@ def from_whatever(data): INPUT: - - ``data`` - can be a string, a list of strings, or a - file stream, or whatever. + - ``data`` - can be a string, a list/iterable of strings, or a + readable file-like object. EXAMPLES:: @@ -36,40 +38,76 @@ def from_whatever(data): sage: l = ['N@@?N@UGAGG?gGlKCMO',':P_`cBaC_ACd`C_@BC`ABDHaEH_@BF_@CHIK_@BCEHKL_BIKM_BFGHI'] sage: graphs_list.from_whatever(l) [Graph on 15 vertices, Looped multi-graph on 17 vertices] + sage: graphs_list.from_whatever('\n'.join(l)) + [Graph on 15 vertices, Looped multi-graph on 17 vertices] + + This example happens to be a mix a sparse and non-sparse graphs, so we + don't explicitly put a ``.g6`` or ``.s6`` extension, which implies just one + or the other:: + + sage: filename = tmp_filename() + sage: with open(filename, 'w') as fobj: + ....: _ = fobj.write('\n'.join(l)) + sage: with open(filename) as fobj: + ....: graphs_list.from_whatever(fobj) + [Graph on 15 vertices, Looped multi-graph on 17 vertices] + """ + + return _from_whatever(data) + + +def _from_whatever(data, fmt=None): """ + Implementation details of :func:`from_whatever`. + + ``fmt`` can be either ``'graph6'``, ``sparse6``, or ``None``, with the + latter case indicating that the `Graph` constructor should determine this + for itself. + """ + from sage.graphs.graph import Graph - if isinstance(data, IOBase): - if data.name[data.name.rindex('.'):] == '.g6': - return from_graph6(data) - elif data.name[data.name.rindex('.'):] == '.s6': - return from_sparse6(data) - else: # convert to list of lines, do each separately - L = data.readlines() - return from_whatever(L) - if isinstance(data, list): - l = [] - for d in data: - if isinstance(d, str): - nn = d.rfind('\n') - if nn == -1: - sparse = bool(d[0] == ':') - l.append(Graph(d, sparse=sparse)) - elif len(d) == nn + 1: - sparse = bool(d[0] == ':') - l.append(Graph(d[:nn], sparse=sparse)) - else: - l.append(from_whatever(d)) - else: - l.append(from_whatever(d)) - return l + if isinstance(data, str): - data = data.split('\n') - l = [] - for d in data: - if not d == '': - sparse = bool(d[0] == ':') - l.append(Graph(d, sparse=sparse)) - return l + lines = data.splitlines() + else: + lines = try_read(data, splitlines=True) + + if lines is not None and fmt is None: + # In this case the format should be 'forced' by the filename + if hasattr(data, 'name'): + if data.name.endswith('.g6'): + fmt = 'graph6' + elif data.name.endswith('.s6'): + fmt = 'sparse6' + else: + try: + lines = iter(data) + except TypeError: + raise TypeError( + "Must be a string, an iterable of strings, or a readable " + "file-like object") + + if fmt == 'graph6': + kwargs = {'format': fmt} + elif fmt == 'sparse6': + kwargs = {'format': fmt, 'sparse': True} # probably implied? + else: + kwargs = {} # We let Graph guess + + out = [] + for line in lines: + if not isinstance(line, str): + raise TypeError("Must be an iterable of strings") + line = line.strip() + if not line: + continue + + if '\n' in line: + out.append(_from_whatever(line.splitlines(), fmt=fmt)) + else: + out.append(Graph(line, **kwargs)) + + return out def from_graph6(data): @@ -89,34 +127,7 @@ def from_graph6(data): sage: graphs_list.from_graph6(l) [Graph on 15 vertices, Graph on 25 vertices] """ - from sage.graphs.graph import Graph - if isinstance(data, str): - data = data.split('\n') - l = [] - for d in data: - if not d == '': - l.append(Graph(d, format='graph6')) - return l - elif isinstance(data, list): - l = [] - for d in data: - if isinstance(d, str): - nn = d.rfind('\n') - if nn == -1: - l.append(Graph(d, format='graph6')) - elif len(d) == nn + 1: - l.append(Graph(d[:nn], format='graph6')) - else: - l.append(from_graph6(d)) - else: - l.append(from_graph6(d)) - return l - elif isinstance(data, IOBase): - strlist = data.readlines() - l = [] - for s in strlist: - l.append(Graph(s[:s.rfind('\n')], format='graph6')) - return l + return _from_whatever(data, fmt='graph6') def from_sparse6(data): @@ -136,37 +147,10 @@ def from_sparse6(data): sage: graphs_list.from_sparse6(l) [Looped multi-graph on 17 vertices, Looped multi-graph on 39 vertices] """ - from sage.graphs.graph import Graph - if isinstance(data, str): - data = data.split('\n') - l = [] - for d in data: - if not d == '': - l.append(Graph(d, format='sparse6', sparse=True)) - return l - elif isinstance(data, list): - l = [] - for d in data: - if isinstance(d, str): - nn = d.rfind('\n') - if nn == -1: - l.append(Graph(d, format='sparse6', sparse=True)) - elif len(d) == nn + 1: - l.append(Graph(d[:nn], format='sparse6', sparse=True)) - else: - l.append(from_sparse6(d)) - else: - l.append(from_sparse6(d)) - return l - elif isinstance(data, IOBase): - strlist = data.readlines() - l = [] - for s in strlist: - l.append(Graph(s[:s.rfind('\n')], format='sparse6', sparse=True)) - return l + return _from_whatever(data, fmt='sparse6') -def to_graph6(list, file=None, output_list=False): +def to_graph6(graphs, file=None, output_list=False): r""" Converts a list of Sage graphs to a single string of graph6 graphs. If file is specified, then the string will be written quietly to @@ -176,7 +160,7 @@ def to_graph6(list, file=None, output_list=False): INPUT: - - ``list`` - a Python list of Sage Graphs + - ``graphs`` - a Python list of Sage Graphs - ``file`` - (optional) a file stream to write to (must be in 'w' mode) @@ -191,22 +175,10 @@ def to_graph6(list, file=None, output_list=False): sage: graphs_list.to_graph6(l) 'ShCHGD@?K?_@?@?C_GGG@??cG?G?GK_?C\nIheA@GUAo\n' """ - l = '' - for G in list: - l += G.graph6_string() + '\n' - if file is None: - if output_list: - a = l.split('\n') - a = a[:len(a) - 1] - return a - else: - return l - else: - file.write(l) - file.flush() + return _to_graph6(graphs, file=file, output_list=output_list) -def to_sparse6(list, file=None, output_list=False): +def to_sparse6(graphs, file=None, output_list=False): r""" Converts a list of Sage graphs to a single string of sparse6 graphs. If file is specified, then the string will be written @@ -231,19 +203,27 @@ def to_sparse6(list, file=None, output_list=False): sage: graphs_list.to_sparse6(l) ':S_`abcaDe`Fg_HijhKfLdMkNcOjP_BQ\n:I`ES@obGkqegW~\n' """ - l = '' - for G in list: - l += G.sparse6_string() + '\n' - if file is None: - if output_list: - a = l.split('\n') - a = a[:len(a) - 1] - return a - else: - return l + return _to_graph6(graphs, file=file, output_list=output_list, sparse=True) + + +def _to_graph6(graphs, file=None, output_list=False, sparse=False): + """Internal implementation of :func:`to_graph6` and :func:`to_sparse6`.""" + + if sparse: + method = 'sparse6_string' else: - file.write(l) - file.flush() + method = 'graph6_string' + + strs = [getattr(g, method)() for g in graphs] + + if file or not output_list: + strs = '\n'.join(strs) + '\n' + + if file is None: + return strs + + file.write(strs) + file.flush() def to_graphics_array(graph_list, **kwds): From ec35cf05bcb01b01c652c351181d342aa4443c4c Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Wed, 27 Jun 2018 11:57:55 +0000 Subject: [PATCH 150/264] fixes for the documentation to build necessary due to the use of newline literals in the doctests --- src/sage/graphs/graph_list.py | 2 +- src/sage/misc/misc.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/graphs/graph_list.py b/src/sage/graphs/graph_list.py index fed5ac4cd13..b365aa34651 100644 --- a/src/sage/graphs/graph_list.py +++ b/src/sage/graphs/graph_list.py @@ -22,7 +22,7 @@ def from_whatever(data): - """ + r""" Returns a list of Sage Graphs, given a list of whatever kind of data. diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index f1bd45c24c4..56c6eeca09c 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -117,7 +117,7 @@ def sage_makedirs(dir): "can read and write it.") def try_read(obj, splitlines=False): - """ + r""" Determine if a given object is a readable file-like object and if so read and return its contents. From 460135abd0a72846aebede426b722d1de9f9c7d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jori=20M=C3=A4ntysalo?= Date: Thu, 6 Sep 2018 13:15:19 +0300 Subject: [PATCH 151/264] Set of sets instead of SetPartition. --- src/sage/combinat/posets/lattices.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/posets/lattices.py b/src/sage/combinat/posets/lattices.py index ef091c200af..e96e3971b12 100644 --- a/src/sage/combinat/posets/lattices.py +++ b/src/sage/combinat/posets/lattices.py @@ -4180,8 +4180,6 @@ def is_constructible_by_doublings(self, type): lattices. The same reference gives a test for being constructible by convex or by any subset. """ - from sage.combinat.set_partition import SetPartition - if type not in ['interval', 'lower', 'upper', 'convex', 'any']: raise ValueError("type must be one of 'interval', 'lower', 'upper', 'convex' or 'any'") @@ -4197,7 +4195,7 @@ def is_constructible_by_doublings(self, type): found = set() for v in H: if H.out_degree(v) == 1: - S = SetPartition(H.congruence([[v, next(H.neighbor_out_iterator(v))]])) + S = frozenset(map(frozenset, H.congruence([[v, next(H.neighbor_out_iterator(v))]]))) if S in found: return False found.add(S) @@ -4208,7 +4206,7 @@ def is_constructible_by_doublings(self, type): found = set() for v in H: if H.in_degree(v) == 1: - S = SetPartition(H.congruence([[v, next(H.neighbor_in_iterator(v))]])) + S = frozenset(map(frozenset, H.congruence([[v, next(H.neighbor_in_iterator(v))]]))) if S in found: return False found.add(S) From 230d71f5957453a176b48029fa8ed619abd3a227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 6 Sep 2018 14:51:38 +0200 Subject: [PATCH 152/264] using raise instead of assert to check prec input of eta expansion --- src/sage/modular/etaproducts.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/modular/etaproducts.py b/src/sage/modular/etaproducts.py index 4c3cd8c56b5..f7a33fffe11 100644 --- a/src/sage/modular/etaproducts.py +++ b/src/sage/modular/etaproducts.py @@ -814,6 +814,7 @@ def _repr_(self): else: return "(c_{%s%s})" % (self.width(), ((self.label and (","+self.label)) or "")) + def qexp_eta(ps_ring, prec): r""" Return the q-expansion of `\eta(q) / q^{1/24}`, where @@ -823,15 +824,13 @@ def qexp_eta(ps_ring, prec): \eta(q) = q^{1/24}\prod_{n=1}^\infty (1-q^n), - as an element of ps_ring, to precision prec. INPUT: - - ``ps_ring`` - (PowerSeriesRing): a power series ring - - - ``prec`` - (integer): the number of terms to compute. + - ``ps_ring`` -- (PowerSeriesRing): a power series ring + - ``prec`` -- (integer): the number of terms to compute OUTPUT: An element of ps_ring which is the q-expansion of `\eta(q)/q^{1/24}` truncated to prec terms. @@ -851,7 +850,8 @@ def qexp_eta(ps_ring, prec): 1 - q - q^2 + q^5 + q^7 - q^12 - q^15 + q^22 + q^26 - q^35 - q^40 + q^51 + q^57 - q^70 - q^77 + q^92 + O(q^100) """ prec = Integer(prec) - assert prec > 0, "prec must be a positive integer" + if not prec > 0: + raise ValueError("prec must be a positive integer") v = [Integer(0)] * prec pm = Integer(1) v[0] = pm From f96220bef96d7cc8e9f0ff9208870c88bf0799d1 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 6 Sep 2018 15:37:04 +0200 Subject: [PATCH 153/264] trac #26200: remove methods from cliquer from global namespace --- src/sage/graphs/all.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/sage/graphs/all.py b/src/sage/graphs/all.py index 050339ebe14..1a964a1cee7 100644 --- a/src/sage/graphs/all.py +++ b/src/sage/graphs/all.py @@ -14,7 +14,6 @@ import sage.graphs.partial_cube from . import graph_list as graphs_list lazy_import("sage.graphs", "graph_coloring") -from sage.graphs.cliquer import * from .graph_database import graph_db_info lazy_import("sage.graphs.graph_editor", "graph_editor") @@ -28,4 +27,20 @@ sage: import sage.graphs.graph_editor sage: 'sagenb.misc.support' in sys.modules False + +Test that methods all_max_clique, max_clique and clique_number from +sage.graphs.cliquer are no longer in the global namespace (:trac:`26200`):: + + sage: all_max_clique(Graph()) + Traceback (most recent call last): + ... + NameError: name 'all_max_clique' is not defined + sage: max_clique(Graph()) + Traceback (most recent call last): + ... + NameError: name 'max_clique' is not defined + sage: clique_number(Graph()) + Traceback (most recent call last): + ... + NameError: name 'clique_number' is not defined """ From 4bd57d078c0847f6694267a2ca6291c9bd3c2d30 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Thu, 6 Sep 2018 13:41:29 +0000 Subject: [PATCH 154/264] py2: on python 2 this test must use explicit unicode strings --- src/sage/misc/misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index 56c6eeca09c..9b8ea57d908 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -165,13 +165,13 @@ def try_read(obj, splitlines=False): I/O buffers:: - sage: buf = io.StringIO('a\nb\nc') + sage: buf = io.StringIO(u'a\nb\nc') sage: print(try_read(buf)) a b c sage: _ = buf.seek(0); try_read(buf, splitlines=True) - ['a\n', 'b\n', 'c'] + [u'a\n', u'b\n', u'c'] sage: buf = io.BytesIO(b'a\nb\nc') sage: try_read(buf) == b'a\nb\nc' True From 8d455d905a4b5f44126a07abf987d9b25ed7bbff Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 6 Sep 2018 16:23:30 +0200 Subject: [PATCH 155/264] trac #26200: fix doctests in cliquer --- src/sage/graphs/cliquer.pyx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/graphs/cliquer.pyx b/src/sage/graphs/cliquer.pyx index 007c2314fbf..bd2f812dc9d 100644 --- a/src/sage/graphs/cliquer.pyx +++ b/src/sage/graphs/cliquer.pyx @@ -54,6 +54,7 @@ def max_clique(graph): EXAMPLES:: sage: C=graphs.PetersenGraph() + sage: from sage.graphs.cliquer import max_clique sage: max_clique(C) [7, 9] @@ -184,11 +185,11 @@ def clique_number(graph): EXAMPLES:: sage: C = Graph('DJ{') - sage: clique_number(C) + sage: C.clique_number() 4 sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]}) sage: G.show(figsize=[2,2]) - sage: clique_number(G) + sage: G.clique_number() 3 TESTS:: From aa37776fa713fe2c164b1efd1724a24f7dddc8f4 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 6 Sep 2018 16:42:39 +0200 Subject: [PATCH 156/264] trac #26200: use lazy_import and add deprecation warning instead of brutal removal --- src/sage/graphs/all.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/sage/graphs/all.py b/src/sage/graphs/all.py index 1a964a1cee7..0eee6b1e4e7 100644 --- a/src/sage/graphs/all.py +++ b/src/sage/graphs/all.py @@ -14,6 +14,9 @@ import sage.graphs.partial_cube from . import graph_list as graphs_list lazy_import("sage.graphs", "graph_coloring") +lazy_import("sage.graphs.cliquer", ['all_max_clique', 'max_clique', + 'clique_number'], + deprecation=26200) from .graph_database import graph_db_info lazy_import("sage.graphs.graph_editor", "graph_editor") @@ -32,15 +35,15 @@ sage.graphs.cliquer are no longer in the global namespace (:trac:`26200`):: sage: all_max_clique(Graph()) - Traceback (most recent call last): - ... - NameError: name 'all_max_clique' is not defined + doctest:...: DeprecationWarning: Importing all_max_clique from here is deprecated. If you need to use it, please import it directly from sage.graphs.cliquer + See https://trac.sagemath.org/26200 for details. + [[]] sage: max_clique(Graph()) - Traceback (most recent call last): - ... - NameError: name 'max_clique' is not defined + doctest:...: DeprecationWarning: Importing max_clique from here is deprecated. If you need to use it, please import it directly from sage.graphs.cliquer + See https://trac.sagemath.org/26200 for details. + [] sage: clique_number(Graph()) - Traceback (most recent call last): - ... - NameError: name 'clique_number' is not defined + doctest:...: DeprecationWarning: Importing clique_number from here is deprecated. If you need to use it, please import it directly from sage.graphs.cliquer + See https://trac.sagemath.org/26200 for details. + 0 """ From ab7d21969f69ea3b8851345caded10e11a13cbd5 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 6 Sep 2018 16:43:50 +0200 Subject: [PATCH 157/264] trac #26200: correct test message --- src/sage/graphs/all.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/all.py b/src/sage/graphs/all.py index 0eee6b1e4e7..114a10dd187 100644 --- a/src/sage/graphs/all.py +++ b/src/sage/graphs/all.py @@ -32,7 +32,7 @@ False Test that methods all_max_clique, max_clique and clique_number from -sage.graphs.cliquer are no longer in the global namespace (:trac:`26200`):: +sage.graphs.cliquer are deprecated from the global namespace (:trac:`26200`):: sage: all_max_clique(Graph()) doctest:...: DeprecationWarning: Importing all_max_clique from here is deprecated. If you need to use it, please import it directly from sage.graphs.cliquer From a0f77daec0de42f952510cd4cca5cfb97af3cf11 Mon Sep 17 00:00:00 2001 From: Nils Bruin Date: Thu, 6 Sep 2018 07:59:06 -0700 Subject: [PATCH 158/264] python3 compatibility --- src/sage/schemes/riemann_surfaces/riemann_surface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index 85b8966cef0..8ee7c2407f8 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -299,9 +299,9 @@ def differential_basis_baker(f): if len(B.monomials()) > 1: return None from sage.geometry.polyhedron.constructor import Polyhedron - D = { (k[0],k[1]): v for k,v in f.dict().iteritems()} + D = { (k[0],k[1]): v for k,v in f.dict().items() } P = Polyhedron(D) - kT=k['t'] + kT = k['t'] #here we check the additional genericity conditions: that the polynomials #along the edges of the newton polygon are square-free. for e in P.bounded_edges(): From 4948be4027a669f722bb8f9357686cf247a106ff Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 6 Sep 2018 17:21:28 +0200 Subject: [PATCH 159/264] trac #26201: remove deprecation introduced in #18938 --- src/sage/graphs/generic_graph.py | 60 +++----------------------------- 1 file changed, 4 insertions(+), 56 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index a115b428ec5..3d05331a23a 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -15165,8 +15165,7 @@ def triangles_count(self, algorithm=None): raise ValueError('unknown algorithm "{}"'.format(algorithm)) def shortest_path(self, u, v, by_weight=False, algorithm=None, - weight_function=None, check_weight=True, - bidirectional=None): + weight_function=None, check_weight=True): r""" Returns a list of vertices representing some shortest path from u to v: if there is no path from u to v, the list is empty. @@ -15223,11 +15222,6 @@ def shortest_path(self, u, v, by_weight=False, algorithm=None, - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. - - ``bidirectional`` - Deprecated and replaced by Algorithm: now it has - no effect. Before, if it was True, the algorithm would expand vertices - from ``u`` and ``v`` at the same time, making two spheres of half the - usual radius. - EXAMPLES:: sage: D = graphs.DodecahedralGraph() @@ -15305,10 +15299,6 @@ def shortest_path(self, u, v, by_weight=False, algorithm=None, if weight_function is None and by_weight: weight_function = lambda e:e[2] - if bidirectional is not None: - deprecation(18938, "Variable 'bidirectional' is deprecated and " + - "replaced by 'algorithm'.") - if u == v: # to avoid a NetworkX bug return [u] @@ -15338,8 +15328,7 @@ def shortest_path(self, u, v, by_weight=False, algorithm=None, raise ValueError('unknown algorithm "{}"'.format(algorithm)) def shortest_path_length(self, u, v, by_weight=False, algorithm=None, - weight_function=None, check_weight=True, - bidirectional=None, weight_sum=None): + weight_function=None, check_weight=True): r""" Returns the minimal length of a path from u to v. @@ -15396,16 +15385,6 @@ def shortest_path_length(self, u, v, by_weight=False, algorithm=None, - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. - - ``bidirectional`` - Deprecated and replaced by Algorithm: now it has - no effect. Before, if it was True, the algorithm would expand vertices - from ``u`` and ``v`` at the same time, making two spheres of half the - usual radius. - - - ``weight_sum`` - Deprecated: now it has no effect. Before, it was used - to decide if the algorithm should return the number of edges in the - shortest path or the length of the (weighted) path. Now it has the - same value as ``by_weight``. - EXAMPLES: Standard examples:: @@ -15475,9 +15454,6 @@ def shortest_path_length(self, u, v, by_weight=False, algorithm=None, if not self.has_vertex(v): raise ValueError("vertex '{}' is not in the (di)graph".format(v)) - if weight_sum is not None: - deprecation(18938, "Now weight_sum is replaced by by_weight.") - if u == v: # to avoid a NetworkX bug return 0 @@ -15493,10 +15469,6 @@ def shortest_path_length(self, u, v, by_weight=False, algorithm=None, if algorithm in ['BFS', 'Dijkstra_NetworkX', 'Bellman-Ford_Boost']: return self.shortest_path_lengths(u, by_weight, algorithm, weight_function, check_weight)[v] - if bidirectional is not None: - deprecation(18938, "Variable 'bidirectional' is deprecated and " + - "replaced by 'algorithm'.") - if by_weight: if algorithm == 'BFS_Bid': raise ValueError("the 'BFS_Bid' algorithm does not " + @@ -15831,8 +15803,7 @@ def _path_length(self, path, by_weight=False, weight_function=None): return len(path) - 1 def shortest_path_lengths(self, u, by_weight=False, algorithm=None, - weight_function=None, check_weight=True, - weight_sums=None): + weight_function=None, check_weight=True): r""" Computes the length of a shortest path from u to any other vertex. @@ -15877,10 +15848,6 @@ def shortest_path_lengths(self, u, by_weight=False, algorithm=None, - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. - - ``weight_sums`` - Deprecated: now this variable has no effect. Before, - it was used to decide whether the number of edges or the sum of their - lengths was outputted. Now we use variable ``by_weight`` to decide. - EXAMPLES: Unweighted case:: @@ -15931,9 +15898,6 @@ def shortest_path_lengths(self, u, by_weight=False, algorithm=None, sage: d1 == d2 == d3 == d4 True """ - if weight_sums is not None: - deprecation(18938, "Now weight_sums is replaced by by_weight.") - if weight_function is not None: by_weight = True elif by_weight: @@ -15981,8 +15945,7 @@ def shortest_path_lengths(self, u, by_weight=False, algorithm=None, raise ValueError('unknown algorithm "{}"'.format(algorithm)) def shortest_path_all_pairs(self, by_weight=False, algorithm=None, - weight_function=None, check_weight=True, - default_weight=None): + weight_function=None, check_weight=True): r""" Computes a shortest path between each pair of vertices. @@ -16026,10 +15989,6 @@ def shortest_path_all_pairs(self, by_weight=False, algorithm=None, - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. - - ``default_weight`` - Deprecated: now it has no effect. Before, it was - used to assign a weight to edges with no label. Now it has been - replaced by ``weight_function``. - OUTPUT: A tuple ``(dist, pred)``. They are both dicts of dicts. The first @@ -16103,13 +16062,6 @@ def shortest_path_all_pairs(self, by_weight=False, algorithm=None, 3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3}, 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}}) - Now, ``default_weight`` does not work anymore:: - - sage: G.shortest_path_all_pairs(by_weight = True, default_weight=200) - Traceback (most recent call last): - ... - ValueError: The weight function cannot find the weight of (0, 1, None). - It can be replaced by choosing an appropriate weight_function:: sage: G.shortest_path_all_pairs(weight_function=lambda e:(e[2] if e[2] is not None else 200)) @@ -16245,10 +16197,6 @@ def shortest_path_all_pairs(self, by_weight=False, algorithm=None, ... RuntimeError: Dijkstra algorithm does not work with negative weights. Use Bellman-Ford instead """ - if default_weight is not None: - deprecation(18938, "Variable default_weight is deprecated: hence," + - " it is ignored. Please, use weight_function, instead.") - if weight_function is not None: by_weight = True elif by_weight: From 339a9a63e4b9e1b47dd16f23f776045e29394a31 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Thu, 6 Sep 2018 19:30:29 +0200 Subject: [PATCH 160/264] trac #26201: implement reviewers comments --- src/sage/graphs/generic_graph.py | 146 ++++++++++++++----------------- 1 file changed, 66 insertions(+), 80 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 3d05331a23a..af099999fb2 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -3768,12 +3768,12 @@ def min_spanning_tree(self, INPUT: - - ``weight_function`` (function) - a function that inputs an edge ``e`` - and outputs its weight. An edge has the form ``(u,v,l)``, where ``u`` - and ``v`` are vertices, ``l`` is a label (that can be of any kind). - The ``weight_function`` can be used to transform the label into a - weight (note that, if the weight returned is not convertible to a - float, an error is raised). In particular: + - ``weight_function`` (function) - a function that takes as input an + edge ``e`` and outputs its weight. An edge has the form ``(u,v,l)``, + where ``u`` and ``v`` are vertices, ``l`` is a label (that can be of + any kind). The ``weight_function`` can be used to transform the label + into a weight (note that, if the weight returned is not convertible to + a float, an error is raised). In particular: - if ``weight_function`` is not ``None``, the weight of an edge ``e`` is ``weight_function(e)``; @@ -13623,10 +13623,10 @@ def distance_all_pairs(self, by_weight=False, algorithm=None, ``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are positive, ``'Floyd-Warshall-Cython'`` otherwise. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. @@ -13727,10 +13727,10 @@ def eccentricity(self, v=None, by_weight=False, algorithm=None, unweighted graphs, ``'Dijkstra_Boost'`` if all weights are positive, ``'Johnson_Boost'`` otherwise. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. @@ -13929,10 +13929,10 @@ def radius(self, by_weight=False, algorithm=None, weight_function=None, unweighted graphs, ``'Dijkstra_Boost'`` if all weights are positive, ``'Johnson_Boost'``, otherwise. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. @@ -14022,10 +14022,10 @@ def diameter(self, by_weight=False, algorithm = None, weight_function=None, ``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are positive, ``'Johnson_Boost'``, otherwise. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. @@ -14130,10 +14130,10 @@ def center(self, by_weight=False, algorithm=None, weight_function=None, unweighted graphs, ``'Dijkstra_Boost'`` if all weights are positive, ``'Johnson_Boost'``, otherwise. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. @@ -14502,10 +14502,10 @@ def periphery(self, by_weight=False, algorithm=None, weight_function=None, unweighted graphs, ``'Dijkstra_Boost'`` if all weights are positive, ``'Johnson_Boost'``, otherwise. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. @@ -14711,10 +14711,10 @@ def centrality_closeness(self, vert=None, by_weight=False, algorithm=None, ``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are positive, ``'Johnson_Boost'`` otherwise. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. @@ -15214,10 +15214,10 @@ def shortest_path(self, u, v, by_weight=False, algorithm=None, weights before running the algorithm. If there are, the user should explicitly input ``algorithm='Bellman-Ford_Boost'``. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. @@ -15377,10 +15377,10 @@ def shortest_path_length(self, u, v, by_weight=False, algorithm=None, weights before running the algorithm. If there are, the user should explicitly input ``algorithm='Bellman-Ford_Boost'``. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. @@ -15574,10 +15574,10 @@ def shortest_paths(self, u, by_weight=False, algorithm=None, ``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are positive, ``'Bellman-Ford_Boost'`` otherwise. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. @@ -15751,10 +15751,10 @@ def _path_length(self, path, by_weight=False, weight_function=None): - ``by_weight`` (boolean) - if ``True``, the edges in the graph are weighted; if ``False``, all edges have weight 1. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. EXAMPLES: @@ -15840,10 +15840,10 @@ def shortest_path_lengths(self, u, by_weight=False, algorithm=None, ``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are positive, ``'Bellman-Ford_Boost'`` otherwise. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. @@ -15981,10 +15981,10 @@ def shortest_path_all_pairs(self, by_weight=False, algorithm=None, ``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are positive, ``'Floyd-Warshall-Cython'`` otherwise. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. @@ -16062,20 +16062,6 @@ def shortest_path_all_pairs(self, by_weight=False, algorithm=None, 3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3}, 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}}) - It can be replaced by choosing an appropriate weight_function:: - - sage: G.shortest_path_all_pairs(weight_function=lambda e:(e[2] if e[2] is not None else 200)) - ({0: {0: 0, 1: 200, 2: 5, 3: 4, 4: 2}, - 1: {0: 200, 1: 0, 2: 200, 3: 201, 4: 202}, - 2: {0: 5, 1: 200, 2: 0, 3: 1, 4: 3}, - 3: {0: 4, 1: 201, 2: 1, 3: 0, 4: 2}, - 4: {0: 2, 1: 202, 2: 3, 3: 2, 4: 0}}, - {0: {0: None, 1: 0, 2: 3, 3: 4, 4: 0}, - 1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0}, - 2: {0: 4, 1: 2, 2: None, 3: 2, 4: 3}, - 3: {0: 4, 1: 2, 2: 3, 3: None, 4: 3}, - 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}}) - Checking that distances are equal regardless of the algorithm used:: sage: g = graphs.Grid2dGraph(5,5) @@ -16358,10 +16344,10 @@ def wiener_index(self, by_weight=False, algorithm=None, unweighted graphs, ``'Dijkstra_Boost'`` if all weights are positive, ``'Johnson_Boost'``, otherwise. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. @@ -16471,10 +16457,10 @@ def average_distance(self, by_weight=False, algorithm=None, unweighted graphs, ``'Dijkstra_Boost'`` if all weights are positive, ``'Johnson_Boost'``, otherwise. - - ``weight_function`` (function) - a function that inputs an edge - ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` - is automatically set to ``True``. If ``None`` and ``by_weight`` is - ``True``, we use the edge label ``l`` as a weight. + - ``weight_function`` (function) - a function that takes as input an + edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l`` as a weight. - ``check_weight`` (boolean) - if ``True``, we check that the weight_function outputs a number for each edge. From 4400367d983fa74e82d23915efb31b01b14d3f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 6 Sep 2018 20:06:23 +0200 Subject: [PATCH 161/264] fix conversion from gp strings to sage strings --- src/sage/interfaces/gp.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/sage/interfaces/gp.py b/src/sage/interfaces/gp.py index 6f68253a146..86f401571a5 100644 --- a/src/sage/interfaces/gp.py +++ b/src/sage/interfaces/gp.py @@ -881,7 +881,6 @@ def _reduce(self): True sage: gp(E.sage()) == E False - """ return repr(self) @@ -908,7 +907,17 @@ def _sage_(self): [3 4] sage: gp(M).sage() == M True + + Conversion of strings:: + + sage: s = gp('"foo"') + sage: s.sage() + 'foo' + sage: type(s.sage()) + """ + if self.is_string(): + return str(self) return pari(str(self)).sage() def is_string(self): @@ -921,9 +930,8 @@ def is_string(self): True sage: gp('[1,2,3]').is_string() False - """ - return repr(self.type())=='t_STR' + return repr(self.type()) == 't_STR' def __long__(self): """ From c534789e7a09ef82af93579332ddb7063fc0dda0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Sep 2018 11:36:07 +0200 Subject: [PATCH 162/264] py3: some care for xrange in the src/doc folder --- src/doc/de/tutorial/programming.rst | 3 ++- src/doc/en/thematic_tutorials/numtheory_rsa.rst | 2 +- .../en/thematic_tutorials/structures_in_coding_theory.rst | 4 ++-- src/doc/en/thematic_tutorials/tutorial-comprehensions.rst | 6 ++++-- .../en/thematic_tutorials/tutorial-programming-python.rst | 6 ++++-- src/doc/en/tutorial/programming.rst | 3 ++- src/doc/fr/tutorial/programming.rst | 3 ++- src/doc/ja/tutorial/programming.rst | 3 ++- src/doc/pt/tutorial/programming.rst | 3 ++- src/doc/ru/tutorial/programming.rst | 3 ++- 10 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/doc/de/tutorial/programming.rst b/src/doc/de/tutorial/programming.rst index e9aeb746e84..231ad0d3a03 100644 --- a/src/doc/de/tutorial/programming.rst +++ b/src/doc/de/tutorial/programming.rst @@ -544,7 +544,8 @@ nichtnegativen ganzen Zahlen bis :math:`10000000`. :: - sage: v = (n^2 for n in xrange(10000000)) + sage: v = (n^2 for n in xrange(10000000)) # py2 + sage: v = (n^2 for n in range(10000000)) # py3 sage: next(v) 0 sage: next(v) diff --git a/src/doc/en/thematic_tutorials/numtheory_rsa.rst b/src/doc/en/thematic_tutorials/numtheory_rsa.rst index 789dd73d3f2..fa09b751449 100644 --- a/src/doc/en/thematic_tutorials/numtheory_rsa.rst +++ b/src/doc/en/thematic_tutorials/numtheory_rsa.rst @@ -116,7 +116,7 @@ say, ``/home/mvngu/totient.sage``, organizing it as follows to enhance readability. :: L = [] - for n in xrange(1, 21): + for n in range(1, 21): if gcd(n, 20) == 1: L.append(n) L diff --git a/src/doc/en/thematic_tutorials/structures_in_coding_theory.rst b/src/doc/en/thematic_tutorials/structures_in_coding_theory.rst index 584271a22a2..9288e1b5152 100644 --- a/src/doc/en/thematic_tutorials/structures_in_coding_theory.rst +++ b/src/doc/en/thematic_tutorials/structures_in_coding_theory.rst @@ -494,7 +494,7 @@ So we only need to override ``transmit_unsafe``! Let us do it:: ....: number_err = self.number_errors() ....: V = self.input_space() ....: F = GF(2) - ....: for i in sample(xrange(V.dimension()), number_err): + ....: for i in sample(range(V.dimension()), number_err): ....: w[i] += F.one() ....: return w @@ -699,7 +699,7 @@ derive from the one that follows. number_err = self.number_errors() V = self.input_space() F = GF(2) - for i in sample(xrange(V.dimension()), number_err): + for i in sample(range(V.dimension()), number_err): w[i] += F.one() return w diff --git a/src/doc/en/thematic_tutorials/tutorial-comprehensions.rst b/src/doc/en/thematic_tutorials/tutorial-comprehensions.rst index f4b6bdadba0..3f4eff7aa9d 100644 --- a/src/doc/en/thematic_tutorials/tutorial-comprehensions.rst +++ b/src/doc/en/thematic_tutorials/tutorial-comprehensions.rst @@ -118,9 +118,11 @@ idioms give the same results; however, the second idiom is much more memory efficient (for large examples) as it does not expand any list in memory:: - sage: sum( [ binomial(8, i) for i in range(9) ] ) + sage: sum([binomial(8, i) for i in range(9)]) 256 - sage: sum( binomial(8, i) for i in xrange(9) ) + sage: sum(binomial(8, i) for i in xrange(9)) # py2 + 256 + sage: sum(binomial(8, i) for i in range(9)) # py3 256 .. TOPIC:: Exercises diff --git a/src/doc/en/thematic_tutorials/tutorial-programming-python.rst b/src/doc/en/thematic_tutorials/tutorial-programming-python.rst index 81a2c0e905a..5899ac46e56 100644 --- a/src/doc/en/thematic_tutorials/tutorial-programming-python.rst +++ b/src/doc/en/thematic_tutorials/tutorial-programming-python.rst @@ -262,7 +262,8 @@ be negative. Use range to construct the list `[10, 7, 4, 1, -2]`. .. SEEALSO:: - - :func:`xrange`: returns an iterator rather than building a list. + - :func:`xrange`: returns an iterator rather than building a list, + (only for Python2, replaced by range in Python 3). - :func:`srange`: like range but with Sage integers; see below. - :func:`xsrange`: like xrange but with Sage integers. @@ -710,7 +711,8 @@ braces, ``{}``, with comma-separated entries given in the form A second method is to use the constructor :class:`dict` which admits a list (or actually any iterable) of 2-tuples *(key, value)*:: - sage: dd = dict((i,i^2) for i in xrange(10)) + sage: dd = dict((i,i^2) for i in xrange(10)) # py2 + sage: dd = dict((i,i^2) for i in range(10)) # py3 sage: dd {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81} diff --git a/src/doc/en/tutorial/programming.rst b/src/doc/en/tutorial/programming.rst index 4d821f636dd..8739bb535bb 100644 --- a/src/doc/en/tutorial/programming.rst +++ b/src/doc/en/tutorial/programming.rst @@ -524,7 +524,8 @@ nonnegative integers up to :math:`10000000`. :: - sage: v = (n^2 for n in xrange(10000000)) + sage: v = (n^2 for n in xrange(10000000)) # py2 + sage: v = (n^2 for n in range(10000000)) # py3 sage: next(v) 0 sage: next(v) diff --git a/src/doc/fr/tutorial/programming.rst b/src/doc/fr/tutorial/programming.rst index 4eaa3a9cf64..09110ef450a 100644 --- a/src/doc/fr/tutorial/programming.rst +++ b/src/doc/fr/tutorial/programming.rst @@ -540,7 +540,8 @@ d'entiers positifs jusqu'à :math:`10000000`. :: - sage: v = (n^2 for n in xrange(10000000)) + sage: v = (n^2 for n in xrange(10000000)) # py2 + sage: v = (n^2 for n in range(10000000)) # py3 sage: next(v) 0 sage: next(v) diff --git a/src/doc/ja/tutorial/programming.rst b/src/doc/ja/tutorial/programming.rst index 83eb61f2efa..3aacad6749d 100644 --- a/src/doc/ja/tutorial/programming.rst +++ b/src/doc/ja/tutorial/programming.rst @@ -503,7 +503,8 @@ Pythonには集合(set)型が組込まれている. :: - sage: v = (n^2 for n in xrange(10000000)) + sage: v = (n^2 for n in xrange(10000000)) # py2 + sage: v = (n^2 for n in range(10000000)) # py3 sage: next(v) 0 sage: next(v) diff --git a/src/doc/pt/tutorial/programming.rst b/src/doc/pt/tutorial/programming.rst index 9dd806e1d9e..d79f13ed55a 100644 --- a/src/doc/pt/tutorial/programming.rst +++ b/src/doc/pt/tutorial/programming.rst @@ -547,7 +547,8 @@ sobre o quadrados dos números inteiros até :math:`10000000`. :: - sage: v = (n^2 for n in xrange(10000000)) + sage: v = (n^2 for n in xrange(10000000)) # py2 + sage: v = (n^2 for n in range(10000000)) # py3 sage: next(v) 0 sage: next(v) diff --git a/src/doc/ru/tutorial/programming.rst b/src/doc/ru/tutorial/programming.rst index 8580a3ca913..a394cb4bddc 100644 --- a/src/doc/ru/tutorial/programming.rst +++ b/src/doc/ru/tutorial/programming.rst @@ -508,7 +508,8 @@ http://docs.python.org/lib/typesmapping.html) произвольным объе :: - sage: v = (n^2 for n in xrange(10000000)) + sage: v = (n^2 for n in xrange(10000000)) # py2 + sage: v = (n^2 for n in range(10000000)) # py3 sage: next(v) 0 sage: next(v) From e4a73fe08c8f8db1dfa52cc10f0ad2a6fa2aa9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Sep 2018 13:29:12 +0200 Subject: [PATCH 163/264] py3: make doctest pass in universal cyclotomic field --- src/sage/rings/universal_cyclotomic_field.py | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sage/rings/universal_cyclotomic_field.py b/src/sage/rings/universal_cyclotomic_field.py index 41a0c0ef5c8..afee7ec7f05 100644 --- a/src/sage/rings/universal_cyclotomic_field.py +++ b/src/sage/rings/universal_cyclotomic_field.py @@ -271,9 +271,9 @@ def __init__(self, parent, obj): r""" INPUT: - - ``parent`` - a universal cyclotomic field + - ``parent`` -- a universal cyclotomic field - - ``obj`` - a libgap element (either an integer, a rational or a + - ``obj`` -- a libgap element (either an integer, a rational or a cyclotomic) TESTS:: @@ -463,8 +463,6 @@ def conductor(self): """ return self._obj.Conductor().sage() - field_order = deprecated_function_alias(18152, conductor) - def _symbolic_(self, R): r""" TESTS:: @@ -761,7 +759,7 @@ def denominator(self): def multiplicative_order(self): r""" - The multiplicative order. + Return the multiplicative order. EXAMPLES:: @@ -778,7 +776,7 @@ def multiplicative_order(self): def additive_order(self): r""" - The additive order. + Return the additive order. EXAMPLES:: @@ -985,7 +983,8 @@ def galois_conjugates(self, n=None): n = k if n is None else ZZ(n) if not k.divides(n): raise ValueError("n = {} must be a multiple of the conductor ({})".format(n, k)) - return [P.element_class(P, obj.GaloisCyc(i)) for i in n.coprime_integers(n)] + return [P.element_class(P, obj.GaloisCyc(i)) + for i in n.coprime_integers(n)] def norm_of_galois_extension(self): r""" @@ -1005,7 +1004,8 @@ def norm_of_galois_extension(self): """ obj = self._obj k = obj.Conductor().sage() - return libgap.Product(libgap([obj.GaloisCyc(i) for i in range(k) if k.gcd(i) == 1])).sage() + return libgap.Product(libgap([obj.GaloisCyc(i) for i in range(k) + if k.gcd(i) == 1])).sage() def minpoly(self, var='x'): r""" @@ -1239,9 +1239,9 @@ def _element_constructor_(self, elt): sage: UCF('[[0, 1], [0, 2]]') Traceback (most recent call last): ... - TypeError: [ [ 0, 1 ], [ 0, 2 ] ] of type not valid to initialize an - element of the universal cyclotomic field + TypeError: [ [ 0, 1 ], [ 0, 2 ] ] + of type not valid + to initialize an element of the universal cyclotomic field .. TODO:: From 175ec80b623f97f9072fb18e3f203ebe90a31a45 Mon Sep 17 00:00:00 2001 From: Vincent Klein Date: Fri, 7 Sep 2018 13:44:30 +0200 Subject: [PATCH 164/264] Trac #26205: LatticePolytopeClass add rich comparision ... methods --- src/sage/geometry/lattice_polytope.py | 81 +++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index 68e508cd424..4df4ad5f1a2 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -132,6 +132,7 @@ from sage.sets.set import Set_generic from sage.structure.all import Sequence from sage.structure.sage_object import SageObject +from sage.structure.richcmp import * from copy import copy import collections @@ -596,6 +597,86 @@ def __eq__(self, other): return (isinstance(other, LatticePolytopeClass) and self._vertices == other._vertices) + def __lt__(self, other): + r""" + Return if this element is less than ``other``. + + TESTS:: + + sage: p1 = LatticePolytope([(1,0), (0,1), (-1,-1)]) + sage: p2 = LatticePolytope([(1,0), (0,1), (-1,-1)]) + sage: p3 = LatticePolytope([(0,1), (1,0), (-1,-1)]) + sage: p1 < p2 + False + sage: p2 < p1 + False + sage: p1 < p3 + False + sage: p3 < p1 + True + """ + return richcmp(self._vertices, other._vertices, op_LT) + + def __le__(self, other): + r""" + Return if this element is less than or equal to ``other``. + + TESTS:: + + sage: p1 = LatticePolytope([(1,0), (0,1), (-1,-1)]) + sage: p2 = LatticePolytope([(1,0), (0,1), (-1,-1)]) + sage: p3 = LatticePolytope([(0,1), (1,0), (-1,-1)]) + sage: p1 <= p2 + True + sage: p2 <= p1 + True + sage: p1 <= p3 + False + sage: p3 <= p1 + True + """ + return richcmp(self._vertices, other._vertices, op_LE) + + def __gt__(self, other): + r""" + Return if this element is greater than ``other``. + + TESTS:: + + sage: p1 = LatticePolytope([(1,0), (0,1), (-1,-1)]) + sage: p2 = LatticePolytope([(1,0), (0,1), (-1,-1)]) + sage: p3 = LatticePolytope([(0,1), (1,0), (-1,-1)]) + sage: p1 > p2 + False + sage: p2 > p1 + False + sage: p1 > p3 + True + sage: p3 > p1 + False + """ + return richcmp(self._vertices, other._vertices, op_GT) + + def __ge__(self, other): + r""" + Return if this element is greater than or equal to ``other``. + + TESTS:: + + sage: p1 = LatticePolytope([(1,0), (0,1), (-1,-1)]) + sage: p2 = LatticePolytope([(1,0), (0,1), (-1,-1)]) + sage: p3 = LatticePolytope([(0,1), (1,0), (-1,-1)]) + sage: p1 >= p2 + True + sage: p2 >= p1 + True + sage: p1 >= p3 + True + sage: p3 >= p1 + False + """ + return richcmp(self._vertices, other._vertices, op_GE) + @cached_method def __hash__(self): r""" From c9daec034a82529a671c676eee5f1a111070039e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Sep 2018 13:52:11 +0200 Subject: [PATCH 165/264] py3: fix doctest in /arith/numerical_approx.pxd --- src/sage/arith/numerical_approx.pxd | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/sage/arith/numerical_approx.pxd b/src/sage/arith/numerical_approx.pxd index d7bedc7118a..7dc55ed6ca4 100644 --- a/src/sage/arith/numerical_approx.pxd +++ b/src/sage/arith/numerical_approx.pxd @@ -11,10 +11,17 @@ cpdef inline long digits_to_bits(d) except -1: Traceback (most recent call last): ... ValueError: number of digits must be positive - sage: digits_to_bits("10") + + TESTS:: + + sage: digits_to_bits("10") # py2 Traceback (most recent call last): ... TypeError: a float is required + sage: digits_to_bits("10") # py3 + Traceback (most recent call last): + ... + TypeError: must be real number, not str """ if d is None: return 53 From 3e9531f286bbade51cef0c315cf97ba1aef007f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Sep 2018 14:25:06 +0200 Subject: [PATCH 166/264] py3: fixes in database/conway and tests/py3_syntax --- src/sage/databases/conway.py | 8 +++----- src/sage/tests/py3_syntax.py | 8 +++++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/databases/conway.py b/src/sage/databases/conway.py index 1d4abcfeb17..b2a984628c3 100644 --- a/src/sage/databases/conway.py +++ b/src/sage/databases/conway.py @@ -179,10 +179,8 @@ def __iter__(self): sage: c = ConwayPolynomials() sage: itr = iter(c) - sage: next(itr) + sage: next(itr) # random (65537, 4) - sage: next(itr) - (2, 1) """ for a, b in iteritems(self._store): for c in b: @@ -221,9 +219,9 @@ def polynomial(self, p, n): RuntimeError: Conway polynomial over F_97 of degree 128 not in database. """ try: - return self[p,n] + return self[p, n] except KeyError: - raise RuntimeError("Conway polynomial over F_%s of degree %s not in database."%(p,n)) + raise RuntimeError("Conway polynomial over F_%s of degree %s not in database." % (p, n)) def has_polynomial(self, p, n): """ diff --git a/src/sage/tests/py3_syntax.py b/src/sage/tests/py3_syntax.py index 026f5d065cf..bb90332fdc7 100644 --- a/src/sage/tests/py3_syntax.py +++ b/src/sage/tests/py3_syntax.py @@ -35,6 +35,8 @@ import subprocess from sage.env import SAGE_SRC +from sage.cpython.string import bytes_to_str + class SortedDirectoryWalkerABC(object): r""" @@ -172,7 +174,7 @@ def test(self, *filenames): EXAMPLES:: sage: import os, tempfile - sage: src = tempfile.NamedTemporaryFile(suffix='.py', delete=False) + sage: src = tempfile.NamedTemporaryFile(suffix='.py', mode='w+', delete=False) sage: _ = src.write('print "invalid print statement"') sage: src.close() sage: from sage.tests.py3_syntax import Python3SyntaxTest @@ -216,6 +218,6 @@ def test(self, *filenames): return print('Invalid Python 3 syntax found:') if stdout: - print(stdout) + print(bytes_to_str(stdout)) if stderr: - print(stderr) + print(bytes_to_str(stderr)) From 7a2802e2a8be1ef3c40fa60a5c24ed8002b5cd78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 4 Sep 2018 22:08:13 +0200 Subject: [PATCH 167/264] various py3 fixes in src/doc and src/sage/tests --- src/doc/ca/intro/index.rst | 6 ++++-- src/doc/ja/tutorial/interactive_shell.rst | 5 ----- src/sage/tests/arxiv_0812_2725.py | 4 ++-- src/sage/tests/french_book/polynomes.py | 4 ++-- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/doc/ca/intro/index.rst b/src/doc/ca/intro/index.rst index 7fd49c8ce59..fae3ba8d6b9 100644 --- a/src/doc/ca/intro/index.rst +++ b/src/doc/ca/intro/index.rst @@ -201,8 +201,10 @@ Per exemple:: sage: C = Set(S) sage: S [121, 49, 25, 9, 9, 25, 49, 121] - sage: C + sage: C # random {121, 9, 49, 25} + sage: sorted(C) + [9, 25, 49, 121] La `i`-èssima entrada d'una seqüència (o d'un conjunt) `C` és ``C[i]``. Però compte perquè Sage @@ -257,7 +259,7 @@ de la manera següent:: Podem aplicar una funció a tots els elements d'una llista de la manera següent:: - sage: map(cos, [0,pi..6*pi]) + sage: [cos(t) for t in [0,pi..6*pi]] [1, -1, 1, -1, 1, -1, 1] diff --git a/src/doc/ja/tutorial/interactive_shell.rst b/src/doc/ja/tutorial/interactive_shell.rst index f7269176a5c..f0d0dd92a11 100644 --- a/src/doc/ja/tutorial/interactive_shell.rst +++ b/src/doc/ja/tutorial/interactive_shell.rst @@ -423,11 +423,6 @@ IPythonのクイック レファレンスガイドを見たければ, ``%quick :: - sage: 3_2 - Traceback (most recent call last): - ... - SyntaxError: invalid syntax - sage: EllipticCurve([0,infinity]) Traceback (most recent call last): ... diff --git a/src/sage/tests/arxiv_0812_2725.py b/src/sage/tests/arxiv_0812_2725.py index 5fb5cb987e1..08cb8cf06f6 100644 --- a/src/sage/tests/arxiv_0812_2725.py +++ b/src/sage/tests/arxiv_0812_2725.py @@ -202,8 +202,8 @@ def setp_to_edges(p): The main example from the paper:: sage: from sage.tests.arxiv_0812_2725 import * - sage: setp_to_edges(Set(map(Set, [[1,5],[2,4,9],[3],[6,12],[7,10,11],[8]]))) - [[7, 10], [10, 11], [2, 4], [4, 9], [1, 5], [6, 12]] + sage: sorted(setp_to_edges(Set(map(Set, [[1,5],[2,4,9],[3],[6,12],[7,10,11],[8]])))) + [[1, 5], [2, 4], [4, 9], [6, 12], [7, 10], [10, 11]] """ q = [sorted(list(b)) for b in p] ans = [] diff --git a/src/sage/tests/french_book/polynomes.py b/src/sage/tests/french_book/polynomes.py index 9173d8bcb02..593fcd68239 100644 --- a/src/sage/tests/french_book/polynomes.py +++ b/src/sage/tests/french_book/polynomes.py @@ -286,7 +286,7 @@ Sage example in ./polynomes.tex, line 1034:: - sage: Qx(map(lift_sym, num))/Qx(map(lift_sym, den)) + sage: Qx([lift_sym(a) for a in num]) / Qx([lift_sym(b) for b in den]) (-10*x^3 + 105*x)/(x^4 - 45*x^2 + 105) Sage example in ./polynomes.tex, line 1042:: @@ -294,7 +294,7 @@ sage: def mypade(pol, n, k): ....: x = ZpZx.gen(); ....: n,d = ZpZx(pol).rational_reconstruct(x^n, k-1, n-k) - ....: return Qx(map(lift_sym, n))/Qx(map(lift_sym, d)) + ....: return Qx([lift_sym(a) for a in n]) / Qx([lift_sym(b) for b in d]) Sage example in ./polynomes.tex, line 1109:: From c477c6380532c977bbd94d93799182554c4bac1c Mon Sep 17 00:00:00 2001 From: Vincent Klein Date: Fri, 7 Sep 2018 14:38:00 +0200 Subject: [PATCH 168/264] Trac #26205: - use `__richcmp__` instead of rich ... comparision methods. - Remove `__eq__`, `__ne__`, `__lt__`, `__le__`, `__gt__`, and `__ge__` methods. --- src/sage/geometry/lattice_polytope.py | 105 ++------------------------ 1 file changed, 8 insertions(+), 97 deletions(-) diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index 4df4ad5f1a2..c4244de9355 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -132,7 +132,7 @@ from sage.sets.set import Set_generic from sage.structure.all import Sequence from sage.structure.sage_object import SageObject -from sage.structure.richcmp import * +from sage.structure.richcmp import richcmp_method, richcmp from copy import copy import collections @@ -463,7 +463,7 @@ def is_LatticePolytope(x): """ return isinstance(x, LatticePolytopeClass) - +@richcmp_method class LatticePolytopeClass(SageObject, collections.Hashable): r""" Create a lattice polytope. @@ -560,7 +560,7 @@ def __contains__(self, point): """ return self._contains(point) - def __eq__(self, other): + def __richcmp__(self, other, op): r""" Compare ``self`` with ``other``. @@ -568,14 +568,8 @@ def __eq__(self, other): - ``other`` -- anything. - OUTPUT: - - - ``True`` if ``other`` is a :class:`lattice polytope - ` equal to ``self``, ``False`` otherwise. - - .. NOTE:: - - Two lattice polytopes are equal if they have the same vertices + .. Note :: + Two lattice polytopes are equal if they have the same vertices listed in the same order. TESTS:: @@ -593,19 +587,6 @@ def __eq__(self, other): False sage: p1 == 0 False - """ - return (isinstance(other, LatticePolytopeClass) - and self._vertices == other._vertices) - - def __lt__(self, other): - r""" - Return if this element is less than ``other``. - - TESTS:: - - sage: p1 = LatticePolytope([(1,0), (0,1), (-1,-1)]) - sage: p2 = LatticePolytope([(1,0), (0,1), (-1,-1)]) - sage: p3 = LatticePolytope([(0,1), (1,0), (-1,-1)]) sage: p1 < p2 False sage: p2 < p1 @@ -614,18 +595,6 @@ def __lt__(self, other): False sage: p3 < p1 True - """ - return richcmp(self._vertices, other._vertices, op_LT) - - def __le__(self, other): - r""" - Return if this element is less than or equal to ``other``. - - TESTS:: - - sage: p1 = LatticePolytope([(1,0), (0,1), (-1,-1)]) - sage: p2 = LatticePolytope([(1,0), (0,1), (-1,-1)]) - sage: p3 = LatticePolytope([(0,1), (1,0), (-1,-1)]) sage: p1 <= p2 True sage: p2 <= p1 @@ -634,18 +603,6 @@ def __le__(self, other): False sage: p3 <= p1 True - """ - return richcmp(self._vertices, other._vertices, op_LE) - - def __gt__(self, other): - r""" - Return if this element is greater than ``other``. - - TESTS:: - - sage: p1 = LatticePolytope([(1,0), (0,1), (-1,-1)]) - sage: p2 = LatticePolytope([(1,0), (0,1), (-1,-1)]) - sage: p3 = LatticePolytope([(0,1), (1,0), (-1,-1)]) sage: p1 > p2 False sage: p2 > p1 @@ -654,18 +611,6 @@ def __gt__(self, other): True sage: p3 > p1 False - """ - return richcmp(self._vertices, other._vertices, op_GT) - - def __ge__(self, other): - r""" - Return if this element is greater than or equal to ``other``. - - TESTS:: - - sage: p1 = LatticePolytope([(1,0), (0,1), (-1,-1)]) - sage: p2 = LatticePolytope([(1,0), (0,1), (-1,-1)]) - sage: p3 = LatticePolytope([(0,1), (1,0), (-1,-1)]) sage: p1 >= p2 True sage: p2 >= p1 @@ -675,7 +620,9 @@ def __ge__(self, other): sage: p3 >= p1 False """ - return richcmp(self._vertices, other._vertices, op_GE) + if not isinstance(other, LatticePolytopeClass): + return NotImplemented + return richcmp(self._vertices, other._vertices, op) @cached_method def __hash__(self): @@ -695,42 +642,6 @@ def __hash__(self): # FIXME: take into account other things that may be preset?.. return hash(self._vertices) - def __ne__(self, other): - r""" - Compare ``self`` with ``other``. - - INPUT: - - - ``other`` -- anything. - - OUTPUT: - - - ``False`` if ``other`` is a :class:`lattice polytope - ` equal to ``self``, ``True`` otherwise. - - .. NOTE:: - - Two lattice polytopes are if they have the same vertices listed in - the same order. - - TESTS:: - - sage: p1 = LatticePolytope([(1,0), (0,1), (-1,-1)]) - sage: p2 = LatticePolytope([(1,0), (0,1), (-1,-1)]) - sage: p3 = LatticePolytope([(0,1), (1,0), (-1,-1)]) - sage: p1 != p1 - False - sage: p1 != p2 - False - sage: p1 is p2 - False - sage: p1 != p3 - True - sage: p1 != 0 - True - """ - return not (self == other) - def __reduce__(self): r""" Reduction function. Does not store data that can be relatively fast From 788ebed961554d2ff163869392eba326f520e11e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Sep 2018 15:18:38 +0200 Subject: [PATCH 169/264] py3: fix some doctests in plot/ folder --- src/sage/plot/animate.py | 10 ++++------ src/sage/plot/arc.py | 2 +- src/sage/plot/colors.py | 33 +++++++++++++++++++++++++++++---- src/sage/plot/disk.py | 6 +++--- src/sage/plot/ellipse.py | 7 ++++--- src/sage/plot/graphics.py | 12 +++++++++--- src/sage/plot/plot3d/texture.py | 6 +++--- src/sage/plot/text.py | 4 ++-- 8 files changed, 55 insertions(+), 25 deletions(-) diff --git a/src/sage/plot/animate.py b/src/sage/plot/animate.py index 2ed16ac1b81..eaad0e7833d 100644 --- a/src/sage/plot/animate.py +++ b/src/sage/plot/animate.py @@ -44,8 +44,6 @@ Animate as an APNG_:: sage: a.apng() # long time - doctest:...: DeprecationWarning: use tmp_filename instead - See http://trac.sagemath.org/17234 for details. An animated :class:`sage.plot.graphics.GraphicsArray` of rotating ellipses:: @@ -123,7 +121,7 @@ from sage.misc.fast_methods import WithEqualityById from sage.structure.sage_object import SageObject -from sage.misc.temporary_file import tmp_dir, tmp_filename, graphics_filename +from sage.misc.temporary_file import tmp_dir, tmp_filename from . import plot import sage.misc.misc import sage.misc.viewer @@ -608,7 +606,7 @@ def gif(self, delay=20, savefile=None, iterations=0, show_path=False, raise OSError(msg) else: if not savefile: - savefile = graphics_filename(ext='.gif') + savefile = tmp_filename(ext='.gif') if not savefile.endswith('.gif'): savefile += '.gif' savefile = os.path.abspath(savefile) @@ -894,7 +892,7 @@ def ffmpeg(self, savefile=None, show_path=False, output_format=None, else: if output_format[0] != '.': output_format = '.'+output_format - savefile = graphics_filename(ext=output_format) + savefile = tmp_filename(ext=output_format) else: if output_format is None: suffix = os.path.splitext(savefile)[1] @@ -999,7 +997,7 @@ def apng(self, savefile=None, show_path=False, delay=20, iterations=0): """ pngdir = self.png() if savefile is None: - savefile = graphics_filename('.png') + savefile = tmp_filename('.png') with open(savefile, "wb") as out: apng = APngAssembler( out, len(self), diff --git a/src/sage/plot/arc.py b/src/sage/plot/arc.py index 53917e03eb5..d4d1eeeb926 100644 --- a/src/sage/plot/arc.py +++ b/src/sage/plot/arc.py @@ -48,7 +48,7 @@ class Arc(GraphicPrimitive): sage: from sage.plot.arc import Arc sage: print(Arc(0,0,1,1,pi/4,pi/4,pi/2,{})) - Arc with center (0.0,0.0) radii (1.0,1.0) angle 0.785398163397 inside the sector (0.785398163397,1.57079632679) + Arc with center (0.0,0.0) radii (1.0,1.0) angle 0.78539816339... inside the sector (0.78539816339...,1.5707963267...) """ def __init__(self, x, y, r1, r2, angle, s1, s2, options): """ diff --git a/src/sage/plot/colors.py b/src/sage/plot/colors.py index 899811e28ed..1ef66845f32 100644 --- a/src/sage/plot/colors.py +++ b/src/sage/plot/colors.py @@ -804,14 +804,22 @@ def __truediv__(self, right): True sage: yellow.__div__(1/4) RGB color (0.0, 0.0, 0.0) + + TESTS:: + sage: Color('black') / 0.0 Traceback (most recent call last): ... ZeroDivisionError: float division by zero - sage: papayawhip / yellow + + sage: papayawhip / yellow # py2 Traceback (most recent call last): ... TypeError: float() argument must be a string or a number + sage: papayawhip / yellow # py3 + Traceback (most recent call last): + ... + TypeError: float() argument must be a string or a number, not 'Color' """ return self * (1 / float(right)) @@ -1242,10 +1250,18 @@ def float_to_html(r, g, b): '#070f05' sage: float_to_html(*Color('brown').rgb()) '#a52a2a' - sage: float_to_html((0.2, 0.6, 0.8)) + + TESTS:: + + sage: float_to_html((0.2, 0.6, 0.8)) # py2 Traceback (most recent call last): ... TypeError: float_to_html() takes exactly 3 arguments (1 given) + + sage: float_to_html((0.2, 0.6, 0.8)) # py3 + Traceback (most recent call last): + ... + TypeError: float_to_html() missing 2 required positional arguments: 'g' and 'b' """ return "#%06x" % float_to_integer(r, g, b) @@ -1278,10 +1294,18 @@ def float_to_integer(r, g, b): 462597 sage: float_to_integer(*Color('brown').rgb()) 10824234 - sage: float_to_integer((0.2, 0.6, 0.8)) + + TESTS:: + + sage: float_to_integer((0.2, 0.6, 0.8)) # py2 Traceback (most recent call last): ... TypeError: float_to_integer() takes exactly 3 arguments (1 given) + + sage: float_to_integer((0.2, 0.6, 0.8)) # py3 + Traceback (most recent call last): + ... + TypeError: float_to_integer() missing 2 required positional arguments: 'g' and 'b' """ r, g, b = map(mod_one, (r, g, b)) return int(r * 255) << 16 | int(g * 255) << 8 | int(b * 255) @@ -1686,7 +1710,7 @@ def __delitem__(self, name): sage: from sage.plot.colors import Colormaps sage: maps = Colormaps() sage: count = len(maps) - sage: maps.popitem() + sage: maps.popitem() # random (u'Spectral', ) sage: count - 1 == len(maps) True @@ -1694,4 +1718,5 @@ def __delitem__(self, name): self.load_maps() del self.maps[name] + colormaps = Colormaps() diff --git a/src/sage/plot/disk.py b/src/sage/plot/disk.py index 7a6f21c7954..c5667314955 100644 --- a/src/sage/plot/disk.py +++ b/src/sage/plot/disk.py @@ -49,7 +49,7 @@ class Disk(GraphicPrimitive): sage: from sage.plot.disk import Disk sage: D = Disk((1,2), 2, (pi/2,pi), {'zorder':3}) sage: D - Disk defined by (1.0,2.0) with r=2.0 spanning (1.57079632679, 3.14159265359) radians + Disk defined by (1.0,2.0) with r=2.0 spanning (1.5707963267..., 3.1415926535...) radians sage: D.options()['zorder'] 3 sage: D.x @@ -141,9 +141,9 @@ def _repr_(self): sage: P = disk((3, 3), 1, (0, pi/2)) sage: p = P[0]; p - Disk defined by (3.0,3.0) with r=1.0 spanning (0.0, 1.57079632679) radians + Disk defined by (3.0,3.0) with r=1.0 spanning (0.0, 1.5707963267...) radians """ - return "Disk defined by (%s,%s) with r=%s spanning (%s, %s) radians"%(self.x, + return "Disk defined by (%s,%s) with r=%s spanning (%s, %s) radians" % (self.x, self.y, self.r, self.rad1, self.rad2) def _render_on_subplot(self, subplot): diff --git a/src/sage/plot/ellipse.py b/src/sage/plot/ellipse.py index 0bdffdf0933..244543f5a57 100644 --- a/src/sage/plot/ellipse.py +++ b/src/sage/plot/ellipse.py @@ -44,7 +44,7 @@ class Ellipse(GraphicPrimitive): sage: from sage.plot.ellipse import Ellipse sage: Ellipse(0, 0, 2, 1, pi/4, {}) - Ellipse centered at (0.0, 0.0) with radii (2.0, 1.0) and angle 0.785398163397 + Ellipse centered at (0.0, 0.0) with radii (2.0, 1.0) and angle 0.78539816339... """ def __init__(self, x, y, r1, r2, angle, options): """ @@ -67,8 +67,9 @@ def __init__(self, x, y, r1, r2, angle, options): self.r2 = float(r2) if self.r1 <= 0 or self.r2 <= 0: raise ValueError("both radii must be positive") - self.angle = fmod(angle,2*pi) - if self.angle < 0: self.angle += 2*pi + self.angle = fmod(angle, 2 * pi) + if self.angle < 0: + self.angle += 2 * pi GraphicPrimitive.__init__(self, options) def get_minmax_data(self): diff --git a/src/sage/plot/graphics.py b/src/sage/plot/graphics.py index aa9b0c2d64e..381179ae8ba 100644 --- a/src/sage/plot/graphics.py +++ b/src/sage/plot/graphics.py @@ -1120,10 +1120,16 @@ def plot(self): It does not accept any argument (:trac:`19539`):: - sage: S.plot(1) + sage: S.plot(1) # py2 Traceback (most recent call last): ... TypeError: plot() takes exactly 1 argument (2 given) + + sage: S.plot(1) # py3 + Traceback (most recent call last): + ... + TypeError: plot() takes 1 positional argument but 2 were given + sage: S.plot(hey="hou") Traceback (most recent call last): ... @@ -1589,7 +1595,7 @@ def show(self, **kwds): You can add a title to a plot:: - sage: show(plot(sin,-4,4), title='A plot of $\sin(x)$') + sage: show(plot(sin,-4,4), title=r'A plot of $\sin(x)$') You can also provide the position for the title to the plot. In the plot below the title is placed on the bottom left of the figure.:: @@ -1624,7 +1630,7 @@ def show(self, **kwds): background. This behavior can be recovered by passing in certain ``legend_options``:: - sage: p = plot(sin(x), legend_label='$\sin(x)$') + sage: p = plot(sin(x), legend_label=r'$\sin(x)$') sage: p.show(legend_options={'back_color': (0.9,0.9,0.9), ....: 'shadow': False}) diff --git a/src/sage/plot/plot3d/texture.py b/src/sage/plot/plot3d/texture.py index f85574aabe2..dfe6596d23d 100644 --- a/src/sage/plot/plot3d/texture.py +++ b/src/sage/plot/plot3d/texture.py @@ -339,18 +339,18 @@ def hex_rgb(self): sage: Texture((1, .5, 0)).hex_rgb() 'ff7f00' """ - return "%02x%02x%02x" % tuple(int(255*s) for s in self.color) + return "%02x%02x%02x" % tuple(int(255 * s) for s in self.color) def tachyon_str(self): r""" - Converts Texture object to string suitable for Tachyon ray tracer. + Convert Texture object to string suitable for Tachyon ray tracer. EXAMPLES:: sage: from sage.plot.plot3d.texture import Texture sage: t = Texture(opacity=0.6) sage: t.tachyon_str() - 'Texdef texture...\n Ambient 0.333333333333 Diffuse 0.666666666667 Specular 0.0 Opacity 0.6\n Color 0.4 0.4 1.0\n TexFunc 0' + 'Texdef texture...\n Ambient 0.33333333333... Diffuse 0.66666666666... Specular 0.0 Opacity 0.6\n Color 0.4 0.4 1.0\n TexFunc 0' """ total_color = float(sum(self.ambient) + sum(self.diffuse) + sum(self.specular)) if total_color == 0: diff --git a/src/sage/plot/text.py b/src/sage/plot/text.py index 5438e919434..600c7c8b1cb 100644 --- a/src/sage/plot/text.py +++ b/src/sage/plot/text.py @@ -83,9 +83,9 @@ def _repr_(self): sage: T = text("I like cool constants", (pi,e)) sage: t=T[0];t - Text 'I like cool constants' at the point (3.14159265359,2.71828182846) + Text 'I like cool constants' at the point (3.1415926535...,2.7182818284...) """ - return "Text '%s' at the point (%s,%s)"%(self.string, self.x, self.y) + return "Text '%s' at the point (%s,%s)" % (self.string, self.x, self.y) def _allowed_options(self): """ From bbfa2f361a5948a66461d8a48238aa518c3fa5fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Sep 2018 15:24:07 +0200 Subject: [PATCH 170/264] more fix in plot/graphics --- src/sage/plot/graphics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/plot/graphics.py b/src/sage/plot/graphics.py index 381179ae8ba..603e54a430e 100644 --- a/src/sage/plot/graphics.py +++ b/src/sage/plot/graphics.py @@ -1943,7 +1943,7 @@ def show(self, **kwds): of length two is used. Otherwise, a warning is raised:: sage: plot(x, -4, 4, title='Plot x', title_pos=0.05) - doctest:...: RichReprWarning: Exception in _rich_repr_ while displaying object: 'title_pos' must be a list or tuple of two real numbers. + doctest:...: ...RichReprWarning: Exception in _rich_repr_ while displaying object: 'title_pos' must be a list or tuple of two real numbers. Graphics object consisting of 1 graphics primitive TESTS: @@ -2517,7 +2517,7 @@ def matplotlib(self, filename=None, ``typeset`` must not be set to an arbitrary string:: sage: plot(x, typeset='garbage') - doctest:...: RichReprWarning: Exception in _rich_repr_ while + doctest:...: ...RichReprWarning: Exception in _rich_repr_ while displaying object: typeset must be set to one of 'default', 'latex', or 'type1'; got 'garbage'. Graphics object consisting of 1 graphics primitive From 6df2e84914d3c5715d6b0e21a9146ddd9cc0d5be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Sep 2018 16:20:02 +0200 Subject: [PATCH 171/264] py3: fixing doctests in logic/ --- src/sage/logic/boolformula.py | 2 +- src/sage/logic/logic.py | 4 ++-- src/sage/logic/logicparser.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/logic/boolformula.py b/src/sage/logic/boolformula.py index 6754e361a7b..c500841cf25 100644 --- a/src/sage/logic/boolformula.py +++ b/src/sage/logic/boolformula.py @@ -967,7 +967,7 @@ def satformat(self): s += '0 ' else: varname = '' - while i < self.__expression[i] not in '|) ': + if self.__expression[i] not in '|) ': varname += self.__expression[i] i += 1 s += vars_num[varname] + ' ' diff --git a/src/sage/logic/logic.py b/src/sage/logic/logic.py index 30ea4f8a51f..41f739b273a 100644 --- a/src/sage/logic/logic.py +++ b/src/sage/logic/logic.py @@ -867,10 +867,10 @@ def tokenize(s, toks): i += 1 if len(tok) > 0: - if tok[0] not in string.letters: + if tok[0] not in string.ascii_letters: valid = 0 for c in tok: - if c not in string.letters and c not in string.digits and c != '_': + if c not in string.ascii_letters and c not in string.digits and c != '_': valid = 0 if valid == 1: diff --git a/src/sage/logic/logicparser.py b/src/sage/logic/logicparser.py index 063c6979be0..cabba37dec4 100644 --- a/src/sage/logic/logicparser.py +++ b/src/sage/logic/logicparser.py @@ -496,10 +496,10 @@ def tokenize(s): i += 1 if len(tok) > 0: - if tok[0] not in string.letters: + if tok[0] not in string.ascii_letters: valid = 0 for c in tok: - if c not in string.letters and c not in string.digits and c != '_': + if c not in string.ascii_letters and c not in string.digits and c != '_': valid = 0 if valid == 1: From fbaf7ded155041e3d3df5aa019c9eaeb3689d7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Sep 2018 16:39:44 +0200 Subject: [PATCH 172/264] py3: fix doctests in combinat/sf/ --- src/sage/combinat/sf/sf.py | 4 ++-- src/sage/combinat/sf/sfa.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/sf/sf.py b/src/sage/combinat/sf/sf.py index 315c6385777..d2f7599b673 100644 --- a/src/sage/combinat/sf/sf.py +++ b/src/sage/combinat/sf/sf.py @@ -649,8 +649,8 @@ class function on the symmetric group where the elements sage: ksp3 = SymS3.ksplit() sage: ksp3(Qp[2,1,1,1]) ksp3[2, 1, 1, 1] + t^2*ksp3[2, 2, 1] + (t^3+t^2)*ksp3[3, 1, 1] + t^4*ksp3[3, 2] - sage: [ks(ksp3(la)) for la in ksp3(Qp[2,1,1,1]).support()] - [ks3[2, 2, 1], ks3[2, 1, 1, 1] + t*ks3[2, 2, 1], ks3[3, 2], ks3[3, 1, 1]] + sage: [ks(ksp3(la)) for la in sorted(ksp3(Qp[2,1,1,1]).support())] + [ks3[2, 1, 1, 1] + t*ks3[2, 2, 1], ks3[2, 2, 1], ks3[3, 1, 1], ks3[3, 2]] .. rubric:: dual `k`-Schur functions diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index be5a33dcba3..c81509458ec 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -153,7 +153,7 @@ 1 sage: z.length() 2 - sage: z.support() + sage: sorted(z.support()) [[1, 1, 1], [2, 1]] sage: z.degree() 3 From 5e463601d7270e815ab5791851686f56cca716ae Mon Sep 17 00:00:00 2001 From: Vincent Klein Date: Fri, 7 Sep 2018 16:43:49 +0200 Subject: [PATCH 173/264] Trac #26205: Fix ".. NOTE::" syntax --- src/sage/geometry/lattice_polytope.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index c4244de9355..3123c8f9c05 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -568,7 +568,8 @@ def __richcmp__(self, other, op): - ``other`` -- anything. - .. Note :: + .. NOTE:: + Two lattice polytopes are equal if they have the same vertices listed in the same order. From 1c6ce1c6ab745990c48d3ca7a52a593d3c3788b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Sep 2018 16:51:05 +0200 Subject: [PATCH 174/264] just use range, not xrange --- src/doc/en/thematic_tutorials/tutorial-programming-python.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/doc/en/thematic_tutorials/tutorial-programming-python.rst b/src/doc/en/thematic_tutorials/tutorial-programming-python.rst index 5899ac46e56..4e96d9c1444 100644 --- a/src/doc/en/thematic_tutorials/tutorial-programming-python.rst +++ b/src/doc/en/thematic_tutorials/tutorial-programming-python.rst @@ -711,8 +711,7 @@ braces, ``{}``, with comma-separated entries given in the form A second method is to use the constructor :class:`dict` which admits a list (or actually any iterable) of 2-tuples *(key, value)*:: - sage: dd = dict((i,i^2) for i in xrange(10)) # py2 - sage: dd = dict((i,i^2) for i in range(10)) # py3 + sage: dd = dict((i,i^2) for i in range(10)) sage: dd {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81} From d3ba009423c74aade3c881466d33388d14ccf18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Sep 2018 17:10:36 +0200 Subject: [PATCH 175/264] py3 fix doctests in probability/ --- src/sage/probability/random_variable.py | 31 ++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/sage/probability/random_variable.py b/src/sage/probability/random_variable.py index 1aee21a641c..ae67a7597ea 100644 --- a/src/sage/probability/random_variable.py +++ b/src/sage/probability/random_variable.py @@ -21,6 +21,7 @@ from sage.rings.real_mpfr import (RealField, is_RealField) from sage.rings.rational_field import is_RationalField from sage.sets.set import Set +from pprint import pformat ################################################################################ ################################################################################ @@ -95,11 +96,12 @@ def __call__(self,x): return RR(self._function[x]) except KeyError: # Need some condition for x being a valid domain element: - # raise IndexError, "Argument x (= %s) is not a valid domain element." % x + # raise IndexError("Argument x (= %s) is not a valid domain element." % x) return RR(0) def __repr__(self): - return "Discrete random variable defined by %s" % self._function + F = pformat(self._function) + return "Discrete random variable defined by %s" % F def function(self): """ @@ -294,6 +296,7 @@ def __init__(self, domain, RR): def domain(self): return self._domain + class DiscreteProbabilitySpace(ProbabilitySpace_generic,DiscreteRandomVariable): r""" The discrete probability space @@ -308,15 +311,17 @@ def __init__(self, X, P, codomain = None, check = False): sage: S = [ i for i in range(16) ] sage: P = {} - sage: for i in range(15): P[i] = 2^(-i-1) - sage: P[15] = 2^-16 + sage: for i in range(15): P[i] = 2^(-i-1) + sage: P[15] = 2^-15 sage: X = DiscreteProbabilitySpace(S,P) + sage: sum(X.function().values()) + 1 sage: X.domain() (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) sage: X.set() {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} sage: X.entropy().n() - 1.99972534179688 + 1.99993896484375 A probability space can be defined on any list of elements:: @@ -325,7 +330,7 @@ def __init__(self, X, P, codomain = None, check = False): sage: P = { 'A':1/2, 'B':1/4, 'C':1/4 } sage: X = DiscreteProbabilitySpace(S,P) sage: X - Discrete probability space defined by {'A': 1/2, 'C': 1/4, 'B': 1/4} + Discrete probability space defined by {'A': 1/2, 'B': 1/4, 'C': 1/4} sage: X.entropy().n() 1.50000000000000 """ @@ -334,7 +339,7 @@ def __init__(self, X, P, codomain = None, check = False): if not is_RealField(codomain) and not is_RationalField(codomain): raise TypeError("Argument codomain (= %s) must be the reals or rationals" % codomain) if check: - one = sum([ P[x] for x in P.keys() ]) + one = sum(P.values()) if is_RationalField(codomain): if not one == 1: raise TypeError("Argument P (= %s) does not define a probability function") @@ -345,7 +350,17 @@ def __init__(self, X, P, codomain = None, check = False): DiscreteRandomVariable.__init__(self, self, P, codomain, check) def __repr__(self): - return "Discrete probability space defined by %s" % self.function() + """ + TESTS:: + + sage: S = list(range(4)) + sage: P = {i: 2^(-i-1) for i in range(3)} + sage: P[4] = 2^-3 + sage: DiscreteProbabilitySpace(S,P) + Discrete probability space defined by {0: 1/2, 1: 1/4, 2: 1/8, 4: 1/8} + """ + F = pformat(self.function()) + return "Discrete probability space defined by %s" % F def set(self): r""" From 753c720a95ce208e5d3209c87a65e1a659259a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Sep 2018 17:36:08 +0200 Subject: [PATCH 176/264] final cleanup of invalid escape sequences --- .../root_system/integrable_representations.py | 2 +- src/sage/interfaces/fricas.py | 28 +++++++++---------- src/sage/interfaces/gap.py | 17 ++++++----- src/sage/interfaces/magma.py | 10 +++---- src/sage/misc/sagedoc.py | 3 +- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/sage/combinat/root_system/integrable_representations.py b/src/sage/combinat/root_system/integrable_representations.py index f1c0d53d9b2..e60edb325ed 100644 --- a/src/sage/combinat/root_system/integrable_representations.py +++ b/src/sage/combinat/root_system/integrable_representations.py @@ -264,7 +264,7 @@ def root_lattice(self): @cached_method def level(self): - """ + r""" Return the level of ``self``. The level of a highest weight representation `V_{\Lambda}` is diff --git a/src/sage/interfaces/fricas.py b/src/sage/interfaces/fricas.py index 16a236e78fb..1d2fcdc3053 100644 --- a/src/sage/interfaces/fricas.py +++ b/src/sage/interfaces/fricas.py @@ -208,7 +208,7 @@ FRICAS_MULTI_LINE_START = 2 # and when it doesn't FRICAS_LINE_LENGTH = 80 # length of a line, should match the line length in sage # the following messages have, unfortunately, no markup. -FRICAS_WHAT_OPERATIONS_STRING = "Operations whose names satisfy the above pattern\(s\):" +FRICAS_WHAT_OPERATIONS_STRING = r"Operations whose names satisfy the above pattern\(s\):" FRICAS_ERROR_IN_LIBRARY_CODE = ">> Error detected within library code:" # only the last command should be necessary to make the interface @@ -227,8 +227,8 @@ " (princ #\\Newline))))") FRICAS_LINENUMBER_OFF_CODE = ")lisp (setf |$IOindex| NIL)" -FRICAS_FIRST_PROMPT = "\(1\) -> " -FRICAS_LINENUMBER_OFF_PROMPT = "\(NIL\) -> " +FRICAS_FIRST_PROMPT = r"\(1\) -> " +FRICAS_LINENUMBER_OFF_PROMPT = r"\(NIL\) -> " class FriCAS(ExtraTabCompletion, Expect): """ @@ -344,7 +344,7 @@ def _commands(self): True """ output = self.eval(")what operations", reformat=False) - m = re.search(FRICAS_WHAT_OPERATIONS_STRING + "\n(.*)\n\|startKeyedMsg\|", output, flags = re.DOTALL) + m = re.search(FRICAS_WHAT_OPERATIONS_STRING + r"\n(.*)\n\|startKeyedMsg\|", output, flags = re.DOTALL) l = m.groups()[0].split() return l @@ -511,7 +511,7 @@ def _check_errors(self, line, output): """ # otherwise there might be a message - m = re.search("\|startKeyedMsg\|\n(.*)\n\|endOfKeyedMsg\|", output, flags = re.DOTALL) + m = re.search(r"\|startKeyedMsg\|\n(.*)\n\|endOfKeyedMsg\|", output, flags = re.DOTALL) if m: replacements = [('|startKeyedMsg|\n', ''), ('|endOfKeyedMsg|', '')] @@ -568,7 +568,7 @@ def get(self, var): """ output = self.eval(str(var), reformat=False) # if there is AlgebraOutput we ask no more - m = re.search("\|startAlgebraOutput\|\n(.*)\n\|endOfAlgebraOutput\|", output, flags = re.DOTALL) + m = re.search(r"\|startAlgebraOutput\|\n(.*)\n\|endOfAlgebraOutput\|", output, flags = re.DOTALL) if m: lines = m.groups()[0].split("\n") if max(len(line) for line in lines) < FRICAS_LINE_LENGTH: @@ -1012,12 +1012,12 @@ def _latex_(self): sage: latex(fricas("integrate(sin(x+1/x),x)")) # optional - fricas \int ^{\displaystyle x} {{\sin \left( {{{{{ \%O} ^{2}}+1} \over \%O}} \right)} \ {d \%O}} """ - replacements = [('\sp ', '^'), - ('\sp{', '^{'), - ('\sb ', '_'), - ('\sb{', '_{')] + replacements = [(r'\sp ', '^'), + (r'\sp{', '^{'), + (r'\sb ', '_'), + (r'\sb{', '_{')] P = self._check_valid() - s = P.get_string("first tex(%s)" %self._name) + s = P.get_string("first tex(%s)" % self._name) for old, new in replacements: s = s.replace(old, new) return s @@ -1084,7 +1084,7 @@ def _get_sage_type(self, domain): raise NotImplementedError("The translation of FriCAS type %s to sage is not yet implemented." %domain) def _sage_expression(self, unparsed_InputForm): - """ + r""" Convert an expression to an element of the Symbolic Ring. This does not depend on `self`. Instead, for practical @@ -1266,8 +1266,8 @@ def convert_rootOf(x, y): return ex.subs(rootOf_ev) def _sage_(self): - """ - Convert self to a Sage object. + r""" + Convert ``self`` to a Sage object. EXAMPLES: diff --git a/src/sage/interfaces/gap.py b/src/sage/interfaces/gap.py index f0a3b0bf034..55d41aa7e47 100644 --- a/src/sage/interfaces/gap.py +++ b/src/sage/interfaces/gap.py @@ -353,7 +353,7 @@ def _synchronize(self, timeout=0.5, cmd='%s;'): cmd = str(rnd)+';' try: E.sendline(cmd) - E.expect('@[nf][@J\s>]*'+str(rnd), timeout=timeout) + E.expect(r'@[nf][@J\s>]*'+str(rnd), timeout=timeout) E.send(' ') E.expect('@i', timeout=timeout) except pexpect.TIMEOUT: @@ -405,7 +405,7 @@ def interrupt(self, tries=None, timeout=1, quit_on_fail=True): # send a dummy command E.sendline('224433409;') # read everything up to the actual output of the command - E.expect('@[nf][@J\s>]*224433409', timeout=timeout) + E.expect(r'@[nf][@J\s>]*224433409', timeout=timeout) E.send(' ') # the following input prompt should be the current input # prompt but GAP might be too confused to display it @@ -418,11 +418,11 @@ def interrupt(self, tries=None, timeout=1, quit_on_fail=True): E.sendline() time.sleep(0.1) E.sendline('224433437;') - E.expect('@[nf][@J\s>]*224433437', timeout=timeout) + E.expect(r'@[nf][@J\s>]*224433437', timeout=timeout) E.sendline() time.sleep(0.1) E.sendline('224433479;') - E.expect('@[nf][@J\s>]*224433479', timeout=timeout) + E.expect(r'@[nf][@J\s>]*224433479', timeout=timeout) E.send(' ') # the following input prompt is now the current input prompt E.expect('@i', timeout=timeout) @@ -675,7 +675,7 @@ def _keyboard_interrupt(self): raise KeyboardInterrupt("Ctrl-c pressed while running %s"%self) def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if_needed=True): - """ + r""" Evaluate a line of commands. REMARK: @@ -733,7 +733,6 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if Restarting Gap and trying again sage: a 3 - """ #if line.find('\n') != -1: # raise ValueError, "line must not contain any newlines" @@ -1257,8 +1256,8 @@ def _start(self): self.save_workspace() # Now, as self._expect exists, we can compile some useful pattern: self._compiled_full_pattern = self._expect.compile_pattern_list([ - '@p\d+\.','@@','@[A-Z]','@[123456!"#$%&][^+]*\+', - '@e','@c','@f','@h','@i','@m','@n','@r','@s\d','@w.*\+','@x','@z']) + r'@p\d+\.','@@','@[A-Z]',r'@[123456!"#$%&][^+]*\+', + '@e','@c','@f','@h','@i','@m','@n','@r',r'@s\d',r'@w.*\+','@x','@z']) # read everything up to the first "ready" prompt self._expect.expect("@i") @@ -1360,7 +1359,7 @@ def help(self, s, pager=True): self.eval(r'\$SAGE.tempfile := "%s";' % tmp_to_use) line = Expect.eval(self, "? %s" % s) Expect.eval(self, "? 1") - match = re.search("Page from (\d+)", line) + match = re.search(r"Page from (\d+)", line) if match is None: print(line) else: diff --git a/src/sage/interfaces/magma.py b/src/sage/interfaces/magma.py index 178dfb589cf..4348b7d8ba9 100644 --- a/src/sage/interfaces/magma.py +++ b/src/sage/interfaces/magma.py @@ -223,7 +223,7 @@ PROMPT = ">>>" SAGE_REF = "_sage_ref" -SAGE_REF_RE = re.compile('%s\d+' % SAGE_REF) +SAGE_REF_RE = re.compile(r'%s\d+' % SAGE_REF) from sage.env import SAGE_EXTCODE, DOT_SAGE import sage.misc.misc @@ -2205,7 +2205,7 @@ def gen_names(self): return self.__gen_names def evaluate(self, *args): - """ + r""" Evaluate self at the inputs. INPUT: @@ -2805,10 +2805,10 @@ class MagmaGBLogPrettyPrinter: A device which filters Magma Groebner basis computation logs. """ cmd_inpt = re.compile("^>>>$") - app_inpt = re.compile("^Append\(~_sage_, 0\);$") + app_inpt = re.compile(r"^Append\(~_sage_, 0\);$") - deg_curr = re.compile("^Basis length\: (\d+), queue length\: (\d+), step degree\: (\d+), num pairs\: (\d+)$") - pol_curr = re.compile("^Number of pair polynomials\: (\d+), at (\d+) column\(s\), .*") + deg_curr = re.compile(r"^Basis length\: (\d+), queue length\: (\d+), step degree\: (\d+), num pairs\: (\d+)$") + pol_curr = re.compile(r"^Number of pair polynomials\: (\d+), at (\d+) column\(s\), .*") def __init__(self, verbosity=1, style='magma'): """ diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index fdb5322bc3b..dd9dfc264dd 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -1150,9 +1150,10 @@ def search_src(string, extra1='', extra2='', extra3='', extra4='', extra3=extra3, extra4=extra4, extra5=extra5, **kwds) + def search_doc(string, extra1='', extra2='', extra3='', extra4='', extra5='', **kwds): - """ + r""" Search Sage HTML documentation for lines containing ``string``. The search is case-insensitive by default. From ff930cfb29a37db36b70d400335bbd9ff8e178a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Sep 2018 22:03:37 +0200 Subject: [PATCH 177/264] py3: hash for integer vectors with constraints --- src/sage/combinat/integer_vector.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/sage/combinat/integer_vector.py b/src/sage/combinat/integer_vector.py index 2603dcf01bb..753ff8020bf 100644 --- a/src/sage/combinat/integer_vector.py +++ b/src/sage/combinat/integer_vector.py @@ -1271,6 +1271,23 @@ def __ne__(self, rhs): """ return not self.__eq__(rhs) + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: hash(IntegerVectors(min_slope=0)) == hash(IntegerVectors(min_slope=0)) + True + sage: hash(IntegerVectors(2, min_slope=0)) == hash(IntegerVectors(2, min_slope=0)) + True + sage: hash(IntegerVectors(2, 3, min_slope=0)) == hash(IntegerVectors(2, 3, min_slope=0)) + True + sage: hash(IntegerVectors(min_slope=0)) != hash(IntegerVectors(min_slope=3)) + True + """ + return hash((self.n, self.k, tuple(self.constraints.items()))) + def __contains__(self, x): """ TESTS:: From 0d85e303b488d67448e8589732b6f082325efcfa Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Fri, 7 Sep 2018 15:14:46 -0700 Subject: [PATCH 178/264] trac 26219: Steenrod algebra fixes for Python 3. --- .../algebras/steenrod/steenrod_algebra.py | 20 ++++++------ .../steenrod/steenrod_algebra_mult.py | 32 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/sage/algebras/steenrod/steenrod_algebra.py b/src/sage/algebras/steenrod/steenrod_algebra.py index 75d80874f1f..455bbaffe23 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra.py +++ b/src/sage/algebras/steenrod/steenrod_algebra.py @@ -400,8 +400,8 @@ sage: for mono, coeff in c: print((coeff, mono)) (1, (5,)) (1, (2, 1)) - sage: c.monomial_coefficients() - {(2, 1): 1, (5,): 1} + sage: c.monomial_coefficients() == {(2, 1): 1, (5,): 1} + True sage: sorted(c.monomials(), key=lambda x: x.support()) [Sq(2,1), Sq(5)] sage: sorted(c.support()) @@ -3081,8 +3081,8 @@ class Element(CombinatorialFreeModule.Element): sage: for mono, coeff in c: print((coeff, mono)) (1, (5,)) (1, (2, 1)) - sage: c.monomial_coefficients() - {(2, 1): 1, (5,): 1} + sage: c.monomial_coefficients() == {(2, 1): 1, (5,): 1} + True sage: sorted(c.monomials(), key=lambda x: x.support()) [Sq(2,1), Sq(5)] sage: sorted(c.support()) @@ -3313,8 +3313,8 @@ def _basis_dictionary(self,basis): EXAMPLES:: sage: c = Sq(2) * Sq(1) - sage: c._basis_dictionary('milnor') - {(0, 1): 1, (3,): 1} + sage: c._basis_dictionary('milnor') == {(0, 1): 1, (3,): 1} + True sage: c Sq(0,1) + Sq(3) sage: c._basis_dictionary('serre-cartan') @@ -3322,8 +3322,8 @@ def _basis_dictionary(self,basis): sage: c.change_basis('serre-cartan') Sq^2 Sq^1 sage: d = Sq(0,0,1) - sage: d._basis_dictionary('arnonc') - {(2, 5): 1, (4, 2, 1): 1, (4, 3): 1, (7,): 1} + sage: sorted(d._basis_dictionary('arnonc').items()) + [((2, 5), 1), ((4, 2, 1), 1), ((4, 3), 1), ((7,), 1)] sage: d.change_basis('arnonc') Sq^2 Sq^5 + Sq^4 Sq^2 Sq^1 + Sq^4 Sq^3 + Sq^7 @@ -3334,8 +3334,8 @@ def _basis_dictionary(self,basis): {((), (1, 2)): 2} sage: e 2 P(1,2) - sage: e._basis_dictionary('serre-cartan') - {(0, 7, 0, 2, 0): 2, (0, 8, 0, 1, 0): 2} + sage: sorted(e._basis_dictionary('serre-cartan').items()) + [((0, 7, 0, 2, 0), 2), ((0, 8, 0, 1, 0), 2)] sage: e.change_basis('adem') 2 P^7 P^2 + 2 P^8 P^1 """ diff --git a/src/sage/algebras/steenrod/steenrod_algebra_mult.py b/src/sage/algebras/steenrod/steenrod_algebra_mult.py index c615b11eb48..862bb4ebb4f 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra_mult.py +++ b/src/sage/algebras/steenrod/steenrod_algebra_mult.py @@ -226,12 +226,12 @@ def milnor_multiplication(r,s): EXAMPLES:: sage: from sage.algebras.steenrod.steenrod_algebra_mult import milnor_multiplication - sage: milnor_multiplication((2,), (1,)) - {(0, 1): 1, (3,): 1} - sage: milnor_multiplication((4,), (2,1)) - {(0, 3): 1, (2, 0, 1): 1, (6, 1): 1} - sage: milnor_multiplication((2,4), (0,1)) - {(2, 0, 0, 1): 1, (2, 5): 1} + sage: milnor_multiplication((2,), (1,)) == {(0, 1): 1, (3,): 1} + True + sage: sorted(milnor_multiplication((4,), (2,1)).items()) + [((0, 3), 1), ((2, 0, 1), 1), ((6, 1), 1)] + sage: sorted(milnor_multiplication((2,4), (0,1)).items()) + [((2, 0, 0, 1), 1), ((2, 5), 1)] These examples correspond to the following product computations: @@ -394,14 +394,14 @@ def milnor_multiplication_odd(m1,m2,p): EXAMPLES:: sage: from sage.algebras.steenrod.steenrod_algebra_mult import milnor_multiplication_odd - sage: milnor_multiplication_odd(((0,2),(5,)), ((1,),(1,)), 5) - {((0, 1, 2), (0, 1)): 4, ((0, 1, 2), (6,)): 4} + sage: sorted(milnor_multiplication_odd(((0,2),(5,)), ((1,),(1,)), 5).items()) + [(((0, 1, 2), (0, 1)), 4), (((0, 1, 2), (6,)), 4)] sage: milnor_multiplication_odd(((0,2,4),()), ((1,3),()), 7) {((0, 1, 2, 3, 4), ()): 6} sage: milnor_multiplication_odd(((0,2,4),()), ((1,5),()), 7) {((0, 1, 2, 4, 5), ()): 1} - sage: milnor_multiplication_odd(((),(6,)), ((),(2,)), 3) - {((), (0, 2)): 1, ((), (4, 1)): 1, ((), (8,)): 1} + sage: sorted(milnor_multiplication_odd(((),(6,)), ((),(2,)), 3).items()) + [(((), (0, 2)), 1), (((), (4, 1)), 1), (((), (8,)), 1)] These examples correspond to the following product computations: @@ -739,8 +739,8 @@ def adem(a, b, c=0, p=2, generic=None): {(3, 1): 1} sage: adem(4,2) {(4, 2): 1} - sage: adem(4,4) - {(6, 2): 1, (7, 1): 1} + sage: adem(4,4) == {(6, 2): 1, (7, 1): 1} + True If `p` is given and is odd, then with two inputs `a` and `b`, the Adem relation for `P^a P^b` is computed. With three inputs `a`, @@ -760,10 +760,10 @@ def adem(a, b, c=0, p=2, generic=None): {(0, 3, 0, 1, 0): 1} sage: adem(1,0,1, p=7) {(0, 2, 0): 2} - sage: adem(1,1,1, p=5) - {(0, 2, 1): 1, (1, 2, 0): 1} - sage: adem(1,1,2, p=5) - {(0, 3, 1): 1, (1, 3, 0): 2} + sage: adem(1,1,1, p=5) == {(0, 2, 1): 1, (1, 2, 0): 1} + True + sage: adem(1,1,2, p=5) == {(0, 3, 1): 1, (1, 3, 0): 2} + True """ if generic is None: generic = False if p==2 else True From fbdf0226fc2baf8684a774fec46aed337ab76a75 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Fri, 7 Sep 2018 16:21:52 -0700 Subject: [PATCH 179/264] trac 26220: change import statements for CoercionException. --- src/sage/categories/pushout.py | 3 +-- src/sage/combinat/free_dendriform_algebra.py | 2 +- src/sage/combinat/free_prelie_algebra.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index bd274e8fbf9..3a1f1715ccf 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -6,6 +6,7 @@ import six from sage.misc.lazy_import import lazy_import +from sage.structure.coerce_exceptions import CoercionException from .functor import Functor, IdentityFunctor_generic lazy_import('sage.categories.commutative_additive_groups', 'CommutativeAdditiveGroups') @@ -14,8 +15,6 @@ lazy_import('sage.categories.objects', 'Objects') lazy_import('sage.categories.rings', 'Rings', at_startup=True) -lazy_import('sage.structure.parent', 'CoercionException') - # TODO, think through the rankings, and override pushout where necessary. class ConstructionFunctor(Functor): diff --git a/src/sage/combinat/free_dendriform_algebra.py b/src/sage/combinat/free_dendriform_algebra.py index be6238dbc6a..ebc81f126bc 100644 --- a/src/sage/combinat/free_dendriform_algebra.py +++ b/src/sage/combinat/free_dendriform_algebra.py @@ -31,7 +31,7 @@ from sage.misc.cachefunc import cached_method from sage.sets.family import Family from sage.misc.lazy_import import lazy_import -lazy_import('sage.structure.parent', 'CoercionException') +from sage.structure.coerce_exceptions import CoercionException class FreeDendriformAlgebra(CombinatorialFreeModule): diff --git a/src/sage/combinat/free_prelie_algebra.py b/src/sage/combinat/free_prelie_algebra.py index e15e2e8e8c9..7961b286c29 100644 --- a/src/sage/combinat/free_prelie_algebra.py +++ b/src/sage/combinat/free_prelie_algebra.py @@ -37,7 +37,7 @@ from sage.misc.cachefunc import cached_method from sage.sets.family import Family -lazy_import('sage.structure.parent', 'CoercionException') +from sage.structure.coerce_exceptions import CoercionException class FreePreLieAlgebra(CombinatorialFreeModule): From d6c3b5f71741dedfddb0c4aa8e54f00ee1f81a90 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Fri, 7 Sep 2018 23:00:15 -0700 Subject: [PATCH 180/264] trac 26221: enable hash for FreeMonoid_class_with_category --- src/sage/monoids/free_monoid.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/sage/monoids/free_monoid.py b/src/sage/monoids/free_monoid.py index 99577298ff5..4c98679fc01 100644 --- a/src/sage/monoids/free_monoid.py +++ b/src/sage/monoids/free_monoid.py @@ -205,29 +205,6 @@ def __init__(self, n, names=None): #self._assign_names(names) Monoid_class.__init__(self,names) - def __eq__(self, other): - """ - Test for equality. - """ - if self is other: - return True - if not isinstance(other, FreeMonoid_class): - return False - if self.__ngens != other.__ngens: - return False - try: - if self.variable_names() != other.variable_names(): - return False - except ValueError: - pass - return True - - def __ne__(self, other): - """ - Test for unequality. - """ - return not (self == other) - def _repr_(self): return "Free monoid on %s generators %s"%(self.__ngens,self.gens()) From f1c2207ba84cedb03fbf9e2ec01c12f8857192e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Sep 2018 15:53:55 +0200 Subject: [PATCH 181/264] py3: fix doctests in quivers/ --- src/sage/quivers/morphism.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/sage/quivers/morphism.py b/src/sage/quivers/morphism.py index 18232b2413f..1a1cc2cfded 100644 --- a/src/sage/quivers/morphism.py +++ b/src/sage/quivers/morphism.py @@ -563,6 +563,32 @@ def __ne__(self, other): # If all that holds just check the vectors return self._vector != other._vector + def __bool__(self): + """ + Return whether ``self`` is the zero morphism. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces = {1: QQ^2, 2: QQ^2, 3:QQ^1} + sage: maps = {(1, 2, 'a'): [[1, 0], [0, 0]], (1, 2, 'b'): [[0, 0], [0, 1]], (2, 3, 'c'): [[1], [1]]} + sage: M = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: x = M({2: (1, -1)}) + sage: y = M({3: (1,)}) + sage: z = M.zero() + sage: g = S.hom([x, y], M) + sage: h = S.hom([z, z], M) + sage: bool(g) + True + sage: bool(h) + False + """ + return any(self._vector) + + __nonzero__ = __bool__ + def __mul__(self, other): """ This function overrides the ``*`` operator From 868da7a467a9bae910046f6c6fd68c5db1ea9efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sat, 8 Sep 2018 09:40:11 +0200 Subject: [PATCH 182/264] py3: partial fix in sat/ --- src/sage/sat/converters/polybori.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/sat/converters/polybori.py b/src/sage/sat/converters/polybori.py index 98bc2f05edf..a2e90b7d640 100644 --- a/src/sage/sat/converters/polybori.py +++ b/src/sage/sat/converters/polybori.py @@ -189,7 +189,7 @@ def phi(self): def zero_blocks(self, f): """ - Divides the zero set of ``f`` into blocks. + Divide the zero set of ``f`` into blocks. EXAMPLES:: @@ -197,10 +197,10 @@ def zero_blocks(self, f): sage: from sage.sat.converters.polybori import CNFEncoder sage: from sage.sat.solvers.dimacs import DIMACS sage: e = CNFEncoder(DIMACS(), B) - sage: sorted(e.zero_blocks(a*b*c)) - [{c: 0}, {b: 0}, {a: 0}] + sage: sorted(sorted(d.items()) for d in e.zero_blocks(a*b*c)) + [[(c, 0)], [(b, 0)], [(a, 0)]] - .. note:: + .. NOTE:: This function is randomised. """ @@ -331,7 +331,7 @@ def clauses_dense(self, f): if self.use_xor_clauses: self.solver.add_xor_clause(f, rhs=not equal_zero) - elif f > self.cutting_number: + elif len(f) > self.cutting_number: for fpart, this_equal_zero in self.split_xor(f, equal_zero): ll = len(fpart) for p in self.permutations(ll, this_equal_zero): From 77c38e29a3d15b56e15ffaa19d887619eeedd9d4 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sat, 8 Sep 2018 20:57:17 +1000 Subject: [PATCH 183/264] Fixing typo in variable name. --- src/sage/combinat/symmetric_group_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index 6e4d861ec79..bcab7f51554 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -1145,9 +1145,9 @@ def central_orthogonal_idempotent(self, la, block=True): if not block or not p: la_index = indices[la] big_coeff = character_table[la_index][0] / factorial(self.n) - character_row = character_table[lam_index] + character_row = character_table[la_index] cpi = {g: big_coeff * character_row[indices[g.cycle_type()]] - for g in G} + for g in G} else: # We compute the cycle types of the permutations cycles = {} From 25a34e0a73d7744507db37d56a8a64decdffc49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sat, 8 Sep 2018 13:00:33 +0200 Subject: [PATCH 184/264] py3: fix doctests in quadratic_forms/ --- src/sage/quadratic_forms/genera/normal_form.py | 4 ++-- src/sage/quadratic_forms/quadratic_form__siegel_product.py | 7 ++----- src/sage/rings/rational.pyx | 2 ++ 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/sage/quadratic_forms/genera/normal_form.py b/src/sage/quadratic_forms/genera/normal_form.py index 1cabb2c84be..b93b31753aa 100644 --- a/src/sage/quadratic_forms/genera/normal_form.py +++ b/src/sage/quadratic_forms/genera/normal_form.py @@ -1434,8 +1434,8 @@ def _two_adic_normal_forms(G, partial=False): if not partial: Dk, B1k = _homogeneous_normal_form(Dk, wk) B[h[i]:h[i+1],:] = B1k * B[h[i]:h[i+1], :] - UVlist.append(range(h[i], h[i+1] - wk)) - Wlist.append(range(h[i+1]-wk, h[i+1])) + UVlist.append(list(range(h[i], h[i+1] - wk))) + Wlist.append(list(range(h[i+1]-wk, h[i+1]))) else: UVlist.append([]) Wlist.append([]) diff --git a/src/sage/quadratic_forms/quadratic_form__siegel_product.py b/src/sage/quadratic_forms/quadratic_form__siegel_product.py index 60ac5c271ee..2a6154d946c 100644 --- a/src/sage/quadratic_forms/quadratic_form__siegel_product.py +++ b/src/sage/quadratic_forms/quadratic_form__siegel_product.py @@ -85,15 +85,14 @@ def siegel_product(self, u): ## DIAGNOSTIC verbose("n = " + str(n)) verbose("d = " + str(d)) - verbose("In siegel_product: d = ", d, "\n"); - + verbose("In siegel_product: d = " + str(d) + "\n"); ## Product of "bad" places to omit S = 2 * d * u ## DIAGNOSTIC verbose("siegel_product Break 1. \n") - verbose(" u = ", u, "\n") + verbose(" u = " + str(u) + "\n") ## Make the odd generic factors @@ -170,10 +169,8 @@ def siegel_product(self, u): verbose(" u = " +str(u) + "\n") verbose(" include = " + str(include) + "\n") - include *= Q_normal.local_density(p, u) - ## DIAGNOSTIC #cout << " Including the p = " << p << " factor: " << local_density(Q_normal, p, u) << endl; diff --git a/src/sage/rings/rational.pyx b/src/sage/rings/rational.pyx index 12ca1b222b3..75e0861c0c6 100644 --- a/src/sage/rings/rational.pyx +++ b/src/sage/rings/rational.pyx @@ -3401,6 +3401,8 @@ cdef class Rational(sage.structure.element.FieldElement): else: return q+1 + __round__ = round + def real(self): """ Returns the real part of ``self``, which is ``self``. From e149a723fb7285849ef85336db3428ae979d9e73 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sat, 8 Sep 2018 21:24:25 +1000 Subject: [PATCH 185/264] A few documentation tweaks. --- src/sage/calculus/desolvers.py | 116 ++++++++++++++++----------------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/src/sage/calculus/desolvers.py b/src/sage/calculus/desolvers.py index a90f91b7c2b..b2372cce928 100644 --- a/src/sage/calculus/desolvers.py +++ b/src/sage/calculus/desolvers.py @@ -88,27 +88,25 @@ maxima = Maxima() def fricas_desolve(de, dvar, ics, ivar): - """ + r""" Solve an ODE using FriCAS. EXAMPLES:: sage: x = var('x') sage: y = function('y')(x) - sage: desolve(diff(y,x) + y - 1, y, algorithm="fricas") # optional - fricas + sage: desolve(diff(y,x) + y - 1, y, algorithm="fricas") # optional - fricas _C0*e^(-x) + 1 - sage: desolve(diff(y, x) + y == y^3*sin(x), y, algorithm="fricas") # optional - fricas + sage: desolve(diff(y, x) + y == y^3*sin(x), y, algorithm="fricas") # optional - fricas -1/5*(2*cos(x)*y(x)^2 + 4*sin(x)*y(x)^2 - 5)*e^(-2*x)/y(x)^2 TESTS:: sage: from sage.calculus.desolvers import fricas_desolve - sage: Y = fricas_desolve(diff(y,x) + y - 1, y, [42,1783], x) # optional - fricas - sage: Y.subs(x=42) # optional - fricas + sage: Y = fricas_desolve(diff(y,x) + y - 1, y, [42,1783], x) # optional - fricas + sage: Y.subs(x=42) # optional - fricas 1783 - - """ from sage.interfaces.fricas import fricas from sage.symbolic.ring import SR @@ -126,7 +124,7 @@ def fricas_desolve(de, dvar, ics, ivar): return y def fricas_desolve_system(des, dvars, ics, ivar): - """ + r""" Solve a system of first order ODEs using FriCAS. EXAMPLES:: @@ -136,11 +134,11 @@ def fricas_desolve_system(des, dvars, ics, ivar): sage: y = function('y')(t) sage: de1 = diff(x,t) + y - 1 == 0 sage: de2 = diff(y,t) - x + 1 == 0 - sage: desolve_system([de1, de2], [x, y], algorithm="fricas") # optional - fricas + sage: desolve_system([de1, de2], [x, y], algorithm="fricas") # optional - fricas [x(t) == _C0*cos(t) + cos(t)^2 + _C1*sin(t) + sin(t)^2, y(t) == -_C1*cos(t) + _C0*sin(t) + 1] - sage: desolve_system([de1, de2], [x,y], [0,1,2], algorithm="fricas") # optional - fricas + sage: desolve_system([de1, de2], [x,y], [0,1,2], algorithm="fricas") # optional - fricas [x(t) == cos(t)^2 + sin(t)^2 - sin(t), y(t) == cos(t) + 1] TESTS:: @@ -148,13 +146,14 @@ def fricas_desolve_system(des, dvars, ics, ivar): sage: from sage.calculus.desolvers import fricas_desolve_system sage: t = var('t') sage: x = function('x')(t) - sage: fricas_desolve_system([diff(x,t) + 1 == 0], [x], None, t) # optional - fricas + sage: fricas_desolve_system([diff(x,t) + 1 == 0], [x], None, t) # optional - fricas [x(t) == _C0 - t] sage: y = function('y')(t) sage: de1 = diff(x,t) + y - 1 == 0 sage: de2 = diff(y,t) - x + 1 == 0 - sage: sol = fricas_desolve_system([de1,de2], [x,y], [0,1,-1], t); sol # optional - fricas + sage: sol = fricas_desolve_system([de1,de2], [x,y], [0,1,-1], t) # optional - fricas + sage: sol # optional - fricas [x(t) == cos(t)^2 + sin(t)^2 + 2*sin(t), y(t) == -2*cos(t) + 1] """ @@ -178,17 +177,18 @@ def fricas_desolve_system(des, dvars, ics, ivar): return [dvar == sol for dvar, sol in zip(dvars, sols)] -def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, algorithm=None): +def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, + algorithm="maxima"): r""" Solve a 1st or 2nd order linear ODE, including IVP and BVP. INPUT: - - ``de`` - an expression or equation representing the ODE + - ``de`` -- an expression or equation representing the ODE - - ``dvar`` - the dependent variable (hereafter called `y`) + - ``dvar`` -- the dependent variable (hereafter called `y`) - - ``ics`` - (optional) the initial or boundary conditions + - ``ics`` -- (optional) the initial or boundary conditions - for a first-order equation, specify the initial `x` and `y` @@ -201,11 +201,11 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, - gives an error if the solution is not SymbolicEquation (as happens for example for a Clairaut equation) - - ``ivar`` - (optional) the independent variable (hereafter called + - ``ivar`` -- (optional) the independent variable (hereafter called `x`), which must be specified if there is more than one - independent variable in the equation. + independent variable in the equation - - ``show_method`` - (optional) if true, then Sage returns pair + - ``show_method`` -- (optional) if ``True``, then Sage returns pair ``[solution, method]``, where method is the string describing the method which has been used to get a solution (Maxima uses the following order for first order equations: linear, separable, @@ -215,17 +215,16 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, this property is not recognized by Maxima and the equation is solved as exact. - - ``contrib_ode`` - (optional) if true, ``desolve`` allows to solve + - ``contrib_ode`` -- (optional) if ``True``, ``desolve`` allows to solve Clairaut, Lagrange, Riccati and some other equations. This may take a long time and is thus turned off by default. Initial conditions can be used only if the result is one SymbolicEquation (does not contain a singular solution, for example). - - ``algorithm`` - (optional, default None) -- one of - - - 'maxima' - use maxima (the default) + - ``algorithm`` -- (default: ``'maxima'``) one of - - 'fricas' - use FriCAS (the optional fricas spkg has to be installed) + * ``'maxima'`` - use maxima + * ``'fricas'`` - use FriCAS (the optional fricas spkg has to be installed) OUTPUT: @@ -367,7 +366,7 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, conditions, but you cannot put (sometimes desired) the initial condition at `x=0`, since this point is a singular point of the equation. Anyway, if the solution should be bounded at `x=0`, then - _K2=0.:: + ``_K2=0``. :: sage: desolve(x^2*diff(y,x,x)+x*diff(y,x)+(x^2-4)*y==0,y) _K1*bessel_J(2, x) + _K2*bessel_Y(2, x) @@ -379,7 +378,7 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, ... NotImplementedError, "Maxima was unable to solve this ODE. Consider to set option contrib_ode to True." - Another difficult ODE with error - moreover, it takes a long time :: + Another difficult ODE with error - moreover, it takes a long time:: sage: desolve(sqrt(y)*diff(y,x)+e^(y)+cos(x)-sin(x+y)==0,y,contrib_ode=True) # not tested @@ -395,7 +394,7 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, These two examples produce an error (as expected, Maxima 5.18 cannot solve equations from initial conditions). Maxima 5.18 - returns false answer in this case!:: + returns false answer in this case! :: sage: desolve(diff(y,x,2)+y*(diff(y,x,1))^3==0,y,[0,1,2]).expand() # not tested Traceback (click to the left for traceback) @@ -469,24 +468,25 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, sage: desolve(diff(y,x,2)+2*diff(y,x)+y == 0,y,[0,3,pi/2,2],show_method=True) [(2*x*(2*e^(1/2*pi) - 3)/pi + 3)*e^(-x), 'constcoeff'] - Using algorithm='fricas' we can invoke FriCAS' differential - equation solver. For example, it can solve higher order linear - equations:: + Using ``algorithm='fricas'`` we can invoke the differential + equation solver from FriCAS. For example, it can solve higher + order linear equations:: sage: de = x^3*diff(y, x, 3) + x^2*diff(y, x, 2) - 2*x*diff(y, x) + 2*y - 2*x^4 - sage: Y = desolve(de, y, algorithm="fricas"); Y # optional - fricas - (2*x^3 - 3*x^2 + 1)*_C0/x + (x^3 - 1)*_C1/x + (x^3 - 3*x^2 - 1)*_C2/x + 1/15*(x^5 - 10*x^3 + 20*x^2 + 4)/x + sage: Y = desolve(de, y, algorithm="fricas"); Y # optional - fricas + (2*x^3 - 3*x^2 + 1)*_C0/x + (x^3 - 1)*_C1/x + + (x^3 - 3*x^2 - 1)*_C2/x + 1/15*(x^5 - 10*x^3 + 20*x^2 + 4)/x The initial conditions are then interpreted as `[x_0, y(x_0), - y'(x_0), \dots, y^(n)(x_0)]`:: + y'(x_0), \ldots, y^(n)(x_0)]`:: - sage: Y = desolve(de, y, ics=[1,3,7], algorithm="fricas"); Y # optional - fricas + sage: Y = desolve(de, y, ics=[1,3,7], algorithm="fricas"); Y # optional - fricas 1/15*(x^5 + 15*x^3 + 50*x^2 - 21)/x FriCAS can also solve some non-linear equations:: - sage: de = diff(y, x) == y/(x+y*log(y)) - sage: Y = desolve(de, y, algorithm="fricas"); Y # optional - fricas + sage: de = diff(y, x) == y / (x+y*log(y)) + sage: Y = desolve(de, y, algorithm="fricas"); Y # optional - fricas 1/2*(log(y(x))^2*y(x) - 2*x)/y(x) TESTS: @@ -544,15 +544,11 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, sage: desolve(diff(y, x) == sqrt(abs(y)), dvar=y, ivar=x) sqrt(-y(x))*(sgn(y(x)) - 1) + (sgn(y(x)) + 1)*sqrt(y(x)) == _C + x - AUTHORS: - David Joyner (1-2006) - - Robert Bradshaw (10-2008) - - Robert Marik (10-2009) - """ if is_SymbolicEquation(de): de = de.lhs() - de.rhs() @@ -567,10 +563,11 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False, if len(ivars) != 1: raise ValueError("Unable to determine independent variable, please specify.") ivar = ivars[0] + if algorithm == "fricas": return fricas_desolve(de, dvar, ics, ivar) - elif algorithm is not None and algorithm != "maxima": - raise ValueError("Unknown algorithm: %s" % algorithm) + elif algorithm != "maxima": + raise ValueError("unknown algorithm %s" % algorithm) de00 = de._maxima_() P = de00.parent() @@ -833,30 +830,30 @@ def sanitize_var(exprs): # 'y(x) -> y(x) def desolve_system(des, vars, ics=None, ivar=None, algorithm=None): - """ - Solve a system of any size of 1st order ODEs. Initial conditions are optional. + r""" + Solve a system of any size of 1st order ODEs. Initial conditions + are optional. One dimensional systems are passed to :meth:`desolve_laplace`. INPUT: - - ``des`` - list of ODEs + - ``des`` -- list of ODEs - - ``vars`` - list of dependent variables + - ``vars`` -- list of dependent variables - - ``ics`` - (optional) list of initial values for ``ivar`` and ``vars``. - If ``ics`` is defined, it should provide initial conditions for each variable, - otherwise an exception would be raised. + - ``ics`` -- (optional) list of initial values for ``ivar`` and ``vars``; + if ``ics`` is defined, it should provide initial conditions for each + variable, otherwise an exception would be raised - - ``ivar`` - (optional) the independent variable, which must be + - ``ivar`` -- (optional) the independent variable, which must be specified if there is more than one independent variable in the - equation. + equation - - ``algorithm`` - (optional, default None) -- one of + - ``algorithm`` -- (default: ``'maxima'``) one of - - 'maxima' - use maxima (the default) - - - 'fricas' - use FriCAS (the optional fricas spkg has to be installed) + * ``'maxima'`` - use maxima + * ``'fricas'`` - use FriCAS (the optional fricas spkg has to be installed) EXAMPLES:: @@ -871,7 +868,7 @@ def desolve_system(des, vars, ics=None, ivar=None, algorithm=None): The same system solved using FriCAS:: - sage: desolve_system([de1, de2], [x,y], algorithm='fricas') # optional - fricas + sage: desolve_system([de1, de2], [x,y], algorithm='fricas') # optional - fricas [x(t) == _C0*cos(t) + cos(t)^2 + _C1*sin(t) + sin(t)^2, y(t) == -_C1*cos(t) + _C0*sin(t) + 1] @@ -937,7 +934,6 @@ def desolve_system(des, vars, ics=None, ivar=None, algorithm=None): ... ValueError: Initial conditions aren't complete: number of vars is different from number of dependent variables. Got ics = [1, 1], vars = [x1(t), x2(t)] - AUTHORS: - Robert Bradshaw (10-2008) @@ -947,7 +943,7 @@ def desolve_system(des, vars, ics=None, ivar=None, algorithm=None): if len(ics) != (len(vars) + 1): raise ValueError("Initial conditions aren't complete: number of vars is different from number of dependent variables. Got ics = {0}, vars = {1}".format(ics, vars)) - if len(des)==1 and algorithm is None: + if len(des) == 1 and algorithm == "maxima": return desolve_laplace(des[0], vars[0], ics=ics, ivar=ivar) ivars = set([]) for i, de in enumerate(des): @@ -962,8 +958,8 @@ def desolve_system(des, vars, ics=None, ivar=None, algorithm=None): if algorithm == "fricas": return fricas_desolve_system(des, vars, ics, ivar) - elif algorithm is not None and algorithm != "maxima": - raise ValueError("Unknown algorithm: %s" % algorithm) + elif algorithm != "maxima": + raise ValueError("unknown algorithm %s" % algorithm) dvars = [v._maxima_() for v in vars] if ics is not None: From a593677038dd782c5fcd4839f6a2aa4d3d901e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sat, 8 Sep 2018 16:41:23 +0200 Subject: [PATCH 186/264] py3: fix Kleshchev partitions --- src/sage/combinat/partition_kleshchev.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/partition_kleshchev.py b/src/sage/combinat/partition_kleshchev.py index faa2bd33904..2dd86149df0 100644 --- a/src/sage/combinat/partition_kleshchev.py +++ b/src/sage/combinat/partition_kleshchev.py @@ -87,6 +87,7 @@ from sage.rings.all import NN, ZZ, IntegerModRing from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation +from sage.cpython.getattr import raw_getattr from collections import defaultdict @@ -1444,7 +1445,7 @@ def __init__(self, e, multicharge, convention): self._level = len(multicharge) if self._level == 1: self.Element = KleshchevPartitionCrystal - self._element_constructor_ = Partitions._element_constructor_.__func__ + self._element_constructor_ = raw_getattr(Partitions, '_element_constructor_') else: self.Element = KleshchevPartitionTupleCrystal @@ -1641,7 +1642,7 @@ def __init__(self, e, multicharge=(0,), size=0, convention='RS'): self._level = len(multicharge) if self._level == 1: self.Element = KleshchevPartition - self._element_constructor_ = Partitions._element_constructor_.__func__ + self._element_constructor_ = raw_getattr(Partitions, '_element_constructor_') else: self.Element = KleshchevPartitionTuple super(KleshchevPartitions_size, self).__init__(category=FiniteEnumeratedSets()) From 62e5ebc7ae58f5c9eef8d6351449b1d76ed8af9b Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 8 Sep 2018 20:24:39 +0200 Subject: [PATCH 187/264] add conversion of erfc to fricas --- src/sage/functions/error.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/sage/functions/error.py b/src/sage/functions/error.py index 54146a7efab..553ab1ca3ac 100644 --- a/src/sage/functions/error.py +++ b/src/sage/functions/error.py @@ -379,7 +379,7 @@ class Function_erfc(BuiltinFunction): .. MATH:: \frac{2}{\sqrt{\pi}} \int_t^\infty e^{-x^2} dx. - + EXAMPLES:: sage: erfc(6) @@ -393,6 +393,14 @@ class Function_erfc(BuiltinFunction): 0.520499877813047 sage: erf(0.5) 0.520499877813047 + + TESTS: + + Check that :trac:`25991` is fixed:: + + sage: erfc(x)._fricas_() # optional - fricas + - erf(x) + 1 + """ def __init__(self): r""" @@ -407,7 +415,7 @@ def __init__(self): latex_name=r"\operatorname{erfc}", conversions=dict(maxima='erfc', sympy='erfc', - fricas='erfc', + fricas='(x+->1-erf(x))', giac='erfc')) def _eval_(self, x): @@ -493,7 +501,7 @@ def __init__(self): sage: _ = var('z,t') sage: PDF = exp(-x^2 /2)/sqrt(2*pi) sage: integralExpr = integrate(PDF,x,z,oo).subs(z==log(t)) - sage: y = solve(integralExpr==z,t)[0].rhs().subs(z==1/4) + sage: y = solve(integralExpr==z,t)[0].rhs().subs(z==1/4) sage: y e^(sqrt(2)*erfinv(1/2)) sage: y.n() @@ -750,4 +758,3 @@ def _derivative_(self, x, diff_param=None): return cos(pi*x**2/2) fresnel_cos = Function_Fresnel_cos() - From c464573f422c08ffb1ece7fd6594f89884fcfa89 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sun, 9 Sep 2018 07:16:58 +1000 Subject: [PATCH 188/264] Fixing bad default algorithm. --- src/sage/calculus/desolvers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/calculus/desolvers.py b/src/sage/calculus/desolvers.py index b2372cce928..e7cc3d7d995 100644 --- a/src/sage/calculus/desolvers.py +++ b/src/sage/calculus/desolvers.py @@ -829,7 +829,7 @@ def sanitize_var(exprs): # 'y(x) -> y(x) return soln -def desolve_system(des, vars, ics=None, ivar=None, algorithm=None): +def desolve_system(des, vars, ics=None, ivar=None, algorithm="maxima"): r""" Solve a system of any size of 1st order ODEs. Initial conditions are optional. From 04e08f7371ab22f6c3318f7ea0de544968b66004 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sun, 8 Jul 2018 05:09:42 +1000 Subject: [PATCH 189/264] Speedup for morphism comparisons by not creating temporary list and some cleanup. --- src/sage/schemes/affine/affine_morphism.py | 16 +++++------ .../schemes/projective/projective_morphism.py | 27 +++++++++---------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/sage/schemes/affine/affine_morphism.py b/src/sage/schemes/affine/affine_morphism.py index 6993a4ef3da..60d6d8d9310 100644 --- a/src/sage/schemes/affine/affine_morphism.py +++ b/src/sage/schemes/affine/affine_morphism.py @@ -233,11 +233,11 @@ def __eq__(self, right): INPUT: - - ``right`` - a map on affine space. + - ``right`` -- a map on affine space OUTPUT: - - Boolean - True if the two affine maps define the same map. + ``True`` if the two affine maps define the same map. EXAMPLES:: @@ -250,7 +250,7 @@ def __eq__(self, right): sage: f == g False - :: + :: sage: A. = AffineSpace(CC, 3) sage: H = End(A) @@ -262,7 +262,7 @@ def __eq__(self, right): return False if self.parent() != right.parent(): return False - return all(self[i] == right[i] for i in range(len(self._polys))) + return all(val == right._polys[i] for i,val in enumerate(self._polys)) def __ne__(self, right): """ @@ -270,11 +270,11 @@ def __ne__(self, right): INPUT: - - ``right`` - a map on affine space. + - ``right`` -- a map on affine space OUTPUT: - - Boolean - True if the two affine maps define the same map. + ``True`` if the two affine maps define the same map. EXAMPLES:: @@ -291,9 +291,7 @@ def __ne__(self, right): return True if self.parent() != right.parent(): return True - if all(self[i] == right[i] for i in range(len(self._polys))): - return False - return True + return any(val != right._polys[i] for i,val in enumerate(self._polys)) @lazy_attribute def _fastpolys(self): diff --git a/src/sage/schemes/projective/projective_morphism.py b/src/sage/schemes/projective/projective_morphism.py index ff24a8f60d0..a2b31f08693 100644 --- a/src/sage/schemes/projective/projective_morphism.py +++ b/src/sage/schemes/projective/projective_morphism.py @@ -424,11 +424,12 @@ def __eq__(self, right): INPUT: - - ``right`` - a map on projective space. + - ``right`` -- a map on projective space OUTPUT: - - Boolean - True if ``self`` and ``right`` define the same projective map. False otherwise. + ``True`` if ``self`` and ``right`` define the same projective map. + ``False`` otherwise. EXAMPLES:: @@ -439,7 +440,7 @@ def __eq__(self, right): sage: f == g False - :: + :: sage: P. = ProjectiveSpace(QQ, 1) sage: P2. = ProjectiveSpace(CC, 1) @@ -450,7 +451,7 @@ def __eq__(self, right): sage: f == g False - :: + :: sage: P. = ProjectiveSpace(QQ, 1) sage: H = End(P) @@ -464,8 +465,8 @@ def __eq__(self, right): if self.parent() != right.parent(): return False n = len(self._polys) - return all(self[i] * right[j] == self[j] * right[i] - for i in range(n) for j in range(i + 1, n)) + return all(self._polys[i] * right._polys[j] == self._polys[j] * right._polys[i] + for i in range(n) for j in range(i+1, n)) def __ne__(self, right): """ @@ -473,11 +474,12 @@ def __ne__(self, right): INPUT: - - ``right`` -- a map on projective space. + - ``right`` -- a map on projective space OUTPUT: - - Boolean -- True if ``self`` and ``right`` define different projective maps. False otherwise. + ``True`` if ``self`` and ``right`` define different projective maps. + ``False`` otherwise. EXAMPLES:: @@ -488,7 +490,7 @@ def __ne__(self, right): sage: f != g True - :: + :: sage: P. = ProjectiveSpace(QQ, 2) sage: H = Hom(P, P) @@ -501,11 +503,8 @@ def __ne__(self, right): if self.parent() != right.parent(): return True n = len(self._polys) - for i in range(0, n): - for j in range(i + 1, n): - if self._polys[i] * right._polys[j] != self._polys[j] * right._polys[i]: - return True - return False + return any(self._polys[i] * right._polys[j] != self._polys[j] * right._polys[i] + for i in range(n) for j in range(i + 1, n)) def as_dynamical_system(self): """ From fbc4759fb67a1158a17d0417813d132b333876ec Mon Sep 17 00:00:00 2001 From: Meghana M Reddy Date: Sun, 9 Sep 2018 05:11:42 +0530 Subject: [PATCH 190/264] Bug fix --- src/sage/graphs/connectivity.pyx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index 2127c25309a..da18c953ee4 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -3536,6 +3536,14 @@ class TriconnectivitySPQR: w = x wnum = self.newnum[w] + # update the values used in the while loop check + temp_node = self.adj[w].get_head() + temp = temp_node.get_data() + if temp in self.reverse_edges: + temp_target = temp[0] + else: + temp_target = temp[1] + # start type-1 check if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ (self.parent[v] != self.start_vertex or outv >= 2): From 8c1e9ea9b7c24392604bf60d934b7b1fabe2a354 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Sat, 8 Sep 2018 18:04:20 -0700 Subject: [PATCH 191/264] trac 26221: FreeMonoid: use UniqueRepresentation instead of UniqueFactory. --- src/sage/monoids/free_monoid.py | 178 ++++++++++-------------- src/sage/monoids/free_monoid_element.py | 2 +- src/sage/monoids/string_monoid.py | 8 +- 3 files changed, 80 insertions(+), 108 deletions(-) diff --git a/src/sage/monoids/free_monoid.py b/src/sage/monoids/free_monoid.py index 4c98679fc01..25402189974 100644 --- a/src/sage/monoids/free_monoid.py +++ b/src/sage/monoids/free_monoid.py @@ -34,111 +34,11 @@ from sage.combinat.words.finite_word import FiniteWord_class -from sage.structure.factory import UniqueFactory +from sage.structure.unique_representation import UniqueRepresentation from sage.misc.decorators import rename_keyword from sage.rings.all import ZZ -class FreeMonoidFactory(UniqueFactory): - """ - Create the free monoid in `n` generators. - - INPUT: - - - ``n`` - integer - - - ``names`` - names of generators - - OUTPUT: free monoid - - EXAMPLES:: - - sage: FreeMonoid(0,'') - Free monoid on 0 generators () - sage: F. = FreeMonoid(5); F - Free monoid on 5 generators (a, b, c, d, e) - sage: F(1) - 1 - sage: mul([ a, b, a, c, b, d, c, d ], F(1)) - a*b*a*c*b*d*c*d - """ - def create_key(self, n, names): - n = int(n) - names = normalize_names(n, names) - return (n, names) - def create_object(self, version, key, **kwds): - return FreeMonoid_class(*key) - -FreeMonoid_factory = FreeMonoidFactory("sage.monoids.free_monoid.FreeMonoid_factory") - -@rename_keyword(deprecation=15289, n="index_set") -def FreeMonoid(index_set=None, names=None, commutative=False, **kwds): - r""" - Return a free monoid on `n` generators or with the generators indexed by - a set `I`. - - We construct free monoids by specifing either: - - - the number of generators and/or the names of the generators - - the indexing set for the generators - - INPUT: - - - ``index_set`` -- an indexing set for the generators; if an integer, - than this becomes `\{0, 1, \ldots, n-1\}` - - - ``names`` -- names of generators - - - ``commutative`` -- (default: ``False``) whether the free monoid is - commutative or not - - OUTPUT: - - A free monoid. - - EXAMPLES:: - - sage: F. = FreeMonoid(); F - Free monoid on 5 generators (a, b, c, d, e) - sage: FreeMonoid(index_set=ZZ) - Free monoid indexed by Integer Ring - - sage: F. = FreeMonoid(abelian=True); F - Free abelian monoid on 3 generators (x, y, z) - sage: FreeMonoid(index_set=ZZ, commutative=True) - Free abelian monoid indexed by Integer Ring - - TESTS:: - - sage: FreeMonoid(index_set=ZZ, names='x,y,z') - Free monoid indexed by Integer Ring - """ - if 'abelian' in kwds: - commutative = kwds.pop('abelian') - - if commutative: - from sage.monoids.free_abelian_monoid import FreeAbelianMonoid - return FreeAbelianMonoid(index_set, names, **kwds) - - if isinstance(index_set, str): # Swap args (this works if names is None as well) - names, index_set = index_set, names - - if index_set is None and names is not None: - if isinstance(names, str): - index_set = names.count(',') - else: - index_set = len(names) - - if index_set not in ZZ: - if names is not None: - names = normalize_names(-1, names) - from sage.monoids.indexed_free_monoid import IndexedFreeMonoid - return IndexedFreeMonoid(index_set, names=names, **kwds) - - if names is None: - raise ValueError("names must be specified") - return FreeMonoid_factory(index_set, names) - def is_FreeMonoid(x): """ Return True if `x` is a free monoid. @@ -159,16 +59,88 @@ def is_FreeMonoid(x): sage: is_FreeMonoid(FreeAbelianMonoid(index_set=ZZ)) False """ - if isinstance(x, FreeMonoid_class): + if isinstance(x, FreeMonoid): return True from sage.monoids.indexed_free_monoid import IndexedFreeMonoid return isinstance(x, IndexedFreeMonoid) -class FreeMonoid_class(Monoid_class): +class FreeMonoid(Monoid_class, UniqueRepresentation): """ The free monoid on `n` generators. """ + @staticmethod + def __classcall_private__(cls, index_set=None, names=None, + commutative=False, **kwds): + r""" + Return a free monoid on `n` generators or with the generators + indexed by a set `I`. + + We construct free monoids by specifing either: + + - the number of generators and/or the names of the generators + - the indexing set for the generators + + INPUT: + + - ``index_set`` -- an indexing set for the generators; if an + integer, than this becomes `\{0, 1, \ldots, n-1\}` + + - ``names`` -- names of generators + + - ``commutative`` -- (default: ``False``) whether the free + monoid is commutative or not + + OUTPUT: + + A free monoid. + + EXAMPLES:: + + sage: F. = FreeMonoid(); F + Free monoid on 5 generators (a, b, c, d, e) + sage: FreeMonoid(index_set=ZZ) + Free monoid indexed by Integer Ring + + sage: F. = FreeMonoid(abelian=True); F + Free abelian monoid on 3 generators (x, y, z) + sage: FreeMonoid(index_set=ZZ, commutative=True) + Free abelian monoid indexed by Integer Ring + + TESTS:: + + sage: FreeMonoid(index_set=ZZ, names='x,y,z') + Free monoid indexed by Integer Ring + """ + + if 'abelian' in kwds: + commutative = kwds.pop('abelian') + + if commutative: + from sage.monoids.free_abelian_monoid import FreeAbelianMonoid + return FreeAbelianMonoid(index_set, names, **kwds) + + if isinstance(index_set, str): # Swap args (this works if names is None as well) + names, index_set = index_set, names + + if index_set is None and names is not None: + if isinstance(names, str): + index_set = names.count(',') + else: + index_set = len(names) + + if index_set not in ZZ: + if names is not None: + names = normalize_names(-1, names) + from sage.monoids.indexed_free_monoid import IndexedFreeMonoid + return IndexedFreeMonoid(index_set, names=names, **kwds) + + if names is None: + raise ValueError("names must be specified") + names = normalize_names(index_set, names) + return super(FreeMonoid, cls).__classcall__(cls, index_set, names) + Element = FreeMonoidElement + def __init__(self, n, names=None): """ Create free monoid on `n` generators. diff --git a/src/sage/monoids/free_monoid_element.py b/src/sage/monoids/free_monoid_element.py index efa97f0d825..add4a903555 100644 --- a/src/sage/monoids/free_monoid_element.py +++ b/src/sage/monoids/free_monoid_element.py @@ -49,7 +49,7 @@ class FreeMonoidElement(MonoidElement): sage: x**(-1) Traceback (most recent call last): ... - TypeError: bad operand type for unary ~: 'FreeMonoid_class_with_category.element_class' + TypeError: bad operand type for unary ~: 'FreeMonoid_with_category.element_class' """ def __init__(self, F, x, check=True): """ diff --git a/src/sage/monoids/string_monoid.py b/src/sage/monoids/string_monoid.py index c3e3b50971c..72405c723ca 100644 --- a/src/sage/monoids/string_monoid.py +++ b/src/sage/monoids/string_monoid.py @@ -20,7 +20,7 @@ #***************************************************************************** -from .free_monoid import FreeMonoid_class +from .free_monoid import FreeMonoid from .string_monoid_element import StringMonoidElement from .string_ops import strip_encoding @@ -195,7 +195,7 @@ def AlphabeticStrings(): #***************************************************************************** -class StringMonoid_class(FreeMonoid_class): +class StringMonoid_class(FreeMonoid): r""" A free string monoid on `n` generators. """ @@ -221,8 +221,8 @@ def __init__(self, n, alphabet=()): """ # Names must be alphabetical -- omitted since printing is # defined locally. - # FreeMonoid_class.__init__(self, n, names = alphabet) - FreeMonoid_class.__init__(self, n) + # FreeMonoid.__init__(self, n, names = alphabet) + FreeMonoid.__init__(self, n) self._alphabet = alphabet def __contains__(self, x): From 24dedf599a03fddf79e54bf7f7aef784c749db44 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Sat, 8 Sep 2018 18:18:18 -0700 Subject: [PATCH 192/264] trac 26221: fix the docstrings. --- src/sage/monoids/free_monoid.py | 66 +++++++++++++++++---------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/src/sage/monoids/free_monoid.py b/src/sage/monoids/free_monoid.py index 25402189974..6200cd0e64c 100644 --- a/src/sage/monoids/free_monoid.py +++ b/src/sage/monoids/free_monoid.py @@ -72,27 +72,8 @@ class FreeMonoid(Monoid_class, UniqueRepresentation): def __classcall_private__(cls, index_set=None, names=None, commutative=False, **kwds): r""" - Return a free monoid on `n` generators or with the generators - indexed by a set `I`. - - We construct free monoids by specifing either: - - - the number of generators and/or the names of the generators - - the indexing set for the generators - - INPUT: - - - ``index_set`` -- an indexing set for the generators; if an - integer, than this becomes `\{0, 1, \ldots, n-1\}` - - - ``names`` -- names of generators - - - ``commutative`` -- (default: ``False``) whether the free - monoid is commutative or not - - OUTPUT: - - A free monoid. + Construct a free monoid or a free abelian monoid, depending on the + input. Also, normalize the input. EXAMPLES:: @@ -100,18 +81,13 @@ def __classcall_private__(cls, index_set=None, names=None, Free monoid on 5 generators (a, b, c, d, e) sage: FreeMonoid(index_set=ZZ) Free monoid indexed by Integer Ring - sage: F. = FreeMonoid(abelian=True); F Free abelian monoid on 3 generators (x, y, z) sage: FreeMonoid(index_set=ZZ, commutative=True) Free abelian monoid indexed by Integer Ring - - TESTS:: - sage: FreeMonoid(index_set=ZZ, names='x,y,z') Free monoid indexed by Integer Ring """ - if 'abelian' in kwds: commutative = kwds.pop('abelian') @@ -141,17 +117,29 @@ def __classcall_private__(cls, index_set=None, names=None, Element = FreeMonoidElement - def __init__(self, n, names=None): + def __init__(self, index_set, names=None, **kwds): """ - Create free monoid on `n` generators. + Return a free monoid on `n` generators or with the generators + indexed by a set `I`. + + We construct free monoids by specifing either: + + - the number of generators and/or the names of the generators + - the indexing set for the generators INPUT: - - ``n`` - integer + - ``index_set`` -- an indexing set for the generators; if an + integer `n`, than this becomes `\{0, 1, \ldots, n-1\}` - - ``names`` - (optional) variable name or list of - variable names + - ``names`` -- names of generators + + - ``commutative`` -- (default: ``False``) whether the free + monoid is commutative or not + OUTPUT: + + A free monoid. EXAMPLES:: @@ -164,11 +152,25 @@ def __init__(self, n, names=None): sage: F Free monoid on 3 generators (a0, a1, a2) - :: + sage: F. = FreeMonoid(); F + Free monoid on 5 generators (a, b, c, d, e) + sage: FreeMonoid(index_set=ZZ) + Free monoid indexed by Integer Ring + + sage: F. = FreeMonoid(abelian=True); F + Free abelian monoid on 3 generators (x, y, z) + sage: FreeMonoid(index_set=ZZ, commutative=True) + Free abelian monoid indexed by Integer Ring + + TESTS:: sage: M = FreeMonoid(3, names=['a','b','c']) sage: TestSuite(M).run() """ + # The variable name 'index_set' is meaningful for + # __classcall_private__. Once you reach this point, it should + # be an integer. + n = index_set if not isinstance(n, integer_types + (Integer,)): raise TypeError("n (=%s) must be an integer."%n) if n < 0: From f7d20463f4a1b53907d9a2b614c93d286a8738db Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Sat, 8 Sep 2018 18:56:55 -0700 Subject: [PATCH 193/264] trac 26221: remove superfluous caching in string_monoid.py. --- src/sage/monoids/string_monoid.py | 226 +++++++----------------------- 1 file changed, 54 insertions(+), 172 deletions(-) diff --git a/src/sage/monoids/string_monoid.py b/src/sage/monoids/string_monoid.py index 72405c723ca..8aae4667a48 100644 --- a/src/sage/monoids/string_monoid.py +++ b/src/sage/monoids/string_monoid.py @@ -24,177 +24,6 @@ from .string_monoid_element import StringMonoidElement from .string_ops import strip_encoding -import weakref - -_cache = {} - -def BinaryStrings(): - r""" - Returns the free binary string monoid on generators `\{ 0, 1 \}`. - - OUTPUT: - - - Free binary string monoid. - - EXAMPLES:: - - sage: S = BinaryStrings(); S - Free binary string monoid - sage: u = S('') - sage: u - - sage: x = S('0') - sage: x - 0 - sage: y = S('1') - sage: y - 1 - sage: z = S('01110') - sage: z - 01110 - sage: x*y^3*x == z - True - sage: u*x == x*u - True - """ - # Here we cache the binary strings to make them unique - if 2 in _cache: - S = _cache[2]() - if not S is None: - return S - S = BinaryStringMonoid() - _cache[2] = weakref.ref(S) - return S - - -def OctalStrings(): - r""" - Returns the free octal string monoid on generators `\{ 0, 1, \dots, 7 \}`. - - OUTPUT: - - - Free octal string monoid. - - EXAMPLES:: - - sage: S = OctalStrings(); S - Free octal string monoid - sage: x = S.gens() - sage: x[0] - 0 - sage: x[7] - 7 - sage: x[0] * x[3]^3 * x[5]^4 * x[6] - 033355556 - """ - # Here we cache the octal strings to make them unique - if 8 in _cache: - S = _cache[8]() - if not S is None: - return S - S = OctalStringMonoid() - _cache[8] = weakref.ref(S) - return S - - -def HexadecimalStrings(): - r""" - Returns the free hexadecimal string monoid on generators - `\{ 0, 1, \dots , 9, a, b, c, d, e, f \}`. - - OUTPUT: - - - Free hexadecimal string monoid. - - EXAMPLES:: - - sage: S = HexadecimalStrings(); S - Free hexadecimal string monoid - sage: x = S.gen(0) - sage: y = S.gen(10) - sage: z = S.gen(15) - sage: z - f - sage: x*y^3*z - 0aaaf - """ - # Here we cache the hexadecimal strings to make them unique - if 16 in _cache: - S = _cache[16]() - if not S is None: - return S - S = HexadecimalStringMonoid() - _cache[16] = weakref.ref(S) - return S - - -def Radix64Strings(): - r""" - Returns the free radix 64 string monoid on 64 generators - - :: - - A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z, - a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z, - 0,1,2,3,4,5,6,7,8,9,+,/ - - OUTPUT: - - - Free radix 64 string monoid. - - EXAMPLES:: - - sage: S = Radix64Strings(); S - Free radix 64 string monoid - sage: x = S.gens() - sage: x[0] - A - sage: x[62] - + - sage: x[63] - / - """ - # Here we cache the radix-64 strings to make them unique - if 64 in _cache: - S = _cache[64]() - if not S is None: - return S - S = Radix64StringMonoid() - _cache[64] = weakref.ref(S) - return S - - -def AlphabeticStrings(): - r""" - Returns the string monoid on generators A-Z: - `\{ A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z \}`. - - OUTPUT: - - - Free alphabetic string monoid on A-Z. - - EXAMPLES:: - - sage: S = AlphabeticStrings(); S - Free alphabetic string monoid on A-Z - sage: x = S.gens() - sage: x[0] - A - sage: x[25] - Z - """ - # Here we cache the alphabetic strings to make them unique - if 26 in _cache: - S = _cache[26]() - if not S is None: - return S - S = AlphabeticStringMonoid() - _cache[26] = weakref.ref(S) - return S - -#***************************************************************************** - - class StringMonoid_class(FreeMonoid): r""" A free string monoid on `n` generators. @@ -287,7 +116,6 @@ def gen(self, i=0): # Specific global string monoids #***************************************************************************** - class BinaryStringMonoid(StringMonoid_class): r""" The free binary string monoid on generators `\{ 0, 1 \}`. @@ -304,6 +132,30 @@ def __init__(self): sage: x = S.gens() sage: x[0]*x[1]**5 * (x[0]*x[1]) 01111101 + + sage: u = S('') + sage: u + + sage: x = S('0') + sage: x + 0 + sage: y = S('1') + sage: y + 1 + sage: z = S('01110') + sage: z + 01110 + sage: x*y^3*x == z + True + sage: u*x == x*u + True + + TESTS:: + + sage: BinaryStrings() == BinaryStrings() + True + sage: BinaryStrings() is BinaryStrings() + True """ StringMonoid_class.__init__(self, 2, ['0', '1']) @@ -396,6 +248,8 @@ def encoding(self, S, padic=False): # """ # return 2 +BinaryStrings = BinaryStringMonoid + class OctalStringMonoid(StringMonoid_class): r""" @@ -415,6 +269,12 @@ def __init__(self): 07070701650165 sage: S([ i for i in range(8) ]) 01234567 + sage: x[0] + 0 + sage: x[7] + 7 + sage: x[0] * x[3]^3 * x[5]^4 * x[6] + 033355556 """ StringMonoid_class.__init__(self, 8, [ str(i) for i in range(8) ]) @@ -450,6 +310,8 @@ def __call__(self, x, check=True): else: raise TypeError("Argument x (= %s) is of the wrong type." % x) +OctalStrings = OctalStringMonoid + class HexadecimalStringMonoid(StringMonoid_class): r""" @@ -471,6 +333,14 @@ def __init__(self): 0a0a0a019f019f sage: S([ i for i in range(16) ]) 0123456789abcdef + + sage: x = S.gen(0) + sage: y = S.gen(10) + sage: z = S.gen(15) + sage: z + f + sage: x*y^3*z + 0aaaf """ alph = '0123456789abcdef' StringMonoid_class.__init__(self, 16, [ alph[i] for i in range(16) ]) @@ -552,6 +422,8 @@ def encoding(self, S, padic=False): hex_string.extend(hex_chars) return self(hex_string) +HexadecimalStrings = HexadecimalStringMonoid + class Radix64StringMonoid(StringMonoid_class): r""" @@ -571,6 +443,12 @@ def __init__(self): yKyKyK8BTj8BTj sage: S([ i for i in range(64) ]) ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ + sage: x[0] + A + sage: x[62] + + + sage: x[63] + / """ alph = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' StringMonoid_class.__init__(self, 64, [ alph[i] for i in range(64) ]) @@ -610,6 +488,8 @@ def __call__(self, x, check=True): else: raise TypeError("Argument x (= %s) is of the wrong type." % x) +Radix64Strings = Radix64StringMonoid + class AlphabeticStringMonoid(StringMonoid_class): """ @@ -918,3 +798,5 @@ def encoding(self, S): 'THECATINTHEHAT' """ return self(strip_encoding(S)) + +AlphabeticStrings = AlphabeticStringMonoid From f6a8055699f658b10e5ad6dfe026263453993139 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Tue, 4 Sep 2018 14:27:27 -0700 Subject: [PATCH 194/264] trac 26027: morphisms for graded commutative algebras --- src/sage/algebras/commutative_dga.py | 427 +++++++++++++++++++++++++++ src/sage/rings/polynomial/plural.pyx | 96 +++++- 2 files changed, 520 insertions(+), 3 deletions(-) diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index 1ed0a05defd..ff001725068 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -95,6 +95,8 @@ from sage.modules.free_module import VectorSpace from sage.modules.free_module_element import vector from sage.rings.all import ZZ +from sage.rings.homset import RingHomset_generic +from sage.rings.morphism import RingHomomorphism_im_gens from sage.rings.polynomial.term_order import TermOrder from sage.rings.quotient_ring import QuotientRing_nc from sage.rings.quotient_ring_element import QuotientRingElement @@ -1172,6 +1174,44 @@ def _element_constructor_(self, x, coerce=True): return self.element_class(self, x) + def _Hom_(self, B, category): + """ + Return the homset from ``self`` to ``B`` in the category ``category``. + + INPUT: + + - ``B`` -- a graded commative algebra + - ``category`` -- a subcategory of graded algebras or ``None`` + + EXAMPLES:: + + sage: A. = GradedCommutativeAlgebra(QQ) + sage: B. = GradedCommutativeAlgebra(QQ, degrees=(1,2,3)) + sage: C. = GradedCommutativeAlgebra(GF(17)) + sage: Hom(A,A) + Set of Homomorphisms from Graded Commutative Algebra with generators ('x', 'y') in degrees (1, 1) over Rational Field to Graded Commutative Algebra with generators ('x', 'y') in degrees (1, 1) over Rational Field + sage: Hom(A,B) + Set of Homomorphisms from Graded Commutative Algebra with generators ('x', 'y') in degrees (1, 1) over Rational Field to Graded Commutative Algebra with generators ('a', 'b', 'c') in degrees (1, 2, 3) over Rational Field + sage: Hom(A,C) + Traceback (most recent call last): + ... + NotImplementedError: homomorphisms of graded commutative algebras have only been implemented when the base rings are the same + """ + R = self.base_ring() + # The base rings need to be checked before the categories, or + # else the function sage.categories.homset.Hom catches the + # TypeError and uses the wrong category (the meet of the + # categories for self and B, which might be the category of + # rings). + if R != B.base_ring(): + raise NotImplementedError('homomorphisms of graded commutative ' + 'algebras have only been implemented ' + 'when the base rings are the same') + cat = Algebras(R).Graded() + if category is not None and not category.is_subcategory(cat): + raise TypeError("%s is not a subcategory of graded algebras"%category) + return GCAlgebraHomset(self, B, category=category) + def differential(self, diff): """ Construct a differential on ``self``. @@ -1432,6 +1472,50 @@ def basis_coefficients(self, total=False): lift = self.lift() return [lift.monomial_coefficient(x.lift()) for x in basis] + def _im_gens_(self, codomain, im_gens): + """ + Return the image of ``self`` in ``codomain`` under the map that + sends ``self.parent().gens()`` to ``im_gens``. + + INPUT: + + - ``codomain`` -- a graded commutative algebra + + - ``im_gens`` -- a tuple of elements `f(x)` in + ``codomain``, one for each `x` in + ``self.parent().gens()``, that defines a homomorphism + `f` from ``self.parent()`` to ``codomain`` + + OUTPUT: + + The image of ``self`` in ``codomain`` under the above + homomorphism `f`. + + EXAMPLES:: + + sage: A. = GradedCommutativeAlgebra(QQ) + sage: H = Hom(A,A) + sage: f = H([y,x,x]) + sage: f(x) + y + sage: f(3*x*y) + -3*x*y + sage: f(y*z) + 0 + sage: f(1) + 1 + """ + # This is not meant to be called directly, but rather + # through the ``_call_`` method for morphisms. So no + # error checking (e.g., whether each element of im_gens is + # in codomain) is done here. + result = codomain.zero() + for mono, coeff in self.dict().iteritems(): + term = prod([gen**x for (x, gen) in zip(mono, im_gens)], + codomain.one()) + result += coeff*term + return result + class GCAlgebra_multigraded(GCAlgebra): """ @@ -2676,6 +2760,349 @@ def GradedCommutativeAlgebra(ring, names=None, degrees=None, relations=None): else: return GCAlgebra(ring, names=names, degrees=degrees) +################################################ +# Morphisms + +class GCAlgebraMorphism(RingHomomorphism_im_gens): + """ + Create a morphism between two :class:`graded commutative algebras `. + + INPUT: + + - ``parent`` -- the parent homset + + - ``im_gens`` -- the images, in the codomain, of the generators of + the domain + + - ``check`` -- boolean (default: ``True``); check whether the + proposed map is actually an algebra map; if the domain and + codomain have differentials, also check that the map respects + those. + + EXAMPLES:: + + sage: A. = GradedCommutativeAlgebra(QQ) + sage: H = Hom(A,A) + sage: f = H([y,x]) + sage: f + Graded Commutative Algebra endomorphism of Graded Commutative Algebra with generators ('x', 'y') in degrees (1, 1) over Rational Field + Defn: (x, y) --> (y, x) + sage: f(x*y) + -x*y + """ + def __init__(self, parent, im_gens, check=True): + r""" + TESTS: + + The entries in ``im_gens`` must lie in the codomain:: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1,2)) + sage: B. = GradedCommutativeAlgebra(QQ, degrees=(1,2)) + sage: H = Hom(A,A) + sage: H([x,b]) + Traceback (most recent call last): + ... + ValueError: not all elements of im_gens are in the codomain + + Note that morphisms do not need to respect the grading; + whether they do can be tested with the method + :meth:`is_graded`:: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1,2)) + sage: H = Hom(A,A) + sage: f = H([x,x]) + sage: f + Graded Commutative Algebra endomorphism of Graded Commutative Algebra with generators ('x', 'y') in degrees (1, 2) over Rational Field + Defn: (x, y) --> (x, x) + sage: f.is_graded() + False + sage: TestSuite(f).run(skip="_test_category") + + Since `x^2=0` but `y^2 \neq 0`, the following does not define a valid morphism:: + + sage: H([y,y]) + Traceback (most recent call last): + ... + ValueError: the proposed morphism does not respect the relations + + This is okay in characteristic two since then `x^2 \neq 0`:: + + sage: A2. = GradedCommutativeAlgebra(GF(2), degrees=(1,2)) + sage: H2 = Hom(A2,A2) + sage: H2([y,y]) + Graded Commutative Algebra endomorphism of Graded Commutative Algebra with generators ('x', 'y') in degrees (1, 2) over Finite Field of size 2 + Defn: (x, y) --> (y, y) + + The "nc-relations" `a*b = -b*a`, for `a` and `b` in odd + degree, are checked first, and we can see this when using more + generators:: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1,1,2)) + sage: Hom(A,A)([x,z,z]) + Traceback (most recent call last): + ... + ValueError: the proposed morphism does not respect the nc-relations + + Other relations:: + + sage: B. = GradedCommutativeAlgebra(QQ, degrees=(1,1,1)) + sage: D = B.quotient(B.ideal(x*y)) + sage: H = Hom(D,D) + sage: D.inject_variables() + Defining x, y, z + sage: H([x,z,z]) + Traceback (most recent call last): + ... + ValueError: the proposed morphism does not respect the relations + + The morphisms must respect the differentials, when present:: + + sage: B. = GradedCommutativeAlgebra(QQ, degrees=(1,1,1)) + sage: C = B.cdg_algebra({z: x*y}) + sage: C.inject_variables() + Defining x, y, z + sage: H = Hom(C,C) + sage: H([x,z,z]) + Traceback (most recent call last): + ... + ValueError: the proposed morphism does not respect the differentials + """ + domain = parent.domain() + codomain = parent.codomain() + + # We use check=False here because checking of nc-relations is + # not implemented in RingHomomorphism_im_gens.__init__. + # We check these relations below. + RingHomomorphism_im_gens.__init__(self, parent=parent, + im_gens=im_gens, + check=False) + self._im_gens = tuple(im_gens) + # Now check that the relations are respected. + if check: + if any(x not in codomain for x in im_gens): + raise ValueError('not all elements of im_gens are in ' + 'the codomain') + R = domain.cover_ring() + from_free = dict(zip(R.free_algebra().gens(), im_gens)) + from_R = dict(zip(R.gens(), im_gens)) + # First check the nc-relations: x*y=-y*x for x, y in odd + # degrees. These are in the form of a dictionary, with + # typical entry left:right. + for left in R.relations(): + zero = left.subs(from_free) - R.relations()[left].subs(from_R) + if zero: + raise ValueError('the proposed morphism does not respect ' + 'the nc-relations') + # Now check any extra relations, including x**2=0 for x in + # odd degree. These are defined by a list of generators of + # the defining ideal. + for g in domain.defining_ideal().gens(): + zero = g.subs(from_R) + if zero: + raise ValueError('the proposed morphism does not respect ' + 'the relations') + # If the domain and codomain have differentials, check + # those, too. + if (isinstance(domain, DifferentialGCAlgebra) and + isinstance(codomain, DifferentialGCAlgebra)): + dom_diff = domain.differential() + cod_diff = codomain.differential() + if any(cod_diff(self(g)) != self(dom_diff(g)) + for g in domain.gens()): + raise ValueError('the proposed morphism does not respect ' + 'the differentials') + + def _call_(self, x): + """ + Evaluate this morphism on ``x``. + + INPUT: + + - ``x`` -- an element of the domain + + EXAMPLES:: + + sage: A. = GradedCommutativeAlgebra(GF(2)) + sage: H = Hom(A,A) + sage: g = H([y,y]) + sage: g(x) + y + sage: g(x*y) + y^2 + """ + return x._im_gens_(self.codomain(), self.im_gens()) + + def is_graded(self, total=False): + """ + Return ``True`` if this morphism is graded. + + That is, return ``True`` if `f(x)` is zero, or if `f(x)` is + homogeneous and has the same degree as `x`, for each generator + `x`. + + INPUT: + + - ``total`` (optional, default ``False``) -- if ``True``, use + the total degree to determine whether the morphism is graded + (relevant only in the multigraded case) + + EXAMPLES:: + + sage: C. = GradedCommutativeAlgebra(QQ, degrees=(1,1,2)) + sage: H = Hom(C,C) + sage: H([a, b, a*b + 2*a]).is_graded() + False + sage: H([a, b, a*b]).is_graded() + True + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=((1,0), (1,0))) + sage: B. = GradedCommutativeAlgebra(QQ, degrees=((1,0), (0,1))) + sage: H = Hom(A,B) + sage: H([y,0]).is_graded() + True + sage: H([z,z]).is_graded() + False + sage: H([z,z]).is_graded(total=True) + True + """ + return all(not y or # zero is always allowed as an image + (y.is_homogeneous() + and x.degree(total=total) == y.degree(total=total)) + for (x,y) in zip(self.domain().gens(), self.im_gens())) + + def _repr_type(self): + """ + EXAMPLES:: + + sage: B. = GradedCommutativeAlgebra(QQ, degrees=(1,1,1)) + sage: C = B.cdg_algebra({z: x*y}) + sage: Hom(B,B)([z,y,x])._repr_type() + 'Graded Commutative Algebra' + sage: C.inject_variables() + Defining x, y, z + sage: Hom(C,C)([x,0,0])._repr_type() + 'Commutative Differential Graded Algebra' + """ + if (isinstance(self.domain(), DifferentialGCAlgebra) and + isinstance(self.codomain(), DifferentialGCAlgebra)): + return "Commutative Differential Graded Algebra" + return "Graded Commutative Algebra" + + def _repr_defn(self): + """ + EXAMPLES:: + + sage: A. = GradedCommutativeAlgebra(QQ) + sage: Hom(A,A)([y,x])._repr_defn() + '(x, y) --> (y, x)' + """ + gens = self.domain().gens() + return "{} --> {}".format(gens, self._im_gens) + + +################################################ +# Homsets + +class GCAlgebraHomset(RingHomset_generic): + """ + Set of morphisms between two graded commutative algebras. + + .. NOTE:: + + Homsets (and thus morphisms) have only been implemented when + the base fields are the same for the domain and codomain. + + EXAMPLES:: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1,2)) + sage: H = Hom(A,A) + sage: H([x,y]) == H.identity() + True + sage: H([x,x]) == H.identity() + False + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1,2)) + sage: B. = GradedCommutativeAlgebra(QQ, degrees=(1,1)) + sage: H = Hom(A,B) + sage: H([y,0]) + Graded Commutative Algebra morphism: + From: Graded Commutative Algebra with generators ('w', 'x') in degrees (1, 2) over Rational Field + To: Graded Commutative Algebra with generators ('y', 'z') in degrees (1, 1) over Rational Field + Defn: (w, x) --> (y, 0) + sage: H([y,y*z]) + Graded Commutative Algebra morphism: + From: Graded Commutative Algebra with generators ('w', 'x') in degrees (1, 2) over Rational Field + To: Graded Commutative Algebra with generators ('y', 'z') in degrees (1, 1) over Rational Field + Defn: (w, x) --> (y, y*z) + """ + + @cached_method + def zero(self): + """ + Construct the "zero" morphism of this homset: the map sending each + generator to zero. + + EXAMPLES:: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1,2)) + sage: B. = GradedCommutativeAlgebra(QQ, degrees=(1,1,1)) + sage: zero = Hom(A,B).zero() + sage: zero(x) == zero(y) == 0 + True + """ + return GCAlgebraMorphism(self, [self.codomain().zero()] + * self.domain().ngens()) + + @cached_method + def identity(self): + """ + Construct the identity morphism of this homset. + + EXAMPLES:: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1,2)) + sage: H = Hom(A,A) + sage: H([x,y]) == H.identity() + True + sage: H([x,x]) == H.identity() + False + """ + if self.domain() != self.codomain(): + raise TypeError('identity map is only defined for ' + 'endomorphism sets') + return GCAlgebraMorphism(self, self.domain().gens()) + + def __call__(self, im_gens, check=True): + """ + Create a homomorphism. + + INPUT: + + - ``im_gens`` -- the images of the generators of the domain + + EXAMPLES:: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1,2)) + sage: B. = GradedCommutativeAlgebra(QQ, degrees=(1,1)) + sage: H = Hom(A,B) + sage: H([y,0]) + Graded Commutative Algebra morphism: + From: Graded Commutative Algebra with generators ('w', 'x') in degrees (1, 2) over Rational Field + To: Graded Commutative Algebra with generators ('y', 'z') in degrees (1, 1) over Rational Field + Defn: (w, x) --> (y, 0) + sage: H([y,y*z]) + Graded Commutative Algebra morphism: + From: Graded Commutative Algebra with generators ('w', 'x') in degrees (1, 2) over Rational Field + To: Graded Commutative Algebra with generators ('y', 'z') in degrees (1, 1) over Rational Field + Defn: (w, x) --> (y, y*z) + """ + from sage.categories.map import Map + if isinstance(im_gens, Map): + return self._coerce_impl(im_gens) + else: + return GCAlgebraMorphism(self, im_gens, check=check) + + ################################################ # Miscellaneous utility classes and functions diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index b4a9d3ef58f..b1395c6fa5f 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -132,7 +132,7 @@ from sage.rings.polynomial.multi_polynomial_ideal import NCPolynomialIdeal from sage.rings.polynomial.polydict import ETuple from sage.rings.ring import check_default_category -from sage.structure.element cimport CommutativeRingElement, Element, ModuleElement +from sage.structure.element cimport CommutativeRingElement, Element, ModuleElement, RingElement from sage.structure.factory import UniqueFactory from sage.structure.parent cimport Parent from sage.structure.parent_gens cimport ParentWithGens @@ -461,6 +461,30 @@ cdef class NCPolynomialRing_plural(Ring): sage: P._element_constructor_(0) 0 + + From the parent free algebra:: + + sage: F. = FreeAlgebra(QQ,3) + sage: G = F.g_algebra({y*x: -x*y}) + sage: G._element_constructor_(y*x) + -x*y + + From another free algebra:: + + sage: A. = FreeAlgebra(QQ, 2) + sage: G._element_constructor_(b) + Traceback (most recent call last): + ... + ValueError: unable to construct an element of this ring + + From another g-algebra:: + + sage: B = A.g_algebra({b*a: -a*b}) + sage: abar, bbar = B.gens() + sage: G._element_constructor_(bbar) + Traceback (most recent call last): + ... + ValueError: unable to construct an element of this ring """ if element == 0: @@ -485,6 +509,8 @@ cdef class NCPolynomialRing_plural(Ring): elif element.parent() == self: # is this safe? _p = p_Copy((element)._poly, _ring) + else: + raise ValueError("unable to construct an element of this ring") elif isinstance(element, CommutativeRingElement): # base ring elements @@ -509,6 +535,13 @@ cdef class NCPolynomialRing_plural(Ring): _n = sa2si(element,_ring) _p = p_NSet(_n, _ring) + elif isinstance(element, RingElement): + # the parent free algebra + if element.parent() == self.free_algebra(): + return element(self.gens()) + else: + raise ValueError("unable to construct an element of this ring") + # Accepting int elif isinstance(element, int): if isinstance(base_ring, FiniteField_prime_modn): @@ -527,7 +560,7 @@ cdef class NCPolynomialRing_plural(Ring): _p = p_NSet(_n, _ring) else: - raise NotImplementedError("not able to interprete "+repr(element) + + raise NotImplementedError("not able to interpret "+repr(element) + " of type "+ repr(type(element)) + " as noncommutative polynomial") ### ?????? return new_NCP(self,_p) @@ -548,10 +581,24 @@ cdef class NCPolynomialRing_plural(Ring): sage: P._coerce_map_from_(ZZ) True """ - if self.base_ring().has_coerce_map_from(S): return True + def free_algebra(self): + """ + The free algebra of which this is the quotient. + + EXAMPLES:: + + sage: A. = FreeAlgebra(QQ, 3) + sage: P = A.g_algebra(relations={y*x:-x*y}, order = 'lex') + sage: B = P.free_algebra() + sage: A == B + True + """ + from sage.algebras.free_algebra import FreeAlgebra + return FreeAlgebra(self.base_ring(), names=self.variable_names(), order=self.term_order()) + def __hash__(self): """ Return a hash for this noncommutative ring, that is, a hash of the string @@ -2657,6 +2704,49 @@ cdef class NCPolynomial_plural(RingElement): else: return False + def __call__(self, *x, **kwds): + """ + EXAMPLES:: + + sage: F.=FreeAlgebra(QQ,3) + sage: G = F.g_algebra({y*x: -x*y}) + sage: G.inject_variables() + Defining x, y, z + sage: a = x+y+x*y + sage: a.subs(x=0, y=1) + 1 + sage: a.subs(x=y,y=x) == x + y - x*y + True + """ + # Modified version of method from algebras/free_algebra_element.py. + if isinstance(x[0], tuple): + x = x[0] + + if len(x) != self.parent().ngens(): + raise ValueError("must specify as many values as generators in parent") + + # I don't start with 0, because I don't want to preclude evaluation with + # arbitrary objects (e.g. matrices) because of funny coercion. + + result = None + for m in self.monomials(): + c = self.monomial_coefficient(m) + summand = None + for (elt, pow) in zip(x, m.exponents()[0]): + if summand is None: + summand = elt**pow + else: + summand *= elt**pow + + if result is None: + result = c*summand + else: + result += c*summand + + if result is None: + return self.parent()(0) + return result + ##################################################################### From 9d79cdc8797764361918ffb668deb5cbdbcedc96 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Sat, 8 Sep 2018 22:44:27 -0700 Subject: [PATCH 195/264] trac 26027: use self.zero() instead of self(0) in a few places. Also don't use method _im_gens for elements: just move that code to the _call_ method for morphisms, and move the doctests from _im_gens to there. --- src/sage/algebras/commutative_dga.py | 66 +++++++---------------- src/sage/algebras/free_algebra_element.py | 2 +- src/sage/rings/polynomial/plural.pyx | 2 +- 3 files changed, 22 insertions(+), 48 deletions(-) diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index ff001725068..a8be2026f16 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -1472,50 +1472,6 @@ def basis_coefficients(self, total=False): lift = self.lift() return [lift.monomial_coefficient(x.lift()) for x in basis] - def _im_gens_(self, codomain, im_gens): - """ - Return the image of ``self`` in ``codomain`` under the map that - sends ``self.parent().gens()`` to ``im_gens``. - - INPUT: - - - ``codomain`` -- a graded commutative algebra - - - ``im_gens`` -- a tuple of elements `f(x)` in - ``codomain``, one for each `x` in - ``self.parent().gens()``, that defines a homomorphism - `f` from ``self.parent()`` to ``codomain`` - - OUTPUT: - - The image of ``self`` in ``codomain`` under the above - homomorphism `f`. - - EXAMPLES:: - - sage: A. = GradedCommutativeAlgebra(QQ) - sage: H = Hom(A,A) - sage: f = H([y,x,x]) - sage: f(x) - y - sage: f(3*x*y) - -3*x*y - sage: f(y*z) - 0 - sage: f(1) - 1 - """ - # This is not meant to be called directly, but rather - # through the ``_call_`` method for morphisms. So no - # error checking (e.g., whether each element of im_gens is - # in codomain) is done here. - result = codomain.zero() - for mono, coeff in self.dict().iteritems(): - term = prod([gen**x for (x, gen) in zip(mono, im_gens)], - codomain.one()) - result += coeff*term - return result - class GCAlgebra_multigraded(GCAlgebra): """ @@ -2929,8 +2885,26 @@ def _call_(self, x): y sage: g(x*y) y^2 - """ - return x._im_gens_(self.codomain(), self.im_gens()) + + sage: B. = GradedCommutativeAlgebra(QQ) + sage: H = Hom(B,B) + sage: f = H([y,x,x]) + sage: f(x) + y + sage: f(3*x*y) + -3*x*y + sage: f(y*z) + 0 + sage: f(1) + 1 + """ + codomain = self.codomain() + result = codomain.zero() + for mono, coeff in x.dict().iteritems(): + term = prod([gen**y for (y, gen) in zip(mono, self.im_gens())], + codomain.one()) + result += coeff*term + return result def is_graded(self, total=False): """ diff --git a/src/sage/algebras/free_algebra_element.py b/src/sage/algebras/free_algebra_element.py index a3f4dc2640c..b7ee988da71 100644 --- a/src/sage/algebras/free_algebra_element.py +++ b/src/sage/algebras/free_algebra_element.py @@ -177,7 +177,7 @@ def extract_from(kwds,g): result += c*m(x) if result is None: - return self.parent()(0) + return self.parent().zero() return result def _mul_(self, y): diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index b1395c6fa5f..4607545bb7f 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -2744,7 +2744,7 @@ cdef class NCPolynomial_plural(RingElement): result += c*summand if result is None: - return self.parent()(0) + return self.parent().zero() return result From a25fe51f655acaac05ac7fcdd8c070f839feefe8 Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Sun, 9 Sep 2018 10:48:40 +0300 Subject: [PATCH 196/264] trac #26078: combining unused base classes and changing module behavior for subalgebras --- src/sage/algebras/lie_algebras/ideal.py | 16 + src/sage/algebras/lie_algebras/subalgebra.py | 603 ++++++++----------- 2 files changed, 265 insertions(+), 354 deletions(-) diff --git a/src/sage/algebras/lie_algebras/ideal.py b/src/sage/algebras/lie_algebras/ideal.py index 62800890e4e..a94040dbcf2 100644 --- a/src/sage/algebras/lie_algebras/ideal.py +++ b/src/sage/algebras/lie_algebras/ideal.py @@ -213,6 +213,9 @@ def basis(self): m = L.module() B = m.basis() + # use ambient module in case L is an ideal or subalgebra + m = m.ambient_module() + sm = m.submodule([self._to_m(X) for X in self.gens()]) d = 0 @@ -226,6 +229,19 @@ def basis(self): return Family(reversed([self.element_class(self, self._from_m(v)) for v in sm.echelonized_basis()])) + def lie_algebra_generators(self): + r""" + Return the generating set of ``self`` as a Lie algebra. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) + sage: S = L.ideal(x) + sage: S.lie_algebra_generators() + Family (x, z) + """ + return self.basis() + def reduce(self, X): r""" Reduce an element of the ambient Lie algebra modulo ``self``. diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index ab50a714f19..5485d32bb85 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -30,320 +30,7 @@ from sage.structure.unique_representation import UniqueRepresentation -class LieSubset(Parent, UniqueRepresentation): - r""" - An abstract base class for a subset of a Lie algebra. - - INPUT: - - - ``ambient`` -- the Lie algebra containing the subset - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset - sage: L. = LieAlgebra(QQ) - sage: S = LieSubset(L); S - Abstract subset of Free Lie algebra generated by (X, Y) over Rational Field - sage: S.category() - Category of subobjects of sets - - TESTS:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset - sage: L. = LieAlgebra(QQ, abelian=True) - sage: K. = LieAlgebra(QQ, {}) - sage: LieSubset(L) == LieSubset(K) - True - """ - - def _repr_(self): - r""" - Return a string representation of ``self``. - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset - sage: L. = LieAlgebra(QQ) - sage: LieSubset(L) - Abstract subset of Free Lie algebra generated by (X, Y) over Rational Field - """ - return "Abstract subset of %s" % self.ambient() - - def __init__(self, ambient, category=None): - r""" - Initialize ``self``. - - TESTS:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset - sage: L. = LieAlgebra(QQ) - sage: S = LieSubset(L) - sage: TestSuite(S).run() - """ - self._ambient = ambient - cat = Sets().Subobjects() - category = cat.or_subcategory(category) - super(LieSubset, self).__init__(ambient.base_ring(), category=category) - - # register a coercion to the ambient Lie algebra - H = Hom(self, ambient) - f = SetMorphism(H, self.lift) - ambient.register_coercion(f) - - def __getitem__(self, x): - r""" - If `x` is a pair `(a, b)`, return the Lie bracket `[a, b]`. - Otherwise try to return the `x`-th element of ``self``. - - This replicates the convenience syntax for Lie brackets of Lie algebras. - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset - sage: L. = LieAlgebra(QQ, representation="polynomial") - sage: S = LieSubset(L) - sage: a = S(x); b = S(y) - sage: S[a, b] - x*y - y*x - sage: S[a, a + S[a,b]] - x^2*y - 2*x*y*x + y*x^2 - """ - if isinstance(x, tuple) and len(x) == 2: - return self(x[0])._bracket_(self(x[1])) - return super(LieSubset, self).__getitem__(x) - - def _an_element_(self): - r""" - Return an element of ``self``. - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset - sage: L. = LieAlgebra(QQ) - sage: S = LieSubset(L) - sage: S._an_element_() - X + Y - """ - return self.element_class(self, self.ambient()._an_element_()) - - def _element_constructor_(self, x): - r""" - Convert ``x`` into ``self``. - - EXAMPLES: - - Elements of subsets are created directly from elements - of the ambient Lie algebra:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset - sage: L. = LieAlgebra(ZZ, {('x','y'): {'x': 1}}) - sage: S = LieSubset(L) - sage: S(x) - x - sage: S(x).parent() - Abstract subset of Lie algebra on 2 generators (x, y) over Integer Ring - - A list of 2 elements is interpreted as a Lie bracket:: - - sage: S([S(x), S(y)]) - x - sage: S([S(x), S(y)]) == S(L[x, y]) - True - """ - if isinstance(x, list) and len(x) == 2: - return self(x[0])._bracket_(self(x[1])) - - try: - P = x.parent() -# if P is self: -# return x - if x.parent() == self.ambient(): - return self.element_class(self, x) - except AttributeError: - pass - - return super(LieSubset, self)._element_constructor_(x) - - @cached_method - def zero(self): - r""" - Return the element `0`. - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset - sage: L. = LieAlgebra(QQ, representation="polynomial") - sage: S = LieSubset(L) - sage: S.zero() - 0 - sage: S.zero() == S(L.zero()) - True - """ - return self.element_class(self, self.ambient().zero()) - - def ambient(self): - r""" - Return the ambient Lie algebra of ``self``. - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset - sage: L. = LieAlgebra(QQ, abelian=True) - sage: S = LieSubset(L) - sage: S.ambient() is L - True - """ - return self._ambient - - def lift(self, X): - r""" - Coerce an element ``X`` of ``self`` into the ambient Lie algebra. - - INPUT: - - - ``X`` -- an element of ``self`` - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset - sage: L. = LieAlgebra(QQ, abelian=True) - sage: S = LieSubset(L) - sage: sx = S(x); sx - x - sage: sx.parent() - Abstract subset of Abelian Lie algebra on 2 generators (x, y) over Rational Field - sage: a = S.lift(sx); a - x - sage: a.parent() - Abelian Lie algebra on 2 generators (x, y) over Rational Field - """ - return X.value - - def retract(self, x): - r""" - Retract ``x`` to ``self``. - - INPUT: - - - ``x`` -- an element of the ambient Lie algebra - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset - sage: L. = LieAlgebra(QQ, abelian=True) - sage: S = LieSubset(L) - sage: S.retract(x) - x - sage: S.retract(x).parent() - Abstract subset of Abelian Lie algebra on 2 generators (x, y) over Rational Field - """ - return self.element_class(self, x) - - class Element(LieAlgebraElementWrapper): - r""" - Wrap an element of the ambient Lie algebra as an element. - """ - - def _bracket_(self, x): - """ - Return the Lie bracket ``[self, x]``. - - Assumes ``x`` and ``self`` have the same parent. - - INPUT: - - - ``x`` -- an element of the same Lie subalgebra as ``self`` - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset - sage: L. = LieAlgebra(QQ) - sage: S = LieSubset(L) - sage: S(x)._bracket_(S(y)) - [x, y] - """ - P = self.parent() - self_lift = P.lift(self) - x_lift = P.lift(x) - return P.retract(P.ambient().bracket(self_lift, x_lift)) - - -class LieSubset_with_gens(LieSubset): - r""" - An abstract base class for a subset of a Lie algebra with a generating set. - - INPUT: - - - ``ambient`` -- the Lie algebra containing the subset - - ``gens`` -- a tuple of generators from the ambient Lie algebra - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset_with_gens - sage: L. = LieAlgebra(QQ) - sage: L = L.Lyndon() - sage: LieSubset_with_gens(L, (X, L[X, Y])) - Abstract subset with generators (X, [X, Y]) of Free Lie algebra - generated by (X, Y) over Rational Field in the Lyndon basis - """ - - def __init__(self, ambient, gens, category=None): - r""" - Initialize ``self``. - - TESTS:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset_with_gens - sage: L. = LieAlgebra(QQ) - sage: S = LieSubset_with_gens(L, (X,)) - sage: TestSuite(S).run() - """ - self._gens = gens - super(LieSubset_with_gens, self).__init__(ambient, category=category) - - def _repr_(self): - r""" - Return a string representation of ``self``. - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset_with_gens - sage: L. = LieAlgebra(QQ) - sage: LieSubset_with_gens(L, (X,)) - Abstract subset with generators (X,) of Free Lie algebra generated by (X, Y) over Rational Field - """ - return "Abstract subset with generators %s of %s" % (self.gens(), self.ambient()) - - def _an_element_(self): - r""" - Return an element of ``self``. - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset_with_gens - sage: L. = LieAlgebra(QQ) - sage: S = LieSubset_with_gens(L, (X,)) - sage: S._an_element_() - X - """ - return self.element_class(self, self.gens()[0]) - - def gens(self): - r""" - Return the generating set of ``self``. - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.subalgebra import LieSubset_with_gens - sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) - sage: S = LieSubset_with_gens(L, (x,)) - sage: S.gens() - (x,) - """ - return self._gens - - -class LieSubalgebra_finite_dimensional_with_basis(LieSubset_with_gens): +class LieSubalgebra_finite_dimensional_with_basis(Parent, UniqueRepresentation): r""" A Lie subalgebra of a finite dimensional Lie algebra with basis. @@ -400,7 +87,9 @@ def __classcall_private__(cls, ambient, gens, category=None): """ Normalize input to ensure a unique representation. - EXAMPLES:: + EXAMPLES: + + Various ways to input one generator:: sage: L. = LieAlgebra(QQ, {('X','Y'): {'X': 1}}) sage: S1 = L.subalgebra(X) @@ -408,14 +97,23 @@ def __classcall_private__(cls, ambient, gens, category=None): sage: S3 = L.subalgebra([X]) sage: S1 is S2 and S2 is S3 True + + Zero generators are ignored:: + + sage: S1 = L.subalgebra(X) + sage: S2 = L.subalgebra((X, 0)) + sage: S3 = L.subalgebra([X, 0, 0]) + sage: S1 is S2 and S2 is S3 + True + sage: T1 = L.subalgebra(0) + sage: T2 = L.subalgebra([]) + sage: T3 = L.subalgebra([0, 0]) + sage: T1 is T2 and T2 is T3 + True """ if not isinstance(gens, (list, tuple)): gens = [gens] - gens = tuple(ambient(gen) for gen in gens) - - gens = tuple(gens) - if len(gens) == 0: - gens = (ambient.zero(),) + gens = tuple(ambient(gen) for gen in gens if not gen.is_zero()) if isinstance(ambient, LieSubalgebra_finite_dimensional_with_basis): # a nested subalgebra is a subalgebra @@ -428,6 +126,26 @@ def __classcall_private__(cls, ambient, gens, category=None): sup = super(LieSubalgebra_finite_dimensional_with_basis, cls) return sup.__classcall__(cls, ambient, gens, category) + def __init__(self, ambient, gens, category=None): + r""" + Initialize ``self``. + + TESTS:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: S = L.subalgebra(X) + sage: TestSuite(S).run() + """ + self._ambient = ambient + self._gens = gens + sup = super(LieSubalgebra_finite_dimensional_with_basis, self) + sup.__init__(ambient.base_ring(), category=category) + + # register a coercion to the ambient Lie algebra + H = Hom(self, ambient) + f = SetMorphism(H, self.lift) + ambient.register_coercion(f) + def __contains__(self, x): r""" Return ``True`` if ``x`` is an element of ``self``. @@ -457,8 +175,59 @@ def __contains__(self, x): """ if x in self.ambient(): x = self.ambient()(x) - return x.to_vector() in self.ambient_submodule() - return super(LieSubset, self).__contains__(x) + return x.to_vector() in self.module() + sup = super(LieSubalgebra_finite_dimensional_with_basis, self) + return sup.__contains__(x) + + def __getitem__(self, x): + r""" + If `x` is a pair `(a, b)`, return the Lie bracket `[a, b]`. + Otherwise try to return the `x`-th element of ``self``. + + This replicates the convenience syntax for Lie brackets of Lie algebras. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) + sage: S = L.subalgebra([x, y]) + sage: a = S(x); b = S(y) + sage: S[a, b] + z + sage: S[a, a + S[a,b]] + 0 + """ + if isinstance(x, tuple) and len(x) == 2: + return self(x[0])._bracket_(self(x[1])) + super(LieSubalgebra_finite_dimensional_with_basis, self) + return sup.__getitem__(x) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: L.subalgebra([X, Y]) + Subalgebra generated by (X, Y) of Abelian Lie algebra on 2 generators (X, Y) over Rational Field + """ + gens = self.lie_algebra_generators() + if len(gens) == 1: + gens = gens[0] + return "Subalgebra generated by %s of %s" % (gens, self.ambient()) + + def _an_element_(self): + r""" + Return an element of ``self``. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: S = L.subalgebra([X, Y]) + sage: S._an_element_() + X + """ + return self.element_class(self, self.lie_algebra_generators()[0]) def _element_constructor_(self, x): """ @@ -476,31 +245,94 @@ def _element_constructor_(self, x): sage: S(y).parent() Subalgebra generated by (x, y) of Lie algebra on 4 generators (x, y, z, w) over Integer Ring - A vector of length equal to the dimension of the subalgebra is + A vector contained in the module corresponding to the subalgebra is interpreted as a coordinate vector:: - sage: S(vector(ZZ, [2,3,5])) + sage: S.module() + Free module of degree 4 and rank 3 over Integer Ring + User basis matrix: + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + sage: S(vector(ZZ, [2, 3, 5, 0])) 2*x + 3*y + 5*z + + A list of 2 elements is interpreted as a Lie bracket:: + + sage: S([S(x), S(y)]) + z + sage: S([S(x), S(y)]) == S(L[x, y]) + True """ + try: + P = x.parent() + if P is self: + return x + if P == self.ambient(): + return self.retract(x) + except AttributeError: + pass + if x in self.module(): return self.from_vector(x) - return super(LieSubalgebra_finite_dimensional_with_basis, self)._element_constructor_(x) + if isinstance(x, list) and len(x) == 2: + return self(x[0])._bracket_(self(x[1])) + + sup = super(LieSubalgebra_finite_dimensional_with_basis, self) + return sup._element_constructor_(x) - def _repr_(self): + @cached_method + def zero(self): r""" - Return a string representation of ``self``. + Return the element `0`. EXAMPLES:: - sage: L. = LieAlgebra(QQ, abelian=True) - sage: L.subalgebra([X, Y]) - Subalgebra generated by (X, Y) of Abelian Lie algebra on 2 generators (X, Y) over Rational Field + sage: L. = LieAlgebra(QQ, abelian=True) + sage: S = L.subalgebra(x) + sage: S.zero() + 0 + sage: S.zero() == S(L.zero()) + True """ - gens = self.gens() - if len(gens) == 1: - gens = gens[0] - return "Subalgebra generated by %s of %s" % (gens, self.ambient()) + return self.element_class(self, self.ambient().zero()) + + def ambient(self): + r""" + Return the ambient Lie algebra of ``self``. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: S = L.subalgebra(x) + sage: S.ambient() is L + True + """ + return self._ambient + + def lift(self, X): + r""" + Coerce an element ``X`` of ``self`` into the ambient Lie algebra. + + INPUT: + + - ``X`` -- an element of ``self`` + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: S = L.subalgebra(x) + sage: sx = S(x); sx + x + sage: sx.parent() + Subalgebra generated by x of Abelian Lie algebra on 2 generators (x, y) over Rational Field + sage: a = S.lift(sx); a + x + sage: a.parent() + Abelian Lie algebra on 2 generators (x, y) over Rational Field + """ + return X.value def retract(self, X): r""" @@ -537,15 +369,40 @@ def retract(self, X): if X not in self: raise ValueError("the element %s is not in %s" % (X, self)) - sup = super(LieSubalgebra_finite_dimensional_with_basis, self) - return sup.retract(X) + return self.element_class(self, X) + + def gens(self): + r""" + Return the generating set of ``self``. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) + sage: S = L.subalgebra(x) + sage: S.gens() + (x,) + """ + return self._gens + + def lie_algebra_generators(self): + r""" + Return the generating set of ``self`` as a Lie algebra. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) + sage: S = L.subalgebra(x) + sage: S.lie_algebra_generators() + (x,) + """ + return self._gens @cached_method def basis(self): r""" Return a basis of ``self``. - EXAMPLES: + EXAMPLES:: sage: sc = {('x','y'): {'z': 1}, ('x','z'): {'w': 1}} sage: L. = LieAlgebra(QQ, sc) @@ -554,7 +411,7 @@ def basis(self): """ L = self.ambient() m = L.module() - sm = m.submodule([X.to_vector() for X in self.gens()]) + sm = m.submodule([X.to_vector() for X in self.lie_algebra_generators()]) d = 0 while sm.dimension() > d: @@ -623,10 +480,10 @@ def basis_matrix(self): [0 1 0] [0 0 1] """ - return self.ambient_submodule().basis_matrix() + return self.module().basis_matrix() @cached_method - def ambient_submodule(self): + def module(self, sparse=False): r""" Return the submodule of the ambient Lie algebra corresponding to ``self``. @@ -635,14 +492,17 @@ def ambient_submodule(self): sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) sage: S = L.subalgebra([X, Y]) - sage: S.ambient_submodule() - Sparse free module of degree 3 and rank 3 over Integer Ring + sage: S.module() + Free module of degree 3 and rank 3 over Integer Ring User basis matrix: [1 0 0] [0 1 0] [0 0 3] """ - m = self.ambient().module() + try: + m = self.ambient().module(sparse=sparse) + except TypeError: + m = self.ambient().module() ambientbasis = [self.lift(X).to_vector() for X in self.basis()] return m.submodule_with_basis(ambientbasis) @@ -676,9 +536,12 @@ def is_ideal(self, A): for b in B for ab in AB]) except (ValueError, TypeError): return False - return b_mat.row_space().is_submodule(self.ambient_submodule()) + return b_mat.row_space().is_submodule(self.module()) - class Element(LieSubset.Element): + class Element(LieAlgebraElementWrapper): + r""" + Wrap an element of the ambient Lie algebra as an element. + """ def __getitem__(self, i): r""" @@ -694,13 +557,37 @@ def __getitem__(self, i): sage: el[2] 9 """ - return self.monomial_coefficients()[i] + try: + return self.monomial_coefficients()[i] + except IndexError: + return self.parent().base_ring().zero() + + def _bracket_(self, x): + """ + Return the Lie bracket ``[self, x]``. + + Assumes ``x`` and ``self`` have the same parent. + + INPUT: + + - ``x`` -- an element of the same Lie subalgebra as ``self`` + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('X','Y'): {'Z': 1}}) + sage: S = L.subalgebra([X, Y]) + sage: S(X)._bracket_(S(Y)) + Z + """ + P = self.parent() + self_lift = self.value + x_lift = x.value + return P.retract(self_lift._bracket_(x_lift)) def to_vector(self): r""" Return the vector in ``g.module()`` corresponding to the - element ``self`` of ``g`` (where ``g`` is the parent of - ``self``). + element ``self`` of ``g`` (where ``g`` is the parent of ``self``). EXAMPLES:: @@ -709,10 +596,14 @@ def to_vector(self): sage: S.basis() Family (X, Y, 3*Z) sage: S(2*Y + 9*Z).to_vector() - (0, 2, 3) + (0, 2, 9) + sage: S2 = L.subalgebra([Y, Z]) + sage: S2.basis() + Family (Y, Z) + sage: S2(2*Y + 9*Z).to_vector() + (0, 2, 9) """ - sm = self.parent().ambient_submodule() - return sm.coordinate_vector(self.parent().lift(self).to_vector()) + return self.value.to_vector() def monomial_coefficients(self, copy=True): r""" @@ -732,7 +623,11 @@ def monomial_coefficients(self, copy=True): sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) sage: S = L.subalgebra([X, Y]) sage: S(2*Y + 9*Z).monomial_coefficients() - {0: 0, 1: 2, 2: 3} + {1: 2, 2: 3} + sage: S2 = L.subalgebra([Y, Z]) + sage: S2(2*Y + 9*Z).monomial_coefficients() + {0: 2, 1: 9} """ - v = self.to_vector() - return dict(zip(range(len(v)), v)) + sm = self.parent().module() + v = sm.coordinate_vector(self.to_vector()) + return {k: v[k] for k in range(len(v)) if not v[k].is_zero()} From 64ec99048e3635490dd8baa8d45763a73fd86592 Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Sun, 9 Sep 2018 11:12:08 +0300 Subject: [PATCH 197/264] trac #26078: to_vector() now gives an element whose parent is the correct module --- src/sage/algebras/lie_algebras/subalgebra.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index 5485d32bb85..93cced4a5c9 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -602,8 +602,17 @@ def to_vector(self): Family (Y, Z) sage: S2(2*Y + 9*Z).to_vector() (0, 2, 9) + + TESTS:: + + sage: L. = LieAlgebra(ZZ, abelian=True) + sage: S = L.subalgebra(X) + sage: S(X).to_vector() in S.module() + True + sage: S(X).to_vector().parent() is S.module() + True """ - return self.value.to_vector() + return self.parent().module()(self.value.to_vector()) def monomial_coefficients(self, copy=True): r""" From 65f2da3ad652bb77dcd27e8dee42358c8be37971 Mon Sep 17 00:00:00 2001 From: David Coudert Date: Sun, 9 Sep 2018 11:20:56 +0200 Subject: [PATCH 198/264] trac #25598: remove storing of vertices in _Component --- src/sage/graphs/connectivity.pyx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index c5e1e0cb4f2..66c32f272c7 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2631,15 +2631,12 @@ class _Component: - `type_c` -- type of the component (0, 1, or 2). """ self.edge_list = _LinkedList() - self.vertices = set() for e in edge_list: self.add_edge(e) self.component_type = type_c def add_edge(self, e): self.edge_list.append(_LinkedListNode(e)) - self.vertices.add(e[0]) - self.vertices.add(e[1]) def finish_tric_or_poly(self, e): r""" @@ -2650,7 +2647,7 @@ class _Component: depending on the number of edges belonging to it. """ self.add_edge(e) - if self.edge_list.get_length() > len(self.vertices): + if self.edge_list.get_length() > 3: self.component_type = 2 else: self.component_type = 1 From 4981fd4411d664ad87ae310baa0cefed5fb3a167 Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Sun, 9 Sep 2018 17:10:18 +0300 Subject: [PATCH 199/264] trac #26078: moved ideal nilpotency construction out of category code --- src/sage/algebras/lie_algebras/ideal.py | 13 ++++ src/sage/algebras/lie_algebras/subalgebra.py | 13 ++++ ...ional_nilpotent_lie_algebras_with_basis.py | 60 ------------------- 3 files changed, 26 insertions(+), 60 deletions(-) diff --git a/src/sage/algebras/lie_algebras/ideal.py b/src/sage/algebras/lie_algebras/ideal.py index a94040dbcf2..b7d766147ae 100644 --- a/src/sage/algebras/lie_algebras/ideal.py +++ b/src/sage/algebras/lie_algebras/ideal.py @@ -101,6 +101,16 @@ class LieIdeal_finite_dimensional_with_basis(LieSubalgebra_finite_dimensional_wi sage: I = L.ideal(X + Y) sage: TestSuite(I).run() + + Verify that an ideal of a nilpotent Lie algebra is nilpotent:: + + sage: L = LieAlgebra(QQ, 3, step=4) + sage: x,y,z = L.homogeneous_component_basis(1) + sage: I = L.ideal(z) + sage: I in LieAlgebras(QQ).Nilpotent() + True + sage: I.step() + 3 """ @staticmethod @@ -126,6 +136,9 @@ def __classcall_private__(cls, ambient, gens, category=None): gens = (ambient.zero(),) cat = LieAlgebras(ambient.base_ring()).FiniteDimensional().WithBasis() + if ambient in LieAlgebras(ambient.base_ring()).Nilpotent(): + cat = cat.Nilpotent() + category = cat.Subobjects().or_subcategory(category) sup = super(LieIdeal_finite_dimensional_with_basis, cls) diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index 93cced4a5c9..4576ce5bcd3 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -80,6 +80,16 @@ class LieSubalgebra_finite_dimensional_with_basis(Parent, UniqueRepresentation): sage: S = L.subalgebra(X + Y) sage: TestSuite(S).run() + + Verify that a subalgebra of a nilpotent Lie algebra is nilpotent:: + + sage: L = LieAlgebra(QQ, 3, step=4) + sage: x,y,z = L.homogeneous_component_basis(1) + sage: S = L.subalgebra([x, y]) + sage: S in LieAlgebras(QQ).Nilpotent() + True + sage: S.step() + 4 """ @staticmethod @@ -121,6 +131,9 @@ def __classcall_private__(cls, ambient, gens, category=None): ambient = ambient.ambient() cat = LieAlgebras(ambient.base_ring()).FiniteDimensional().WithBasis() + if ambient in LieAlgebras(ambient.base_ring()).Nilpotent(): + cat = cat.Nilpotent() + category = cat.Subobjects().or_subcategory(category) sup = super(LieSubalgebra_finite_dimensional_with_basis, cls) diff --git a/src/sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py index 227ddaa32a0..702111e98b1 100644 --- a/src/sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py @@ -116,63 +116,3 @@ def is_nilpotent(self): True """ return True - - def subalgebra(self, gens): - r""" - Return the subalgebra of ``self`` generated by ``gens``. - - INPUT: - - - ``gens`` -- a list of generators of the subalgebra - - EXAMPLES: - - A subalgebra of a nilpotent Lie algebra is nilpotent:: - - sage: L = LieAlgebra(QQ, 3, step=4) - sage: x,y,z = L.homogeneous_component_basis(1) - sage: S = L.subalgebra([x, y]) - sage: L.dimension() - 32 - sage: S.dimension() - 8 - sage: S.category() - Category of subobjects of finite dimensional nilpotent lie algebras with basis over Rational Field - sage: S.step() - 4 - """ - from sage.algebras.lie_algebras.subalgebra import LieSubalgebra_finite_dimensional_with_basis - C = LieAlgebras(self.base_ring()).FiniteDimensional().WithBasis() - C = C.Nilpotent().Subobjects() - return LieSubalgebra_finite_dimensional_with_basis(self, gens, - category=C) - - def ideal(self, gens): - r""" - Return the ideal of ``self`` generated by ``gens``. - - INPUT: - - - ``gens`` -- a list of generators of the ideal - - EXAMPLES: - - An ideal of a nilpotent Lie algebra is nilpotent:: - - sage: L = LieAlgebra(QQ, 3, step=4) - sage: x,y,z = L.homogeneous_component_basis(1) - sage: I = L.ideal(z) - sage: L.dimension() - 32 - sage: I.dimension() - 24 - sage: I.category() - Category of subobjects of finite dimensional nilpotent lie algebras with basis over Rational Field - sage: I.step() - 3 - """ - from sage.algebras.lie_algebras.ideal import LieIdeal_finite_dimensional_with_basis - C = LieAlgebras(self.base_ring()).FiniteDimensional().WithBasis() - C = C.Nilpotent().Subobjects() - return LieIdeal_finite_dimensional_with_basis(self, gens, - category=C) From 542b2bf87a417e16eab33803adf6ce250c68858a Mon Sep 17 00:00:00 2001 From: David Coudert Date: Sun, 9 Sep 2018 19:22:08 +0200 Subject: [PATCH 200/264] trac #26228: fix issue in graph_coloring --- src/sage/graphs/graph_coloring.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/sage/graphs/graph_coloring.py b/src/sage/graphs/graph_coloring.py index da197cfdb71..1b745648b85 100644 --- a/src/sage/graphs/graph_coloring.py +++ b/src/sage/graphs/graph_coloring.py @@ -1320,6 +1320,14 @@ def linear_arboricity(g, plus_one=None, hex_colors=False, value_only=False, solv sage: from sage.graphs.graph_coloring import linear_arboricity sage: sorted([linear_arboricity(G, value_only=True) for G in graphs(4)]) [0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2] + + Test parameter ``hex_color`` (:trac:`26228`):: + + sage: from sage.graphs.graph_coloring import linear_arboricity + sage: g = graphs.GridGraph([4,4]) + sage: d = linear_arboricity(g, hex_colors=True) + sage: sorted(col for col,E in d.items()) + ['#00ffff', '#ff0000'] """ g._scream_if_not_simple() from sage.rings.integer import Integer @@ -1409,7 +1417,7 @@ def add(uv, i): add((u,v),i) if hex_colors: - return dict(zip(rainbow(len(classes)), classes)) + return dict(zip(rainbow(len(answer)), answer)) else: return answer @@ -1523,6 +1531,14 @@ def acyclic_edge_coloring(g, hex_colors=False, value_only=False, k=0, solver = N sage: from sage.graphs.graph_coloring import acyclic_edge_coloring sage: sorted([acyclic_edge_coloring(G, value_only=True) for G in graphs(4)]) [2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5] + + Test parameter ``hex_color`` (:trac:`26228`):: + + sage: from sage.graphs.graph_coloring import acyclic_edge_coloring + sage: g = graphs.CompleteGraph(4) + sage: d = acyclic_edge_coloring(g, hex_colors=True) + sage: sorted(col for col,E in d.items()) + ['#0066ff', '#00ff66', '#cbff00', '#cc00ff', '#ff0000'] """ g._scream_if_not_simple(allow_multiple_edges=True) @@ -1616,7 +1632,7 @@ def add(uv, i): add((u,v),i) if hex_colors: - return dict(zip(rainbow(len(classes)),classes)) + return dict(zip(rainbow(len(answer)),answer)) else: return answer From eb226812d88fcfcc0509ae4cb4d0eade494d6e1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 9 Sep 2018 21:03:34 +0200 Subject: [PATCH 201/264] construction functor for free Zinbiel algebras --- src/sage/algebras/free_zinbiel_algebra.py | 353 ++++++++++++++++++++++ 1 file changed, 353 insertions(+) diff --git a/src/sage/algebras/free_zinbiel_algebra.py b/src/sage/algebras/free_zinbiel_algebra.py index b663ffc861a..68942096652 100644 --- a/src/sage/algebras/free_zinbiel_algebra.py +++ b/src/sage/algebras/free_zinbiel_algebra.py @@ -15,14 +15,21 @@ # (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** +from six import iteritems from sage.misc.cachefunc import cached_method from sage.categories.magmatic_algebras import MagmaticAlgebras +from sage.categories.magmas import Magmas +from sage.categories.pushout import (ConstructionFunctor, + CompositeConstructionFunctor, + IdentityConstructionFunctor) from sage.categories.rings import Rings +from sage.categories.functor import Functor 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 +from sage.structure.coerce_exceptions import CoercionException class FreeZinbielAlgebra(CombinatorialFreeModule): @@ -170,6 +177,8 @@ def __classcall_private__(cls, R, n=None, names=None): names = names.split(',') elif n is None: n = len(names) + if R not in Rings(): + raise TypeError("argument R must be a ring") return super(FreeZinbielAlgebra, cls).__classcall__(cls, R, n, tuple(names)) def __init__(self, R, n, names): @@ -236,6 +245,24 @@ def algebra_generators(self): A = self.variable_names() return Family(A, lambda g: self.monomial(self._indices(g))) + def change_ring(self, R): + """ + Return the free Zinbiel algebra in the same variables over `R`. + + INPUT: + + - `R` -- a ring + + EXAMPLES:: + + sage: A = algebras.FreeZinbiel(ZZ, 'f,g,h') + sage: A.change_ring(QQ) + Free Zinbiel algebra on generators (Z[f], Z[g], Z[h]) + over Rational Field + """ + A = self.variable_names() + return FreeZinbielAlgebra(R, n=len(A), names=A) + @cached_method def gens(self): """ @@ -249,6 +276,21 @@ def gens(self): """ return tuple(self.algebra_generators()) + def degree_on_basis(self, t): + """ + Return the degree of a word in the free Zinbiel algebra. + + This is the length. + + EXAMPLES:: + + sage: A = algebras.FreeZinbiel(QQ, 'x,y') + sage: W = A.basis().keys() + sage: A.degree_on_basis(W('xy')) + 2 + """ + return len(t) + def product_on_basis(self, x, y): """ Return the product of the basis elements indexed by ``x`` and ``y``. @@ -273,3 +315,314 @@ def product_on_basis(self, x, y): return self.monomial(y) x0 = self._indices(x[0]) return self.sum_of_monomials(x0 + sh for sh in x[1:].shuffle(y)) + + def _element_constructor_(self, x): + r""" + Convert ``x`` into ``self``. + + EXAMPLES:: + + sage: R = algebras.FreeZinbiel(QQ, 'x,y') + sage: x, y = R.gens() + sage: R(x) + Z[x] + sage: R(x+4*y) + Z[x] + 4*Z[y] + + sage: W = R.basis().keys() + sage: R(W('x')) + Z[x] + sage: D = algebras.FreeZinbiel(ZZ, 'x,y') + sage: X, Y = D.gens() + sage: R(X-Y).parent() + Free Zinbiel algebra on generators (Z[x], Z[y]) over Rational Field + + TESTS:: + + sage: R. = algebras.FreeZinbiel(QQ) + sage: S. = algebras.FreeZinbiel(GF(3)) + sage: R(z) + Traceback (most recent call last): + ... + TypeError: not able to convert this to this algebra + """ + if x in self.basis().keys(): + return self.monomial(x) + try: + P = x.parent() + if isinstance(P, FreeZinbielAlgebra): + if P is self: + return x + if self._coerce_map_from_(P): + return self.element_class(self, x.monomial_coefficients()) + except AttributeError: + raise TypeError('not able to convert this to this algebra') + else: + raise TypeError('not able to convert this to this algebra') + # Ok, not a Zinbiel algebra element (or should not be viewed as one). + + def _coerce_map_from_(self, R): + r""" + Return ``True`` if there is a coercion from ``R`` into ``self`` + and ``False`` otherwise. + + The things that coerce into ``self`` are + + - free Zinbiel algebras whose set `E` of labels is + a subset of the corresponding self of ``set`, and whose base + ring has a coercion map into ``self.base_ring()`` + + EXAMPLES:: + + sage: F = algebras.FreeZinbiel(GF(7), 'x,y,z'); F + Free Zinbiel algebra on generators (Z[x], Z[y], Z[z]) + over Finite Field of size 7 + + Elements of the free Zinbiel algebra canonically coerce in:: + + sage: x, y, z = F.gens() + sage: F.coerce(x+y) == x+y + True + + The free Zinbiel algebra over `\ZZ` on `x, y, z` coerces in, since + `\ZZ` coerces to `\GF{7}`:: + + sage: G = algebras.FreeZinbiel(ZZ, 'x,y,z') + sage: Gx,Gy,Gz = G.gens() + sage: z = F.coerce(Gx+Gy); z + Z[x] + Z[y] + sage: z.parent() is F + True + + However, `\GF{7}` does not coerce to `\ZZ`, so the free Zinbiel + algebra over `\GF{7}` does not coerce to the one over `\ZZ`:: + + sage: G.coerce(y) + Traceback (most recent call last): + ... + TypeError: no canonical coercion from Free Zinbiel algebra on + generators (Z[x], Z[y], Z[z]) over Finite Field of size 7 to + Free Zinbiel algebra on generators (Z[x], Z[y], Z[z]) + over Integer Ring + + TESTS:: + + sage: F = algebras.FreeZinbiel(ZZ, 'x,y,z') + sage: G = algebras.FreeZinbiel(QQ, 'x,y,z') + sage: H = algebras.FreeZinbiel(ZZ, 'y') + sage: F._coerce_map_from_(G) + False + sage: G._coerce_map_from_(F) + True + sage: F._coerce_map_from_(H) + True + sage: F._coerce_map_from_(QQ) + False + sage: G._coerce_map_from_(QQ) + False + sage: F.has_coerce_map_from(PolynomialRing(ZZ, 3, 'x,y,z')) + False + """ + # free Zinbiel algebras in a subset of variables + # over any base that coerces in: + if isinstance(R, FreeZinbielAlgebra): + if all(x in self.variable_names() for x in R.variable_names()): + if self.base_ring().has_coerce_map_from(R.base_ring()): + return True + return False + + def construction(self): + """ + Return a pair ``(F, R)``, where ``F`` is a :class:`ZinbielFunctor` + and `R` is a ring, such that ``F(R)`` returns ``self``. + + EXAMPLES:: + + sage: P = algebras.FreeZinbiel(ZZ, 'x,y') + sage: x,y = P.gens() + sage: F, R = P.construction() + sage: F + Zinbiel[x,y] + sage: R + Integer Ring + sage: F(ZZ) is P + True + sage: F(QQ) + Free Zinbiel algebra on generators (Z[x], Z[y]) over Rational Field + """ + return ZinbielFunctor(self.variable_names()), self.base_ring() + + +class ZinbielFunctor(ConstructionFunctor): + """ + A constructor for free Zinbiel algebras. + + EXAMPLES:: + + sage: P = algebras.FreeZinbiel(ZZ, 'x,y') + sage: x,y = P.gens() + sage: F = P.construction()[0]; F + Zinbiel[x,y] + + sage: A = GF(5)['a,b'] + sage: a, b = A.gens() + sage: F(A) + Free Zinbiel algebra on generators (Z[x], Z[y]) + over Multivariate Polynomial Ring in a, b over Finite Field of size 5 + + sage: f = A.hom([a+b,a-b],A) + sage: F(f) + Generic endomorphism of Free Zinbiel algebra on generators (Z[x], Z[y]) + over Multivariate Polynomial Ring in a, b over Finite Field of size 5 + + + sage: F(f)(a * F(A)(x)) + (a+b)*Z[x] + """ + rank = 9 + + def __init__(self, vars): + """ + EXAMPLES:: + + sage: F = sage.algebras.free_zinbiel_algebra.ZinbielFunctor(['x','y']) + sage: F + Zinbiel[x,y] + sage: F(ZZ) + Free Zinbiel algebra on generators (Z[x], Z[y]) over Integer Ring + """ + Functor.__init__(self, Rings(), Magmas()) + self.vars = vars + + def _apply_functor(self, R): + """ + Apply the functor to an object of ``self``'s domain. + + EXAMPLES:: + + sage: R = algebras.FreeZinbiel(ZZ, 'x,y,z') + sage: F = R.construction()[0]; F + Zinbiel[x,y,z] + sage: type(F) + + sage: F(ZZ) # indirect doctest + Free Zinbiel algebra on generators (Z[x], Z[y], Z[z]) + over Integer Ring + """ + return FreeZinbielAlgebra(R, len(self.vars), self.vars) + + def _apply_functor_to_morphism(self, f): + """ + Apply the functor ``self`` to the ring morphism `f`. + + TESTS:: + + sage: R = algebras.FreeZinbiel(ZZ, 'x').construction()[0] + sage: R(ZZ.hom(GF(3))) # indirect doctest + Generic morphism: + From: Free Zinbiel algebra on generators (Z[x],) over Integer Ring + To: Free Zinbiel algebra on generators (Z[x],) over Finite Field of size 3 + """ + dom = self(f.domain()) + codom = self(f.codomain()) + + def action(x): + return codom._from_dict({a: f(b) + for a, b in iteritems(x.monomial_coefficients())}) + return dom.module_morphism(function=action, codomain=codom) + + def __eq__(self, other): + """ + EXAMPLES:: + + sage: F = algebras.FreeZinbiel(ZZ, 'x,y,z').construction()[0] + sage: G = algebras.FreeZinbiel(QQ, 'x,y,z').construction()[0] + sage: F == G + True + sage: G == loads(dumps(G)) + True + sage: G = algebras.FreeZinbiel(QQ, 'x,y').construction()[0] + sage: F == G + False + """ + if not isinstance(other, ZinbielFunctor): + return False + return self.vars == other.vars + + def __mul__(self, other): + """ + If two Zinbiel functors are given in a row, form a single + Zinbiel functor with all of the variables. + + EXAMPLES:: + + sage: F = sage.algebras.free_zinbiel_algebra.ZinbielFunctor(['x','y']) + sage: G = sage.algebras.free_zinbiel_algebra.ZinbielFunctor(['t']) + sage: G * F + Zinbiel[x,y,t] + """ + if isinstance(other, IdentityConstructionFunctor): + return self + if isinstance(other, ZinbielFunctor): + if set(self.vars).intersection(other.vars): + raise CoercionException("Overlapping variables (%s,%s)" % + (self.vars, other.vars)) + return ZinbielFunctor(other.vars + self.vars) + elif (isinstance(other, CompositeConstructionFunctor) and + isinstance(other.all[-1], ZinbielFunctor)): + return CompositeConstructionFunctor(other.all[:-1], + self * other.all[-1]) + else: + return CompositeConstructionFunctor(other, self) + + def merge(self, other): + """ + Merge ``self`` with another construction functor, or return None. + + EXAMPLES:: + + sage: F = sage.algebras.free_zinbiel_algebra.ZinbielFunctor(['x','y']) + sage: G = sage.algebras.free_zinbiel_algebra.ZinbielFunctor(['t']) + sage: F.merge(G) + Zinbiel[x,y,t] + sage: F.merge(F) + Zinbiel[x,y] + + Now some actual use cases:: + + sage: R = algebras.FreeZinbiel(ZZ, 'x,y,z') + sage: x,y,z = R.gens() + sage: 1/2 * x + 1/2*Z[x] + sage: parent(1/2 * x) + Free Zinbiel algebra on generators (Z[x], Z[y], Z[z]) + over Rational Field + + sage: S = algebras.FreeZinbiel(QQ, 'z,t') + sage: z,t = S.gens() + sage: x + t + Z[t] + Z[x] + sage: parent(x + t) + Free Zinbiel algebra on generators (Z[z], Z[t], Z[x], Z[y]) + over Rational Field + """ + if isinstance(other, ZinbielFunctor): + if self.vars == other.vars: + return self + ret = list(self.vars) + cur_vars = set(ret) + for v in other.vars: + if v not in cur_vars: + ret.append(v) + return ZinbielFunctor(ret) + else: + return None + + def _repr_(self): + """ + TESTS:: + + sage: algebras.FreeZinbiel(QQ,'x,y,z,t').construction()[0] + Zinbiel[x,y,z,t] + """ + return "Zinbiel[%s]" % ','.join(self.vars) From 8d3b5050403616744dc49b2b58f9b43e25dff3b6 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Sun, 9 Sep 2018 13:22:26 -0700 Subject: [PATCH 202/264] trac 26221: fix a bug and add a doctest for it. Move the main documentation to the class-level docstring. --- src/sage/monoids/free_monoid.py | 106 +++++++++++++++++++------------- 1 file changed, 62 insertions(+), 44 deletions(-) diff --git a/src/sage/monoids/free_monoid.py b/src/sage/monoids/free_monoid.py index 6200cd0e64c..d12104298b4 100644 --- a/src/sage/monoids/free_monoid.py +++ b/src/sage/monoids/free_monoid.py @@ -66,7 +66,48 @@ def is_FreeMonoid(x): class FreeMonoid(Monoid_class, UniqueRepresentation): """ - The free monoid on `n` generators. + Return a free monoid on `n` generators or with the generators + indexed by a set `I`. + + We construct free monoids by specifing either: + + - the number of generators and/or the names of the generators + - the indexing set for the generators + + INPUT: + + - ``index_set`` -- an indexing set for the generators; if an + integer `n`, than this becomes `\{0, 1, \ldots, n-1\}` + + - ``names`` -- names of generators + + - ``commutative`` -- (default: ``False``) whether the free + monoid is commutative or not + + OUTPUT: + + A free monoid. + + EXAMPLES:: + + sage: F = FreeMonoid(3,'x'); F + Free monoid on 3 generators (x0, x1, x2) + sage: x = F.gens() + sage: x[0]*x[1]**5 * (x[0]*x[2]) + x0*x1^5*x0*x2 + sage: F = FreeMonoid(3, 'a') + sage: F + Free monoid on 3 generators (a0, a1, a2) + + sage: F. = FreeMonoid(); F + Free monoid on 5 generators (a, b, c, d, e) + sage: FreeMonoid(index_set=ZZ) + Free monoid indexed by Integer Ring + + sage: F. = FreeMonoid(abelian=True); F + Free abelian monoid on 3 generators (x, y, z) + sage: FreeMonoid(index_set=ZZ, commutative=True) + Free abelian monoid indexed by Integer Ring """ @staticmethod def __classcall_private__(cls, index_set=None, names=None, @@ -85,8 +126,21 @@ def __classcall_private__(cls, index_set=None, names=None, Free abelian monoid on 3 generators (x, y, z) sage: FreeMonoid(index_set=ZZ, commutative=True) Free abelian monoid indexed by Integer Ring - sage: FreeMonoid(index_set=ZZ, names='x,y,z') - Free monoid indexed by Integer Ring + sage: F = FreeMonoid(index_set=ZZ, names='x,y,z') + sage: G = FreeMonoid(index_set=ZZ, names=['x', 'y', 'z']) + sage: F == G + True + sage: F is G + True + + sage: FreeMonoid(2, names='a,b') is FreeMonoid(names=['a','b']) + True + + Fix a bug when ``index_set`` is ``None`` and ``names`` is a + string (:trac:`26221`):: + + sage: FreeMonoid(2, names=['a','b']) is FreeMonoid(names='a,b') + True """ if 'abelian' in kwds: commutative = kwds.pop('abelian') @@ -100,7 +154,7 @@ def __classcall_private__(cls, index_set=None, names=None, if index_set is None and names is not None: if isinstance(names, str): - index_set = names.count(',') + index_set = names.count(',') + 1 else: index_set = len(names) @@ -117,60 +171,24 @@ def __classcall_private__(cls, index_set=None, names=None, Element = FreeMonoidElement - def __init__(self, index_set, names=None, **kwds): + def __init__(self, n, names=None): """ Return a free monoid on `n` generators or with the generators indexed by a set `I`. - We construct free monoids by specifing either: - - - the number of generators and/or the names of the generators - - the indexing set for the generators - INPUT: - - ``index_set`` -- an indexing set for the generators; if an - integer `n`, than this becomes `\{0, 1, \ldots, n-1\}` + - ``n`` -- number of generators - ``names`` -- names of generators - - ``commutative`` -- (default: ``False``) whether the free - monoid is commutative or not - - OUTPUT: - - A free monoid. - - EXAMPLES:: - - sage: F = FreeMonoid(3,'x'); F - Free monoid on 3 generators (x0, x1, x2) - sage: x = F.gens() - sage: x[0]*x[1]**5 * (x[0]*x[2]) - x0*x1^5*x0*x2 - sage: F = FreeMonoid(3, 'a') - sage: F - Free monoid on 3 generators (a0, a1, a2) - - sage: F. = FreeMonoid(); F - Free monoid on 5 generators (a, b, c, d, e) - sage: FreeMonoid(index_set=ZZ) - Free monoid indexed by Integer Ring - - sage: F. = FreeMonoid(abelian=True); F - Free abelian monoid on 3 generators (x, y, z) - sage: FreeMonoid(index_set=ZZ, commutative=True) - Free abelian monoid indexed by Integer Ring - TESTS:: sage: M = FreeMonoid(3, names=['a','b','c']) sage: TestSuite(M).run() + sage: F. = FreeMonoid() + sage: TestSuite(F).run() """ - # The variable name 'index_set' is meaningful for - # __classcall_private__. Once you reach this point, it should - # be an integer. - n = index_set if not isinstance(n, integer_types + (Integer,)): raise TypeError("n (=%s) must be an integer."%n) if n < 0: From 22d389d173bc428fa49b5652e772212da8258d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 10 Sep 2018 08:30:38 +0200 Subject: [PATCH 203/264] trac 26230 reviewer suggestions --- src/sage/algebras/free_zinbiel_algebra.py | 36 ++++++++++++++--------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/sage/algebras/free_zinbiel_algebra.py b/src/sage/algebras/free_zinbiel_algebra.py index 68942096652..f401951fbcf 100644 --- a/src/sage/algebras/free_zinbiel_algebra.py +++ b/src/sage/algebras/free_zinbiel_algebra.py @@ -200,7 +200,7 @@ def __init__(self, R, n, names): if R not in Rings: raise TypeError("argument R must be a ring") indices = Words(Alphabet(n, names=names)) - cat = MagmaticAlgebras(R).WithBasis() + cat = MagmaticAlgebras(R).WithBasis().Graded() self._n = n CombinatorialFreeModule.__init__(self, R, indices, prefix='Z', category=cat) @@ -247,11 +247,11 @@ def algebra_generators(self): def change_ring(self, R): """ - Return the free Zinbiel algebra in the same variables over `R`. + Return the free Zinbiel algebra in the same variables over ``R``. INPUT: - - `R` -- a ring + - ``R`` -- a ring EXAMPLES:: @@ -350,13 +350,10 @@ def _element_constructor_(self, x): return self.monomial(x) try: P = x.parent() - if isinstance(P, FreeZinbielAlgebra): - if P is self: - return x - if self._coerce_map_from_(P): - return self.element_class(self, x.monomial_coefficients()) except AttributeError: raise TypeError('not able to convert this to this algebra') + if isinstance(P, FreeZinbielAlgebra) and self._coerce_map_from_(P): + return self.element_class(self, x.monomial_coefficients(copy=False)) else: raise TypeError('not able to convert this to this algebra') # Ok, not a Zinbiel algebra element (or should not be viewed as one). @@ -425,11 +422,9 @@ def _coerce_map_from_(self, R): """ # free Zinbiel algebras in a subset of variables # over any base that coerces in: - if isinstance(R, FreeZinbielAlgebra): - if all(x in self.variable_names() for x in R.variable_names()): - if self.base_ring().has_coerce_map_from(R.base_ring()): - return True - return False + return (isinstance(R, FreeZinbielAlgebra) + and all(x in self.variable_names() for x in R.variable_names()) + and self.base_ring().has_coerce_map_from(R.base_ring())) def construction(self): """ @@ -528,7 +523,7 @@ def _apply_functor_to_morphism(self, f): def action(x): return codom._from_dict({a: f(b) - for a, b in iteritems(x.monomial_coefficients())}) + for a, b in iteritems(x.monomial_coefficients(copy=False))}) return dom.module_morphism(function=action, codomain=codom) def __eq__(self, other): @@ -549,6 +544,19 @@ def __eq__(self, other): return False return self.vars == other.vars + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: F = algebras.FreeZinbiel(ZZ, 'x,y,z').construction()[0] + sage: G = algebras.FreeZinbiel(QQ, 'x,y,z').construction()[0] + sage: hash(F) == hash(G) + True + """ + return hash(repr(self)) + def __mul__(self, other): """ If two Zinbiel functors are given in a row, form a single From 41f4733f6899bb0fc5600d18d8019da234e9f25c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 10 Sep 2018 15:10:49 +0200 Subject: [PATCH 204/264] fix suffix of temporary file --- src/sage/databases/findstat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/databases/findstat.py b/src/sage/databases/findstat.py index 542f508006d..a3f634cf468 100644 --- a/src/sage/databases/findstat.py +++ b/src/sage/databases/findstat.py @@ -312,7 +312,7 @@ def increasing_tree_shape(elt, compare=min): # the format string for using POST # WARNING: we use cgi.escape to avoid injection problems, thus we expect double quotes as field delimiters. FINDSTAT_POST_HEADER = """ - + @@ -1830,7 +1830,7 @@ def submit(self, max_values=FINDSTAT_MAX_SUBMISSION_VALUES): assert set(args.keys()) == FINDSTAT_EDIT_FIELDS, "It appears that the list of required post variables for editing a statistic has changed. Please update FindStatStatistic.submit()." # write the file - f = tempfile.NamedTemporaryFile(delete=False) + f = tempfile.NamedTemporaryFile(suffix='.html', delete=False) verbose("Created temporary file %s" % f.name, caller_name='FindStat') f.write(FINDSTAT_POST_HEADER) if self.id() == 0: From 0a3784d2902140df4454f1ceb7d9484db992f3fe Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Mon, 10 Sep 2018 19:05:45 +0300 Subject: [PATCH 205/264] trac #26078: combined ideals and subalgebras into one class --- .../en/reference/algebras/lie_algebras.rst | 1 - src/sage/algebras/lie_algebras/ideal.py | 340 ------------------ src/sage/algebras/lie_algebras/subalgebra.py | 332 ++++++++++++++--- ...ite_dimensional_lie_algebras_with_basis.py | 5 +- 4 files changed, 285 insertions(+), 393 deletions(-) delete mode 100644 src/sage/algebras/lie_algebras/ideal.py diff --git a/src/doc/en/reference/algebras/lie_algebras.rst b/src/doc/en/reference/algebras/lie_algebras.rst index 08690abbdf4..0dd2074c226 100644 --- a/src/doc/en/reference/algebras/lie_algebras.rst +++ b/src/doc/en/reference/algebras/lie_algebras.rst @@ -10,7 +10,6 @@ Lie Algebras sage/algebras/lie_algebras/examples sage/algebras/lie_algebras/free_lie_algebra sage/algebras/lie_algebras/heisenberg - sage/algebras/lie_algebras/ideal sage/algebras/lie_algebras/lie_algebra sage/algebras/lie_algebras/lie_algebra_element sage/algebras/lie_algebras/morphism diff --git a/src/sage/algebras/lie_algebras/ideal.py b/src/sage/algebras/lie_algebras/ideal.py deleted file mode 100644 index b7d766147ae..00000000000 --- a/src/sage/algebras/lie_algebras/ideal.py +++ /dev/null @@ -1,340 +0,0 @@ -r""" -Ideals of Lie algebras - -AUTHORS: - -- Eero Hakavuori (2018-08-29): initial version -""" - -# **************************************************************************** -# Copyright (C) 2018 Eero Hakavuori -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# https://www.gnu.org/licenses/ -# **************************************************************************** - -from sage.algebras.lie_algebras.subalgebra import LieSubalgebra_finite_dimensional_with_basis -from sage.categories.lie_algebras import LieAlgebras -from sage.misc.cachefunc import cached_method -from sage.modules.free_module_element import vector -from sage.sets.family import Family - - -class LieIdeal_finite_dimensional_with_basis(LieSubalgebra_finite_dimensional_with_basis): - r""" - An ideal of a finite dimensional Lie algebra with basis. - - INPUT: - - - ``ambient`` -- the Lie algebra containing the ideal - - ``gens`` -- a list of generators of the ideal - - ``category`` -- (optional) a subcategory of subobjects of finite - dimensional Lie algebras with basis - - EXAMPLES: - - An ideal is defined by giving a list of generators:: - - sage: L = lie_algebras.Heisenberg(QQ, 1) - sage: X, Y, Z = L.basis() - sage: I = L.ideal([X, Z]); I - Ideal (p1, z) of Heisenberg algebra of rank 1 over Rational Field - sage: I.basis() - Family (p1, z) - - Different generating sets can lead to the same basis:: - - sage: I2 = L.ideal(X); I2 - Ideal (p1) of Heisenberg algebra of rank 1 over Rational Field - sage: I2.basis() - Family (p1, z) - - Elements of the ambient Lie algebra can be reduced modulo the ideal:: - - sage: I.reduce(X + 2*Y + 3*Z) - 2*q1 - - The reduction gives elements in a fixed complementary subspace to the ideal. - The complementary subspace is spanned by those basis elements which are not - leading supports of the basis of the ideal:: - - sage: L. = LieAlgebra(SR, {('X','Y'): {'Z': 1}}) - sage: I = L.ideal(X + Y) - sage: I.basis() - Family (X + Y, Z) - sage: el = var('x')*X + var('y')*Y + var('z')*Z; el - x*X + y*Y + z*Z - sage: I.reduce(el) - (x-y)*X - - It is possible to compute with ideals of ideals:: - - sage: sc = {('X','Y'): {'Z': 1}, ('X','Z'): {'W': 1}} - sage: L. = LieAlgebra(QQ, sc) - sage: I = L.ideal(Z); I - Ideal (Z) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field - sage: z, w = I.basis() - sage: J = I.ideal(w); J - Ideal (W) of Ideal (Z) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field - sage: J.basis() - Family (W,) - sage: J.reduce(z + w) - Z - - The zero ideal can be created by giving 0 as a generator or with an empty - list of generators:: - - sage: L. = LieAlgebra(QQ, {('X','Y'): {'Z': 1}}) - sage: I1 = L.ideal(0) - sage: I2 = L.ideal([]) - sage: I1 is I2 - True - sage: I1.basis() - Family () - - TESTS: - - A test suite:: - - sage: I = L.ideal(X + Y) - sage: TestSuite(I).run() - - Verify that an ideal of a nilpotent Lie algebra is nilpotent:: - - sage: L = LieAlgebra(QQ, 3, step=4) - sage: x,y,z = L.homogeneous_component_basis(1) - sage: I = L.ideal(z) - sage: I in LieAlgebras(QQ).Nilpotent() - True - sage: I.step() - 3 - """ - - @staticmethod - def __classcall_private__(cls, ambient, gens, category=None): - """ - Normalize input to ensure a unique representation. - - EXAMPLES:: - - sage: L. = LieAlgebra(QQ, {('X','Y'): {'X': 1}}) - sage: I1 = L.ideal(X) - sage: I2 = L.ideal((X,)) - sage: I3 = L.ideal([X]) - sage: I1 is I2 and I2 is I3 - True - """ - if not isinstance(gens, (list, tuple)): - gens = [gens] - gens = tuple(ambient(gen) for gen in gens) - - gens = tuple(gens) - if len(gens) == 0: - gens = (ambient.zero(),) - - cat = LieAlgebras(ambient.base_ring()).FiniteDimensional().WithBasis() - if ambient in LieAlgebras(ambient.base_ring()).Nilpotent(): - cat = cat.Nilpotent() - - category = cat.Subobjects().or_subcategory(category) - - sup = super(LieIdeal_finite_dimensional_with_basis, cls) - return sup.__classcall__(cls, ambient, gens, category) - - def _repr_(self): - r""" - Return a string representation of ``self``. - - EXAMPLES:: - - sage: L. = LieAlgebra(QQ, abelian=True) - sage: L.ideal([X, Y]) - Ideal (X, Y) of Abelian Lie algebra on 2 generators (X, Y) over Rational Field - """ - return "Ideal %s of %s" % (self._repr_short(), self.ambient()) - - def _repr_short(self): - """ - Represent the list of generators. - - EXAMPLES:: - - sage: L. = LieAlgebra(QQ, abelian=True) - sage: L.ideal([X, Y])._repr_short() - '(X, Y)' - sage: L.ideal(X)._repr_short() - '(X)' - """ - return '(%s)' % (', '.join(str(X) for X in self.gens())) - - # for submodule computations, the order of the basis is reversed so that - # the pivot elements in the echelon form are the leading terms - def _to_m(self, X): - r""" - Return the reversed vector of an element of the ambient Lie algebra. - - INPUT: - - - ``X`` -- an element of the ambient Lie algebra - - EXAMPLES:: - - sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) - sage: I = L.ideal([x, z]) - sage: el = x + 2*y + 3*z - sage: el.to_vector() - (1, 2, 3) - sage: I._to_m(el) - (3, 2, 1) - """ - return vector(self.ambient().base_ring(), reversed(X.to_vector())) - - def _from_m(self, v): - r""" - Return the element of the ambient Lie algebra from a reversed vector. - - INPUT: - - - ``v`` -- a vector - - EXAMPLES:: - - sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) - sage: I = L.ideal([x, z]) - sage: I._from_m([3, 2, 1]) - x + 2*y + 3*z - """ - R = self.ambient().base_ring() - return self.ambient().from_vector(vector(R, reversed(v))) - - @cached_method - def basis(self): - r""" - Return a basis of ``self``. - - EXAMPLES: - - sage: sc = {('x','y'): {'z': 1}, ('x','z'): {'w': 1}} - sage: L. = LieAlgebra(QQ, sc) - sage: L.ideal([x + y + z + w]).basis() - Family (x + y, z, w) - """ - L = self.ambient() - m = L.module() - B = m.basis() - - # use ambient module in case L is an ideal or subalgebra - m = m.ambient_module() - - sm = m.submodule([self._to_m(X) for X in self.gens()]) - d = 0 - - while sm.dimension() > d: - d = sm.dimension() - SB = sm.basis() - sm = m.submodule(sm.basis() + - [self._to_m(L.bracket(v, self._from_m(w))) - for v in B for w in SB]) - - return Family(reversed([self.element_class(self, self._from_m(v)) - for v in sm.echelonized_basis()])) - - def lie_algebra_generators(self): - r""" - Return the generating set of ``self`` as a Lie algebra. - - EXAMPLES:: - - sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) - sage: S = L.ideal(x) - sage: S.lie_algebra_generators() - Family (x, z) - """ - return self.basis() - - def reduce(self, X): - r""" - Reduce an element of the ambient Lie algebra modulo ``self``. - - INPUT: - - - ``X`` -- an element of the ambient Lie algebra - - OUTPUT: - - An element `Y` of the ambient Lie algebra that is contained in a fixed - complementary submodule `V` to ``self`` such that `X = Y` mod ``self``. - - When the base ring of ``self`` is a field, the complementary submodule - `V` is spanned by the elements of the basis that are not the leading - supports of the basis of ``self``. - - EXAMPLES: - - An example reduction in a 6 dimensional Lie algebra:: - - sage: sc = {('a','b'): {'d': 1}, ('a','c'): {'e': 1}, - ....: ('b','c'): {'f': 1}} - sage: L. = LieAlgebra(QQ, sc) - sage: I = L.ideal(c) - sage: I.reduce(a + b + c + d + e + f) - a + b + d - - The reduction of an element is zero if and only if the element belongs - to the ideal:: - - sage: I.reduce(c + e) - 0 - sage: c + e in I - True - - Over non-fields, the complementary submodule may not be spanned by - a subset of the basis of the ambient Lie algebra:: - - sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) - sage: I = L.ideal(Y) - sage: I.basis() - Family (Y, 3*Z) - sage: I.reduce(3*Z) - 0 - sage: I.reduce(Y + 14*Z) - 2*Z - """ - R = self.base_ring() - for Y in self.basis(): - Y = self.lift(Y) - k, c = Y.leading_item() - - if R.is_field(): - X = X - X[k] / c * Y - else: - try: - q, r = X[k].quo_rem(c) - X = X - q * Y - except AttributeError: - pass - - return X - - @cached_method - def is_ideal(self, A): - """ - Return if ``self`` is an ideal of ``A``. - - EXAMPLES:: - - sage: L. = LieAlgebra(QQ, {('x','y'): {'x': 1}}) - sage: I = L.ideal(x) - sage: I.is_ideal(L) - True - sage: I.is_ideal(I) - True - sage: L.is_ideal(I) - False - """ - if A == self.ambient(): - return True - return super(LieIdeal_finite_dimensional_with_basis, self).is_ideal(A) diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index 4576ce5bcd3..00b1328cfb3 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -1,5 +1,5 @@ r""" -Subalgebras of Lie algebras +Subalgebras and ideals of Lie algebras AUTHORS: @@ -38,30 +38,30 @@ class LieSubalgebra_finite_dimensional_with_basis(Parent, UniqueRepresentation): - ``ambient`` -- the Lie algebra containing the subalgebra - ``gens`` -- a list of generators of the subalgebra + - ``ideal`` -- (default: ``False``) a boolean; if ``True``, then ``gens`` + is interpreted as the generating set of an ideal instead of a subalgebra - ``category`` -- (optional) a subcategory of subobjects of finite dimensional Lie algebras with basis EXAMPLES: - A subalgebra is defined by giving a list of generators:: + Subalgebras and ideals are defined by giving a list of generators:: sage: L = lie_algebras.Heisenberg(QQ, 1) sage: X, Y, Z = L.basis() - sage: I = L.subalgebra([X, Z]); I + sage: S = L.subalgebra([X, Z]); S Subalgebra generated by (p1, z) of Heisenberg algebra of rank 1 over Rational Field - sage: I.basis() - Family (p1, z) + sage: I = L.ideal([X, Z]); I + Ideal (p1, z) of Heisenberg algebra of rank 1 over Rational Field - A subalgebra of a subalgebra is a subalgebra of the original:: + An ideal is in general larger than the subalgebra with the same generators:: - sage: sc = {('X','Y'): {'Z': 1}, ('X','Z'): {'W': 1}} - sage: L. = LieAlgebra(QQ, sc) - sage: S1 = L.subalgebra([Y, Z, W]); S1 - Subalgebra generated by (Y, Z, W) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field - sage: S2 = S1.subalgebra(S1.basis()[1:]); S2 - Subalgebra generated by (Z, W) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field - sage: S3 = S2.subalgebra(S2.basis()[1:]); S3 - Subalgebra generated by W of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field + sage: S = L.subalgebra(Y) + sage: S.basis() + Family (q1,) + sage: I = L.ideal(Y) + sage: I.basis() + Family (q1, z) The zero dimensional subalgebra can be created by giving 0 as a generator or with an empty list of generators:: @@ -74,14 +74,64 @@ class LieSubalgebra_finite_dimensional_with_basis(Parent, UniqueRepresentation): sage: S1.basis() Family () + Elements of the ambient Lie algebra can be reduced modulo an + ideal or subalgebra:: + + sage: L. = LieAlgebra(SR, {('X','Y'): {'Z': 1}}) + sage: I = L.ideal(Y) + sage: I.reduce(X + 2*Y + 3*Z) + X + sage: S = L.subalgebra(Y) + sage: S.reduce(X + 2*Y + 3*Z) + X + 3*Z + + The reduction gives elements in a fixed complementary subspace. + When the base ring is a field, the complementary subspace is spanned by + those basis elements which are not leading supports of the basis:: + + sage: I = L.ideal(X + Y) + sage: I.basis() + Family (X + Y, Z) + sage: el = var('x')*X + var('y')*Y + var('z')*Z; el + x*X + y*Y + z*Z + sage: I.reduce(el) + (x-y)*X + + A subalgebra of a subalgebra is a subalgebra of the original:: + + sage: sc = {('X','Y'): {'Z': 1}, ('X','Z'): {'W': 1}} + sage: L. = LieAlgebra(QQ, sc) + sage: S1 = L.subalgebra([Y, Z, W]); S1 + Subalgebra generated by (Y, Z, W) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field + sage: S2 = S1.subalgebra(S1.basis()[1:]); S2 + Subalgebra generated by (Z, W) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field + sage: S3 = S2.subalgebra(S2.basis()[1:]); S3 + Subalgebra generated by (W) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field + + An ideal of an ideal is not necessarily an ideal of the original:: + + sage: I = L.ideal(Y); I + Ideal (Y) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field + sage: J = I.ideal(Z); J + Ideal (Z) of Ideal (Y) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field + sage: J.basis() + Family (Z,) + sage: J.is_ideal(L) + False + sage: K = L.ideal(J.basis().list()) + sage: K.basis() + Family (Z, W) + TESTS: - A test suite:: + Test suites:: sage: S = L.subalgebra(X + Y) sage: TestSuite(S).run() + sage: I = L.ideal(X + Y) + sage: TestSuite(I).run() - Verify that a subalgebra of a nilpotent Lie algebra is nilpotent:: + Verify that subalgebras and ideals of nilpotent Lie algebras are nilpotent:: sage: L = LieAlgebra(QQ, 3, step=4) sage: x,y,z = L.homogeneous_component_basis(1) @@ -90,10 +140,24 @@ class LieSubalgebra_finite_dimensional_with_basis(Parent, UniqueRepresentation): True sage: S.step() 4 + sage: I = L.ideal(z) + sage: I in LieAlgebras(QQ).Nilpotent() + True + sage: I.step() + 3 + + Test computation for a nested ideal:: + + sage: sc = {('X','Y'): {'Z': 1}, ('X','Z'): {'W': 1}} + sage: L. = LieAlgebra(QQ, sc) + sage: I = L.ideal(Y) + sage: J = I.ideal(Z) + sage: J.reduce(I(Z) + I(W)) + W """ @staticmethod - def __classcall_private__(cls, ambient, gens, category=None): + def __classcall_private__(cls, ambient, gens, ideal=False, category=None): """ Normalize input to ensure a unique representation. @@ -125,32 +189,35 @@ def __classcall_private__(cls, ambient, gens, category=None): gens = [gens] gens = tuple(ambient(gen) for gen in gens if not gen.is_zero()) - if isinstance(ambient, LieSubalgebra_finite_dimensional_with_basis): + if not ideal and isinstance(ambient, + LieSubalgebra_finite_dimensional_with_basis): # a nested subalgebra is a subalgebra gens = tuple(ambient.lift(gen) for gen in gens) ambient = ambient.ambient() cat = LieAlgebras(ambient.base_ring()).FiniteDimensional().WithBasis() - if ambient in LieAlgebras(ambient.base_ring()).Nilpotent(): - cat = cat.Nilpotent() - category = cat.Subobjects().or_subcategory(category) + if ambient in LieAlgebras(ambient.base_ring()).Nilpotent(): + category = category.Nilpotent() sup = super(LieSubalgebra_finite_dimensional_with_basis, cls) - return sup.__classcall__(cls, ambient, gens, category) + return sup.__classcall__(cls, ambient, gens, ideal, category) - def __init__(self, ambient, gens, category=None): + def __init__(self, ambient, gens, ideal, category=None): r""" Initialize ``self``. TESTS:: - sage: L. = LieAlgebra(QQ, abelian=True) + sage: L. = LieAlgebra(QQ, {('X','Y'): {'Z': 1}}) sage: S = L.subalgebra(X) sage: TestSuite(S).run() + sage: I = L.ideal(X) + sage: TestSuite(I).run() """ self._ambient = ambient self._gens = gens + self._is_ideal = ideal sup = super(LieSubalgebra_finite_dimensional_with_basis, self) sup.__init__(ambient.base_ring(), category=category) @@ -223,11 +290,35 @@ def _repr_(self): sage: L. = LieAlgebra(QQ, abelian=True) sage: L.subalgebra([X, Y]) Subalgebra generated by (X, Y) of Abelian Lie algebra on 2 generators (X, Y) over Rational Field + sage: L.ideal([X, Y]) + Ideal (X, Y) of Abelian Lie algebra on 2 generators (X, Y) over Rational Field """ - gens = self.lie_algebra_generators() + gens = self.gens() if len(gens) == 1: gens = gens[0] - return "Subalgebra generated by %s of %s" % (gens, self.ambient()) + + if self._is_ideal: + basestr = "Ideal" + else: + basestr = "Subalgebra generated by" + + return "%s %s of %s" % (basestr, self._repr_short(), self.ambient()) + + def _repr_short(self): + """ + Represent the list of generators. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: L.ideal([X, Y])._repr_short() + '(X, Y)' + sage: L.ideal(X)._repr_short() + '(X)' + sage: L.subalgebra(X)._repr_short() + '(X)' + """ + return '(%s)' % (', '.join(str(X) for X in self.gens())) def _an_element_(self): r""" @@ -295,6 +386,46 @@ def _element_constructor_(self, x): sup = super(LieSubalgebra_finite_dimensional_with_basis, self) return sup._element_constructor_(x) + # for submodule computations, the order of the basis is reversed so that + # the pivot elements in the echelon form are the leading terms + def _to_m(self, X): + r""" + Return the reversed vector of an element of the ambient Lie algebra. + + INPUT: + + - ``X`` -- an element of the ambient Lie algebra + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) + sage: I = L.ideal([x, z]) + sage: el = x + 2*y + 3*z + sage: el.to_vector() + (1, 2, 3) + sage: I._to_m(el) + (3, 2, 1) + """ + return vector(self.ambient().base_ring(), reversed(X.to_vector())) + + def _from_m(self, v): + r""" + Return the element of the ambient Lie algebra from a reversed vector. + + INPUT: + + - ``v`` -- a vector + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) + sage: I = L.ideal([x, z]) + sage: I._from_m([3, 2, 1]) + x + 2*y + 3*z + """ + R = self.ambient().base_ring() + return self.ambient().from_vector(vector(R, reversed(v))) + @cached_method def zero(self): r""" @@ -339,7 +470,7 @@ def lift(self, X): sage: sx = S(x); sx x sage: sx.parent() - Subalgebra generated by x of Abelian Lie algebra on 2 generators (x, y) over Rational Field + Subalgebra generated by (x) of Abelian Lie algebra on 2 generators (x, y) over Rational Field sage: a = S.lift(sx); a x sage: a.parent() @@ -401,13 +532,23 @@ def lie_algebra_generators(self): r""" Return the generating set of ``self`` as a Lie algebra. - EXAMPLES:: + EXAMPLES: + + The Lie algebra generators of a subalgebra are the original generators:: sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) sage: S = L.subalgebra(x) sage: S.lie_algebra_generators() (x,) + + The Lie algebra generators of an ideal is usually a larger set:: + + sage: I = L.ideal(x) + sage: I.lie_algebra_generators() + Family (x, z) """ + if self._is_ideal: + return self.basis() return self._gens @cached_method @@ -415,27 +556,44 @@ def basis(self): r""" Return a basis of ``self``. - EXAMPLES:: + EXAMPLES: + + A basis of a subalgebra:: sage: sc = {('x','y'): {'z': 1}, ('x','z'): {'w': 1}} sage: L. = LieAlgebra(QQ, sc) sage: L.subalgebra([x + y, z + w]).basis() Family (x + y, z, w) + + A basis of an ideal:: + + sage: sc = {('x','y'): {'z': 1}, ('x','z'): {'w': 1}} + sage: L. = LieAlgebra(QQ, sc) + sage: L.ideal([x + y + z + w]).basis() + Family (x + y, z, w) """ + L = self.ambient() - m = L.module() - sm = m.submodule([X.to_vector() for X in self.lie_algebra_generators()]) + B = [self._to_m(X) for X in L.basis()] + + # use ambient module in case L is an ideal or subalgebra + m = L.module().ambient_module() + + sm = m.submodule([self._to_m(X) for X in self.gens()]) d = 0 while sm.dimension() > d: d = sm.dimension() SB = sm.basis() - sm = m.submodule(sm.basis() + - [L.bracket(v, w).to_vector() - for v in SB for w in SB]) + if not self._is_ideal: + B = SB + + brackets = [self._to_m(L.bracket(self._from_m(v), self._from_m(w))) + for v in B for w in SB] + sm = m.submodule(sm.basis() + brackets) - return Family(self.element_class(self, L.from_vector(v)) - for v in sm.echelonized_basis()) + return Family(reversed([self.element_class(self, self._from_m(v)) + for v in sm.echelonized_basis()])) def from_vector(self, v): r""" @@ -524,7 +682,9 @@ def is_ideal(self, A): """ Return if ``self`` is an ideal of ``A``. - EXAMPLES:: + EXAMPLES: + + Some subalgebras are ideals:: sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) sage: S1 = L.subalgebra([x]) @@ -536,20 +696,92 @@ def is_ideal(self, A): sage: S3 = L.subalgebra([y, z]) sage: S3.is_ideal(L) True + + All ideals are ideals:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'x': 1}}) + sage: I = L.ideal(x) + sage: I.is_ideal(L) + True + sage: I.is_ideal(I) + True + + TESTS:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'x': 1}}) + sage: I = L.ideal(x) + sage: L.is_ideal(I) + False """ - if A == self: + if A == self.ambient() and self._is_ideal: return True - if A not in LieAlgebras(self.base_ring()).FiniteDimensional().WithBasis(): - raise NotImplementedError("A must be a finite dimensional" - " Lie algebra with basis") - B = self.basis() - AB = A.basis() - try: - b_mat = matrix(A.base_ring(), [A.bracket(b, ab).to_vector() - for b in B for ab in AB]) - except (ValueError, TypeError): - return False - return b_mat.row_space().is_submodule(self.module()) + + sup = super(LieSubalgebra_finite_dimensional_with_basis, self) + return sup.is_ideal(A) + + def reduce(self, X): + r""" + Reduce an element of the ambient Lie algebra modulo ``self``. + + INPUT: + + - ``X`` -- an element of the ambient Lie algebra + + OUTPUT: + + An element `Y` of the ambient Lie algebra that is contained in a fixed + complementary submodule `V` to ``self`` such that `X = Y` mod ``self``. + + When the base ring of ``self`` is a field, the complementary submodule + `V` is spanned by the elements of the basis that are not the leading + supports of the basis of ``self``. + + EXAMPLES: + + An example reduction in a 6 dimensional Lie algebra:: + + sage: sc = {('a','b'): {'d': 1}, ('a','c'): {'e': 1}, + ....: ('b','c'): {'f': 1}} + sage: L. = LieAlgebra(QQ, sc) + sage: I = L.ideal(c) + sage: I.reduce(a + b + c + d + e + f) + a + b + d + + The reduction of an element is zero if and only if the + element belongs to the subalgebra:: + + sage: I.reduce(c + e) + 0 + sage: c + e in I + True + + Over non-fields, the complementary submodule may not be spanned by + a subset of the basis of the ambient Lie algebra:: + + sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) + sage: I = L.ideal(Y) + sage: I.basis() + Family (Y, 3*Z) + sage: I.reduce(3*Z) + 0 + sage: I.reduce(Y + 14*Z) + 2*Z + """ + R = self.base_ring() + for Y in self.basis(): + Y = self.lift(Y) + k, c = Y.leading_item() + + if R.is_field(): + X = X - X[k] / c * Y + else: + try: + q, r = X[k].quo_rem(c) + X = X - q * Y + except AttributeError: + pass + + return X class Element(LieAlgebraElementWrapper): r""" @@ -572,7 +804,7 @@ def __getitem__(self, i): """ try: return self.monomial_coefficients()[i] - except IndexError: + except KeyError: return self.parent().base_ring().zero() def _bracket_(self, x): diff --git a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py index 6750d8c9176..86dd5f2b182 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -609,8 +609,9 @@ def ideal(self, gens): sage: I.reduce(p1 + p2 + q1 + q2 + z) 2*p1 + 2*q1 """ - from sage.algebras.lie_algebras.ideal import LieIdeal_finite_dimensional_with_basis - return LieIdeal_finite_dimensional_with_basis(self, gens) + from sage.algebras.lie_algebras.subalgebra import LieSubalgebra_finite_dimensional_with_basis + return LieSubalgebra_finite_dimensional_with_basis(self, gens, + ideal=True) @cached_method def is_ideal(self, A): From 6d71aefed41ae73745110b28e463977e5abe1bb2 Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Mon, 10 Sep 2018 19:38:00 +0300 Subject: [PATCH 206/264] trac #26078: added category parameter to ideal and subalgebra interface --- src/sage/algebras/lie_algebras/subalgebra.py | 10 ++++- ...ite_dimensional_lie_algebras_with_basis.py | 40 ++++++++++++++++--- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index 00b1328cfb3..5a4441404ca 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -218,6 +218,10 @@ def __init__(self, ambient, gens, ideal, category=None): self._ambient = ambient self._gens = gens self._is_ideal = ideal + + # set initial index set to match the length of gens + self._indices = range(len(gens)) + sup = super(LieSubalgebra_finite_dimensional_with_basis, self) sup.__init__(ambient.base_ring(), category=category) @@ -592,8 +596,10 @@ def basis(self): for v in B for w in SB] sm = m.submodule(sm.basis() + brackets) - return Family(reversed([self.element_class(self, self._from_m(v)) - for v in sm.echelonized_basis()])) + basis = Family(reversed([self.element_class(self, self._from_m(v)) + for v in sm.echelonized_basis()])) + self._indices = range(len(basis)) + return basis def from_vector(self, v): r""" diff --git a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py index 86dd5f2b182..31a3a47fc9d 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -568,13 +568,15 @@ def inner_derivations_basis(self): return tuple([matrix(R, N, N, list(b)) for b in IDer.row_module().basis()]) - def subalgebra(self, gens): + def subalgebra(self, gens, category=None): r""" Return the subalgebra of ``self`` generated by ``gens``. INPUT: - ``gens`` -- a list of generators of the subalgebra + - ``category`` -- (optional) a subcategory of subobjects of finite + dimensional Lie algebras with basis EXAMPLES:: @@ -587,17 +589,34 @@ def subalgebra(self, gens): [1 0 0 0 0] [0 0 1 0 0] [0 0 0 0 1] + + TESTS: + + Test passing an extra category to a subalgebra:: + + sage: L = LieAlgebra(QQ, 3, step=2) + sage: x,y,z = L.homogeneous_component_basis(1) + sage: C = LieAlgebras(QQ).FiniteDimensional().WithBasis() + sage: C = C.Subobjects().Graded().Stratified() + sage: S = L.subalgebra([x, y], category=C) + sage: S.basis().list() + [X_1, X_2, X_12] + sage: S.homogeneous_component_basis(2).list() + [X_12] """ from sage.algebras.lie_algebras.subalgebra import LieSubalgebra_finite_dimensional_with_basis - return LieSubalgebra_finite_dimensional_with_basis(self, gens) + return LieSubalgebra_finite_dimensional_with_basis( + self, gens, category=category) - def ideal(self, gens): + def ideal(self, gens, category=None): r""" Return the ideal of ``self`` generated by ``gens``. INPUT: - ``gens`` -- a list of generators of the ideal + - ``category`` -- (optional) a subcategory of subobjects of finite + dimensional Lie algebras with basis EXAMPLES:: @@ -608,10 +627,21 @@ def ideal(self, gens): [-p1 + p2, -q1 + q2, z] sage: I.reduce(p1 + p2 + q1 + q2 + z) 2*p1 + 2*q1 + + TESTS: + + Test passing an extra category to an ideal:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: C = LieAlgebras(QQ).FiniteDimensional().WithBasis() + sage: C = C.Subobjects().Graded().Stratified() + sage: I = L.ideal([x, y], category=C) + sage: I.homogeneous_component_basis(1).list() + [x, y] """ from sage.algebras.lie_algebras.subalgebra import LieSubalgebra_finite_dimensional_with_basis - return LieSubalgebra_finite_dimensional_with_basis(self, gens, - ideal=True) + return LieSubalgebra_finite_dimensional_with_basis( + self, gens, ideal=True, category=category) @cached_method def is_ideal(self, A): From 3d1e21fcf0de255b63fb1a7141aedfe0c01c97ab Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Mon, 10 Sep 2018 19:49:09 +0300 Subject: [PATCH 207/264] trac #26078: removed initializing of incomplete _indices attribute for subalgebras --- src/sage/algebras/lie_algebras/subalgebra.py | 3 --- .../categories/finite_dimensional_lie_algebras_with_basis.py | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index 5a4441404ca..b7227e6659b 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -219,9 +219,6 @@ def __init__(self, ambient, gens, ideal, category=None): self._gens = gens self._is_ideal = ideal - # set initial index set to match the length of gens - self._indices = range(len(gens)) - sup = super(LieSubalgebra_finite_dimensional_with_basis, self) sup.__init__(ambient.base_ring(), category=category) diff --git a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py index 31a3a47fc9d..e26a650d6ee 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -599,7 +599,7 @@ def subalgebra(self, gens, category=None): sage: C = LieAlgebras(QQ).FiniteDimensional().WithBasis() sage: C = C.Subobjects().Graded().Stratified() sage: S = L.subalgebra([x, y], category=C) - sage: S.basis().list() + sage: S.basis().list() # required to initialize _indices [X_1, X_2, X_12] sage: S.homogeneous_component_basis(2).list() [X_12] @@ -636,6 +636,8 @@ def ideal(self, gens, category=None): sage: C = LieAlgebras(QQ).FiniteDimensional().WithBasis() sage: C = C.Subobjects().Graded().Stratified() sage: I = L.ideal([x, y], category=C) + sage: I.basis().list() # required to initialize _indices + [x, y] sage: I.homogeneous_component_basis(1).list() [x, y] """ From fe91f3bc709974dee7365d761e08cf1267947474 Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Mon, 10 Sep 2018 22:08:30 +0300 Subject: [PATCH 208/264] trac #26078: removed unused imports and fixed a missing variable --- src/sage/algebras/lie_algebras/subalgebra.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index b7227e6659b..25c1364023d 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -20,12 +20,9 @@ from sage.categories.lie_algebras import LieAlgebras from sage.categories.homset import Hom from sage.categories.morphism import SetMorphism -from sage.categories.sets_cat import Sets -from sage.matrix.constructor import matrix from sage.misc.cachefunc import cached_method from sage.modules.free_module_element import vector from sage.sets.family import Family -from sage.structure.element import coercion_model, have_same_parent from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation @@ -279,7 +276,7 @@ def __getitem__(self, x): """ if isinstance(x, tuple) and len(x) == 2: return self(x[0])._bracket_(self(x[1])) - super(LieSubalgebra_finite_dimensional_with_basis, self) + sup = super(LieSubalgebra_finite_dimensional_with_basis, self) return sup.__getitem__(x) def _repr_(self): From 5860e688511b6a4e690000e336cd73177822f0e2 Mon Sep 17 00:00:00 2001 From: rusydi Date: Mon, 10 Sep 2018 21:46:15 +0200 Subject: [PATCH 209/264] initial commit --- src/sage/interfaces/magma.py | 65 ++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/sage/interfaces/magma.py b/src/sage/interfaces/magma.py index 178dfb589cf..f0761c5b500 100644 --- a/src/sage/interfaces/magma.py +++ b/src/sage/interfaces/magma.py @@ -1651,6 +1651,71 @@ def GetVerbose(self, type): """ return int(self.eval('GetVerbose("%s")' % type)) + def set_nthreads(self, n): + """ + Set the number of threads used for parallelized algorithms in Magma. + + INPUT: + + - ``n`` - number of threads + + EXAMPLES:: + + sage: magma.set_nthreads(2) #optional - magma + sage: magma.get_nthreads() #optional - magma + 2 + """ + self.SetNthreads(n) + + def SetNthreads(self, n): + """ + Set the number of threads used for parallelized algorithms in Magma. + + INPUT: + + - ``n`` - number of threads + + .. note:: + + This method is provided to be consistent with the Magma + naming convention. + + EXAMPLES:: + + sage: magma.SetNthreads(2) #optional - magma + sage: magma.GetNthreads() #optional - magma + 2 + """ + self.eval('SetNthreads(%d)' % (n)) + + def get_nthreads(self): + """ + Get the number of threads used in Magma. + + EXAMPLES:: + + sage: magma.set_nthreads(2) #optional - magma + sage: magma.get_nthreads() #optional - magma + 2 + """ + return self.GetNthreads() + + def GetNthreads(self): + """ + Get the number of threads used in Magma. + + .. note:: + + This method is provided to be consistent with the Magma + naming convention. + + EXAMPLES:: + + sage: magma.SetNthreads(2) #optional - magma + sage: magma.GetNthreads() #optional - magma + 2 + """ + return int(self.eval('GetNthreads()')) @instancedoc class MagmaFunctionElement(FunctionElement): From b9537e9dae2677e70747981b827236024c013eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 10 Sep 2018 22:06:28 +0200 Subject: [PATCH 210/264] py3: some easy care for matrices --- src/sage/matrix/matrix_cyclo_dense.pyx | 45 ++++++++++++++--------- src/sage/matrix/matrix_double_dense.pyx | 4 +- src/sage/matrix/matrix_generic_dense.pyx | 4 +- src/sage/matrix/matrix_space.py | 8 +++- src/sage/matrix/matrix_symbolic_dense.pyx | 6 +-- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/sage/matrix/matrix_cyclo_dense.pyx b/src/sage/matrix/matrix_cyclo_dense.pyx index 9274d82df31..b60b14b26b3 100644 --- a/src/sage/matrix/matrix_cyclo_dense.pyx +++ b/src/sage/matrix/matrix_cyclo_dense.pyx @@ -1602,15 +1602,17 @@ cdef class Matrix_cyclo_dense(Matrix_dense): def _echelon_form_multimodular(self, num_primes=10, height_guess=None): """ - Use a multimodular algorithm to find the echelon form of self. + Use a multimodular algorithm to find the echelon form of ``self``. INPUT: - num_primes -- number of primes to work modulo - height_guess -- guess for the height of the echelon form - of self + + - num_primes -- number of primes to work modulo + + - height_guess -- guess for the height of the echelon form of self OUTPUT: - matrix in reduced row echelon form + + - matrix in reduced row echelon form EXAMPLES:: @@ -1640,8 +1642,10 @@ cdef class Matrix_cyclo_dense(Matrix_dense): """ cdef int i cdef Matrix_cyclo_dense res - - verbose("entering _echelon_form_multimodular", level=echelon_verbose_level) + cdef bint is_square + + verbose("entering _echelon_form_multimodular", + level=echelon_verbose_level) denom = self._matrix.denominator() A = denom * self @@ -1649,7 +1653,7 @@ cdef class Matrix_cyclo_dense(Matrix_dense): # This bound is chosen somewhat arbitrarily. Changing it affects the # runtime, not the correctness of the result. if height_guess is None: - height_guess = (A.coefficient_bound()+100)*1000000 + height_guess = (A.coefficient_bound() + 100) * 1000000 # This is all setup to keep track of various data # in the loop below. @@ -1659,16 +1663,17 @@ cdef class Matrix_cyclo_dense(Matrix_dense): n = self._base_ring._n() height_bound = self._ncols * height_guess * A.coefficient_bound() + 1 mod_p_ech_ls = [] - max_pivots = [] + max_pivots = tuple() is_square = self._nrows == self._ncols - verbose("using height bound %s"%height_bound, level=echelon_verbose_level) + verbose("using height bound %s" % height_bound, + level=echelon_verbose_level) while True: # Generate primes to use, and find echelon form # modulo those primes. while found < num_primes or prod <= height_bound: - if (n == 1) or p%n == 1: + if (n == 1) or p % n == 1: try: mod_p_ech, piv_ls = A._echelon_form_one_prime(p) except ValueError: @@ -1705,13 +1710,14 @@ cdef class Matrix_cyclo_dense(Matrix_dense): if found > num_primes: num_primes = found - verbose("computed echelon form mod %s primes"%num_primes, + verbose("computed echelon form mod %s primes" % num_primes, level=echelon_verbose_level) - verbose("current product of primes used: %s"%prod, + verbose("current product of primes used: %s" % prod, level=echelon_verbose_level) # Use CRT to lift back to ZZ - mat_over_ZZ = matrix(ZZ, self._base_ring.degree(), self._nrows * self._ncols) + mat_over_ZZ = matrix(ZZ, self._base_ring.degree(), + self._nrows * self._ncols) _lift_crt(mat_over_ZZ, mod_p_ech_ls) # note: saving the CRT intermediate MultiModularBasis does # not seem to affect the runtime at all @@ -1719,8 +1725,10 @@ cdef class Matrix_cyclo_dense(Matrix_dense): # Attempt to use rational reconstruction to find # our echelon form try: - verbose("attempting rational reconstruction ...", level=echelon_verbose_level) - res = Matrix_cyclo_dense.__new__(Matrix_cyclo_dense, self.parent(), + verbose("attempting rational reconstruction ...", + level=echelon_verbose_level) + res = Matrix_cyclo_dense.__new__(Matrix_cyclo_dense, + self.parent(), None, None, None) res._matrix = matrix_integer_dense_rational_reconstruction(mat_over_ZZ, prod) @@ -1742,11 +1750,12 @@ cdef class Matrix_cyclo_dense(Matrix_dense): # prod) to guarantee its correctness, so loop. num_primes += echelon_primes_increment - verbose("height not sufficient to determine echelon form", level=echelon_verbose_level) + verbose("height not sufficient to determine echelon form", + level=echelon_verbose_level) continue verbose("found echelon form with %s primes, whose product is %s"%(num_primes, prod), level=echelon_verbose_level) - self.cache('pivots', tuple(max_pivots)) + self.cache('pivots', max_pivots) return res def _echelon_form_one_prime(self, p): diff --git a/src/sage/matrix/matrix_double_dense.pyx b/src/sage/matrix/matrix_double_dense.pyx index 66e54a79a4f..c4d7368acbf 100644 --- a/src/sage/matrix/matrix_double_dense.pyx +++ b/src/sage/matrix/matrix_double_dense.pyx @@ -997,12 +997,12 @@ cdef class Matrix_double_dense(Matrix_dense): TESTS: - Bogus values of the ``eps`` keyword will be caught. :: + Bogus values of the ``eps`` keyword will be caught:: sage: A.singular_values(eps='junk') Traceback (most recent call last): ... - ValueError: could not convert string to float: junk + ValueError: could not convert string to float: ... AUTHOR: diff --git a/src/sage/matrix/matrix_generic_dense.pyx b/src/sage/matrix/matrix_generic_dense.pyx index ea725a206ee..a8cc42ca55e 100644 --- a/src/sage/matrix/matrix_generic_dense.pyx +++ b/src/sage/matrix/matrix_generic_dense.pyx @@ -51,9 +51,7 @@ cdef class Matrix_generic_dense(matrix_dense.Matrix_dense): ... TypeError: mutable matrices are unhashable sage: A.set_immutable() - sage: hash(A) - -3948850745060287342 # 64-bit - 1436884114 # 32-bit + sage: H = hash(A) """ def __init__(self, parent, entries=None, copy=None, bint coerce=True): r""" diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index bc68be86182..b0687169cdb 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -1207,10 +1207,16 @@ def __len__(self): 729 sage: 3^(2*3) 729 - sage: len(MatrixSpace(GF(2003),3,2)) + + sage: len(MatrixSpace(GF(2003),3,2)) # py2 Traceback (most recent call last): ... OverflowError: long int too large to convert to int + sage: len(MatrixSpace(GF(2003),3,2)) # py3 + Traceback (most recent call last): + ... + OverflowError: cannot fit 'int' into an index-sized integer + sage: len(MatrixSpace(QQ,3,2)) Traceback (most recent call last): ... diff --git a/src/sage/matrix/matrix_symbolic_dense.pyx b/src/sage/matrix/matrix_symbolic_dense.pyx index 42d7449130c..6d15b0af925 100644 --- a/src/sage/matrix/matrix_symbolic_dense.pyx +++ b/src/sage/matrix/matrix_symbolic_dense.pyx @@ -488,18 +488,18 @@ cdef class Matrix_symbolic_dense(Matrix_generic_dense): if mp is None: mp = self._maxima_lib_().jordan().minimalPoly().expand() d = mp.hipow('x') - mp = [mp.coeff('x', i) for i in xrange(0, d + 1)] + mp = [mp.coeff('x', i) for i in xrange(int(d) + 1)] mp = PolynomialRing(self.base_ring(), 'x')(mp) self.cache('minpoly', mp) return mp.change_variable_name(var) def fcp(self, var='x'): """ - Return the factorization of the characteristic polynomial of self. + Return the factorization of the characteristic polynomial of ``self``. INPUT: - - ``var`` - (default: 'x') name of variable of charpoly + - ``var`` -- (default: 'x') name of variable of charpoly EXAMPLES:: From 5c40f2b0ee248290ad97e451ea4446a8c3b3d9ec Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Tue, 11 Sep 2018 07:38:37 +0300 Subject: [PATCH 211/264] trac #26078: added lazy attribute _indices to subalgebras --- src/sage/algebras/lie_algebras/subalgebra.py | 24 +++++++++++++++---- ...ite_dimensional_lie_algebras_with_basis.py | 12 ++-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index 25c1364023d..1efcbe695ca 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -21,8 +21,10 @@ from sage.categories.homset import Hom from sage.categories.morphism import SetMorphism from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute from sage.modules.free_module_element import vector from sage.sets.family import Family +from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation @@ -424,6 +426,22 @@ def _from_m(self, v): R = self.ambient().base_ring() return self.ambient().from_vector(vector(R, reversed(v))) + @lazy_attribute + def _indices(self): + r""" + Return the set of indices for the basis of ``self``. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: S = L.subalgebra([x, y]) + sage: S._indices + {0, 1} + sage: [S.basis()[k] for k in S._indices] + [x, y] + """ + return FiniteEnumeratedSet(self.basis().keys()) + @cached_method def zero(self): r""" @@ -590,10 +608,8 @@ def basis(self): for v in B for w in SB] sm = m.submodule(sm.basis() + brackets) - basis = Family(reversed([self.element_class(self, self._from_m(v)) - for v in sm.echelonized_basis()])) - self._indices = range(len(basis)) - return basis + return Family(reversed([self.element_class(self, self._from_m(v)) + for v in sm.echelonized_basis()])) def from_vector(self, v): r""" diff --git a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py index e26a650d6ee..4f2ab243734 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -590,17 +590,13 @@ def subalgebra(self, gens, category=None): [0 0 1 0 0] [0 0 0 0 1] - TESTS: - - Test passing an extra category to a subalgebra:: + Passing an extra category to a subalgebra:: sage: L = LieAlgebra(QQ, 3, step=2) sage: x,y,z = L.homogeneous_component_basis(1) sage: C = LieAlgebras(QQ).FiniteDimensional().WithBasis() sage: C = C.Subobjects().Graded().Stratified() sage: S = L.subalgebra([x, y], category=C) - sage: S.basis().list() # required to initialize _indices - [X_1, X_2, X_12] sage: S.homogeneous_component_basis(2).list() [X_12] """ @@ -628,16 +624,12 @@ def ideal(self, gens, category=None): sage: I.reduce(p1 + p2 + q1 + q2 + z) 2*p1 + 2*q1 - TESTS: - - Test passing an extra category to an ideal:: + Passing an extra category to an ideal:: sage: L. = LieAlgebra(QQ, abelian=True) sage: C = LieAlgebras(QQ).FiniteDimensional().WithBasis() sage: C = C.Subobjects().Graded().Stratified() sage: I = L.ideal([x, y], category=C) - sage: I.basis().list() # required to initialize _indices - [x, y] sage: I.homogeneous_component_basis(1).list() [x, y] """ From 5b84e1f028225cfdf7c4ef52fab88bcac971eebd Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 11 Sep 2018 16:51:13 +1000 Subject: [PATCH 212/264] Adding code paths to compute ideals using the L[X, Y] and L.bracket(X, Y). --- src/sage/algebras/lie_algebras/lie_algebra.py | 29 ++++++++++++++- ...ite_dimensional_lie_algebras_with_basis.py | 12 ++++-- src/sage/categories/lie_algebras.py | 37 ++++++++++++++++++- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/sage/algebras/lie_algebras/lie_algebra.py b/src/sage/algebras/lie_algebras/lie_algebra.py index 9726dbb5e08..82c225c3863 100644 --- a/src/sage/algebras/lie_algebras/lie_algebra.py +++ b/src/sage/algebras/lie_algebras/lie_algebra.py @@ -535,7 +535,9 @@ def _element_constructor_(self, x): def __getitem__(self, x): """ - If `x` is a pair `(a, b)`, return the Lie bracket `[a, b]`. + If ``x`` is a pair `(a, b)`, return the Lie bracket `[a, b] + (including if `a` or `b` are Lie (sub)algebras, in which case the + corresponding ideal is constructed). Otherwise try to return the `x`-th element of ``self``. EXAMPLES:: @@ -543,8 +545,33 @@ def __getitem__(self, x): sage: L. = LieAlgebra(QQ, representation="polynomial") sage: L[x, [y, x]] -x^2*y + 2*x*y*x - y*x^2 + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: L[L, L] + Ideal () of Abelian Lie algebra on 2 generators (x, y) over Rational Field + + sage: L = lie_algebras.Heisenberg(QQ, 1) + sage: Z = L[L, L]; Z + Ideal (z) of Heisenberg algebra of rank 1 over Rational Field + sage: L[Z, L] + Ideal () of Heisenberg algebra of rank 1 over Rational Field + + sage: p,q,z = L.basis(); (p, q, z) + (p1, q1, z) + sage: L[p, L] + Ideal (p1) of Heisenberg algebra of rank 1 over Rational Field + sage: L[L, p+q] + Ideal (p1 + q1) of Heisenberg algebra of rank 1 over Rational Field """ if isinstance(x, tuple) and len(x) == 2: + # Check if we need to construct an ideal + if x[0] in LieAlgebras: + if x[1] in LieAlgebras: + return x[0].product_space(x[1]) + return x[0].ideal(x[1]) + elif x[1] in LieAlgebras: + return x[1].ideal(x[0]) + # Otherwise it is the bracket of two elements return self(x[0])._bracket_(self(x[1])) return super(LieAlgebra, self).__getitem__(x) diff --git a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py index d1d8470ed34..8cbf2b95249 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -568,7 +568,7 @@ def inner_derivations_basis(self): return tuple([matrix(R, N, N, list(b)) for b in IDer.row_module().basis()]) - def subalgebra(self, gens, category=None): + def subalgebra(self, *gens, **kwds): r""" Return the subalgebra of ``self`` generated by ``gens``. @@ -601,10 +601,13 @@ def subalgebra(self, gens, category=None): [X_12] """ from sage.algebras.lie_algebras.subalgebra import LieSubalgebra_finite_dimensional_with_basis + if len(gens) == 1 and isinstance(gens[0], (list, tuple)): + gens = gens[0] + category = kwds.pop('category', None) return LieSubalgebra_finite_dimensional_with_basis( self, gens, category=category) - def ideal(self, gens, category=None): + def ideal(self, *gens, **kwds): r""" Return the ideal of ``self`` generated by ``gens``. @@ -629,11 +632,14 @@ def ideal(self, gens, category=None): sage: L. = LieAlgebra(QQ, abelian=True) sage: C = LieAlgebras(QQ).FiniteDimensional().WithBasis() sage: C = C.Subobjects().Graded().Stratified() - sage: I = L.ideal([x, y], category=C) + sage: I = L.ideal(x, y, category=C) sage: I.homogeneous_component_basis(1).list() [x, y] """ from sage.algebras.lie_algebras.subalgebra import LieSubalgebra_finite_dimensional_with_basis + if len(gens) == 1 and isinstance(gens[0], (list, tuple)): + gens = gens[0] + category = kwds.pop('category', None) return LieSubalgebra_finite_dimensional_with_basis( self, gens, ideal=True, category=category) diff --git a/src/sage/categories/lie_algebras.py b/src/sage/categories/lie_algebras.py index 1a54c6e8d4d..487d53268eb 100644 --- a/src/sage/categories/lie_algebras.py +++ b/src/sage/categories/lie_algebras.py @@ -248,6 +248,10 @@ def bracket(self, lhs, rhs): Return the Lie bracket ``[lhs, rhs]`` after coercing ``lhs`` and ``rhs`` into elements of ``self``. + If ``lhs`` and ``rhs`` are Lie algebras, then this constructs + the product space, and if only one of them is a Lie algebra, + then it constructs the corresponding ideal. + EXAMPLES:: sage: L = LieAlgebras(QQ).example() @@ -258,7 +262,31 @@ def bracket(self, lhs, rhs): 0 sage: L.bracket(0, x) 0 - """ + + Constructing the product space:: + + sage: L = lie_algebras.Heisenberg(QQ, 1) + sage: Z = L.bracket(L, L); Z + Ideal (z) of Heisenberg algebra of rank 1 over Rational Field + sage: L.bracket(L, Z) + Ideal () of Heisenberg algebra of rank 1 over Rational Field + + Constructing ideals:: + + sage: p,q,z = L.basis(); (p,q,z) + (p1, q1, z) + sage: L.bracket(3*p, L) + Ideal (3*p1) of Heisenberg algebra of rank 1 over Rational Field + sage: L.bracket(L, q+p) + Ideal (p1 + q1) of Heisenberg algebra of rank 1 over Rational Field + """ + from sage.categories.lie_algebras import LieAlgebras + if lhs in LieAlgebras: + if rhs in LieAlgebras: + return lhs.product_space(rhs) + return lhs.ideal(rhs) + elif rhs in LieAlgebras: + return rhs.ideal(lhs) return self(lhs)._bracket_(self(rhs)) # Do not override this. Instead implement :meth:`_construct_UEA`; @@ -442,7 +470,7 @@ def subalgebra(self, gens, names=None, index_set=None, category=None): #from sage.algebras.lie_algebras.subalgebra import LieSubalgebra #return LieSubalgebra(gens, names, index_set, category) - def ideal(self, gens, names=None, index_set=None, category=None): + def ideal(self, *gens, **kwds): r""" Return the ideal of ``self`` generated by ``gens``. @@ -468,6 +496,11 @@ def ideal(self, gens, names=None, index_set=None, category=None): """ raise NotImplementedError("ideals not yet implemented: see #16824") #from sage.algebras.lie_algebras.ideal import LieIdeal + #if len(gens) == 1 and isinstance(gens[0], (list, tuple)): + # gens = gens[0] + #names = kwds.pop("names", None) + #index_set = kwds.pop("index_set", None) + #category = kwds.pop("category", None) #return LieIdeal(gens, names, index_set, category) def is_ideal(self, A): From 42dd07107a85c823fa17eb0bb6e911b3e8944e17 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 11 Sep 2018 17:16:48 +1000 Subject: [PATCH 213/264] Cythonizing subalgebra elements and caching their monomial coefficients. --- .../lie_algebras/lie_algebra_element.pxd | 4 + .../lie_algebras/lie_algebra_element.pyx | 205 ++++++++++++++++++ src/sage/algebras/lie_algebras/subalgebra.py | 108 +-------- 3 files changed, 214 insertions(+), 103 deletions(-) diff --git a/src/sage/algebras/lie_algebras/lie_algebra_element.pxd b/src/sage/algebras/lie_algebras/lie_algebra_element.pxd index 072632537db..0988343c5cf 100644 --- a/src/sage/algebras/lie_algebras/lie_algebra_element.pxd +++ b/src/sage/algebras/lie_algebras/lie_algebra_element.pxd @@ -13,6 +13,10 @@ cdef class LieAlgebraElementWrapper(ElementWrapper): cdef class LieAlgebraMatrixWrapper(LieAlgebraElementWrapper): pass +cdef class LieSubalgebraElementWrapper(LieAlgebraElementWrapper): + cdef dict _monomial_coefficients + cpdef dict monomial_coefficients(self, bint copy=*) + cdef class StructureCoefficientsElement(LieAlgebraMatrixWrapper): cpdef bracket(self, right) cpdef _bracket_(self, right) diff --git a/src/sage/algebras/lie_algebras/lie_algebra_element.pyx b/src/sage/algebras/lie_algebras/lie_algebra_element.pyx index 1b4184c2650..b5d84142d38 100644 --- a/src/sage/algebras/lie_algebras/lie_algebra_element.pyx +++ b/src/sage/algebras/lie_algebras/lie_algebra_element.pyx @@ -520,6 +520,211 @@ cdef class LieAlgebraMatrixWrapper(LieAlgebraElementWrapper): value.set_immutable() # Make the matrix immutable for hashing LieAlgebraElementWrapper.__init__(self, parent, value) + +cdef class LieSubalgebraElementWrapper(LieAlgebraElementWrapper): + r""" + Wrap an element of the ambient Lie algebra as an element. + """ + def __init__(self, parent, value): + """ + Initialize ``self``. + """ + LieAlgebraElementWrapper.__init__(self, parent, value) + self._monomial_coefficients = None + + def __getitem__(self, i): + r""" + Return the coefficient of ``self`` indexed by ``i``. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('X','Y'): {'Z': 1}}) + sage: S = L.subalgebra([X, Y]) + sage: el = S(2*Y + 9*Z) + sage: el[1] + 2 + sage: el[2] + 9 + """ + if self._monomial_coefficients is None: + # This sets _monomial_coefficients + self.monomial_coefficients(copy=False) + try: + return self._monomial_coefficients[i] + except KeyError: + return self.parent().base_ring().zero() + + def _bracket_(self, x): + """ + Return the Lie bracket ``[self, x]``. + + Assumes ``x`` and ``self`` have the same parent. + + INPUT: + + - ``x`` -- an element of the same Lie subalgebra as ``self`` + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, {('X','Y'): {'Z': 1}}) + sage: S = L.subalgebra([X, Y]) + sage: S(X)._bracket_(S(Y)) + Z + """ + x_lift = ( x).value + return type(self)(self._parent, self.value._bracket_(x_lift)) + + def to_vector(self): + r""" + Return the vector in ``g.module()`` corresponding to the + element ``self`` of ``g`` (where ``g`` is the parent of ``self``). + + EXAMPLES:: + + sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) + sage: S = L.subalgebra([X, Y]) + sage: S.basis() + Family (X, Y, 3*Z) + sage: S(2*Y + 9*Z).to_vector() + (0, 2, 9) + sage: S2 = L.subalgebra([Y, Z]) + sage: S2.basis() + Family (Y, Z) + sage: S2(2*Y + 9*Z).to_vector() + (0, 2, 9) + + TESTS:: + + sage: L. = LieAlgebra(ZZ, abelian=True) + sage: S = L.subalgebra(X) + sage: S(X).to_vector() in S.module() + True + sage: S(X).to_vector().parent() is S.module() + True + """ + return self._parent.module()(self.value.to_vector()) + + cpdef dict monomial_coefficients(self, bint copy=True): + r""" + 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: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) + sage: S = L.subalgebra([X, Y]) + sage: S(2*Y + 9*Z).monomial_coefficients() + {1: 2, 2: 3} + sage: S2 = L.subalgebra([Y, Z]) + sage: S2(2*Y + 9*Z).monomial_coefficients() + {0: 2, 1: 9} + """ + cdef Py_ssize_t k + if self._monomial_coefficients is None: + sm = self.parent().module() + v = sm.coordinate_vector(self.to_vector()) + self._monomial_coefficients = {k: v[k] for k in range(len(v)) if v[k]} + if copy: + return dict(self._monomial_coefficients) + return self._monomial_coefficients + + cpdef _add_(self, right): + """ + Add ``self`` and ``rhs``. + + EXAMPLES:: + + sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) + sage: S = L.subalgebra([X, Y]) + sage: a = S(2*Y + 12*Z) + sage: b = S(X + 2*Y) + sage: (a + b).monomial_coefficients() + {0: 1, 1: 4, 2: 4} + sage: a.monomial_coefficients() # We set a._monomial_coefficients + {1: 2, 2: 4} + sage: b.monomial_coefficients() # We set b._monomial_coefficients + {0: 1, 1: 2} + sage: (a + b).monomial_coefficients() # This is now computed from a and b + {0: 1, 1: 4, 2: 4} + """ + cdef LieSubalgebraElementWrapper ret, other = right + ret = type(self)(self._parent, self.value + other.value) + if self._monomial_coefficients is not None and other._monomial_coefficients is not None: + mc = add(self._monomial_coefficients, other._monomial_coefficients) + ret._monomial_coefficients = mc + return ret + + cpdef _sub_(self, right): + """ + Subtract ``self`` and ``rhs``. + + EXAMPLES:: + + sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) + sage: S = L.subalgebra([X, Y]) + sage: a = S(2*Y + 12*Z) + sage: b = S(X + 2*Y) + sage: (a - b).monomial_coefficients() + {0: -1, 2: 4} + sage: a.monomial_coefficients() # We set a._monomial_coefficients + {1: 2, 2: 4} + sage: b.monomial_coefficients() # We set b._monomial_coefficients + {0: 1, 1: 2} + sage: (a - b).monomial_coefficients() # This is now computed from a and b + {0: -1, 2: 4} + """ + cdef LieSubalgebraElementWrapper ret, other = right + ret = type(self)(self._parent, self.value - other.value) + if self._monomial_coefficients is not None and other._monomial_coefficients is not None: + mc = axpy(-1, other._monomial_coefficients, self._monomial_coefficients) + ret._monomial_coefficients = mc + return ret + + cpdef _acted_upon_(self, scalar, bint self_on_left): + """ + Return the action of a scalar on ``self``. + + EXAMPLES:: + + sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) + sage: S = L.subalgebra([X, Y]) + sage: a = S(2*Y + 12*Z) + sage: (2*a).monomial_coefficients() + {1: 4, 2: 8} + sage: a.monomial_coefficients() # We set a._monomial_coefficients + {1: 2, 2: 4} + sage: (2*a).monomial_coefficients() # This is now computed from a + {1: 4, 2: 8} + """ + # This was copied and IDK if it still applies (TCS): + # With the current design, the coercion model does not have + # enough information to detect apriori that this method only + # accepts scalars; so it tries on some elements(), and we need + # to make sure to report an error. + scalar_parent = parent(scalar) + if scalar_parent != self._parent.base_ring(): + # Temporary needed by coercion (see Polynomial/FractionField tests). + if self._parent.base_ring().has_coerce_map_from(scalar_parent): + scalar = self._parent.base_ring()( scalar ) + else: + return None + cdef LieSubalgebraElementWrapper ret + if self_on_left: + ret = type(self)(self._parent, self.value * scalar) + else: + ret = type(self)(self._parent, scalar * self.value) + if self._monomial_coefficients is not None: + ret._monomial_coefficients = scal(scalar, self._monomial_coefficients, self_on_left) + return ret + cdef class StructureCoefficientsElement(LieAlgebraMatrixWrapper): """ An element of a Lie algebra given by structure coefficients. diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index 1efcbe695ca..c802b7fd1b8 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -16,7 +16,7 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.algebras.lie_algebras.lie_algebra_element import LieAlgebraElementWrapper +from sage.algebras.lie_algebras.lie_algebra_element import LieSubalgebraElementWrapper from sage.categories.lie_algebras import LieAlgebras from sage.categories.homset import Hom from sage.categories.morphism import SetMorphism @@ -737,7 +737,8 @@ def is_ideal(self, A): def reduce(self, X): r""" - Reduce an element of the ambient Lie algebra modulo ``self``. + Reduce an element of the ambient Lie algebra modulo the + ideal ``self``. INPUT: @@ -799,105 +800,6 @@ def reduce(self, X): return X - class Element(LieAlgebraElementWrapper): - r""" - Wrap an element of the ambient Lie algebra as an element. - """ + class Element(LieSubalgebraElementWrapper): + pass - def __getitem__(self, i): - r""" - Return the coefficient of ``self`` indexed by ``i``. - - EXAMPLES:: - - sage: L. = LieAlgebra(QQ, {('X','Y'): {'Z': 1}}) - sage: S = L.subalgebra([X, Y]) - sage: el = S(2*Y + 9*Z) - sage: el[1] - 2 - sage: el[2] - 9 - """ - try: - return self.monomial_coefficients()[i] - except KeyError: - return self.parent().base_ring().zero() - - def _bracket_(self, x): - """ - Return the Lie bracket ``[self, x]``. - - Assumes ``x`` and ``self`` have the same parent. - - INPUT: - - - ``x`` -- an element of the same Lie subalgebra as ``self`` - - EXAMPLES:: - - sage: L. = LieAlgebra(QQ, {('X','Y'): {'Z': 1}}) - sage: S = L.subalgebra([X, Y]) - sage: S(X)._bracket_(S(Y)) - Z - """ - P = self.parent() - self_lift = self.value - x_lift = x.value - return P.retract(self_lift._bracket_(x_lift)) - - def to_vector(self): - r""" - Return the vector in ``g.module()`` corresponding to the - element ``self`` of ``g`` (where ``g`` is the parent of ``self``). - - EXAMPLES:: - - sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) - sage: S = L.subalgebra([X, Y]) - sage: S.basis() - Family (X, Y, 3*Z) - sage: S(2*Y + 9*Z).to_vector() - (0, 2, 9) - sage: S2 = L.subalgebra([Y, Z]) - sage: S2.basis() - Family (Y, Z) - sage: S2(2*Y + 9*Z).to_vector() - (0, 2, 9) - - TESTS:: - - sage: L. = LieAlgebra(ZZ, abelian=True) - sage: S = L.subalgebra(X) - sage: S(X).to_vector() in S.module() - True - sage: S(X).to_vector().parent() is S.module() - True - """ - return self.parent().module()(self.value.to_vector()) - - def monomial_coefficients(self, copy=True): - r""" - 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: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) - sage: S = L.subalgebra([X, Y]) - sage: S(2*Y + 9*Z).monomial_coefficients() - {1: 2, 2: 3} - sage: S2 = L.subalgebra([Y, Z]) - sage: S2(2*Y + 9*Z).monomial_coefficients() - {0: 2, 1: 9} - """ - sm = self.parent().module() - v = sm.coordinate_vector(self.to_vector()) - return {k: v[k] for k in range(len(v)) if not v[k].is_zero()} From fcb488858260db781ccc93200d5e6976389d861d Mon Sep 17 00:00:00 2001 From: Eero Hakavuori Date: Tue, 11 Sep 2018 12:03:10 +0300 Subject: [PATCH 214/264] trac #26078: added missing doctest to subalgebra element initialization --- src/sage/algebras/lie_algebras/lie_algebra_element.pyx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sage/algebras/lie_algebras/lie_algebra_element.pyx b/src/sage/algebras/lie_algebras/lie_algebra_element.pyx index b5d84142d38..48169a42a2d 100644 --- a/src/sage/algebras/lie_algebras/lie_algebra_element.pyx +++ b/src/sage/algebras/lie_algebras/lie_algebra_element.pyx @@ -528,6 +528,12 @@ cdef class LieSubalgebraElementWrapper(LieAlgebraElementWrapper): def __init__(self, parent, value): """ Initialize ``self``. + + TESTS:: + + sage: L. = LieAlgebra(QQ, {('X','Y'): {'Z': 1}}) + sage: S = L.subalgebra([X, Y]) + sage: TestSuite(S(X)).run() """ LieAlgebraElementWrapper.__init__(self, parent, value) self._monomial_coefficients = None From a157801a6f5c32043c24f62ae54369fb02675eed Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Tue, 11 Sep 2018 14:20:04 +0200 Subject: [PATCH 215/264] Fix PARI memory leak in conversion to finite field --- src/sage/rings/finite_rings/element_givaro.pyx | 8 +++++--- src/sage/rings/finite_rings/element_ntl_gf2e.pyx | 8 +++++--- src/sage/rings/finite_rings/finite_field_givaro.py | 6 +++++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/finite_rings/element_givaro.pyx b/src/sage/rings/finite_rings/element_givaro.pyx index 15f1ced77de..d56fd08ab43 100644 --- a/src/sage/rings/finite_rings/element_givaro.pyx +++ b/src/sage/rings/finite_rings/element_givaro.pyx @@ -71,6 +71,7 @@ import sage.rings.finite_rings.finite_field_constructor as finite_field import sage.interfaces.gap from sage.libs.pari.all import pari from cypari2.gen cimport Gen +from cypari2.stack cimport clear_stack from sage.structure.parent cimport Parent @@ -467,7 +468,7 @@ cdef class Cache_givaro(SageObject): if typ(t) == t_INT: res = self.int_to_log(itos(t)) - sig_off() + clear_stack() elif typ(t) == t_POL: res = self._zero_element @@ -478,9 +479,10 @@ cdef class Cache_givaro(SageObject): c = gtolong(gel(t, i+2)) res = self.objectptr.axpyin(res, self.int_to_log(c), x) x = self.objectptr.mul(x,x,g) - sig_off() + clear_stack() else: - raise TypeError("bad PARI type %r" % e.type()) + clear_stack() + raise TypeError(f"unable to convert PARI {e.type()} to {self.parent}") return make_FiniteField_givaroElement(self,res) diff --git a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx index 7ec10e71bf5..c21dddc7ab3 100644 --- a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx +++ b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx @@ -40,6 +40,7 @@ from sage.rings.finite_rings.finite_field_base cimport FiniteField from sage.libs.pari.all import pari from cypari2.gen cimport Gen +from cypari2.stack cimport clear_stack from sage.interfaces.gap import is_GapElement @@ -380,7 +381,7 @@ cdef class Cache_ntl_gf2e(SageObject): if typ(t) == t_INT: GF2E_conv_long(res.x, itos(t)) - sig_off() + clear_stack() elif typ(t) == t_POL: g = self._gen x = self._new() @@ -390,9 +391,10 @@ cdef class Cache_ntl_gf2e(SageObject): if gtolong(gel(t, i+2)): GF2E_add(res.x, res.x, x.x) GF2E_mul(x.x, x.x, g.x) - sig_off() + clear_stack() else: - raise TypeError("bad PARI type %r" % e.type()) + clear_stack() + raise TypeError(f"unable to convert PARI {e.type()} to {self.parent}") return res diff --git a/src/sage/rings/finite_rings/finite_field_givaro.py b/src/sage/rings/finite_rings/finite_field_givaro.py index 1d9218245ce..b21f88faea5 100644 --- a/src/sage/rings/finite_rings/finite_field_givaro.py +++ b/src/sage/rings/finite_rings/finite_field_givaro.py @@ -319,13 +319,17 @@ def _element_constructor_(self, e): PARI elements are interpreted as finite field elements; this PARI flexibility is (absurdly!) liberal:: - sage: k = GF(2**8, 'a') + sage: k. = GF(2^8) sage: k(pari('Mod(1,2)')) 1 sage: k(pari('Mod(2,3)')) a sage: k(pari('Mod(1,3)*a^20')) a^7 + a^5 + a^4 + a^2 + sage: k(pari('O(x)')) + Traceback (most recent call last): + ... + TypeError: unable to convert PARI t_SER to Finite Field in a of size 2^8 We can coerce from PARI finite field implementations:: From b66b48de71d8680fa9a006baea580bd6401fc1de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Sep 2018 15:50:01 +0200 Subject: [PATCH 216/264] trying not to sort edges --- src/sage/graphs/generic_graph.py | 2 +- src/sage/matroids/graphic_matroid.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index d66447b366a..6ce9f0a4fea 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -11028,7 +11028,7 @@ def edge_iterator(self, vertices=None, labels=True, ignore_direction=False): else: return self._backend.iterator_edges(vertices, labels) - def edges_incident(self, vertices=None, labels=True, sort=True): + def edges_incident(self, vertices=None, labels=True, sort=False): """ Returns incident edges to some vertices. diff --git a/src/sage/matroids/graphic_matroid.py b/src/sage/matroids/graphic_matroid.py index 3bd13c21b15..15ab6b4dffe 100644 --- a/src/sage/matroids/graphic_matroid.py +++ b/src/sage/matroids/graphic_matroid.py @@ -54,15 +54,16 @@ sage: isinstance(M1, RegularMatroid) False -Note that if there is not a complete set of unique edge labels, and there are -no parallel edges, then vertex tuples will be used for the ground set. The user -may wish to override this by specifying the ground set, as the vertex tuples will -not be updated if the matroid is modified. +Note that if there is not a complete set of unique edge labels, and +there are no parallel edges, then vertex tuples will be used for the +ground set. The user may wish to override this by specifying the +ground set, as the vertex tuples will not be updated if the matroid is +modified:: sage: G = graphs.DiamondGraph() sage: M1 = Matroid(G) sage: N1 = M1.contract((0,1)) - sage: N1.graph().edges_incident(0) + sage: N1.graph().edges_incident(0, sort=True) [(0, 2, (0, 2)), (0, 2, (1, 2)), (0, 3, (1, 3))] sage: M2 = Matroid(range(G.num_edges()), G) sage: N2 = M2.contract(0) @@ -1630,7 +1631,7 @@ def graphic_coextensions(self, vertices=None, v=None, element=None, cosimple=Fal sage: M = Matroid(range(8), G) sage: I = M.graphic_coextensions(vertices=[0], element='a') sage: for N in I: - ....: N.graph().edges_incident(0) + ....: N.graph().edges_incident(0, sort=True) [(0, 1, 0), (0, 2, 1), (0, 3, 2), (0, 4, 3), (0, 5, 'a')] [(0, 2, 1), (0, 3, 2), (0, 4, 3), (0, 5, 'a')] [(0, 1, 0), (0, 2, 1), (0, 3, 2), (0, 5, 'a')] @@ -1645,7 +1646,7 @@ def graphic_coextensions(self, vertices=None, v=None, element=None, cosimple=Fal sage: N = Matroid(range(4), graphs.CycleGraph(4)) sage: I = N.graphic_coextensions(element='a') sage: for N1 in I: - ....: N1.graph().edges() + ....: N1.graph().edges(sort=True) [(0, 1, 0), (0, 3, 1), (0, 4, 'a'), (1, 2, 2), (2, 3, 3)] [(0, 1, 0), (0, 3, 1), (1, 4, 2), (2, 3, 3), (2, 4, 'a')] sage: sum(1 for n in N.graphic_coextensions(cosimple=True)) From 239f345c775496b4f09bb991c630322a51073502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sat, 8 Sep 2018 14:26:08 +0200 Subject: [PATCH 217/264] py3: change the repr of Finite family (pprint their dict) --- .../en/thematic_tutorials/lie/weyl_groups.rst | 17 ++++++++++------ src/sage/algebras/associated_graded.py | 2 +- src/sage/algebras/clifford_algebra.py | 2 +- src/sage/algebras/free_algebra.py | 2 +- .../lie_algebras/classical_lie_algebra.py | 14 +++++++++---- .../algebras/lie_algebras/free_lie_algebra.py | 2 +- src/sage/algebras/lie_algebras/heisenberg.py | 4 ++-- src/sage/algebras/lie_algebras/lie_algebra.py | 5 ++--- src/sage/algebras/lie_algebras/onsager.py | 2 +- .../lie_algebras/poincare_birkhoff_witt.py | 4 +--- .../lie_algebras/structure_coefficients.py | 2 +- .../quantum_matrix_coordinate_algebra.py | 6 ++---- src/sage/algebras/tensor_algebra.py | 4 ++-- src/sage/algebras/weyl_algebra.py | 6 +++--- src/sage/categories/algebras.py | 2 +- .../finite_dimensional_algebras_with_basis.py | 2 +- .../examples/hopf_algebras_with_basis.py | 2 +- src/sage/categories/examples/monoids.py | 2 +- .../finite_dimensional_algebras_with_basis.py | 2 +- ...ite_dimensional_lie_algebras_with_basis.py | 20 +++++++++---------- src/sage/categories/modules_with_basis.py | 2 +- src/sage/categories/monoids.py | 4 ++-- src/sage/combinat/free_module.py | 4 ++-- src/sage/combinat/root_system/type_super_A.py | 9 ++++++--- src/sage/combinat/root_system/weyl_group.py | 2 +- src/sage/sets/family.py | 15 +++++++++++++- 26 files changed, 80 insertions(+), 58 deletions(-) diff --git a/src/doc/en/thematic_tutorials/lie/weyl_groups.rst b/src/doc/en/thematic_tutorials/lie/weyl_groups.rst index 47bec649dff..c32cd1276c4 100644 --- a/src/doc/en/thematic_tutorials/lie/weyl_groups.rst +++ b/src/doc/en/thematic_tutorials/lie/weyl_groups.rst @@ -83,10 +83,7 @@ construct a list (e.g., using the ``list`` function) or use the method sage: W = WeylGroup("B3",prefix="s") sage: ref = W.reflections(); ref - Finite family {(1, 0, 0): s1*s2*s3*s2*s1, (0, 1, 1): s3*s2*s3, - (0, 1, -1): s2, (0, 0, 1): s3, (1, -1, 0): s1, - (1, 1, 0): s2*s3*s1*s2*s3*s1*s2, (1, 0, -1): s1*s2*s1, - (1, 0, 1): s3*s1*s2*s3*s1, (0, 1, 0): s2*s3*s2} + Finite family {(1, -1, 0): s1, (0, 1, -1): s2, (0, 0, 1): s3, (0, 1, 1): s3*s2*s3, (0, 1, 0): s2*s3*s2, (1, 0, -1): s1*s2*s1, (1, 0, 1): s3*s1*s2*s3*s1, (1, 0, 0): s1*s2*s3*s2*s1, (1, 1, 0): s2*s3*s1*s2*s3*s1*s2} sage: [a1,a2,a3] = W.domain().simple_roots() sage: a1+a2+a3 (1, 0, 0) @@ -103,8 +100,16 @@ and whose values are the roots, you may use the inverse family:: sage: W = WeylGroup("B3",prefix="s") sage: [s1,s2,s3] = W.simple_reflections() sage: altref = W.reflections().inverse_family() - sage: pprint(altref) - Finite family {s3*s2*s3: (0, 1, 1), s2*s3*s2: (0, 1, 0), s1*s2*s3*s2*s1: (1, 0, 0), s1*s2*s1: (1, 0, -1), s1: (1, -1, 0), s2*s3*s1*s2*s3*s1*s2: (1, 1, 0), s2: (0, 1, -1), s3*s1*s2*s3*s1: (1, 0, 1), s3: (0, 0, 1)} + sage: altref + Finite family {s1*s2*s3*s2*s1: (1, 0, 0), + s2*s3*s1*s2*s3*s1*s2: (1, 1, 0), + s3*s1*s2*s3*s1: (1, 0, 1), + s1*s2*s1: (1, 0, -1), + s1: (1, -1, 0), + s2*s3*s2: (0, 1, 0), + s3*s2*s3: (0, 1, 1), + s2: (0, 1, -1), + s3: (0, 0, 1)} sage: altref[s3*s2*s3] (0, 1, 1) diff --git a/src/sage/algebras/associated_graded.py b/src/sage/algebras/associated_graded.py index 89a5f129fc5..53db6310fe7 100644 --- a/src/sage/algebras/associated_graded.py +++ b/src/sage/algebras/associated_graded.py @@ -281,7 +281,7 @@ def algebra_generators(self): 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'])} + Finite family {'x': bar(U['x']), 'y': bar(U['y']), 'z': bar(U['z'])} """ G = self._A.algebra_generators() return Family(G.keys(), lambda x: self(G[x]), name="generator") diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 60400103b60..9642c4ee58c 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -768,7 +768,7 @@ def algebra_generators(self): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: Cl. = CliffordAlgebra(Q) sage: Cl.algebra_generators() - Finite family {'y': y, 'x': x, 'z': z} + Finite family {'x': x, 'y': y, 'z': z} """ d = {x: self.gen(i) for i,x in enumerate(self.variable_names())} return Family(self.variable_names(), lambda x: d[x]) diff --git a/src/sage/algebras/free_algebra.py b/src/sage/algebras/free_algebra.py index 4b694e4defd..540b4e5e243 100644 --- a/src/sage/algebras/free_algebra.py +++ b/src/sage/algebras/free_algebra.py @@ -714,7 +714,7 @@ def algebra_generators(self): sage: F = FreeAlgebra(ZZ,3,'x,y,z') sage: F.algebra_generators() - Finite family {'y': y, 'x': x, 'z': z} + Finite family {'x': x, 'y': y, 'z': z} """ ret = {} for i in range(self.__ngens): diff --git a/src/sage/algebras/lie_algebras/classical_lie_algebra.py b/src/sage/algebras/lie_algebras/classical_lie_algebra.py index de62dbfbd4e..9735732718c 100644 --- a/src/sage/algebras/lie_algebras/classical_lie_algebra.py +++ b/src/sage/algebras/lie_algebras/classical_lie_algebra.py @@ -1321,7 +1321,7 @@ def indices_to_positive_roots_map(self): sage: L.indices_to_positive_roots_map() {1: alpha[1], 2: alpha[2], 3: alpha[1] + alpha[2]} """ - return {i+1: r for i,r in enumerate(self._Q.positive_roots())} + return {i+1: r for i, r in enumerate(self._Q.positive_roots())} @cached_method def lie_algebra_generators(self, str_keys=False): @@ -1337,9 +1337,9 @@ def lie_algebra_generators(self, str_keys=False): sage: L = LieAlgebra(QQ, cartan_type=['A', 1]) sage: L.lie_algebra_generators() - Finite family {-alpha[1]: E[-alpha[1]], alpha[1]: E[alpha[1]], alphacheck[1]: h1} + Finite family {alpha[1]: E[alpha[1]], -alpha[1]: E[-alpha[1]], alphacheck[1]: h1} sage: L.lie_algebra_generators(True) - Finite family {'f1': E[-alpha[1]], 'h1': h1, 'e1': E[alpha[1]]} + Finite family {'e1': E[alpha[1]], 'f1': E[-alpha[1]], 'h1': h1} """ index_set = self._cartan_type.index_set() alpha = self._Q.simple_roots() @@ -1353,14 +1353,20 @@ def lie_algebra_generators(self, str_keys=False): ret['e{}'.format(i)] = B[al] ret['f{}'.format(i)] = B[-al] ret['h{}'.format(i)] = B[alphacheck[i]] + keys = (['e{}'.format(i) for i in index_set] + + ['f{}'.format(i) for i in index_set] + + ['h{}'.format(i) for i in index_set]) else: for i in index_set: al = alpha[i] ret[al] = B[al] ret[-al] = B[-al] ret[alphacheck[i]] = B[alphacheck[i]] + keys = ([alpha[i] for i in index_set] + + [-alpha[i] for i in index_set] + + [alphacheck[i] for i in index_set]) - return Family(ret) + return Family(keys, ret.__getitem__) @cached_method def _part_generators(self, positive=False): diff --git a/src/sage/algebras/lie_algebras/free_lie_algebra.py b/src/sage/algebras/lie_algebras/free_lie_algebra.py index 5d3ba16f042..15233231586 100644 --- a/src/sage/algebras/lie_algebras/free_lie_algebra.py +++ b/src/sage/algebras/lie_algebras/free_lie_algebra.py @@ -375,7 +375,7 @@ def lie_algebra_generators(self): sage: L. = LieAlgebra(QQ) sage: L.lie_algebra_generators() - Finite family {'y': y, 'x': x} + Finite family {'x': x, 'y': y} sage: L.lie_algebra_generators()['x'].parent() Free Lie algebra generated by (x, y) over Rational Field in the Lyndon basis """ diff --git a/src/sage/algebras/lie_algebras/heisenberg.py b/src/sage/algebras/lie_algebras/heisenberg.py index 04315f1f335..70249546d1a 100644 --- a/src/sage/algebras/lie_algebras/heisenberg.py +++ b/src/sage/algebras/lie_algebras/heisenberg.py @@ -234,7 +234,7 @@ def lie_algebra_generators(self): sage: H = lie_algebras.Heisenberg(QQ, 1) sage: H.lie_algebra_generators() - Finite family {'q1': q1, 'p1': p1} + Finite family {'p1': p1, 'q1': q1} sage: H = lie_algebras.Heisenberg(QQ, 0) sage: H.lie_algebra_generators() Finite family {'z': z} @@ -258,7 +258,7 @@ def basis(self): sage: H = lie_algebras.Heisenberg(QQ, 1) sage: H.basis() - Finite family {'q1': q1, 'p1': p1, 'z': z} + Finite family {'p1': p1, 'q1': q1, 'z': z} """ d = {} for i in range(1, self._n+1): diff --git a/src/sage/algebras/lie_algebras/lie_algebra.py b/src/sage/algebras/lie_algebras/lie_algebra.py index 9726dbb5e08..16bf1e18b0f 100644 --- a/src/sage/algebras/lie_algebras/lie_algebra.py +++ b/src/sage/algebras/lie_algebras/lie_algebra.py @@ -751,7 +751,7 @@ def lie_algebra_generators(self): sage: L. = LieAlgebra(QQ, representation="polynomial") sage: L.lie_algebra_generators() - Finite family {'y': y, 'x': x} + Finite family {'x': x, 'y': y} """ return Family(self._indices, self.monomial, name="monomial map") @@ -1199,8 +1199,7 @@ def lie_algebra_generators(self): sage: S = GroupAlgebra(G, QQ) sage: L = LieAlgebra(associative=S) sage: L.lie_algebra_generators() - Finite family {(2,3): (2,3), (1,2): (1,2), (1,3): (1,3), - (1,2,3): (1,2,3), (1,3,2): (1,3,2), (): ()} + Finite family {(): (), (1,2): (1,2), (1,2,3): (1,2,3), (1,3,2): (1,3,2), (2,3): (2,3), (1,3): (1,3)} """ if self._gens is not None: return self._gens diff --git a/src/sage/algebras/lie_algebras/onsager.py b/src/sage/algebras/lie_algebras/onsager.py index 19830c85f9b..85cf44c4c35 100644 --- a/src/sage/algebras/lie_algebras/onsager.py +++ b/src/sage/algebras/lie_algebras/onsager.py @@ -214,7 +214,7 @@ def lie_algebra_generators(self): sage: O = lie_algebras.OnsagerAlgebra(QQ) sage: O.lie_algebra_generators() - Finite family {'A1': A[1], 'A0': A[0]} + Finite family {'A0': A[0], 'A1': A[1]} """ d = {"A0": self.basis()[0,0], "A1": self.basis()[0,1]} return Family(self._names, d.__getitem__) diff --git a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py index 0ee27bee575..eed2508761b 100644 --- a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py +++ b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py @@ -331,9 +331,7 @@ def algebra_generators(self): sage: L = lie_algebras.sl(QQ, 2) sage: PBW = L.pbw_basis() sage: PBW.algebra_generators() - Finite family {-alpha[1]: PBW[-alpha[1]], - alpha[1]: PBW[alpha[1]], - alphacheck[1]: PBW[alphacheck[1]]} + Finite family {alpha[1]: PBW[alpha[1]], alphacheck[1]: PBW[alphacheck[1]], -alpha[1]: PBW[-alpha[1]]} """ G = self._indices.gens() return Family(self._indices._indices, lambda x: self.monomial(G[x]), diff --git a/src/sage/algebras/lie_algebras/structure_coefficients.py b/src/sage/algebras/lie_algebras/structure_coefficients.py index 6df420eec19..8c5a93ec8ec 100644 --- a/src/sage/algebras/lie_algebras/structure_coefficients.py +++ b/src/sage/algebras/lie_algebras/structure_coefficients.py @@ -96,7 +96,7 @@ class LieAlgebraWithStructureCoefficients(FinitelyGeneratedLieAlgebra, IndexedGe sage: L = LieAlgebra(QQ, 'x,y', {('x','y'):{'x':1}}) sage: L.basis() - Finite family {'y': y, 'x': x} + Finite family {'x': x, 'y': y} """ @staticmethod def __classcall_private__(cls, R, s_coeff, names=None, index_set=None, **kwds): diff --git a/src/sage/algebras/quantum_matrix_coordinate_algebra.py b/src/sage/algebras/quantum_matrix_coordinate_algebra.py index 67a2a1857f0..6220e1dad30 100644 --- a/src/sage/algebras/quantum_matrix_coordinate_algebra.py +++ b/src/sage/algebras/quantum_matrix_coordinate_algebra.py @@ -584,8 +584,7 @@ def algebra_generators(self): sage: O = algebras.QuantumMatrixCoordinate(2) sage: O.algebra_generators() - Finite family {(1, 2): x[1,2], (1, 1): x[1,1], - (2, 1): x[2,1], (2, 2): x[2,2]} + Finite family {(1, 1): x[1,1], (1, 2): x[1,2], (2, 1): x[2,1], (2, 2): x[2,2]} """ l = [(i, j) for i in range(1, self._m + 1) for j in range(1, self._n + 1)] @@ -792,8 +791,7 @@ def algebra_generators(self): sage: O = algebras.QuantumGL(2) sage: O.algebra_generators() - Finite family {(1, 2): x[1,2], 'c': c, (1, 1): x[1,1], - (2, 1): x[2,1], (2, 2): x[2,2]} + Finite family {(1, 1): x[1,1], (1, 2): x[1,2], (2, 1): x[2,1], (2, 2): x[2,2], 'c': c} """ l = [(i, j) for i in range(1, self._n + 1) for j in range(1, self._n + 1)] diff --git a/src/sage/algebras/tensor_algebra.py b/src/sage/algebras/tensor_algebra.py index e65a3832f9f..fb3cad1d8ed 100644 --- a/src/sage/algebras/tensor_algebra.py +++ b/src/sage/algebras/tensor_algebra.py @@ -87,7 +87,7 @@ class TensorAlgebra(CombinatorialFreeModule): sage: TA.base_ring() Rational Field sage: TA.algebra_generators() - Finite family {'a': B['a'], 'c': B['c'], 'b': B['b']} + Finite family {'a': B['a'], 'b': B['b'], 'c': B['c']} """ def __init__(self, M, prefix='T', category=None, **options): r""" @@ -461,7 +461,7 @@ def algebra_generators(self): sage: C = CombinatorialFreeModule(QQ, ['a','b','c']) sage: TA = TensorAlgebra(C) sage: TA.algebra_generators() - Finite family {'a': B['a'], 'c': B['c'], 'b': B['b']} + Finite family {'a': B['a'], 'b': B['b'], 'c': B['c']} sage: m = SymmetricFunctions(QQ).m() sage: Tm = TensorAlgebra(m) sage: Tm.algebra_generators() diff --git a/src/sage/algebras/weyl_algebra.py b/src/sage/algebras/weyl_algebra.py index cfeefaa0fe7..3062d8edb0d 100644 --- a/src/sage/algebras/weyl_algebra.py +++ b/src/sage/algebras/weyl_algebra.py @@ -789,7 +789,7 @@ def algebra_generators(self): sage: R. = QQ[] sage: W = DifferentialWeylAlgebra(R) sage: W.algebra_generators() - Finite family {'dz': dz, 'dx': dx, 'dy': dy, 'y': y, 'x': x, 'z': z} + Finite family {'x': x, 'y': y, 'z': z, 'dx': dx, 'dy': dy, 'dz': dz} """ d = {x: self.gen(i) for i,x in enumerate(self.variable_names())} return Family(self.variable_names(), lambda x: d[x]) @@ -807,7 +807,7 @@ def variables(self): sage: W. = DifferentialWeylAlgebra(QQ) sage: W.variables() - Finite family {'y': y, 'x': x, 'z': z} + Finite family {'x': x, 'y': y, 'z': z} """ N = self.variable_names()[:self._n] d = {x: self.gen(i) for i,x in enumerate(N) } @@ -826,7 +826,7 @@ def differentials(self): sage: W. = DifferentialWeylAlgebra(QQ) sage: W.differentials() - Finite family {'dz': dz, 'dx': dx, 'dy': dy} + Finite family {'dx': dx, 'dy': dy, 'dz': dz} """ N = self.variable_names()[self._n:] d = {x: self.gen(self._n+i) for i,x in enumerate(N) } diff --git a/src/sage/categories/algebras.py b/src/sage/categories/algebras.py index 95f7385acc7..07c166264cd 100644 --- a/src/sage/categories/algebras.py +++ b/src/sage/categories/algebras.py @@ -162,7 +162,7 @@ def algebra_generators(self): (containing the arrows a:x->y and b:x->y) over Rational Field sage: S = A.semisimple_quotient() sage: S.algebra_generators() - Finite family {'y': B['y'], 'x': B['x'], 'b': 0, 'a': 0} + Finite family {'x': B['x'], 'y': B['y'], 'a': 0, 'b': 0} .. TODO:: this could possibly remove the elements that retract to zero. """ diff --git a/src/sage/categories/examples/finite_dimensional_algebras_with_basis.py b/src/sage/categories/examples/finite_dimensional_algebras_with_basis.py index 6e1f31a2a2d..a3721f106b7 100644 --- a/src/sage/categories/examples/finite_dimensional_algebras_with_basis.py +++ b/src/sage/categories/examples/finite_dimensional_algebras_with_basis.py @@ -122,7 +122,7 @@ def algebra_generators(self): the path algebra of the Kronecker quiver (containing the arrows a:x->y and b:x->y) over Rational Field sage: A.algebra_generators() - Finite family {'y': y, 'x': x, 'b': b, 'a': a} + Finite family {'x': x, 'y': y, 'a': a, 'b': b} """ return self.basis() diff --git a/src/sage/categories/examples/hopf_algebras_with_basis.py b/src/sage/categories/examples/hopf_algebras_with_basis.py index 1808884d00d..e506b8b46a5 100644 --- a/src/sage/categories/examples/hopf_algebras_with_basis.py +++ b/src/sage/categories/examples/hopf_algebras_with_basis.py @@ -90,7 +90,7 @@ def algebra_generators(self): sage: A = HopfAlgebrasWithBasis(QQ).example(); A An example of Hopf algebra with basis: the group algebra of the Dihedral group of order 6 as a permutation group over Rational Field sage: A.algebra_generators() - Finite family {(1,3): B[(1,3)], (1,2,3): B[(1,2,3)]} + Finite family {(1,2,3): B[(1,2,3)], (1,3): B[(1,3)]} """ return Family(self._group.gens(), self.monomial) diff --git a/src/sage/categories/examples/monoids.py b/src/sage/categories/examples/monoids.py index 453ea61ae97..f316b459721 100644 --- a/src/sage/categories/examples/monoids.py +++ b/src/sage/categories/examples/monoids.py @@ -131,7 +131,7 @@ def monoid_generators(self): sage: M = Monoids().example(); M An example of a monoid: the free monoid generated by ('a', 'b', 'c', 'd') sage: M.monoid_generators() - Finite family {'a': 'a', 'c': 'c', 'b': 'b', 'd': 'd'} + Finite family {'a': 'a', 'b': 'b', 'c': 'c', 'd': 'd'} sage: a,b,c,d = M.monoid_generators() sage: a*d*c*b 'adcb' diff --git a/src/sage/categories/finite_dimensional_algebras_with_basis.py b/src/sage/categories/finite_dimensional_algebras_with_basis.py index 2edac3a098f..b4b3b85c711 100644 --- a/src/sage/categories/finite_dimensional_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_algebras_with_basis.py @@ -284,7 +284,7 @@ def semisimple_quotient(self): sage: S in Algebras(QQ).Semisimple() True sage: S.basis() - Finite family {'y': B['y'], 'x': B['x']} + Finite family {'x': B['x'], 'y': B['y']} sage: xs,ys = sorted(S.basis()) sage: (xs + ys) * xs B['x'] diff --git a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py index b90cd7c82be..1c8d0225d11 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -330,7 +330,7 @@ def structure_coefficients(self, include_zeros=False): sage: L.structure_coefficients() Finite family {} sage: L.structure_coefficients(True) - Finite family {(0, 1): (0, 0, 0), (1, 2): (0, 0, 0), (0, 2): (0, 0, 0)} + Finite family {(0, 1): (0, 0, 0), (0, 2): (0, 0, 0), (1, 2): (0, 0, 0)} :: @@ -338,15 +338,15 @@ def structure_coefficients(self, include_zeros=False): sage: S = GroupAlgebra(G, QQ) sage: L = LieAlgebra(associative=S) sage: L.structure_coefficients() - Finite family {((1,2), (1,3,2)): (2,3) - (1,3), - ((1,3,2), (1,3)): (2,3) - (1,2), - ((1,2), (1,2,3)): -(2,3) + (1,3), - ((1,2,3), (1,3)): -(2,3) + (1,2), - ((2,3), (1,3)): -(1,2,3) + (1,3,2), - ((1,2), (2,3)): -(1,2,3) + (1,3,2), - ((1,3,2), (2,3)): (1,2) - (1,3), - ((1,2), (1,3)): (1,2,3) - (1,3,2), - ((1,2,3), (2,3)): -(1,2) + (1,3)} + Finite family {((2,3), (1,3)): -(1,2,3) + (1,3,2), + ((1,2), (2,3)): -(1,2,3) + (1,3,2), + ((1,2), (1,2,3)): -(2,3) + (1,3), + ((1,2), (1,3,2)): (2,3) - (1,3), + ((1,2), (1,3)): (1,2,3) - (1,3,2), + ((1,2,3), (2,3)): -(1,2) + (1,3), + ((1,2,3), (1,3)): -(2,3) + (1,2), + ((1,3,2), (2,3)): (1,2) - (1,3), + ((1,3,2), (1,3)): (2,3) - (1,2)} """ d = {} B = self.basis() diff --git a/src/sage/categories/modules_with_basis.py b/src/sage/categories/modules_with_basis.py index d4120a17af7..b2827996abf 100644 --- a/src/sage/categories/modules_with_basis.py +++ b/src/sage/categories/modules_with_basis.py @@ -215,7 +215,7 @@ def basis(self): sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) sage: F.basis() - Finite family {'a': B['a'], 'c': B['c'], 'b': B['b']} + Finite family {'a': B['a'], 'b': B['b'], 'c': B['c']} :: diff --git a/src/sage/categories/monoids.py b/src/sage/categories/monoids.py index b6ad1ae35fd..af5cffbc5e8 100644 --- a/src/sage/categories/monoids.py +++ b/src/sage/categories/monoids.py @@ -482,9 +482,9 @@ def algebra_generators(self): An example of a monoid: the free monoid generated by ('a', 'b', 'c', 'd') sage: M.monoid_generators() - Finite family {'a': 'a', 'c': 'c', 'b': 'b', 'd': 'd'} + Finite family {'a': 'a', 'b': 'b', 'c': 'c', 'd': 'd'} sage: M.algebra(ZZ).algebra_generators() - Finite family {'a': B['a'], 'c': B['c'], 'b': B['b'], 'd': B['d']} + Finite family {'a': B['a'], 'b': B['b'], 'c': B['c'], 'd': B['d']} sage: Z12 = Monoids().Finite().example(); Z12 An example of a finite multiplicative monoid: diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index 129c0aa703a..7a75e3a6031 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -77,7 +77,7 @@ class CombinatorialFreeModule(UniqueRepresentation, Module, IndexedGenerators): sage: e = F.basis() sage: e - Finite family {'a': B['a'], 'c': B['c'], 'b': B['b']} + Finite family {'a': B['a'], 'b': B['b'], 'c': B['c']} :: @@ -380,7 +380,7 @@ def __init__(self, R, basis_keys=None, element_class=None, category=None, sage: F = CombinatorialFreeModule(QQ, ['a','b','c'], category = FiniteDimensionalModulesWithBasis(QQ)) sage: F.basis() - Finite family {'a': B['a'], 'c': B['c'], 'b': B['b']} + Finite family {'a': B['a'], 'b': B['b'], 'c': B['c']} sage: F.category() Category of finite dimensional vector spaces with basis over Rational Field diff --git a/src/sage/combinat/root_system/type_super_A.py b/src/sage/combinat/root_system/type_super_A.py index f859853c523..ffee30ff45a 100644 --- a/src/sage/combinat/root_system/type_super_A.py +++ b/src/sage/combinat/root_system/type_super_A.py @@ -30,8 +30,11 @@ class AmbientSpace(ambient_space.AmbientSpace): sage: AL = R.ambient_space(); AL Ambient space of the Root system of type ['A', [2, 1]] sage: AL.basis() - Finite family {-2: (0, 1, 0, 0, 0), 2: (0, 0, 0, 0, 1), -3: (1, 0, 0, 0, 0), - -1: (0, 0, 1, 0, 0), 1: (0, 0, 0, 1, 0)} + Finite family {-3: (1, 0, 0, 0, 0), + -2: (0, 1, 0, 0, 0), + -1: (0, 0, 1, 0, 0), + 1: (0, 0, 0, 1, 0), + 2: (0, 0, 0, 0, 1)} """ def __init__(self, root_system, base_ring, index_set=None): """ @@ -582,7 +585,7 @@ def symmetrizer(self): EXAMPLES:: sage: CartanType(['A', [2,3]]).symmetrizer() - Finite family {0: 1, 1: -1, 2: -1, 3: -1, -1: 1, -2: 1} + Finite family {-2: 1, -1: 1, 0: 1, 1: -1, 2: -1, 3: -1} """ from sage.sets.family import Family def ell(i): return ZZ.one() if i <= 0 else -ZZ.one() diff --git a/src/sage/combinat/root_system/weyl_group.py b/src/sage/combinat/root_system/weyl_group.py index 6c16f45433e..90197fe9d2f 100644 --- a/src/sage/combinat/root_system/weyl_group.py +++ b/src/sage/combinat/root_system/weyl_group.py @@ -356,7 +356,7 @@ def reflections(self): sage: W = WeylGroup("B2", prefix="s") sage: refdict = W.reflections(); refdict - Finite family {(1, -1): s1, (1, 1): s2*s1*s2, (1, 0): s1*s2*s1, (0, 1): s2} + Finite family {(1, -1): s1, (0, 1): s2, (1, 1): s2*s1*s2, (1, 0): s1*s2*s1} sage: [r+refdict[r].action(r) for r in refdict.keys()] [(0, 0), (0, 0), (0, 0), (0, 0)] diff --git a/src/sage/sets/family.py b/src/sage/sets/family.py index db94e7e016a..0896b82978d 100644 --- a/src/sage/sets/family.py +++ b/src/sage/sets/family.py @@ -35,6 +35,7 @@ #***************************************************************************** import types from copy import copy +from pprint import pformat, saferepr from six import itervalues from six.moves import range @@ -659,8 +660,20 @@ def _repr_(self): sage: from sage.sets.family import FiniteFamily sage: FiniteFamily({3: 'a'}) # indirect doctest Finite family {3: 'a'} + + sage: FiniteFamily({3: 'a', 4: 'b'}) # indirect doctest + Finite family {3: 'a', 4: 'b'} + + sage: FiniteFamily({3: 'a', 4: 'b'}, keys=[4,3]) # indirect doctest + Finite family {4: 'b', 3: 'a'} """ - return "Finite family %s"%self._dictionary + if self._keys is None: + d = ' '.join(pformat(self._dictionary)[1:-1].splitlines()) + else: + d = ', '.join('{}: {}'.format(saferepr(key), + saferepr(self._dictionary[key])) + for key in self._keys) + return 'Finite family {{{}}}'.format(d) def __contains__(self, x): """ From d73a9fed2ca4e5d4f55115de8fbbb538e862034a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Sep 2018 16:49:52 +0200 Subject: [PATCH 218/264] py3: fix finance doctest --- src/sage/finance/stock.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/sage/finance/stock.py b/src/sage/finance/stock.py index 1c9ba479516..0864cae2db0 100644 --- a/src/sage/finance/stock.py +++ b/src/sage/finance/stock.py @@ -562,21 +562,12 @@ def load_from_file(self, file): 1212407640 187.75 188.00 187.75 188.00 2000, 1212405780 187.80 187.80 187.80 187.80 100 ] - - This tests a file that doesn't exist:: - - sage: finance.Stock("AAPL").load_from_file("I am not a file") - Traceback (most recent call last): - ... - IOError: [Errno 2] No such file or directory: 'I am not a file' """ - file_obj = open(file, 'r') - R = file_obj.read(); - self.__historical = self._load_from_csv(R) - file_obj.close() + with open(file) as file_obj: + R = file_obj.read() + self.__historical = self._load_from_csv(R) return self.__historical - def _load_from_csv(self, R): r""" EXAMPLES: From 2873e82e6776129b08b5d3182ce08110a2c8f7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Sep 2018 17:12:50 +0200 Subject: [PATCH 219/264] pep cleanup of the file /modular/modsym/relation_matrix.py --- src/sage/modular/modsym/relation_matrix.py | 289 +++------------------ 1 file changed, 31 insertions(+), 258 deletions(-) diff --git a/src/sage/modular/modsym/relation_matrix.py b/src/sage/modular/modsym/relation_matrix.py index 34ba457aebc..f029891fb27 100644 --- a/src/sage/modular/modsym/relation_matrix.py +++ b/src/sage/modular/modsym/relation_matrix.py @@ -5,7 +5,7 @@ symbols classes to compute presentations of spaces in terms of generators and relations, using the standard methods based on Manin symbols. """ -#***************************************************************************** +# **************************************************************************** # Sage: System for Algebra and Geometry Experimentation # # Copyright (C) 2005 William Stein @@ -19,22 +19,19 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from __future__ import absolute_import from six.moves import range -SPARSE = True import sage.matrix.matrix_space as matrix_space from sage.rings.all import Ring from sage.misc.search import search from sage.rings.rational_field import is_RationalField - - import sage.misc.misc as misc - from sage.modular.modsym.manin_symbol_list import ManinSymbolList +SPARSE = True # S = [0,-1; 1,0] # T = [0,-1; 1,-1], @@ -126,10 +123,10 @@ def modS_relations(syms): j, s = syms.apply_S(i) assert j != -1 if i < j: - rels.add( ((i,1),(j,s)) ) + rels.add(((i, 1), (j, s))) else: - rels.add( ((j,s),(i,1)) ) - misc.verbose("finished creating S relations",tm) + rels.add(((j, s), (i, 1))) + misc.verbose("finished creating S relations", tm) return rels @@ -191,10 +188,11 @@ def modI_relations(syms, sign): for i in range(len(syms)): j, s = syms.apply_I(i) assert j != -1 - rels.add( ((i,1),(j,-sign*s)) ) - misc.verbose("finished creating I relations",tm) + rels.add(((i, 1), (j, -sign * s))) + misc.verbose("finished creating I relations", tm) return rels + def T_relation_matrix_wtk_g0(syms, mod, field, sparse): r""" Compute a matrix whose echelon form gives the quotient by 3-term T @@ -237,11 +235,12 @@ def T_relation_matrix_wtk_g0(syms, mod, field, sparse): continue iT_plus_iTT = syms.apply_T(i) + syms.apply_TT(i) j0, s0 = mod[i] - v = {j0:s0} + v = {j0: s0} for j, s in iT_plus_iTT: - if w==2: already_seen.add(j) + if w == 2: + already_seen.add(j) j0, s0 = mod[j] - s0 = s*s0 + s0 = s * s0 if j0 in v: v[j0] += s0 else: @@ -251,13 +250,14 @@ def T_relation_matrix_wtk_g0(syms, mod, field, sparse): row += 1 MAT = matrix_space.MatrixSpace(field, row, - len(syms), sparse=True) + len(syms), sparse=True) R = MAT(entries) if not sparse: R = R.dense_matrix() - misc.verbose("finished (number of rows=%s)"%row, tm) + misc.verbose("finished (number of rows=%s)" % row, tm) return R + def gens_to_basis_matrix(syms, relation_matrix, mod, field, sparse): """ Compute echelon form of 3-term relation matrix, and read off each @@ -307,9 +307,10 @@ def gens_to_basis_matrix(syms, relation_matrix, mod, field, sparse): h = relation_matrix.height() except AttributeError: h = 9999999 - tm = misc.verbose("putting relation matrix in echelon form (height = %s)"%h) + tm = misc.verbose("putting relation matrix in echelon form (height = %s)" % h) if h < 10: - A = relation_matrix.echelon_form(algorithm='multimodular', height_guess=1) + A = relation_matrix.echelon_form(algorithm='multimodular', + height_guess=1) else: A = relation_matrix.echelon_form() A.set_immutable() @@ -320,15 +321,14 @@ def gens_to_basis_matrix(syms, relation_matrix, mod, field, sparse): basis_set = set(A.nonpivots()) pivots = A.pivots() - basis_mod2 = set([j for j,c in mod if c != 0]) + basis_mod2 = set([j for j, c in mod if c != 0]) basis_set = basis_set.intersection(basis_mod2) basis = sorted(basis_set) ONE = field(1) - misc.verbose("done doing setup",tm) - + misc.verbose("done doing setup", tm) tm = misc.verbose("now forming quotient matrix") M = matrix_space.MatrixSpace(field, len(syms), len(basis), sparse=sparse) @@ -339,14 +339,14 @@ def gens_to_basis_matrix(syms, relation_matrix, mod, field, sparse): for i in basis_mod2: t, l = search(basis, i) if t: - B[i,l] = ONE + B[i, l] = ONE else: _, r = search(pivots, i) # so pivots[r] = i # Set row i to -(row r of A), but where we only take # the non-pivot columns of A: B._set_row_to_negative_of_row_of_A_using_subset_of_columns(i, A, r, basis, cols_index) - misc.verbose("done making quotient matrix",tm) + misc.verbose("done making quotient matrix", tm) # The following is very fast (over Q at least). tm = misc.verbose('now filling in the rest of the matrix') @@ -356,11 +356,12 @@ def gens_to_basis_matrix(syms, relation_matrix, mod, field, sparse): if j != i and s != 0: # ignored in the above matrix k += 1 B.set_row_to_multiple_of_row(i, j, s) - misc.verbose("set %s rows"%k) + misc.verbose("set %s rows" % k) tm = misc.verbose("time to fill in rest of matrix", tm) return B, basis + def compute_presentation(syms, sign, field, sparse=None): r""" Compute the presentation for self, as a quotient of Manin symbols @@ -451,6 +452,7 @@ def compute_presentation(syms, sign, field, sparse=None): B, basis = gens_to_basis_matrix(syms, R, mod, field, sparse) return B, basis, mod + def relation_matrix_wtk_g0(syms, sign, field, sparse): r""" Compute the matrix of relations. Despite the name, this is used for all @@ -492,7 +494,7 @@ def relation_matrix_wtk_g0(syms, sign, field, sparse): rels = modS_relations(syms) if sign != 0: # Let rels = rels union I relations. - rels.update(modI_relations(syms,sign)) + rels.update(modI_relations(syms, sign)) if syms._apply_S_only_0pm1() and is_RationalField(field): from . import relation_matrix_pyx @@ -503,6 +505,7 @@ def relation_matrix_wtk_g0(syms, sign, field, sparse): R = T_relation_matrix_wtk_g0(syms, mod, field, sparse) return R, mod + def sparse_2term_quotient(rels, n, F): r""" Performs Sparse Gauss elimination on a matrix all of whose columns @@ -523,10 +526,8 @@ def sparse_2term_quotient(rels, n, F): - ``F`` - base field - OUTPUT: - - ``mod`` - list such that mod[i] = (j,s), which means that x_i is equivalent to s\*x_j, where the x_j are a basis for the quotient. @@ -584,7 +585,7 @@ def sparse_2term_quotient(rels, n, F): else: # x1 = -c1/c0 * x2. x = free[v0[0]] free[x] = free[v1[0]] - coef[x] = -c1/c0 + coef[x] = -c1 / c0 for i in related_to_me[x]: free[i] = free[x] coef[i] *= coef[x] @@ -598,233 +599,5 @@ def sparse_2term_quotient(rels, n, F): coef[die] = ZERO mod = [(free[i], coef[i]) for i in range(len(free))] - misc.verbose("finished",tm) + misc.verbose("finished", tm) return mod - - - -############################################################# -## The following two sparse_relation_matrix are not -## used by any modular symbols code. They're here for -## historical reasons, and can probably be safely deleted. -############################################################# - -## def sparse_relation_matrix_wt2_g0n(list, field, sign=0): -## r""" -## Create the sparse relation matrix over $\Q$ for Manin symbols of -## weight 2 on $\Gamma_0(N)$, with given sign. - -## INPUT: -## list -- sage.modular.modsym.p1list.List -## OUTPUT: -## A -- a sparse matrix that gives the 2-term and 3-term -## relations between Manin symbols. - -## MORE DETAILS: -## \begin{enumerate} -## \item Create an empty sparse matrix. - -## \item Let $S = [0,-1; 1,0]$, $T = [0,-1; 1,-1]$, $I = [-1,0; 0,1]$. - -## \item Enter the T relations: -## $$ -## x + x T = 0. -## $$ -## Remove x and x*T from reps to consider. - -## \item If sign $\neq 0$, enter the I relations: -## $$ -## x - sign\cdot x\cdot I = 0. -## $$ - -## \item Enter the S relations in the matrix: -## $$ -## x + x S + x S^2 = 0 -## $$ -## by putting 1s at cols corresponding to $x$, $x S$, and $x S^2$. -## Remove $x$, $x S$, and $x S^2$ from list of reps to consider. -## \end{enumerate} -## """ -## ZERO = field(0) -## ONE = field(1) -## TWO = field(2) - -## # This will be a dict of the entries of the sparse matrix, where -## # the notation is entries[(i,j)]=x. -## entries = {} - -## # The current row -## row = 0 - -## ## The S relations -## already_seen= set([]) -## for i in range(len(list)): -## if i in already_seen: -## continue -## u,v = list[i] -## j = list.index(v,-u) -## already_seen.add(j) -## if i != j: -## entries[(row,i)] = ONE -## entries[(row,j)] = ONE -## else: -## entries[(row,i)] = TWO -## row += 1 -## number_of_S_relations = row -## misc.verbose("There were %s S relations"%(number_of_S_relations)) - -## ## The eta relations: -## ## eta((u,v)) = -(-u,v) -## if sign != 0: -## SIGN = field(sign) -## already_seen= set([]) -## for i in range(len(list)): -## if i in already_seen: -## continue -## u, v = list[i] -## j = list.index(-u,v) -## already_seen.add(j) -## if i != j: -## entries[(row,i)] = ONE -## entries[(row,j)] = SIGN*ONE -## else: -## entries[(row,i)] = ONE + SIGN -## row += 1 -## number_of_I_relations = row - number_of_S_relations -## misc.verbose("There were %s I relations"%(number_of_I_relations)) - -## ## The three-term T relations -## already_seen = set([]) -## for i in range(len(list)): -## if i in already_seen: -## continue -## u,v = list[i] -## j1 = list.index(v,-u-v) -## already_seen.add(j1) -## j2 = list.index(-u-v,u) -## already_seen.add(j2) -## v = {i:ZERO, j1:ZERO, j2:ZERO} -## v[i] = ONE -## v[j1] += ONE -## v[j2] += ONE -## for x in v.keys(): -## entries[(row,x)] = v[x] -## row += 1 - -## number_of_T_relations = row - number_of_I_relations - number_of_S_relations -## misc.verbose("There were %s T relations"%(number_of_T_relations)) - -## M = matrix_space.MatrixSpace(RationalField(), row, -## len(list), sparse=True) -## if not sparse: -## M = M.dense_matrix() - -## return M(entries) - -## def sparse_relation_matrix_wtk_g0n(M, field, sign=0): -## r""" -## Create the sparse relation matrix over $\Q$ for Manin symbols of -## given weight on $\Gamma_0(N)$, with given sign. - -## INPUT: -## M -- manin_symbols.ManinSymbolList -## field -- base field -## weight -- the weight, an integer > 2 -## sign -- element of [-1,0,1] - -## OUTPUT: -## A -- a SparseMatrix that gives the 2-term and 3-term relations -## between Manin symbols. - -## MORE DETAILS: -## \begin{enumerate} -## \item Create an empty sparse matrix. - -## \item Let $S = [0,-1; 1,0]$, $T = [0,-1; 1,-1]$, $I = [-1,0; 0,1]$. - -## \item Enter the $T$ relations: -## $$ x + x*T = 0 $$ -## Remove $x$ and $x T$ from reps to consider. - -## \item If sign $\neq 0$, enter the I relations: -## $$ -## x + sign x I = 0. -## $$ - -## \item Enter the $S$ relations in the matrix: -## $$ -## x + x S + x S^2 = 0 -## $$ -## by putting 1's at cols corresponding to $x$, $x S$, and $x S^2$. -## Remove x from list of reps to consider. -## \end{enumerate} -## """ -## weight = M.weight() -## if not (isinstance(weight, int) and weight > 2): -## raise TypeError, "weight must be an int > 2" - -## ZERO = field(0) -## ONE = field(1) -## TWO = field(2) - -## # This will be a dict of the entries of the sparse matrix, where -## # the notation is entries[(i,j)]=x. -## entries = {} - -## # The current row -## row = 0 - -## # The list of Manin symbol triples (i,u,v) -## n = len(M) - -## ## The S relations -## already_seen= set([]) -## for i in range(n): -## if i in already_seen: -## continue -## j, s = M.apply_S(i) -## already_seen.add(j) -## if i != j: -## entries[(row,i)] = ONE -## entries[(row,j)] = field(s) -## else: -## entries[(row,i)] = ONE+field(s) -## row += 1 -## number_of_S_relations = row -## misc.verbose("There were %s S relations"%(number_of_S_relations)) -## cnt = row -## ## The I relations -## if sign != 0: -## SIGN = field(sign) -## already_seen= set([]) -## for i in range(n): -## if i in already_seen: -## continue -## j, s = M.apply_I(i) -## already_seen.add(j) -## if i != j: -## entries[(row,i)] = ONE -## entries[(row,j)] = -SIGN*field(s) -## else: -## entries[(row,i)] = ONE-SIGN*field(s) -## row += 1 -## number_of_I_relations = row - number_of_S_relations -## misc.verbose("There were %s I relations"%(number_of_I_relations)) -## cnt = row - -## ## The T relations -## already_seen = set([]) -## for i in range(n): -## if i in already_seen: -## continue -## iT_plus_iTT = M.apply_T(i) + M.apply_TT(i) -## v = {i:ONE} -## for j, s in iT_plus_iTT: -## if j in v: -## v[j] += field(s) -## else: -## v[j] = field(s) -## for j in v.keys(): -## entries[(row, j)] = v[j] -## row += 1 - From 5a1da0e88be4e121c47d50d6abef6fae215b67ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Sep 2018 17:31:08 +0200 Subject: [PATCH 220/264] remove the deprecated coxeter_matrix + pep cleanup --- .../combinat/root_system/coxeter_matrix.py | 137 ++++++++---------- 1 file changed, 62 insertions(+), 75 deletions(-) diff --git a/src/sage/combinat/root_system/coxeter_matrix.py b/src/sage/combinat/root_system/coxeter_matrix.py index 97af4010bf8..9e797061546 100644 --- a/src/sage/combinat/root_system/coxeter_matrix.py +++ b/src/sage/combinat/root_system/coxeter_matrix.py @@ -1,7 +1,7 @@ """ Coxeter Matrices """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 Mike Hansen , # 2015 Travis Scrimshaw # 2015 Jean-Philippe Labbe @@ -15,8 +15,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from six import add_metaclass from sage.misc.cachefunc import cached_method @@ -203,7 +203,7 @@ def __classcall_private__(cls, data=None, index_set=None, coxeter_type=None, if index_set: index_set = tuple(index_set) else: - index_set = tuple(range(1,n+1)) + index_set = tuple(range(1, n + 1)) if len(set(index_set)) != n: raise ValueError("the given index set is not valid") @@ -247,9 +247,9 @@ def __init__(self, parent, data, coxeter_type, index_set): 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)} + 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): @@ -328,7 +328,7 @@ def _from_graph(cls, graph, coxeter_type_check): [2 4 1 3] [2 2 3 1] - sage: G=Graph() + sage: G = Graph() sage: G.add_edge([0,1,oo]) sage: CoxeterMatrix(G) [ 1 -1] @@ -414,7 +414,7 @@ def samples(self, finite=None, affine=None, crystallographic=None, higher_rank=N Coxeter types, as well as typical representatives of the infinite families. - Here the ``higher_rank`` term denotes non-finite, non-affine, + 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. @@ -485,7 +485,7 @@ def samples(self, finite=None, affine=None, crystallographic=None, higher_rank=N sage: CoxeterMatrix.samples(crystallographic=False) [ - [1 3 2 2] + [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], @@ -554,15 +554,15 @@ def _samples(self): [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]]] + 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]]] + 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]], @@ -626,7 +626,7 @@ def __reduce__(self): def _repr_(self): """ String representation of the Coxeter matrix. - + EXAMPLES:: sage: CM = CoxeterMatrix(['A',3]); CM @@ -658,7 +658,7 @@ def _repr_option(self, key): def _latex_(self): r""" Latex representation of the Coxeter matrix. - + EXAMPLES:: sage: CM = CoxeterMatrix(['A',3]) @@ -671,7 +671,6 @@ def _latex_(self): """ return self._matrix._latex_() - def __iter__(self): """ Return an iterator for the rows of the Coxeter matrix. @@ -690,7 +689,7 @@ def __getitem__(self, key): 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'] @@ -718,7 +717,7 @@ def __hash__(self): sage: CM.__hash__() -506719298606843492 # 64-bit -1917568612 # 32-bit - """ + """ return hash(self._matrix) def __eq__(self, other): @@ -881,12 +880,15 @@ def coxeter_graph(self): """ n = self.rank() I = self.index_set() - val = lambda x: infinity if x == -1 else x + + def val(x): + return 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]]) + if self._matrix[i, j] not in [1, 2]], + format='list_of_edges') G.add_vertices(I) - return G.copy(immutable = True) + return G.copy(immutable=True) def is_simply_laced(self): """ @@ -910,7 +912,7 @@ def is_crystallographic(self): Return whether ``self`` is crystallographic. A Coxeter matrix is crystallographic if all non-diagonal entries - are either 2, 4, or 6. + are either 2, 3, 4, or 6. EXAMPLES:: @@ -979,7 +981,8 @@ def is_affine(self): ##################################################################### -## Type check functions +# Type check functions + def recognize_coxeter_type_from_matrix(coxeter_matrix, index_set): """ @@ -1110,47 +1113,49 @@ def recognize_coxeter_type_from_matrix(coxeter_matrix, index_set): r = S.num_verts() # Handle the special cases first if r == 1: - types.append(CoxeterType(['A',1]).relabel({1: S.vertices()[0]})) + types.append(CoxeterType(['A', 1]).relabel({1: S.vertices()[0]})) continue - if r == 2: # Type B2, G2, or I_2(p) + 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(['A',2]) + if e == 3: # Can't be 2 because it is connected + ct = CoxeterType(['A', 2]) elif e == 4: - ct = CoxeterType(['B',2]) + ct = CoxeterType(['B', 2]) elif e == 6: - 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]) + 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]})) + 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]})) + types.append(ct.relabel({0: S.vertices()[0], + 1: S.vertices()[1]})) continue - test = [['A',r], ['B',r], ['A',r-1,1]] + 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]) + 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]] + 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]) + test.append(['F', 4, 1]) + test.append(['D', r - 1, 1]) if r == 6: - test.append(['E',6]) + test.append(['E', 6]) elif r == 7: - test += [['E',7], ['E',6,1]] + test += [['E', 7], ['E', 6, 1]] elif r == 8: - test += [['E',8], ['E',7,1]] + test += [['E', 8], ['E', 7, 1]] elif r == 9: - test.append(['E',8,1]) + test.append(['E', 8, 1]) found = False for ct in test: @@ -1167,7 +1172,8 @@ def recognize_coxeter_type_from_matrix(coxeter_matrix, index_set): return CoxeterType(types) ##################################################################### -## Other functions +# Other functions + def check_coxeter_matrix(m): """ @@ -1216,8 +1222,8 @@ def check_coxeter_matrix(m): 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]: + 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: @@ -1226,6 +1232,7 @@ def check_coxeter_matrix(m): 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. @@ -1244,25 +1251,5 @@ def coxeter_matrix_as_function(t): [2 3 1 3] [2 2 3 1] """ - t = CartanType(t) - m = t.coxeter_matrix() + m = CartanType(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) - From 4e33b093606891c827d508fdb05e1e5a9433b7e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Sep 2018 17:40:05 +0200 Subject: [PATCH 221/264] remove one deprecated method in crystals --- src/sage/combinat/crystals/alcove_path.py | 52 +++++++---------------- 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/src/sage/combinat/crystals/alcove_path.py b/src/sage/combinat/crystals/alcove_path.py index d7f87284979..b528164c0e0 100644 --- a/src/sage/combinat/crystals/alcove_path.py +++ b/src/sage/combinat/crystals/alcove_path.py @@ -456,26 +456,6 @@ def vertices(self): return [ [] ] + [i[1] for i in l] - def digraph_fast(self, depth=None): - r""" - Return the crystal :class:`graph ` with maximum depth - ``depth`` deep starting at the module generator. - - Deprecated in :trac:`19625`. - - EXAMPLES:: - - sage: crystals.AlcovePaths(['A',2], [1,1]).digraph_fast(depth=3) - doctest:...: DeprecationWarning: digraph_fast is deprecated. Use digraph instead. - See http://trac.sagemath.org/19625 for details. - Digraph on 7 vertices - """ - from sage.misc.superseded import deprecation - deprecation(19625, 'digraph_fast is deprecated. Use digraph instead.') - if not self._highest_weight_crystal: - return super(CrystalOfAlcovePaths, self).digraph() - return super(CrystalOfAlcovePaths, self).digraph(depth=depth) - class CrystalOfAlcovePathsElement(ElementWrapper): """ @@ -571,13 +551,13 @@ def is_admissible(self): else: successors = 'quantum_bruhat_successors' - #start at the identity + # start at the identity w = W.one() for i in self: - t = prod( [ s[j] for j in i.root.associated_reflection() ] ) + t = prod([s[j] for j in i.root.associated_reflection()]) successor = w * t if successor not in getattr(w, successors)(): - return False + return False w = successor return True @@ -832,12 +812,12 @@ def _folding_data(self, i): """ Parent = self.parent() - #self.value contains the admissible sequence as a tuple of Element + # self.value contains the admissible sequence as a tuple of Element finite_cartan_type = Parent._finite_cartan_type # bool J = list(self.value) - #NOTE: R is a RootsWithHeight object and NOT a RootSystem object + # NOTE: R is a RootsWithHeight object and NOT a RootSystem object R = Parent._R weight = Parent.weight @@ -852,16 +832,16 @@ def _folding_data(self, i): max_height_Beta = weight.scalar(Beta.associated_coroot()) - if len(J) == 0: - for k in range( max_height_Beta ) : + if not J: + for k in range(max_height_Beta): x = R(Beta, k) - signs[x]=self._sign(Beta) + signs[x] = self._sign(Beta) signs['infinity'] = self._sign(Beta) - elif len(J) > 0 : - #NOTE: we assume J is sorted by order on Element of RootsWithHeight + else: + # NOTE: we assume J is sorted by order on Element of RootsWithHeight - for k in range( max_height_Beta ): + for k in range(max_height_Beta): x = R(Beta, k) if x <= J[0]: signs[x] = self._sign(Beta) @@ -873,7 +853,6 @@ def _folding_data(self, i): max_height_Beta = weight.scalar( (sign_Beta * Beta).associated_coroot()) - # some optimization so we don't initialize too many objects # range(c1,c2) can be replaced by range(max_height_Beta) but it # checks unnecessary extra things @@ -884,9 +863,9 @@ def _folding_data(self, i): else: c2 = min (max_height_Beta, J[j+1]._cmp_v[0]*max_height_Beta + 1) - for k in range(c1,c2): + for k in range(c1, c2): - x=R( sign_Beta * Beta , k) + x = R( sign_Beta * Beta , k) if ( ( j < len(J) - 1 and J[j] < x <= J[j+1] ) or @@ -894,8 +873,8 @@ def _folding_data(self, i): ): signs[x] = sign_Beta - signs['infinity'] = sign_Beta # tail sign tells something about last step - # in g_alpha + signs['infinity'] = sign_Beta + # tail sign tells something about last step in g_alpha if finite_cartan_type and i == 0: signs = {x: -signs[x] for x in signs} @@ -2038,4 +2017,3 @@ def _test_with_lspaths_crystal(cartan_type, weight, depth=10): G2 = C.digraph(subset=C.subcrystal(max_depth=depth, direction='lower')) return G1.is_isomorphic(G2, edge_labels=True) - From 95eac6596c735fd3f38425beb8b11476dac70339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Sep 2018 17:55:51 +0200 Subject: [PATCH 222/264] remove deprecate argument "use_eclib" in padic elliptic curves --- src/sage/schemes/elliptic_curves/padics.py | 24 ++++++---------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/padics.py b/src/sage/schemes/elliptic_curves/padics.py index 87c79c11063..0d950fcc87f 100644 --- a/src/sage/schemes/elliptic_curves/padics.py +++ b/src/sage/schemes/elliptic_curves/padics.py @@ -69,7 +69,7 @@ def __check_padic_hypotheses(self, p): return p -def _normalize_padic_lseries(self, p, normalize, use_eclib, implementation, precision): +def _normalize_padic_lseries(self, p, normalize, implementation, precision): r""" Normalize parameters for :meth:`padic_lseries`. @@ -81,15 +81,6 @@ def _normalize_padic_lseries(self, p, normalize, use_eclib, implementation, prec sage: u == v True """ - if use_eclib is not None: - from sage.misc.superseded import deprecation - deprecation(812,"Use the option 'implementation' instead of 'use_eclib'") - if implementation == 'pollackstevens': - raise ValueError - if use_eclib: - implementation = 'eclib' - else: - implementation = 'sage' if implementation == 'eclib': if normalize is None: normalize = "L_ratio" @@ -108,7 +99,7 @@ def _normalize_padic_lseries(self, p, normalize, use_eclib, implementation, prec return (p, normalize, implementation, precision) @cached_method(key=_normalize_padic_lseries) -def padic_lseries(self, p, normalize = None, use_eclib = None, implementation = 'eclib', precision = None): +def padic_lseries(self, p, normalize = None, implementation = 'eclib', precision = None): r""" Return the `p`-adic `L`-series of self at `p`, which is an object whose approx method computes @@ -117,17 +108,14 @@ def padic_lseries(self, p, normalize = None, use_eclib = None, implementation = INPUT: + - ``p`` -- prime - - ``p`` - prime - - - ``normalize`` - 'L_ratio' (default), 'period' or 'none'; + - ``normalize`` -- 'L_ratio' (default), 'period' or 'none'; this is describes the way the modular symbols are normalized. See modular_symbol for more details. - - ``use_eclib`` - deprecated, use ``implementation`` instead - - - ``implementation`` - 'eclib' (default), 'sage', 'pollackstevens'; + - ``implementation`` -- 'eclib' (default), 'sage', 'pollackstevens'; Whether to use John Cremona's eclib, the Sage implementation, or Pollack-Stevens' implementation of overconvergent modular symbols. @@ -207,7 +195,7 @@ def padic_lseries(self, p, normalize = None, use_eclib = None, implementation = O(11^0) """ p, normalize, implementation, precision = self._normalize_padic_lseries(p,\ - normalize, use_eclib, implementation, precision) + normalize, implementation, precision) if implementation in ['sage', 'eclib']: if self.ap(p) % p != 0: From 4eded0c2ed460c633795c2d44b9e9fa7236e66f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Sep 2018 19:20:03 +0200 Subject: [PATCH 223/264] fix import --- src/sage/combinat/root_system/all.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/root_system/all.py b/src/sage/combinat/root_system/all.py index 93dc71bb6a1..0f510bc9916 100644 --- a/src/sage/combinat/root_system/all.py +++ b/src/sage/combinat/root_system/all.py @@ -8,7 +8,7 @@ from .cartan_type import CartanType from .dynkin_diagram import DynkinDiagram from .cartan_matrix import CartanMatrix -from .coxeter_matrix import CoxeterMatrix, coxeter_matrix +from .coxeter_matrix import CoxeterMatrix from .coxeter_type import CoxeterType from .root_system import RootSystem, WeylDim lazy_import('sage.combinat.root_system.weyl_group', ['WeylGroup', From f99d86fb91ab5e9e4f5661d9328f0d1698aec890 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Tue, 11 Sep 2018 19:01:53 +0100 Subject: [PATCH 224/264] use $MAKE, not make, in Makefile --- build/pkgs/rubiks/package-version.txt | 2 +- build/pkgs/rubiks/patches/Makefile.patch | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 build/pkgs/rubiks/patches/Makefile.patch diff --git a/build/pkgs/rubiks/package-version.txt b/build/pkgs/rubiks/package-version.txt index c306d6b96de..3b110567fa5 100644 --- a/build/pkgs/rubiks/package-version.txt +++ b/build/pkgs/rubiks/package-version.txt @@ -1 +1 @@ -20070912.p19 +20070912.p20 diff --git a/build/pkgs/rubiks/patches/Makefile.patch b/build/pkgs/rubiks/patches/Makefile.patch new file mode 100644 index 00000000000..954fd0c0d2b --- /dev/null +++ b/build/pkgs/rubiks/patches/Makefile.patch @@ -0,0 +1,18 @@ +diff -ruN rubiks-20070912/Makefile rubiks-20070912.new/Makefile +--- rubiks-20070912/Makefile 2018-09-10 18:20:18.923991000 +0100 ++++ rubiks-20070912.new/Makefile 2018-09-10 18:20:33.203529000 +0100 +@@ -5,12 +5,12 @@ + + all clean: + for dir in $(DIRS); do \ +- (cd $${dir} && make $@)\ ++ (cd $${dir} && $(MAKE) $@)\ + done + + distclean: clean + for dir in $(DIRS); do \ +- (cd $${dir} && make distclean)\ ++ (cd $${dir} && $(MAKE) distclean)\ + done + + install: all From 51c34d260075b066954eb49759e5a822075095dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Sep 2018 20:33:55 +0200 Subject: [PATCH 225/264] trac 26248 fix doctests --- src/sage/schemes/elliptic_curves/padics.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/padics.py b/src/sage/schemes/elliptic_curves/padics.py index 0d950fcc87f..fa19ee352c1 100644 --- a/src/sage/schemes/elliptic_curves/padics.py +++ b/src/sage/schemes/elliptic_curves/padics.py @@ -61,7 +61,7 @@ def __check_padic_hypotheses(self, p): """ p = rings.Integer(p) if not p.is_prime(): - raise ValueError("p = (%s) must be prime"%p) + raise ValueError("p = (%s) must be prime" % p) if p == 2: raise ValueError("p must be odd") if self.conductor() % p == 0 or self.ap(p) % p == 0: @@ -76,8 +76,8 @@ def _normalize_padic_lseries(self, p, normalize, implementation, precision): TESTS:: sage: from sage.schemes.elliptic_curves.padics import _normalize_padic_lseries - sage: u = _normalize_padic_lseries(None, 5, None, None, 'sage', 10) - sage: v = _normalize_padic_lseries(None, 5, "L_ratio", None, 'sage', 10) + sage: u = _normalize_padic_lseries(None, 5, None, 'sage', 10) + sage: v = _normalize_padic_lseries(None, 5, "L_ratio", 'sage', 10) sage: u == v True """ From a6c305826d339ad22036acb74277e260465fc71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Sep 2018 21:49:08 +0200 Subject: [PATCH 226/264] py3: fix doctest in iterator of reflection group --- src/sage/combinat/root_system/reflection_group_c.pyx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/root_system/reflection_group_c.pyx b/src/sage/combinat/root_system/reflection_group_c.pyx index a1c4ef54ac9..4cbb37e67ef 100644 --- a/src/sage/combinat/root_system/reflection_group_c.pyx +++ b/src/sage/combinat/root_system/reflection_group_c.pyx @@ -340,20 +340,22 @@ cdef class Iterator(object): 20% faster. It yields indeed all elements in the group rather than applying a given function. + The output order is not deterministic. + EXAMPLES:: sage: from sage.combinat.root_system.reflection_group_c import Iterator sage: W = CoxeterGroup(['B',2], implementation="permutation") sage: I = Iterator(W, W.number_of_reflections()) - sage: list(I.iter_parabolic()) + sage: sorted(I.iter_parabolic()) [(), - (1,3)(2,6)(5,7), (2,8)(3,7)(4,6), + (1,3)(2,6)(5,7), (1,3,5,7)(2,8,6,4), (1,5)(2,4)(6,8), - (1,7,5,3)(2,4,6,8), (1,5)(2,6)(3,7)(4,8), - (1,7)(3,5)(4,8)] + (1,7)(3,5)(4,8), + (1,7,5,3)(2,4,6,8)] """ cdef int i,j cdef list coset_reps From eee6e0405131530a3b90005dbea82d232d01cee9 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Tue, 11 Sep 2018 22:19:41 +0100 Subject: [PATCH 227/264] do not mix c++ with CFLAGS, and SunOS fix --- build/pkgs/flint/package-version.txt | 2 +- build/pkgs/flint/patches/Makefile.subdirs.patch | 13 +++++++++++++ .../pkgs/flint/patches/linking_SunOS_FreeBSD.patch | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 build/pkgs/flint/patches/Makefile.subdirs.patch diff --git a/build/pkgs/flint/package-version.txt b/build/pkgs/flint/package-version.txt index 2b89f38efd6..c3ca6ae20b1 100644 --- a/build/pkgs/flint/package-version.txt +++ b/build/pkgs/flint/package-version.txt @@ -1 +1 @@ -2.5.2.p2 +2.5.2.p3 diff --git a/build/pkgs/flint/patches/Makefile.subdirs.patch b/build/pkgs/flint/patches/Makefile.subdirs.patch new file mode 100644 index 00000000000..0da3729bf20 --- /dev/null +++ b/build/pkgs/flint/patches/Makefile.subdirs.patch @@ -0,0 +1,13 @@ +diff --git a/Makefile.subdirs b/Makefile.subdirs +index ec05fb0..0a54772 100644 +--- a/Makefile.subdirs ++++ b/Makefile.subdirs +@@ -85,7 +85,7 @@ $(BUILD_DIR)/test/%$(EXEEXT): test/%.c $(BUILD_DIR)/../../test_helpers.o + $(QUIET_CC) $(CC) $(CFLAGS) $(INCS) $< $(BUILD_DIR)/../../test_helpers.o -o $@ $(LIBS) -MMD -MP -MF $@.d -MT "$@" -MT "$@.d" + + $(BUILD_DIR)/test/%$(EXEEXT): test/%.cpp $(BUILD_DIR)/../../test_helpers.o +- $(QUIET_CC) $(CXX) $(CFLAGS) $(INCS) $< $(BUILD_DIR)/../../test_helpers.o -o $@ $(LIBS) -MMD -MP -MF $@.d -MT "$@" -MT "$@.d" ++ $(QUIET_CC) $(CXX) $(CXXFLAGS) $(INCS) $< $(BUILD_DIR)/../../test_helpers.o -o $@ $(LIBS) -MMD -MP -MF $@.d -MT "$@" -MT "$@.d" + + %_RUN: % + @$< diff --git a/build/pkgs/flint/patches/linking_SunOS_FreeBSD.patch b/build/pkgs/flint/patches/linking_SunOS_FreeBSD.patch index de53ccf2bf6..d051b37656d 100644 --- a/build/pkgs/flint/patches/linking_SunOS_FreeBSD.patch +++ b/build/pkgs/flint/patches/linking_SunOS_FreeBSD.patch @@ -19,7 +19,7 @@ index 424ab0a..959a650 100755 # sometimes LDCONFIG is not to be found in the path. Look at some common places. case "$OS" in - MINGW*|CYGWIN*|Darwin) -+ MINGW*|CYGWIN*|Darwin|FreeBSD) ++ MINGW*|CYGWIN*|Darwin|FreeBSD|SunOS) LDCONFIG="true";; *) if [ -z "$LDCONFIG" ]; then From 21e2f640ad8d4f5b728993ab5e9774bf4a5046f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Sep 2018 08:38:16 +0200 Subject: [PATCH 228/264] trac 26242 correct the option "sort" default in the doc --- src/sage/graphs/generic_graph.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 6ce9f0a4fea..ca305832aeb 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -11030,24 +11030,24 @@ def edge_iterator(self, vertices=None, labels=True, ignore_direction=False): def edges_incident(self, vertices=None, labels=True, sort=False): """ - Returns incident edges to some vertices. + Return incident edges to some vertices. - If ``vertices` is a vertex, then it returns the list of edges incident to - that vertex. If ``vertices`` is a list of vertices then it returns the - list of all edges adjacent to those vertices. If ``vertices`` - is None, returns a list of all edges in graph. For digraphs, only - lists outward edges. + If ``vertices` is a vertex, then it returns the list of edges + incident to that vertex. If ``vertices`` is a list of vertices + then it returns the list of all edges adjacent to those + vertices. If ``vertices`` is ``None``, it returns a list of all edges + in graph. For digraphs, only lists outward edges. INPUT: - - ``vertices`` - object (default: None) - a vertex, a list of vertices - or None. + - ``vertices`` -- object (default: ``None``) - a vertex, a list + of vertices or ``None``. - - ``labels`` - bool (default: True) - if False, each edge is a tuple - (u,v) of vertices. - - - ``sort`` - bool (default: True) - if True the returned list is sorted. + - ``labels`` -- boolean (default: ``True``) - if ``False``, each + edge is a tuple (u,v) of vertices. + - ``sort`` -- boolean (default: ``False``) - if ``True`` the + returned list is sorted. EXAMPLES:: From 46af2a4293b8f587370cbc915bca785c7ec3daa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Sep 2018 09:23:11 +0200 Subject: [PATCH 229/264] remove other deprecations in crystals --- src/sage/combinat/crystals/all.py | 57 ------------------- .../combinat/crystals/kirillov_reshetikhin.py | 21 +------ .../combinat/crystals/monomial_crystals.py | 13 +---- .../crystals/tensor_product_element.pyx | 41 +------------ 4 files changed, 4 insertions(+), 128 deletions(-) diff --git a/src/sage/combinat/crystals/all.py b/src/sage/combinat/crystals/all.py index 2efcad9ef12..17a3e817694 100644 --- a/src/sage/combinat/crystals/all.py +++ b/src/sage/combinat/crystals/all.py @@ -6,60 +6,3 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.combinat.crystals', 'catalog', 'crystals') - -lazy_import('sage.combinat.crystals.letters', - 'CrystalOfLetters', - deprecation=(15882, "this is being removed from the global namespace. Use crystals.Letters instead")) - -lazy_import('sage.combinat.crystals.fast_crystals', - 'FastCrystal', - deprecation=(15882, "this is being removed from the global namespace. Use crystals.FastRankTwo instead")) - -lazy_import('sage.combinat.crystals.highest_weight_crystals', - 'HighestWeightCrystal', - deprecation=(15882, "this is being removed from the global namespace. Use crystals.HighestWeight instead")) - -lazy_import('sage.combinat.crystals.kyoto_path_model', - 'KyotoPathModel', - deprecation=(15882, "this is being removed from the global namespace. Use crystals.KyotoPathModel instead")) - -lazy_import('sage.combinat.crystals.direct_sum', - 'DirectSumOfCrystals', - deprecation=(15882, "this is being removed from the global namespace. Use crystals.DirectSum instead")) - -lazy_import('sage.combinat.crystals.tensor_product', - ['CrystalOfTableaux', 'TensorProductOfCrystals'], - deprecation=(15882, "this is being removed from the global namespace. Use crystals. instead")) - -lazy_import('sage.combinat.crystals.spins', - ['CrystalOfSpins', 'CrystalOfSpinsPlus', 'CrystalOfSpinsMinus'], - deprecation=(15882, "this is being removed from the global namespace. Use crystals. instead")) - -lazy_import('sage.combinat.crystals.affine', - ['AffineCrystalFromClassical', 'AffineCrystalFromClassicalAndPromotion'], - deprecation=(15882, "this is being removed from the global namespace. Use crystals. instead")) - -lazy_import('sage.combinat.crystals.elementary_crystals', - ['TCrystal', 'RCrystal', 'ElementaryCrystal', 'ComponentCrystal'], - deprecation=(15882, "this is being removed from the global namespace. Use crystals.elementary. instead")) - -lazy_import('sage.combinat.crystals.kirillov_reshetikhin', - 'KirillovReshetikhinCrystal', - deprecation=(15882, "this is being removed from the global namespace. Use crystals.KirillovResetikhin instead")) - -lazy_import('sage.combinat.crystals.littelmann_path', - ['CrystalOfLSPaths', 'CrystalOfProjectedLevelZeroLSPaths'], - deprecation=(15882, "this is being removed from the global namespace. Use crystals. instead")) - -lazy_import('sage.combinat.crystals.generalized_young_walls', - ['InfinityCrystalOfGeneralizedYoungWalls', 'CrystalOfGeneralizedYoungWalls'], - deprecation=(15882, "this is being removed from the global namespace. Use crystals. instead")) - -lazy_import('sage.combinat.crystals.monomial_crystals', - ['InfinityCrystalOfNakajimaMonomials', 'CrystalOfNakajimaMonomials'], - deprecation=(15882, "this is being removed from the global namespace. Use crystals. instead")) - -lazy_import('sage.combinat.crystals.infinity_crystals', - 'InfinityCrystalOfTableaux', - deprecation=(15882, "this is being removed from the global namespace. Use crystals.infinity.Tableaux instead")) - diff --git a/src/sage/combinat/crystals/kirillov_reshetikhin.py b/src/sage/combinat/crystals/kirillov_reshetikhin.py index 57b859650de..820448c37a4 100644 --- a/src/sage/combinat/crystals/kirillov_reshetikhin.py +++ b/src/sage/combinat/crystals/kirillov_reshetikhin.py @@ -15,7 +15,7 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ #**************************************************************************** # Acknowledgment: most of the design and implementation of this # library is heavily inspired from MuPAD-Combinat. @@ -952,25 +952,6 @@ def promotion_on_highest_weight_vector(self, b): """ return self.from_pm_diagram_to_highest_weight_vector(self.from_highest_weight_vector_to_pm_diagram(b).sigma()) - def promotion_on_highest_weight_vectors(self): - """ - Calculates promotion on `{2,3,...,n}` highest weight vectors. - - EXAMPLES:: - - sage: K = crystals.KirillovReshetikhin(['D',4,1], 2,2) - sage: T = K.classical_decomposition() - sage: hw = [ b for b in T if all(b.epsilon(i)==0 for i in [2,3,4]) ] - sage: f = K.promotion_on_highest_weight_vectors() - doctest:...: DeprecationWarning: Call self.promotion_on_highest_weight_vector directly - See http://trac.sagemath.org/22429 for details. - sage: f(hw[0]) - [[1, 2], [-2, -1]] - """ - from sage.misc.superseded import deprecation - deprecation(22429, "Call self.promotion_on_highest_weight_vector directly") - return self.promotion_on_highest_weight_vector - def from_highest_weight_vector_to_pm_diagram(self, b): r""" This gives the bijection between an element ``b`` in the classical diff --git a/src/sage/combinat/crystals/monomial_crystals.py b/src/sage/combinat/crystals/monomial_crystals.py index 4c47ac4adc8..5cf279fa369 100644 --- a/src/sage/combinat/crystals/monomial_crystals.py +++ b/src/sage/combinat/crystals/monomial_crystals.py @@ -830,7 +830,7 @@ def _normalize_c(c, n): return c @staticmethod - def __classcall_private__(cls, ct, c=None, use_Y=None): + def __classcall_private__(cls, ct, c=None): r""" Normalize input to ensure a unique representation. @@ -846,20 +846,11 @@ def __classcall_private__(cls, ct, c=None, use_Y=None): sage: M is M1 is M2 True """ - if use_Y is not None: - from sage.misc.superseded import deprecation - deprecation(18895, 'use_Y is deprecated; use the set_variables() method instead.') - else: - use_Y = True - cartan_type = CartanType(ct) n = len(cartan_type.index_set()) c = InfinityCrystalOfNakajimaMonomials._normalize_c(c, n) M = super(InfinityCrystalOfNakajimaMonomials, cls).__classcall__(cls, cartan_type, c) - if not use_Y: - M.set_variables('A') - else: - M.set_variables('Y') + M.set_variables('Y') return M def __init__(self, ct, c, category=None): diff --git a/src/sage/combinat/crystals/tensor_product_element.pyx b/src/sage/combinat/crystals/tensor_product_element.pyx index de9027a6abe..76403efd53e 100644 --- a/src/sage/combinat/crystals/tensor_product_element.pyx +++ b/src/sage/combinat/crystals/tensor_product_element.pyx @@ -27,7 +27,7 @@ AUTHORS: # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ #**************************************************************************** from __future__ import print_function, absolute_import @@ -88,45 +88,6 @@ cdef class ImmutableListWithParent(ClonableArray): self._is_immutable = True self._hash = 0 - def reversed(self): - """ - Return a copy of ``self`` but in the reversed order. - - EXAMPLES:: - - sage: b = crystals.Tableaux(['A',2], shape=[2,1]).module_generators[0] - sage: list(b) - [2, 1, 1] - sage: list(b.reversed()) - doctest:warning - ... - DeprecationWarning: reversed() is deprecated; use reversed(self) instead - See http://trac.sagemath.org/22642 for details. - [1, 1, 2] - """ - from sage.misc.superseded import deprecation - deprecation(22642, 'reversed() is deprecated; use reversed(self) instead') - return type(self)(self._parent, list=list(reversed(self._list))) - - def set_index(self, k, value): - """ - Return a sibling of ``self`` obtained by setting the - `k^{th}` entry of self to value. - - EXAMPLES:: - - sage: b = crystals.Tableaux(['A',2], shape=[3]).module_generators[0] - sage: list(b.set_index(0, 2)) - doctest:warning - ... - DeprecationWarning: set_index is deprecated; use _set_index instead - See http://trac.sagemath.org/22642 for details. - [2, 1, 1] - """ - from sage.misc.superseded import deprecation - deprecation(22642, 'set_index is deprecated; use _set_index instead') - return self._set_index(int(k), value) - cpdef _set_index(self, k, value): r""" Return a sibling of ``self`` obtained by setting the From 5b521105c2852de1986750d542721bc890983541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Sep 2018 09:33:12 +0200 Subject: [PATCH 230/264] remove long-time deprecated stuff in calculus/var.pyx --- src/sage/calculus/var.pyx | 48 ++++++++------------------------------- 1 file changed, 10 insertions(+), 38 deletions(-) diff --git a/src/sage/calculus/var.pyx b/src/sage/calculus/var.pyx index 01cf6274bef..d30dc1bac83 100644 --- a/src/sage/calculus/var.pyx +++ b/src/sage/calculus/var.pyx @@ -26,10 +26,10 @@ def var(*args, **kwds): .. NOTE:: - The new variable is both returned and automatically injected - into the global namespace. If you need a symbolic variable in - library code, you must use either ``SR.var()`` - or ``SR.symbol()``. + The new variable is both returned and automatically injected + into the global namespace. If you need a symbolic variable in + library code, you must use either ``SR.var()`` + or ``SR.symbol()``. OUTPUT: @@ -115,34 +115,12 @@ def var(*args, **kwds): sage: parent(theta) Symbolic Ring - - TESTS:: - - sage: var('q',ns=False) - Traceback (most recent call last): - ... - NotImplementedError: The new (Pynac) symbolics are now the only symbolics; please do not use keyword `ns` any longer. - sage: q - Traceback (most recent call last): - ... - NameError: name 'q' is not defined - sage: var('q',ns=1) - doctest:...: DeprecationWarning: The new (Pynac) symbolics are now the only symbolics; please do not use keyword 'ns' any longer. - See http://trac.sagemath.org/6559 for details. - q """ - if len(args)==1: + if len(args) == 1: name = args[0] else: name = args G = globals() # this is the reason the code must be in Cython. - if 'ns' in kwds: - if kwds['ns']: - from sage.misc.superseded import deprecation - deprecation(6559, "The new (Pynac) symbolics are now the only symbolics; please do not use keyword 'ns' any longer.") - else: - raise NotImplementedError("The new (Pynac) symbolics are now the only symbolics; please do not use keyword `ns` any longer.") - kwds.pop('ns') v = SR.var(name, **kwds) if isinstance(v, tuple): for x in v: @@ -151,14 +129,13 @@ def var(*args, **kwds): G[repr(v)] = v return v + def function(s, *args, **kwds): r""" Create a formal symbolic function with the name *s*. INPUT: - - ``args`` - arguments to the function, if specified returns the new - function evaluated at the given arguments (deprecated as of :trac:`17447`) - ``nargs=0`` - number of arguments the function accepts, defaults to variable number of arguments, or 0 - ``latex_name`` - name used when printing in latex mode @@ -191,10 +168,10 @@ def function(s, *args, **kwds): .. NOTE:: - The new function is both returned and automatically injected - into the global namespace. If you use this function in library - code, it is better to use sage.symbolic.function_factory.function, - since it won't touch the global namespace. + The new function is both returned and automatically injected + into the global namespace. If you use this function in library + code, it is better to use sage.symbolic.function_factory.function, + since it will not touch the global namespace. EXAMPLES: @@ -368,11 +345,6 @@ def function(s, *args, **kwds): sage: B B """ - if args: - from sage.misc.superseded import deprecation - deprecation(17447, "Calling function('f',x) is deprecated. Use function('f')(x) instead.") - return function(s, **kwds)(*args) - G = globals() # this is the reason the code must be in Cython. v = new_function(s, **kwds) if isinstance(v, tuple): From 0380ffc1c044dfcea661a9c34316c6f8b7571179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Sep 2018 09:44:35 +0200 Subject: [PATCH 231/264] remove some deprecated stuff in elliptic curves --- src/sage/schemes/elliptic_curves/all.py | 1 - .../schemes/elliptic_curves/constructor.py | 25 +------- .../elliptic_curves/ell_rational_field.py | 64 +++---------------- 3 files changed, 11 insertions(+), 79 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/all.py b/src/sage/schemes/elliptic_curves/all.py index 8905a83ab36..001067d7421 100644 --- a/src/sage/schemes/elliptic_curves/all.py +++ b/src/sage/schemes/elliptic_curves/all.py @@ -22,7 +22,6 @@ EllipticCurve_from_c4c6, EllipticCurve_from_j, EllipticCurve_from_cubic, - EllipticCurve_from_plane_curve, EllipticCurves_with_good_reduction_outside_S) from sage.misc.lazy_import import lazy_import diff --git a/src/sage/schemes/elliptic_curves/constructor.py b/src/sage/schemes/elliptic_curves/constructor.py index 2aa3b20b3fb..79b50daec4a 100644 --- a/src/sage/schemes/elliptic_curves/constructor.py +++ b/src/sage/schemes/elliptic_curves/constructor.py @@ -1367,32 +1367,9 @@ def are_projectively_equivalent(P, Q, base_ring): return matrix(base_ring, [P, Q]).rank() < 2 -def EllipticCurve_from_plane_curve(C, P): - """ - Deprecated way to construct an elliptic curve. - - Use :meth:`~sage.schemes.elliptic_curves.jacobian.Jacobian` instead. - - EXAMPLES:: - - sage: R. = QQ[] - sage: C = Curve(x^3+y^3+z^3) - sage: P = C(1,-1,0) - sage: E = EllipticCurve_from_plane_curve(C,P); E # long time (3s on sage.math, 2013) - doctest:...: DeprecationWarning: use Jacobian(C) instead - See http://trac.sagemath.org/3416 for details. - Elliptic Curve defined by y^2 = x^3 - 27/4 over Rational Field - """ - from sage.misc.superseded import deprecation - deprecation(3416, 'use Jacobian(C) instead') - # Note: this function never used the rational point - from sage.schemes.elliptic_curves.jacobian import Jacobian - return Jacobian(C) - - def EllipticCurves_with_good_reduction_outside_S(S=[], proof=None, verbose=False): r""" - Returns a sorted list of all elliptic curves defined over `Q` + Return a sorted list of all elliptic curves defined over `Q` with good reduction outside the set `S` of primes. INPUT: diff --git a/src/sage/schemes/elliptic_curves/ell_rational_field.py b/src/sage/schemes/elliptic_curves/ell_rational_field.py index 0b6268bdc9d..1ea48888580 100644 --- a/src/sage/schemes/elliptic_curves/ell_rational_field.py +++ b/src/sage/schemes/elliptic_curves/ell_rational_field.py @@ -588,12 +588,6 @@ def pari_curve(self): """ Return the PARI curve corresponding to this elliptic curve. - INPUT: - - - ``prec`` -- Deprecated - - - ``factor`` -- Deprecated - EXAMPLES:: sage: E = EllipticCurve([0, 0, 1, -1, 0]) @@ -648,12 +642,6 @@ def pari_mincurve(self): Return the PARI curve corresponding to a minimal model for this elliptic curve. - INPUT: - - - ``prec`` -- Deprecated - - - ``factor`` -- Deprecated - EXAMPLES:: sage: E = EllipticCurve(RationalField(), ['1/3', '2/3']) @@ -1106,7 +1094,7 @@ def abelian_variety(self): """ return self.modular_symbol_space(sign=0).abelian_variety() - def _modular_symbol_normalize(self, sign, use_eclib, normalize, implementation): + def _modular_symbol_normalize(self, sign, normalize, implementation): r""" Normalize parameters for :meth:`modular_symbol`. @@ -1116,13 +1104,6 @@ def _modular_symbol_normalize(self, sign, use_eclib, normalize, implementation): sage: E.modular_symbol(implementation = 'eclib') is E.modular_symbol(implementation = 'eclib', normalize = 'L_ratio') True """ - if use_eclib is not None: - from sage.misc.superseded import deprecation - deprecation(20864, "Use the option 'implementation' instead of 'use_eclib'") - if use_eclib: - implementation = 'eclib' - else: - implementation = 'sage' if sign not in [1,-1]: raise ValueError("The sign of a modular symbol must be 1 or -1") sign = ZZ(sign) @@ -1135,7 +1116,7 @@ def _modular_symbol_normalize(self, sign, use_eclib, normalize, implementation): return (sign, normalize, implementation) @cached_method(key = _modular_symbol_normalize) - def modular_symbol(self, sign = +1, use_eclib = None, normalize = None, implementation = 'eclib'): + def modular_symbol(self, sign=+1, normalize=None, implementation='eclib'): r""" Return the modular symbol associated to this elliptic curve, with given sign. @@ -1144,8 +1125,6 @@ def modular_symbol(self, sign = +1, use_eclib = None, normalize = None, implemen - ``sign`` - +1 (default) or -1. - - ``use_eclib`` - Deprecated. Use the ``implementation`` parameter instead. - - ``normalize`` - (default: None); either 'L_ratio', 'period', or 'none' when ``implementation`` is 'sage'; ignored if ``implementation`` is ``eclib``. For 'L_ratio', the @@ -1271,12 +1250,11 @@ def modular_symbol(self, sign = +1, use_eclib = None, normalize = None, implemen Modular symbol with sign -1 over Rational Field attached to Elliptic Curve defined by y^2 + y = x^3 - x^2 - 10*x - 20 over Rational Field sage: [Mminus(1/i) for i in [1..11]] [0, 0, 1/2, 1/2, 0, 0, -1/2, -1/2, 0, 0, 0] - """ - sign, normalize, implementation = self._modular_symbol_normalize(sign, use_eclib, normalize, implementation) + sign, normalize, implementation = self._modular_symbol_normalize(sign, normalize, implementation) if implementation == 'eclib': M = ell_modular_symbols.ModularSymbolECLIB(self, sign) - else: # implementation == 'sage': + else: # implementation == 'sage': M = ell_modular_symbols.ModularSymbolSage(self, sign, normalize=normalize) return M @@ -3543,34 +3521,12 @@ def integral_short_weierstrass_model(self): Elliptic Curve defined by y^2 = x^3 - 11*x - 890 over Rational Field """ F = self.minimal_model().short_weierstrass_model() - _,_,_,A,B = F.ainvs() - for p in [2,3]: - e=min(A.valuation(p)/4,B.valuation(p)/6).floor() - A /= Integer(p**(4*e)) - B /= Integer(p**(6*e)) - return constructor.EllipticCurve([A,B]) - - # deprecated function replaced by integral_short_weierstrass_model, see trac 3974. - def integral_weierstrass_model(self): - r""" - Return a model of the form `y^2 = x^3 + ax + b` for this - curve with `a,b\in\ZZ`. - - Note that this function is deprecated, and that you should use - integral_short_weierstrass_model instead as this will be - disappearing in the near future. - - EXAMPLES:: - - sage: E = EllipticCurve('17a1') - sage: E.integral_weierstrass_model() #random - doctest:...: DeprecationWarning: integral_weierstrass_model is deprecated, use integral_short_weierstrass_model instead! - Elliptic Curve defined by y^2 = x^3 - 11*x - 890 over Rational Field - """ - from sage.misc.superseded import deprecation - deprecation(3974, "integral_weierstrass_model is deprecated, use integral_short_weierstrass_model instead!") - return self.integral_short_weierstrass_model() - + _, _, _, A, B = F.ainvs() + for p in [2, 3]: + e = min(A.valuation(p) / 4, B.valuation(p) / 6).floor() + A /= Integer(p**(4 * e)) + B /= Integer(p**(6 * e)) + return constructor.EllipticCurve([A, B]) def _generalized_congmod_numbers(self, M, invariant="both"): """ From 5d3d3a8efec63fb040cfbb48b9cb28142171db55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Sep 2018 10:00:26 +0200 Subject: [PATCH 232/264] more removal of deprecated in elliptic --- .../elliptic_curves/ell_rational_field.py | 16 ++++------------ src/sage/schemes/elliptic_curves/ell_torsion.py | 8 ++------ 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/ell_rational_field.py b/src/sage/schemes/elliptic_curves/ell_rational_field.py index 1ea48888580..a4d3b842e84 100644 --- a/src/sage/schemes/elliptic_curves/ell_rational_field.py +++ b/src/sage/schemes/elliptic_curves/ell_rational_field.py @@ -4025,7 +4025,7 @@ def _torsion_bound(self,number_of_places = 20): k += 1 return bound - def torsion_subgroup(self, algorithm=None): + def torsion_subgroup(self): """ Return the torsion subgroup of this elliptic curve. @@ -4061,13 +4061,11 @@ def torsion_subgroup(self, algorithm=None): try: return self.__torsion_subgroup except AttributeError: - # algorithm is deprecated: if not None, this will give a warning. - # deprecation(20219) - self.__torsion_subgroup = ell_torsion.EllipticCurveTorsionSubgroup(self, algorithm) + self.__torsion_subgroup = ell_torsion.EllipticCurveTorsionSubgroup(self) self.__torsion_order = self.__torsion_subgroup.order() return self.__torsion_subgroup - def torsion_points(self, algorithm=None): + def torsion_points(self): """ Return the torsion points of this elliptic curve as a sorted list. @@ -4088,10 +4086,6 @@ def torsion_points(self, algorithm=None): Torsion Subgroup isomorphic to Z/8 + Z/2 associated to the Elliptic Curve defined by y^2 = x^3 - 1386747*x + 368636886 over Rational Field - sage: T == E.torsion_subgroup(algorithm="doud") - True - sage: T == E.torsion_subgroup(algorithm="lutz_nagell") - True sage: E.torsion_points() [(-1293 : 0 : 1), (-933 : -29160 : 1), @@ -4140,9 +4134,7 @@ def torsion_points(self, algorithm=None): (244 : -3902 : 1), (244 : 3658 : 1)] """ - # algorithm is deprecated: if not None, this will give a warning. - # deprecation(20219) - return sorted(self.torsion_subgroup(algorithm).points()) + return sorted(self.torsion_subgroup().points()) @cached_method def root_number(self, p=None): diff --git a/src/sage/schemes/elliptic_curves/ell_torsion.py b/src/sage/schemes/elliptic_curves/ell_torsion.py index a665c2be5a4..58874554a24 100644 --- a/src/sage/schemes/elliptic_curves/ell_torsion.py +++ b/src/sage/schemes/elliptic_curves/ell_torsion.py @@ -22,7 +22,7 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ #***************************************************************************** from sage.misc.cachefunc import cached_method @@ -127,7 +127,7 @@ class EllipticCurveTorsionSubgroup(groups.AdditiveAbelianGroupWrapper): - Chris Wuthrich - initial implementation over number fields. - John Cremona - additional features and unification. """ - def __init__(self, E, algorithm=None): + def __init__(self, E): r""" Initialization function for EllipticCurveTorsionSubgroup class @@ -154,10 +154,6 @@ def __init__(self, E, algorithm=None): sage: T == loads(dumps(T)) # known bug, see http://trac.sagemath.org/sage_trac/ticket/11599#comment:7 True """ - if algorithm is not None: - from sage.misc.superseded import deprecation - deprecation(20219, "the keyword 'algorithm' is deprecated and no longer used") - self.__E = E self.__K = E.base_field() From 891c75835a433928c1365e472bc1893f7afdc576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Sep 2018 10:02:25 +0200 Subject: [PATCH 233/264] remove *args in symbolic functions --- src/sage/calculus/var.pyx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/sage/calculus/var.pyx b/src/sage/calculus/var.pyx index d30dc1bac83..e36d16817e8 100644 --- a/src/sage/calculus/var.pyx +++ b/src/sage/calculus/var.pyx @@ -130,7 +130,7 @@ def var(*args, **kwds): return v -def function(s, *args, **kwds): +def function(s, **kwds): r""" Create a formal symbolic function with the name *s*. @@ -358,8 +358,10 @@ def function(s, *args, **kwds): def clear_vars(): """ Delete all 1-letter symbolic variables that are predefined at - startup of Sage. Any one-letter global variables that are not - symbolic variables are not cleared. + startup of Sage. + + Any one-letter global variables that are not symbolic variables + are not cleared. EXAMPLES:: From 340cfaae05be4131ceae3a0f71fdf96ad865fb15 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Wed, 12 Sep 2018 09:49:11 +0100 Subject: [PATCH 234/264] do not touch SunOS here --- build/pkgs/flint/patches/linking_SunOS_FreeBSD.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/flint/patches/linking_SunOS_FreeBSD.patch b/build/pkgs/flint/patches/linking_SunOS_FreeBSD.patch index d051b37656d..de53ccf2bf6 100644 --- a/build/pkgs/flint/patches/linking_SunOS_FreeBSD.patch +++ b/build/pkgs/flint/patches/linking_SunOS_FreeBSD.patch @@ -19,7 +19,7 @@ index 424ab0a..959a650 100755 # sometimes LDCONFIG is not to be found in the path. Look at some common places. case "$OS" in - MINGW*|CYGWIN*|Darwin) -+ MINGW*|CYGWIN*|Darwin|FreeBSD|SunOS) ++ MINGW*|CYGWIN*|Darwin|FreeBSD) LDCONFIG="true";; *) if [ -z "$LDCONFIG" ]; then From f50893a1a6789cd042c501ca2826a76f039207e3 Mon Sep 17 00:00:00 2001 From: Vincent Klein Date: Wed, 12 Sep 2018 11:35:48 +0200 Subject: [PATCH 235/264] Trac #26258: py3, fix doctests in `enumerated_sets.py` --- src/sage/categories/enumerated_sets.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/sage/categories/enumerated_sets.py b/src/sage/categories/enumerated_sets.py index a46da87de20..2cf28359602 100644 --- a/src/sage/categories/enumerated_sets.py +++ b/src/sage/categories/enumerated_sets.py @@ -814,7 +814,7 @@ def _an_element_from_iterator(self): TESTS:: sage: super(Parent, C)._an_element_ - Cached version of + Cached version of """ it = iter(self) try: @@ -887,10 +887,10 @@ def map(self, f, name=None): Image of Symmetric group of order 3! as a permutation group by *.reduced_word() sage: R.cardinality() 6 - sage: R.list() - [[], [1], [2, 1], [1, 2], [2], [1, 2, 1]] - sage: [ r for r in R] - [[], [1], [2, 1], [1, 2], [2], [1, 2, 1]] + sage: sorted(R.list()) + [[], [1], [1, 2], [1, 2, 1], [2], [2, 1]] + sage: sorted([ r for r in R]) + [[], [1], [1, 2], [1, 2, 1], [2], [2, 1]] .. warning:: @@ -898,10 +898,10 @@ def map(self, f, name=None): repeated elements:: sage: P = SymmetricGroup(3) - sage: P.list() - [(), (1,2), (1,2,3), (1,3,2), (2,3), (1,3)] - sage: P.map(attrcall('length')).list() - [0, 1, 2, 2, 1, 3] + sage: sorted(P.list()) + [(), (2,3), (1,2), (1,2,3), (1,3,2), (1,3)] + sage: sorted(P.map(attrcall('length')).list()) + [0, 1, 1, 2, 2, 3] .. warning:: @@ -1018,9 +1018,10 @@ def rank(self): EXAMPLES:: sage: F = FiniteSemigroups().example(('a','b','c')) - sage: L = list(F); L - ['a', 'b', 'c', 'ac', 'ab', 'ba', 'bc', 'cb', 'ca', - 'acb', 'abc', 'bca', 'cba', 'bac', 'cab'] + sage: L = list(F) + sage: sorted(L) + ['a', 'ab', 'abc', 'ac', 'acb', 'b', 'ba', 'bac', + 'bc', 'bca', 'c', 'ca', 'cab', 'cb', 'cba'] sage: L[7].rank() 7 """ From cb37c1e5ed4a937c2794f80d0de07a4cfbfb30ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Sep 2018 11:50:31 +0200 Subject: [PATCH 236/264] trac 26256 fixing doctests --- src/sage/symbolic/expression_conversions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/symbolic/expression_conversions.py b/src/sage/symbolic/expression_conversions.py index dd8986ff756..f362241d96c 100644 --- a/src/sage/symbolic/expression_conversions.py +++ b/src/sage/symbolic/expression_conversions.py @@ -182,7 +182,7 @@ def __call__(self, ex=None): Traceback (most recent call last): ... NotImplementedError: composition - sage: c(function('f', x).diff(x)) + sage: c(function('f')(x).diff(x)) Traceback (most recent call last): ... NotImplementedError: derivative @@ -346,7 +346,7 @@ def derivative(self, ex, operator): TESTS:: sage: from sage.symbolic.expression_conversions import Converter - sage: a = function('f', x).diff(x); a + sage: a = function('f')(x).diff(x); a diff(f(x), x) sage: Converter().derivative(a, a.operator()) Traceback (most recent call last): @@ -521,7 +521,7 @@ def derivative(self, ex, operator): :: - sage: f = function('f', x) + sage: f = function('f')(x) sage: df = f.diff(x); df diff(f(x), x) sage: maxima(df) From ddafa36338450833a5cdd86eb3fecb16ad0bff0e Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Wed, 12 Sep 2018 12:13:49 +0000 Subject: [PATCH 237/264] Trac #18438: Always use system Python for build toolchain scripts written in Python. This avoids race conditions, especially during parallel builds, where the build toolchain might try to use Sage's Python to build a package while Sage's Python is also being (re)installed. Conversely, also ensure that sage-pip-install runs pip with Sage's Python and not the system Python. --- build/bin/sage-download-file | 2 +- build/bin/sage-flock | 2 +- build/bin/sage-package | 2 +- build/bin/sage-pip-install | 13 ++++++++----- build/bin/sage-spkg-uninstall | 2 +- build/bin/sage-system-python | 13 +++++++++++++ build/bin/sage-uncompress-spkg | 2 +- 7 files changed, 26 insertions(+), 10 deletions(-) create mode 100755 build/bin/sage-system-python diff --git a/build/bin/sage-download-file b/build/bin/sage-download-file index df5ab3a37f3..83c33f465c7 100755 --- a/build/bin/sage-download-file +++ b/build/bin/sage-download-file @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env sage-system-python # USAGE: # diff --git a/build/bin/sage-flock b/build/bin/sage-flock index f43820ddb93..8a6126f8460 100755 --- a/build/bin/sage-flock +++ b/build/bin/sage-flock @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env sage-system-python # vim: set filetype=python: # USAGE: diff --git a/build/bin/sage-package b/build/bin/sage-package index 9509750ff3f..eb5c98bfe51 100755 --- a/build/bin/sage-package +++ b/build/bin/sage-package @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env sage-system-python # Script to manage third-party tarballs. # diff --git a/build/bin/sage-pip-install b/build/bin/sage-pip-install index 13697965b2f..8f99d3f8e1c 100755 --- a/build/bin/sage-pip-install +++ b/build/bin/sage-pip-install @@ -35,11 +35,14 @@ if [ "$1" != "." ]; then fi +# Note: We need to take care to specify the full path to Sage's Python here +# since when we run it through sage-flock, $SAGE_LOCAL/bin may be removed from +# the $PATH; https://trac.sagemath.org/ticket/18438 if [ "$SAGE_PYTHON3" = yes ]; then - PYTHON=python3 + PYTHON="$SAGE_LOCAL/bin/python3" PIP=pip3 else - PYTHON=python2 + PYTHON="$SAGE_LOCAL/bin/python2" PIP=pip2 fi @@ -62,12 +65,12 @@ fi # We should avoid running pip2/3 while uninstalling a package because that # is prone to race conditions. Therefore, we use a lockfile while -# running pip. This is implemented in the Python script pip2/3-lock. +# running pip. This is implemented in the Python script sage-flock LOCK="$SAGE_LOCAL/var/lock/$PIP.lock" # Keep uninstalling as long as it succeeds while true; do - out=$(sage-flock -x $LOCK $PIP uninstall --disable-pip-version-check -y "$name" 2>&1) + out=$(sage-flock -x $LOCK $PYTHON -m pip uninstall --disable-pip-version-check -y "$name" 2>&1) if [ $? -ne 0 ]; then # Uninstall failed echo >&2 "$out" @@ -86,7 +89,7 @@ done # to apply a shared lock) echo "Installing package $name using $PIP" -sage-flock -s $LOCK $PIP install $pip_install_flags . +sage-flock -s $LOCK $PYTHON -m pip install $pip_install_flags . if [ $? -ne 0 ]; then echo >&2 "Error: installing with $PIP failed" exit 3 diff --git a/build/bin/sage-spkg-uninstall b/build/bin/sage-spkg-uninstall index 94313c024bc..3dca3f65d1f 100755 --- a/build/bin/sage-spkg-uninstall +++ b/build/bin/sage-spkg-uninstall @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env sage-system-python # usage: sage-spkg-uninstall [-h] PKG [SAGE_LOCAL] # diff --git a/build/bin/sage-system-python b/build/bin/sage-system-python new file mode 100755 index 00000000000..c0b8c82e9b6 --- /dev/null +++ b/build/bin/sage-system-python @@ -0,0 +1,13 @@ +#!/usr/bin/env sh + +# Run the system python. +# +# This is primarily for use by the build toolchain so that it can continue +# using the system Python rather than Sage's Python, preventing conflicts +# that might otherwise occur, particularly in parallel builds. +# +# See https://trac.sagemath.org/ticket/18438 + +export PATH="$SAGE_ORIG_PATH" + +exec python $@ diff --git a/build/bin/sage-uncompress-spkg b/build/bin/sage-uncompress-spkg index c3cf032b640..4dead534567 100755 --- a/build/bin/sage-uncompress-spkg +++ b/build/bin/sage-uncompress-spkg @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env sage-system-python # usage: sage-uncompress-spkg [-h] [-d DIR] PKG [FILE] # From d84bfb620292949c4b84d10baf15e45888a6a390 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Wed, 12 Sep 2018 14:20:09 +0000 Subject: [PATCH 238/264] Trac #18438: A different take on sage-system-python This uses the bash "command" built-in to get the path to the system Python (so "bash" had better really be bash--I think we require this anyways), but run the system Python while keeping SAGE_LOCAL on the PATH, which is required for some packages to install properly, such cypari and probably others. --- build/bin/sage-pip-install | 4 ++-- build/bin/sage-system-python | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/bin/sage-pip-install b/build/bin/sage-pip-install index 8f99d3f8e1c..e69eec28ae1 100755 --- a/build/bin/sage-pip-install +++ b/build/bin/sage-pip-install @@ -36,8 +36,8 @@ fi # Note: We need to take care to specify the full path to Sage's Python here -# since when we run it through sage-flock, $SAGE_LOCAL/bin may be removed from -# the $PATH; https://trac.sagemath.org/ticket/18438 +# to emphasize that this command hould use it, and not the system Python; +# see https://trac.sagemath.org/ticket/18438 if [ "$SAGE_PYTHON3" = yes ]; then PYTHON="$SAGE_LOCAL/bin/python3" PIP=pip3 diff --git a/build/bin/sage-system-python b/build/bin/sage-system-python index c0b8c82e9b6..a37f2de0134 100755 --- a/build/bin/sage-system-python +++ b/build/bin/sage-system-python @@ -1,4 +1,4 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash # Run the system python. # @@ -8,6 +8,6 @@ # # See https://trac.sagemath.org/ticket/18438 -export PATH="$SAGE_ORIG_PATH" +PYTHON="$(PATH="$SAGE_ORIG_PATH" command -v python)" -exec python $@ +exec "$PYTHON" "$@" From 600344f6d28edfac957a27f1fafc31a0d6aab8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Sep 2018 16:49:26 +0200 Subject: [PATCH 239/264] py3: hash for products of projective spaces --- src/sage/schemes/product_projective/space.py | 33 +++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/sage/schemes/product_projective/space.py b/src/sage/schemes/product_projective/space.py index 4f560498a19..3b8cd8ff246 100644 --- a/src/sage/schemes/product_projective/space.py +++ b/src/sage/schemes/product_projective/space.py @@ -165,7 +165,8 @@ def ProductProjectiveSpaces(n, R=None, names='x'): X = ProductProjectiveSpaces_field(n, R, names) else: X = ProductProjectiveSpaces_ring(n, R, names) - return(X) + return X + class ProductProjectiveSpaces_ring(AmbientSpace): r""" @@ -327,7 +328,7 @@ def __getitem__(self, i): sage: T[0] Projective Space of dimension 3 over Rational Field """ - return(self._components[i]) + return self._components[i] def __eq__(self, right): r""" @@ -370,6 +371,22 @@ def __ne__(self, other): """ return not (self == other) + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: S. = ProductProjectiveSpaces([3, 2], QQ) + sage: T. = ProductProjectiveSpaces([2, 2], QQ) + sage: U. = ProductProjectiveSpaces([3, 2], QQ) + sage: hash(S) == hash(T) + False + sage: hash(S) == hash(U) + True + """ + return hash(tuple(self._components)) + def __pow__(self, m): """ Return the Cartesian power of this space. @@ -453,7 +470,7 @@ def __mul__(self, right): psi = right.ambient_space().coordinate_ring().hom(list(CR.gens()[n:]), CR) return AS.subscheme([phi(t) for t in self.defining_polynomials()] + [psi(t) for t in right.defining_polynomials()]) else: - raise TypeError('%s must be a projective space, product of projective spaces, or subscheme'%right) + raise TypeError('%s must be a projective space, product of projective spaces, or subscheme' % right) def components(self): r""" @@ -482,7 +499,7 @@ def dimension_relative(self): sage: T.dimension_relative() 5 """ - return(sum(self._dims)) + return sum(self._dims) def dimension_absolute(self): r""" @@ -517,7 +534,7 @@ def dimension_relative_components(self): sage: T.dimension_relative_components() [3, 2] """ - return(self._dims) + return self._dims def dimension_absolute_components(self): r""" @@ -552,7 +569,7 @@ def num_components(self): sage: T.num_components() 3 """ - return(len(self._components)) + return len(self._components) def ngens(self): r""" @@ -569,7 +586,7 @@ def ngens(self): sage: T.ngens() 6 """ - return(sum([P.ngens() for P in self._components])) + return sum([P.ngens() for P in self._components]) def _factors(self, v): r""" @@ -597,7 +614,7 @@ def _factors(self, v): for i in range(len(dims)): splitv.append(v[index:index+dims[i]+1]) index += dims[i]+1 - return(splitv) + return splitv def _degree(self, polynomial): r""" From 1fe1188458b338516c777d9b3b2a82225d827cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Sep 2018 17:05:33 +0200 Subject: [PATCH 240/264] py3: hash for MPolynomial_polydict --- src/sage/rings/polynomial/multi_polynomial_element.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index 1a41b1eafb7..2a296087003 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -1463,9 +1463,12 @@ def __ne__(self,right): return CommutativeRingElement.__ne__(self, right) return self._MPolynomial_element__element != right._MPolynomial_element__element + # required by Python 3 + __hash__ = MPolynomial_element.__hash__ + def __bool__(self): """ - Returns True if self != 0 + Return True if self != 0 .. note:: From c6e1a13564886ee4c7ab66641a03bf2328919b0d Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Wed, 12 Sep 2018 14:52:29 +0000 Subject: [PATCH 241/264] py3: explicitly make CoordinateFunction a new-style class (there does not appear to be any reason it shouldn't be) --- src/sage/rings/number_field/number_field_element.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index 876d719545d..8198cec1890 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -5186,7 +5186,7 @@ cdef class OrderElement_relative(NumberFieldElement_relative): -class CoordinateFunction: +class CoordinateFunction(object): r""" This class provides a callable object which expresses elements in terms of powers of a fixed field generator `\alpha`. @@ -5197,7 +5197,7 @@ class CoordinateFunction: sage: f = (a + 1).coordinates_in_terms_of_powers(); f Coordinate function that writes elements in terms of the powers of a + 1 sage: f.__class__ - + sage: f(a) [-1, 1] sage: f == loads(dumps(f)) From 0e2937dd6d4667584590ef07b83c470418f72f27 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Wed, 12 Sep 2018 15:02:24 +0000 Subject: [PATCH 242/264] py3: remove long-dead code (should have been removed in #9359) --- src/sage/rings/number_field/totallyreal.pyx | 38 --------------------- 1 file changed, 38 deletions(-) diff --git a/src/sage/rings/number_field/totallyreal.pyx b/src/sage/rings/number_field/totallyreal.pyx index fa7f684a92e..296e258aa4e 100644 --- a/src/sage/rings/number_field/totallyreal.pyx +++ b/src/sage/rings/number_field/totallyreal.pyx @@ -532,41 +532,3 @@ def weed_fields(S, Py_ssize_t lenS=0): i += 1 return lenS - -def timestr(m): - r""" - Converts seconds to a human-readable time string. - - INPUT: - - - m -- integer, number of seconds - - OUTPUT: - - The time in days, hours, etc. - - EXAMPLES:: - - sage: sage.rings.number_field.totallyreal.timestr(3765) - '1h 2m 45.0s' - """ - - n = math.floor(m) - p = m-n - outstr = '' - if m >= 60*60*24: - t = n//(60*60*24) - outstr += str(t)[:len(str(t))-2] + 'd ' - n -= t*(60*60*24) - if m >= 60*60: - t = n//(60*60) - outstr += str(t)[:len(str(t))-2] + 'h ' - n -= t*(60*60) - if m >= 60: - t = n//60 - outstr += str(t)[:len(str(t))-2] + 'm ' - n -= t*60 - n += p - outstr += '%.1f' % n + 's' - - return outstr From eacfecd5077da3a91408b81220eb473ff191f8d7 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Wed, 12 Sep 2018 15:08:44 +0000 Subject: [PATCH 243/264] py3: ensure an int is passed to range() --- src/sage/rings/number_field/bdd_height.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/number_field/bdd_height.py b/src/sage/rings/number_field/bdd_height.py index c244ab86386..ce5c6734234 100644 --- a/src/sage/rings/number_field/bdd_height.py +++ b/src/sage/rings/number_field/bdd_height.py @@ -193,7 +193,7 @@ def bdd_height_iq(K, height_bound): # Find principal ideals of bounded norm possible_norm_set = set([]) for n in range(class_number): - for m in range(1, height_bound + 1): + for m in range(1, int(height_bound + 1)): possible_norm_set.add(m*class_group_rep_norms[n]) bdd_ideals = bdd_norm_pr_gens_iq(K, possible_norm_set) From 382464b2b890a9317a17879321753c337f51d59b Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Wed, 12 Sep 2018 15:21:56 +0000 Subject: [PATCH 244/264] py3: improve tests for this method a bit the existing test just tested that hash did not crash which is almost tautological in this case; better add some meaningful tests --- src/sage/rings/number_field/order.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/number_field/order.py b/src/sage/rings/number_field/order.py index ce1f361c7ea..c2abcff6f19 100644 --- a/src/sage/rings/number_field/order.py +++ b/src/sage/rings/number_field/order.py @@ -931,8 +931,14 @@ def __hash__(self): EXAMPLES:: sage: K. = NumberField(x^3 + 2) + sage: L. = NumberField(x^3 + 3) sage: O1 = K.order(a) - sage: h = hash(O1) + sage: hash(O1) == hash(K.order(a)) + True + sage: hash(O1) == hash(K.order(a^2)) + False + sage: hash(O1) == hash(L.order(b)) + False """ return hash((self._K, self._module_rep)) @@ -1100,7 +1106,7 @@ def some_elements(self): TESTS: This also works for trivial extensions:: - + sage: R. = QQ[] sage: K. = QQ.extension(t); K Number Field in a with defining polynomial t From 20b8f38eb776a276a6a7a39fd1b63d216f98da16 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Wed, 12 Sep 2018 15:22:33 +0000 Subject: [PATCH 245/264] py3: fix a few trivial doctest failures due to minor repr differences --- src/sage/rings/number_field/number_field.py | 2 +- src/sage/rings/number_field/number_field_ideal.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 377f30e218f..1b190a5228a 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -9465,7 +9465,7 @@ def __init__(self, n, names, embedding=None, assume_disc_small=False, maximize_a sage: TestSuite(k).run() Failure in _test_gcd_vs_xgcd: ... - AssertionError: The methods gcd and xgcd disagree on Cyclotomic Field of order 3 and degree 2: + AssertionError:... The methods gcd and xgcd disagree on Cyclotomic Field of order 3 and degree 2: gcd(0,2) = 1 xgcd(0,2) = (2, 0, 1) ------------------------------------------------------------ diff --git a/src/sage/rings/number_field/number_field_ideal.py b/src/sage/rings/number_field/number_field_ideal.py index 9fe7d623d20..a90c12665a3 100644 --- a/src/sage/rings/number_field/number_field_ideal.py +++ b/src/sage/rings/number_field/number_field_ideal.py @@ -2087,7 +2087,7 @@ def residues(self): sage: K.=NumberField(x^2+1) sage: res = K.ideal(2).residues(); res - xmrange_iter([[0, 1], [0, 1]], at 0x...>) + xmrange_iter([[0, 1], [0, 1]], at 0x...>) sage: list(res) [0, i, 1, i + 1] sage: list(K.ideal(2+i).residues()) @@ -2150,7 +2150,7 @@ def invertible_residues(self, reduce=True): sage: K.=NumberField(x^2+1) sage: ires = K.ideal(2).invertible_residues(); ires - xmrange_iter([[0, 1]], at 0x...>) + xmrange_iter([[0, 1]], at 0x...>) sage: list(ires) [1, -i] sage: list(K.ideal(2+i).invertible_residues()) From a9117f666d84d70023728b0dc7175ff7dcd69bad Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Wed, 12 Sep 2018 15:24:25 +0000 Subject: [PATCH 246/264] py3: minor doctest fix this test previously relied on scope leakage of the loop variable in the earlier list comprehension; this cannot be relied on in Python 3 --- src/sage/rings/number_field/unit_group.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/rings/number_field/unit_group.py b/src/sage/rings/number_field/unit_group.py index 1bcaf3657b7..1eb6eb77ebe 100644 --- a/src/sage/rings/number_field/unit_group.py +++ b/src/sage/rings/number_field/unit_group.py @@ -668,6 +668,7 @@ def exp(self, exponents): sage: unit = UK.exp(vec) sage: UK.log(unit) (13, 6, 7, 8, 9, 10) + sage: u = UK.gens()[-1] sage: UK.exp(UK.log(u)) == u.value() True From f5d4feb3f90d09e84f5ab4ed8fa8327460165206 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Wed, 12 Sep 2018 15:26:14 +0000 Subject: [PATCH 247/264] py3: skip this test on Python 3; it doesn't make sense without the Python 2 long type --- src/sage/rings/number_field/number_field_element.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index 8198cec1890..1d0a8ba0c23 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -2572,7 +2572,7 @@ cdef class NumberFieldElement(FieldElement): EXAMPLES:: sage: K. = NumberField(x^10 - x - 1) - sage: long(a) + sage: long(a) # py2 Traceback (most recent call last): ... TypeError: cannot coerce nonconstant polynomial to long From 398058a17916ad27a0f4a7e04df0c8f52cdc7f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Sep 2018 17:29:49 +0200 Subject: [PATCH 248/264] remove some deprecated stuff from words --- src/sage/combinat/words/finite_word.py | 37 +++++----------------- src/sage/combinat/words/word_generators.py | 8 +---- 2 files changed, 9 insertions(+), 36 deletions(-) diff --git a/src/sage/combinat/words/finite_word.py b/src/sage/combinat/words/finite_word.py index c38701019db..8d2fc3771fe 100644 --- a/src/sage/combinat/words/finite_word.py +++ b/src/sage/combinat/words/finite_word.py @@ -6213,7 +6213,7 @@ def swap_decrease(self, i): else: return self - def abelian_vector(self, alphabet=None): + def abelian_vector(self): r""" Return the abelian vector of ``self`` counting the occurrences of each letter. @@ -6223,7 +6223,6 @@ def abelian_vector(self, alphabet=None): INPUT: - ``self`` -- word having a parent on a finite alphabet - - ``alphabet`` -- *DEPRECATED* OUTPUT: @@ -6239,17 +6238,7 @@ def abelian_vector(self, alphabet=None): sage: W().abelian_vector() [0, 0] - The argument ``alphabet`` is deprecated:: - - sage: Word('aabaa').abelian_vector('abc') - doctest:...: DeprecationWarning: The argument alphabet of - methods abelian_vector and parikh_vector is deprecated and will - be removed in a future version of Sage. In order to fix this, - you must define your word on a parent with a finite alphabet. - See http://trac.sagemath.org/17058 for details. - [4, 1, 0] - - You may fix the above deprecated use of the ``alphabet`` argument this way:: + The result depends on the alphabet of the parent:: sage: W = Words('abc') sage: W('aabaa').abelian_vector() @@ -6265,24 +6254,14 @@ def abelian_vector(self, alphabet=None): word with a parent on a finite alphabet or use evaluation_dict() instead """ - if alphabet is None: - if self.parent().alphabet().cardinality() is Infinity: - raise TypeError("The alphabet of the parent is infinite; define " - "the word with a parent on a finite alphabet or use " - "evaluation_dict() instead") - alphabet = self.parent().alphabet() - else: - from sage.misc.superseded import deprecation - deprecation(17058, "The argument alphabet of methods abelian_vector " - "and parikh_vector is deprecated and will be " - "removed in a future version of Sage. In order to " - "fix this, you must define your word on a parent " - "with a finite alphabet.") - + alphabet = self.parent().alphabet() + if alphabet.cardinality() is Infinity: + raise TypeError("The alphabet of the parent is infinite; define " + "the word with a parent on a finite alphabet or use " + "evaluation_dict() instead") ev_dict = self.evaluation_dict() - return [ev_dict.get(a,0) for a in alphabet] + return [ev_dict.get(a, 0) for a in alphabet] - parikh_vector = deprecated_function_alias(17058, abelian_vector) evaluation = abelian_vector def robinson_schensted(self): diff --git a/src/sage/combinat/words/word_generators.py b/src/sage/combinat/words/word_generators.py index b9d17cad2b9..8626410cdff 100644 --- a/src/sage/combinat/words/word_generators.py +++ b/src/sage/combinat/words/word_generators.py @@ -839,9 +839,7 @@ def CharacteristicSturmianWord(self, slope, alphabet=(0, 1), bits=None): :: - sage: words.CharacteristicSturmianWord(1/golden_ratio^2, bits=30) - doctest:...: DeprecationWarning: the argument 'bits' is deprecated - See http://trac.sagemath.org/14567 for details. + sage: words.CharacteristicSturmianWord(1/golden_ratio^2) word: 0100101001001010010100100101001001010010... sage: _.length() +Infinity @@ -873,10 +871,6 @@ def CharacteristicSturmianWord(self, slope, alphabet=(0, 1), bits=None): sage: u[1:-1] == v[:-2] True """ - if bits is not None: - from sage.misc.superseded import deprecation - deprecation(14567, "the argument 'bits' is deprecated") - if len(set(alphabet)) != 2: raise TypeError("alphabet does not contain two distinct elements") From 7eac53b9517eb0019b89b978a6db8603aed1657d Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Wed, 12 Sep 2018 16:48:20 +0000 Subject: [PATCH 249/264] py3: fix some map() calls that broke hypergeometric() some other improvements to code readability of this method --- src/sage/symbolic/expression.pyx | 33 +++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 3d60f5fddea..04675989194 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -9997,28 +9997,31 @@ cdef class Expression(CommutativeRingElement): except RuntimeError: return self ops = self.operands() + if op == hypergeometric_M or op == hypergeometric_U: return self.generalized().simplify_hypergeometric(algorithm) + + if algorithm not in ('maxima', 'sage'): + raise NotImplementedError( + "unknown algorithm: '{}'".format(algorithm)) + + simplify = lambda o: o.simplify_hypergeometric(algorithm) + if op == hypergeometric: + a = [simplify(o) for o in ops[0].operands()] + b = [simplify(o) for o in ops[1].operands()] + t = simplify(ops[2]) + if algorithm == 'maxima': - return (self.parent() - (maxima.hgfred(map(lambda o: o.simplify_hypergeometric(algorithm), - ops[0].operands()), - map(lambda o: o.simplify_hypergeometric(algorithm), - ops[1].operands()), - ops[2].simplify_hypergeometric(algorithm)))) + R = self.parent() + return R(maxima.hgfred(a, b, t)) elif algorithm == 'sage': - return (closed_form - (hypergeometric(map(lambda o: o.simplify_hypergeometric(algorithm), - ops[0].operands()), - map(lambda o: o.simplify_hypergeometric(algorithm), - ops[1].operands()), - ops[2].simplify_hypergeometric(algorithm)))) - else: - raise NotImplementedError('unknown algorithm') + return closed_form(hypergeometric(a, b, t)) + if not op: return self - return op(*map(lambda o: o.simplify_hypergeometric(algorithm), ops)) + + return op(*(simplify(o) for o in ops)) hypergeometric_simplify = simplify_hypergeometric From 6151e6e40a5aeab2333952dfbb73dd4ede320c08 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Wed, 12 Sep 2018 16:50:33 +0000 Subject: [PATCH 250/264] trivial whitespace and pep-8 cleanup in this module --- src/sage/functions/hypergeometric.py | 32 +++++++++++++++------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/sage/functions/hypergeometric.py b/src/sage/functions/hypergeometric.py index cefc1f23382..71927142dcd 100644 --- a/src/sage/functions/hypergeometric.py +++ b/src/sage/functions/hypergeometric.py @@ -232,7 +232,7 @@ class Hypergeometric(BuiltinFunction): def __init__(self): """ Initialize class. - + EXAMPLES:: sage: maxima(hypergeometric) @@ -248,13 +248,13 @@ def __init__(self): def __call__(self, a, b, z, **kwargs): """ Return symbolic hypergeometric function expression. - + INPUT: - + - ``a`` -- a list or tuple of parameters - ``b`` -- a list or tuple of parameters - ``z`` -- a number or symbolic expression - + EXAMPLES:: sage: hypergeometric([], [], 1) @@ -267,7 +267,7 @@ def __call__(self, a, b, z, **kwargs): hypergeometric((), (), x) sage: hypergeometric([x], [], x^2) hypergeometric((x,), (), x^2) - + The only simplification that is done automatically is returning 1 if ``z`` is 0. For other simplifications use the ``simplify_hypergeometric`` method. @@ -298,8 +298,9 @@ def _eval_(self, a, b, z, **kwargs): sage: hypergeometric([], [], 0) 1 """ - if not isinstance(a,tuple) or not isinstance(b,tuple): + if not isinstance(a, tuple) or not isinstance(b, tuple): raise TypeError("The first two parameters must be of type list") + if not isinstance(z, Expression) and z == 0: # Expression is excluded return Integer(1) # to avoid call to Maxima @@ -328,8 +329,9 @@ def _evalf_try_(self, a, b, z): # We need to override this for hypergeometric functions since # the first 2 arguments are tuples and the generic _evalf_try_ # cannot handle that. - if not isinstance(a,tuple) or not isinstance(b,tuple): + if not isinstance(a, tuple) or not isinstance(b, tuple): return None + args = list(a) + list(b) + [z] if any(self._is_numerical(x) for x in args): if not any(isinstance(x, Expression) for x in args): @@ -692,7 +694,7 @@ def deflated(self, a, b, z): def _deflated(self, a, b, z): """ Private helper to return list of deflated terms. - + EXAMPLES:: sage: x = hypergeometric([5], [4], 3) @@ -958,7 +960,7 @@ class Hypergeometric_M(BuiltinFunction): def __init__(self): r""" TESTS:: - + sage: maxima(hypergeometric_M(1,1,x)) kummer_m(1,1,_SAGE_VAR_x) sage: latex(hypergeometric_M(1,1,x)) @@ -974,7 +976,7 @@ def __init__(self): def _eval_(self, a, b, z, **kwargs): """ TESTS:: - + sage: (a,b)=var('a,b') sage: hypergeometric_M(a,b,0) 1 @@ -986,7 +988,7 @@ def _eval_(self, a, b, z, **kwargs): def _evalf_(self, a, b, z, parent, algorithm=None): """ TESTS:: - + sage: hypergeometric_M(1,1,1).n() 2.71828182845905 """ @@ -996,7 +998,7 @@ def _evalf_(self, a, b, z, parent, algorithm=None): def _derivative_(self, a, b, z, diff_param): """ TESTS:: - + sage: diff(hypergeometric_M(1,1,x),x,3) hypergeometric_M(4, 4, x) sage: diff(hypergeometric_M(x,1,1),x,3) @@ -1069,7 +1071,7 @@ class Hypergeometric_U(BuiltinFunction): def __init__(self): r""" TESTS:: - + sage: maxima(hypergeometric_U(1,1,x)) kummer_u(1,1,_SAGE_VAR_x) sage: latex(hypergeometric_U(1,1,x)) @@ -1088,7 +1090,7 @@ def _eval_(self, a, b, z, **kwargs): def _evalf_(self, a, b, z, parent, algorithm=None): """ TESTS:: - + sage: hypergeometric_U(1,1,1).n() 0.596347362323194 """ @@ -1098,7 +1100,7 @@ def _evalf_(self, a, b, z, parent, algorithm=None): def _derivative_(self, a, b, z, diff_param): """ TESTS:: - + sage: diff(hypergeometric_U(1,1,x),x,3) -6*hypergeometric_U(4, 4, x) sage: diff(hypergeometric_U(x,1,1),x,3) From 7190e489fca908533929d8cbb8157d90702346d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Sep 2018 19:40:43 +0200 Subject: [PATCH 251/264] trac 26260 fixing doctest --- src/sage/schemes/product_projective/point.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sage/schemes/product_projective/point.py b/src/sage/schemes/product_projective/point.py index 27048312ad1..585bdfa6802 100644 --- a/src/sage/schemes/product_projective/point.py +++ b/src/sage/schemes/product_projective/point.py @@ -246,16 +246,14 @@ def __iter__(self): def __hash__(self): """ - Computes the hash value of this point. + Compute the hash value of this point. OUTPUT: Integer. EXAMPLES:: sage: PP = ProductProjectiveSpaces(Zmod(6), [1, 1]) - sage: hash(PP([5, 1, 2, 4])) - 1266382469 # 32-bit - -855399699883264379 # 64-bit + sage: H = hash(PP([5, 1, 2, 4])) :: From 362b41949c957796e7b1d768b5e0838f13759000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Sep 2018 21:31:18 +0200 Subject: [PATCH 252/264] fixing 2 pyflakes warnings --- src/sage/functions/hypergeometric.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/functions/hypergeometric.py b/src/sage/functions/hypergeometric.py index 71927142dcd..5cae346dd8d 100644 --- a/src/sage/functions/hypergeometric.py +++ b/src/sage/functions/hypergeometric.py @@ -169,7 +169,6 @@ from sage.rings.infinity import Infinity from sage.arith.all import binomial, rising_factorial, factorial from sage.symbolic.constants import pi -from sage.symbolic.all import I from sage.symbolic.function import BuiltinFunction from sage.symbolic.ring import SR from sage.structure.element import get_coercion_model @@ -183,10 +182,10 @@ from .gamma import gamma from .other import sqrt, real_part from .log import exp, log -from .trig import sin from .hyperbolic import cosh, sinh from .error import erf + def rational_param_as_tuple(x): r""" Utility function for converting rational `\,_pF_q` parameters to From c17c35c5b258e41584e9ad133bf47e98edceabc6 Mon Sep 17 00:00:00 2001 From: Vincent Klein Date: Thu, 13 Sep 2018 10:05:31 +0200 Subject: [PATCH 253/264] Trac #26258: Fix doctests for py3; use Compositions(4) ... instead of SymmetricGroup to avoid non deterministic iterator. Enhance rank() doctests. --- src/sage/categories/enumerated_sets.py | 29 +++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/sage/categories/enumerated_sets.py b/src/sage/categories/enumerated_sets.py index 2cf28359602..4ab498bd0d3 100644 --- a/src/sage/categories/enumerated_sets.py +++ b/src/sage/categories/enumerated_sets.py @@ -883,25 +883,25 @@ def map(self, f, name=None): EXAMPLES:: - sage: R = SymmetricGroup(3).map(attrcall('reduced_word')); R - Image of Symmetric group of order 3! as a permutation group by *.reduced_word() + sage: R = Compositions(4).map(attrcall('partial_sums')); R + Image of Compositions of 4 by *.partial_sums() sage: R.cardinality() - 6 - sage: sorted(R.list()) - [[], [1], [1, 2], [1, 2, 1], [2], [2, 1]] - sage: sorted([ r for r in R]) - [[], [1], [1, 2], [1, 2, 1], [2], [2, 1]] + 8 + sage: R.list() + [[1, 2, 3, 4], [1, 2, 4], [1, 3, 4], [1, 4], [2, 3, 4], [2, 4], [3, 4], [4]] + sage: [ r for r in R] + [[1, 2, 3, 4], [1, 2, 4], [1, 3, 4], [1, 4], [2, 3, 4], [2, 4], [3, 4], [4]] .. warning:: If the function is not injective, then there may be repeated elements:: - sage: P = SymmetricGroup(3) - sage: sorted(P.list()) - [(), (2,3), (1,2), (1,2,3), (1,3,2), (1,3)] - sage: sorted(P.map(attrcall('length')).list()) - [0, 1, 1, 2, 2, 3] + sage: P = Compositions(4) + sage: P.list() + [[1, 1, 1, 1], [1, 1, 2], [1, 2, 1], [1, 3], [2, 1, 1], [2, 2], [3, 1], [4]] + sage: P.map(attrcall('major_index')).list() + [6, 3, 4, 1, 5, 2, 3, 0] .. warning:: @@ -1019,11 +1019,10 @@ def rank(self): sage: F = FiniteSemigroups().example(('a','b','c')) sage: L = list(F) - sage: sorted(L) - ['a', 'ab', 'abc', 'ac', 'acb', 'b', 'ba', 'bac', - 'bc', 'bca', 'c', 'ca', 'cab', 'cb', 'cba'] sage: L[7].rank() 7 + sage: all(x.rank() == i for i,x in enumerate(L)) + True """ return self.parent().rank(self) From 4a6f715b0f9ed89a48ae6fa7747732b01c6568d9 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Wed, 12 Sep 2018 16:50:33 +0000 Subject: [PATCH 254/264] py3: for IPython to pprint dicts sorted by key when running the doctests --- src/sage/doctest/forker.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 9e3a6840878..49df3dcb150 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -183,6 +183,15 @@ def init_sage(): from sage.repl.rich_output.backend_doctest import BackendDoctest dm.switch_backend(BackendDoctest()) + # IPython's pretty printer sorts the repr of dicts by their keys by default + # (or their keys' str() if they are not otherwise orderable). However, it + # disables this for CPython 3.6+ opting to instead display dicts' "natural" + # insertion order, which is preserved in those versions). This makes for + # inconsistent results with Python 2 tests that return dicts, so here we + # force the Python 2 style dict printing + import IPython.lib.pretty + IPython.lib.pretty.DICT_IS_ORDERED = False + # Switch on extra debugging from sage.structure.debug_options import debug debug.refine_category_hash_check = True From fcdded3a6804760e893a975cd933dd2858b30776 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Thu, 13 Sep 2018 09:39:37 +0000 Subject: [PATCH 255/264] py3: remove explicit pprint from some doctests where it should no longer be needed --- .../combinat/root_system/weyl_characters.py | 24 +++++++------------ src/sage/knots/link.py | 13 +++++----- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/sage/combinat/root_system/weyl_characters.py b/src/sage/combinat/root_system/weyl_characters.py index 88e7ed20660..14ffc542942 100644 --- a/src/sage/combinat/root_system/weyl_characters.py +++ b/src/sage/combinat/root_system/weyl_characters.py @@ -549,11 +549,10 @@ def _irr_weights(self, hwv): EXAMPLES:: - sage: from pprint import pprint sage: A2=WeylCharacterRing("A2") sage: v = A2.fundamental_weights()[1]; v (1, 0, 0) - sage: pprint(A2._irr_weights(v)) + sage: A2._irr_weights(v) {(1, 0, 0): 1, (0, 1, 0): 1, (0, 0, 1): 1} """ if self._style == "coroots": @@ -579,9 +578,8 @@ def _demazure_weights(self, hwv, word="long", debug=False): EXAMPLES:: - sage: from pprint import pprint sage: B2=WeylCharacterRing("B2", style="coroots") - sage: pprint([B2._demazure_weights(v, word=[1,2]) for v in B2.fundamental_weights()]) + sage: [B2._demazure_weights(v, word=[1,2]) for v in B2.fundamental_weights()] [{(1, 0): 1, (0, 1): 1}, {(-1/2, 1/2): 1, (1/2, -1/2): 1, (1/2, 1/2): 1}] """ alphacheck = self._space.simple_coroots() @@ -605,10 +603,9 @@ def _demazure_helper(self, dd, word="long", debug=False): EXAMPLES:: - sage: from pprint import pprint sage: A2=WeylCharacterRing("A2",style="coroots") sage: dd = {}; dd[(1,1)]=int(1) - sage: pprint(A2._demazure_helper(dd,word=[1,2])) + sage: A2._demazure_helper(dd,word=[1,2]) {(0, 0, 0): 1, (-1, 1, 0): 1, (1, -1, 0): 1, (1, 0, -1): 1, (0, 1, -1): 1} """ if self._style != "coroots": @@ -664,10 +661,9 @@ def _weight_multiplicities(self, x): EXAMPLES:: - sage: from pprint import pprint sage: B2=WeylCharacterRing("B2",style="coroots") sage: chi=2*B2(1,0) - sage: pprint(B2._weight_multiplicities(chi)) + sage: B2._weight_multiplicities(chi) {(0, 0): 2, (-1, 0): 2, (1, 0): 2, (0, -1): 2, (0, 1): 2} """ d = {} @@ -890,11 +886,10 @@ def char_from_weights(self, mdict): EXAMPLES:: - sage: from pprint import pprint sage: A2 = WeylCharacterRing("A2") sage: v = A2._space([3,1,0]); v (3, 1, 0) - sage: d = dict([(x,1) for x in v.orbit()]); pprint(d) + sage: d = dict([(x,1) for x in v.orbit()]); d {(1, 3, 0): 1, (1, 0, 3): 1, (3, 1, 0): 1, @@ -1263,9 +1258,8 @@ def _adams_operation_helper(self, r): EXAMPLES:: - sage: from pprint import pprint sage: A2=WeylCharacterRing("A2") - sage: pprint(A2(1,1,0)._adams_operation_helper(3)) + sage: A2(1,1,0)._adams_operation_helper(3) {(3, 3, 0): 1, (3, 0, 3): 1, (0, 3, 3): 1} """ d = self.weight_multiplicities() @@ -1389,9 +1383,8 @@ def weight_multiplicities(self): EXAMPLES:: - sage: from pprint import pprint sage: B2=WeylCharacterRing("B2",style="coroots") - sage: pprint(B2(0,1).weight_multiplicities()) + sage: B2(0,1).weight_multiplicities() {(-1/2, -1/2): 1, (-1/2, 1/2): 1, (1/2, -1/2): 1, (1/2, 1/2): 1} """ return self.parent()._weight_multiplicities(self) @@ -1481,8 +1474,7 @@ def irreducible_character_freudenthal(hwv, debug=False): EXAMPLES:: - sage: from pprint import pprint - sage: pprint(WeylCharacterRing("A2")(2,1,0).weight_multiplicities()) # indirect doctest + sage: WeylCharacterRing("A2")(2,1,0).weight_multiplicities() # indirect doctest {(1, 1, 1): 2, (1, 2, 0): 1, (1, 0, 2): 1, (2, 1, 0): 1, (2, 0, 1): 1, (0, 1, 2): 1, (0, 2, 1): 1} """ diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 68c1e5048b8..fe11d9c4308 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -751,26 +751,25 @@ def _directions_of_edges(self): EXAMPLES:: - sage: from pprint import pprint sage: L = Link([[1, 3, 2, 4], [2, 3, 1, 4]]) sage: tails, heads = L._directions_of_edges() - sage: pprint(tails) + sage: tails {1: [2, 3, 1, 4], 2: [1, 3, 2, 4], 3: [1, 3, 2, 4], 4: [2, 3, 1, 4]} - sage: pprint(heads) + sage: heads {1: [1, 3, 2, 4], 2: [2, 3, 1, 4], 3: [2, 3, 1, 4], 4: [1, 3, 2, 4]} :: sage: L = Link([[1,5,2,4], [5,3,6,2], [3,1,4,6]]) sage: tails, heads = L._directions_of_edges() - sage: pprint(tails) + sage: tails {1: [3, 1, 4, 6], 2: [1, 5, 2, 4], 3: [5, 3, 6, 2], 4: [3, 1, 4, 6], 5: [1, 5, 2, 4], 6: [5, 3, 6, 2]} - sage: pprint(heads) + sage: heads {1: [1, 5, 2, 4], 2: [5, 3, 6, 2], 3: [3, 1, 4, 6], @@ -782,14 +781,14 @@ def _directions_of_edges(self): sage: L = Link([[1,2,3,3], [2,4,5,5], [4,1,7,7]]) sage: tails, heads = L._directions_of_edges() - sage: pprint(tails) + sage: tails {1: [4, 1, 7, 7], 2: [1, 2, 3, 3], 3: [1, 2, 3, 3], 4: [2, 4, 5, 5], 5: [2, 4, 5, 5], 7: [4, 1, 7, 7]} - sage: pprint(heads) + sage: heads {1: [1, 2, 3, 3], 2: [2, 4, 5, 5], 3: [1, 2, 3, 3], From 469512ff12fdcb370d042b9cf176c86144e96583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 13 Sep 2018 11:50:27 +0200 Subject: [PATCH 256/264] simplify coercion for shuffle algebras --- src/sage/algebras/shuffle_algebra.py | 48 ++++------------------------ 1 file changed, 7 insertions(+), 41 deletions(-) diff --git a/src/sage/algebras/shuffle_algebra.py b/src/sage/algebras/shuffle_algebra.py index 703ee1fdd86..d6f94c58c47 100644 --- a/src/sage/algebras/shuffle_algebra.py +++ b/src/sage/algebras/shuffle_algebra.py @@ -396,18 +396,17 @@ def _element_constructor_(self, x): else: return self.from_base_ring_from_one_basis(x) - def _coerce_impl(self, x): + def _coerce_map_from_(self, R): r""" - Canonical coercion of ``x`` into ``self``. - - Here is what canonically coerces to ``self``: + Return ``True`` if there is a coercion from ``R`` into ``self`` + and ``False`` otherwise. - - this shuffle algebra, + The things that coerce into ``self`` are - - anything that coerces to the base ring of this shuffle algebra, + - Shuffle Algebras in the same variables over a base with a coercion + map into ``self.base_ring()``. - - any shuffle algebra on the same variables, whose base ring - coerces to the base ring of this shuffle algebra. + - Anything with a coercion into ``self.base_ring()``. EXAMPLES:: @@ -457,39 +456,6 @@ def _coerce_impl(self, x): TypeError: no canonical coercion from Shuffle Algebra on 3 generators ['x', 'y', 'z'] over Finite Field of size 7 to Shuffle Algebra on 3 generators ['x', 'y', 'z'] over Integer Ring - """ - try: - R = x.parent() - - # shuffle algebras in the same variables over any base - # that coerces in: - if isinstance(R,ShuffleAlgebra): - if R.variable_names() == self.variable_names(): - if self.has_coerce_map_from(R.base_ring()): - return self(x) - else: - raise TypeError("no natural map between bases of shuffle algebras") - - if isinstance(R, DualPBWBasis): - return self(R.expansion(x)) - - except AttributeError: - pass - - # any ring that coerces to the base ring of this shuffle algebra. - return self._coerce_try(x, [self.base_ring()]) - - def _coerce_map_from_(self, R): - r""" - Return ``True`` if there is a coercion from ``R`` into ``self`` - and ``False`` otherwise. - - The things that coerce into ``self`` are - - - Shuffle Algebras in the same variables over a base with a coercion - map into ``self.base_ring()``. - - - Anything with a coercion into ``self.base_ring()``. TESTS:: From ac2939ced5a9da643dffabe535b742f8eae61821 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Thu, 13 Sep 2018 11:42:56 +0000 Subject: [PATCH 257/264] py3: use sage_getargspec intead of inspect.getargspec --- src/sage/coding/linear_code.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index 5a088dfd51d..d04b8942053 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -203,6 +203,9 @@ class should inherit from this class. Also ``AbstractLinearCode`` should never #****************************************************************************** # python3 from __future__ import division, print_function, absolute_import + +import inspect + from six.moves import range from six import iteritems @@ -226,6 +229,7 @@ class should inherit from this class. Also ``AbstractLinearCode`` should never from sage.rings.integer import Integer from sage.modules.free_module import VectorSpace from sage.misc.cachefunc import cached_method +from sage.misc.sageinspect import sage_getargspec from sage.misc.superseded import deprecation, deprecated_function_alias from sage.misc.randstate import current_randstate from sage.features.gap import GapPackage @@ -304,13 +308,12 @@ def _explain_constructor(cl): sage: _explain_constructor(cl) "The constructor requires the arguments ['number_errors'].\nIt takes the optional arguments ['algorithm'].\nIt accepts unspecified arguments as well.\nSee the documentation of sage.coding.information_set_decoder.LinearCodeInformationSetDecoder for more details." """ - import inspect if inspect.isclass(cl): - argspec = inspect.getargspec(cl.__init__) + argspec = sage_getargspec(cl.__init__) skip = 2 # skip the self and code arguments else: # Not a class, assume it's a factory function posing as a class - argspec = inspect.getargspec(cl) + argspec = sage_getargspec(cl) skip = 1 # skip code argument if argspec.defaults: args = argspec.args[skip:-len(argspec.defaults)] From c5f3006d4e0b1ab82b5cf71bd929999a698cdfd0 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Thu, 13 Sep 2018 11:43:14 +0000 Subject: [PATCH 258/264] trivial whitespace and pep8 cleanup --- src/sage/coding/information_set_decoder.py | 4 ++-- src/sage/coding/linear_code.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/coding/information_set_decoder.py b/src/sage/coding/information_set_decoder.py index 340fcde8b66..20bd5da5a46 100644 --- a/src/sage/coding/information_set_decoder.py +++ b/src/sage/coding/information_set_decoder.py @@ -137,7 +137,7 @@ def name(self): Return the name of this ISD algorithm. EXAMPLES:: - + sage: C = codes.GolayCode(GF(2)) sage: from sage.coding.information_set_decoder import LeeBrickellISDAlgorithm sage: A = LeeBrickellISDAlgorithm(C, (0,2)) @@ -315,7 +315,7 @@ def __hash__(self): True """ return hash(str(self)) - + def _repr_(self): r""" Returns a string representation of this ISD algorithm. diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index d04b8942053..18f53b4b008 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -4406,9 +4406,9 @@ def __eq__(self, other): sage: D1 == D2 True """ - return isinstance(other, LinearCodeSyndromeDecoder)\ - and self.code() == other.code()\ - and self.maximum_error_weight() == other.maximum_error_weight() + return (isinstance(other, LinearCodeSyndromeDecoder) and + self.code() == other.code() and + self.maximum_error_weight() == other.maximum_error_weight()) def __hash__(self): """ @@ -4495,7 +4495,7 @@ def _build_lookup_table(self): sage: H = Matrix(K,[[1,2,1],[2*a+1,a,1]]) sage: C = codes.from_parity_check_matrix(H) sage: D = codes.decoders.LinearCodeSyndromeDecoder(C) - sage: D.syndrome_table() + sage: D.syndrome_table() {(0, 0): (0, 0, 0), (0, 1): (0, 1, 0), (0, 2): (0, 2, 0), From 1a40b752cf284a3fab558a8aab66d36d393a7b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 13 Sep 2018 14:15:15 +0200 Subject: [PATCH 259/264] cleanup of Cusps class over number fields (Parent, UniqueRepr, pep8) --- src/sage/modular/all.py | 2 +- src/sage/modular/cusps_nf.py | 360 +++++++++++------------------- src/sage/structure/parent_old.pyx | 15 +- 3 files changed, 137 insertions(+), 240 deletions(-) diff --git a/src/sage/modular/all.py b/src/sage/modular/all.py index d3cc256fa89..6ed24eb52f7 100644 --- a/src/sage/modular/all.py +++ b/src/sage/modular/all.py @@ -33,7 +33,7 @@ from .local_comp.all import * -from .cusps_nf import NFCusp, NFCusps, NFCusps_clear_cache, Gamma0_NFCusps +from .cusps_nf import NFCusp, NFCusps, Gamma0_NFCusps from .btquotients.all import * diff --git a/src/sage/modular/cusps_nf.py b/src/sage/modular/cusps_nf.py index e5cae017ee2..b358b45d880 100644 --- a/src/sage/modular/cusps_nf.py +++ b/src/sage/modular/cusps_nf.py @@ -7,9 +7,7 @@ EXAMPLES: -The space of cusps over a number field k: - -:: +The space of cusps over a number field k:: sage: k. = NumberField(x^2 + 5) sage: kCusps = NFCusps(k); kCusps @@ -17,9 +15,7 @@ sage: kCusps is NFCusps(k) True -Define a cusp over a number field: - -:: +Define a cusp over a number field:: sage: NFCusp(k, a, 2/(a+1)) Cusp [a - 5: 2] of Number Field in a with defining polynomial x^2 + 5 @@ -28,9 +24,7 @@ sage: NFCusp(k,oo) Cusp Infinity of Number Field in a with defining polynomial x^2 + 5 -Different operations with cusps over a number field: - -:: +Different operations with cusps over a number field:: sage: alpha = NFCusp(k, 3, 1/a + 2); alpha Cusp [a + 10: 7] of Number Field in a with defining polynomial x^2 + 5 @@ -45,9 +39,7 @@ sage: alpha.apply([0, 1, -1,0]) Cusp [7: -a - 10] of Number Field in a with defining polynomial x^2 + 5 -Check Gamma0(N)-equivalence of cusps: - -:: +Check Gamma0(N)-equivalence of cusps:: sage: N = k.ideal(3) sage: alpha = NFCusp(k, 3, a + 1) @@ -55,9 +47,7 @@ sage: alpha.is_Gamma0_equivalent(beta, N) True -Obtain transformation matrix for equivalent cusps: - -:: +Obtain transformation matrix for equivalent cusps:: sage: t, M = alpha.is_Gamma0_equivalent(beta, N, Transformation=True) sage: M[2] in N @@ -67,62 +57,39 @@ sage: alpha.apply(M) == beta True -List representatives for Gamma_0(N) - equivalence classes of cusps: - -:: +List representatives for Gamma_0(N) - equivalence classes of cusps:: sage: Gamma0_NFCusps(N) [Cusp [0: 1] of Number Field in a with defining polynomial x^2 + 5, Cusp [1: 3] of Number Field in a with defining polynomial x^2 + 5, ...] """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2009, Maite Aranes # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from six import integer_types -from sage.structure.parent_base import ParentWithBase +from sage.structure.parent import Parent from sage.structure.element import Element, is_InfinityElement from sage.structure.richcmp import richcmp, rich_to_bool +from sage.structure.unique_representation import UniqueRepresentation -from sage.misc.cachefunc import cached_method -from sage.misc.superseded import deprecated_function_alias - -_nfcusps_cache = {} - -_list_reprs_cache = {} +from sage.misc.cachefunc import cached_method, cached_function -def NFCusps_clear_list_reprs_cache(): - """ - Clear the global cache of lists of representatives for ideal classes. - - EXAMPLES:: - - sage: sage.modular.cusps_nf.NFCusps_clear_list_reprs_cache() - sage: k. = NumberField(x^3 + 11) - sage: N = k.ideal(a+1) - sage: sage.modular.cusps_nf.list_of_representatives(N) - (Fractional ideal (1), Fractional ideal (17, a - 5)) - sage: sorted(sage.modular.cusps_nf._list_reprs_cache) - [Fractional ideal (a + 1)] - sage: sage.modular.cusps_nf.NFCusps_clear_list_reprs_cache() - sage: sorted(sage.modular.cusps_nf._list_reprs_cache) - [] - """ - global _list_reprs_cache - _list_reprs_cache = {} - +@cached_function def list_of_representatives(N): """ - Returns a list of ideals, coprime to the ideal ``N``, representatives of + Return a list of ideals, coprime to the ideal ``N``, representatives of the ideal classes of the corresponding number field. - Note: This list, used every time we check `\\Gamma_0(N)` - equivalence of - cusps, is cached. + .. NOTE:: + + This list, used every time we check `\\Gamma_0(N)` - equivalence of + cusps, is cached. INPUT: @@ -135,12 +102,6 @@ def list_of_representatives(N): EXAMPLES:: - sage: sage.modular.cusps_nf.NFCusps_clear_list_reprs_cache() - sage: sorted(sage.modular.cusps_nf._list_reprs_cache) - [] - - :: - sage: from sage.modular.cusps_nf import list_of_representatives sage: k. = NumberField(x^4 + 13*x^3 - 11) sage: N = k.ideal(713, a + 208) @@ -148,43 +109,12 @@ def list_of_representatives(N): (Fractional ideal (1), Fractional ideal (47, a - 9), Fractional ideal (53, a - 16)) - - The output of ``list_of_representatives`` has been cached:: - - sage: sorted(sage.modular.cusps_nf._list_reprs_cache) - [Fractional ideal (713, a + 208)] - sage: sage.modular.cusps_nf._list_reprs_cache[N] - (Fractional ideal (1), - Fractional ideal (47, a - 9), - Fractional ideal (53, a - 16)) - """ - if N in _list_reprs_cache: - lreps = _list_reprs_cache[N] - if not (lreps is None): return lreps - lreps = NFCusps_ideal_reps_for_levelN(N)[0] - _list_reprs_cache[N] = lreps - return lreps - -def NFCusps_clear_cache(): """ - Clear the global cache of sets of cusps over number fields. + return NFCusps_ideal_reps_for_levelN(N)[0] - EXAMPLES:: - sage: sage.modular.cusps_nf.NFCusps_clear_cache() - sage: k. = NumberField(x^3 + 51) - sage: kCusps = NFCusps(k); kCusps - Set of all cusps of Number Field in a with defining polynomial x^3 + 51 - sage: sorted(sage.modular.cusps_nf._nfcusps_cache) - [Number Field in a with defining polynomial x^3 + 51] - sage: NFCusps_clear_cache() - sage: sorted(sage.modular.cusps_nf._nfcusps_cache) - [] - """ - global _nfcusps_cache - _nfcusps_cache = {} - -def NFCusps(number_field, use_cache=True): +@cached_function +def NFCusps(number_field): r""" The set of cusps of a number field `K`, i.e. `\mathbb{P}^1(K)`. @@ -192,9 +122,6 @@ def NFCusps(number_field, use_cache=True): - ``number_field`` -- a number field - - ``use_cache`` -- bool (default=True) - to set a cache of number fields - and their associated sets of cusps - OUTPUT: The set of cusps over the given number field. @@ -207,45 +134,20 @@ def NFCusps(number_field, use_cache=True): sage: kCusps is NFCusps(k) True - Saving and loading works: - - :: + Saving and loading works:: sage: loads(kCusps.dumps()) == kCusps True + """ + return NFCuspsSpace(number_field) - We test use_cache: - :: +# ************************************************************************* +# NFCuspsSpace class * +# ************************************************************************* - sage: NFCusps_clear_cache() - sage: k. = NumberField(x^2 + 11) - sage: kCusps = NFCusps(k, use_cache=False) - sage: sage.modular.cusps_nf._nfcusps_cache - {} - sage: kCusps = NFCusps(k, use_cache=True) - sage: sage.modular.cusps_nf._nfcusps_cache - {Number Field in a with defining polynomial x^2 + 11: ...} - sage: kCusps is NFCusps(k, use_cache=False) - False - sage: kCusps is NFCusps(k, use_cache=True) - True - """ - if use_cache: - key = number_field - if key in _nfcusps_cache: - C = _nfcusps_cache[key] - if not (C is None): return C - - C = NFCuspsSpace(number_field) - if use_cache: - _nfcusps_cache[key] = C - return C - -#************************************************************************** -#* NFCuspsSpace class * -#************************************************************************** -class NFCuspsSpace(ParentWithBase): + +class NFCuspsSpace(UniqueRepresentation, Parent): """ The set of cusps of a number field. See ``NFCusps`` for full documentation. @@ -255,7 +157,6 @@ class NFCuspsSpace(ParentWithBase): sage: kCusps = NFCusps(k); kCusps Set of all cusps of Number Field in a with defining polynomial x^2 + 5 """ - def __init__(self, number_field): """ See ``NFCusps`` for full documentation. @@ -267,7 +168,7 @@ def __init__(self, number_field): Set of all cusps of Number Field in a with defining polynomial x^3 + x^2 + 13 """ self.__number_field = number_field - ParentWithBase.__init__(self, self) + Parent.__init__(self, self) def __eq__(self, right): """ @@ -375,11 +276,11 @@ def zero(self): """ Return the zero cusp. - NOTE: + .. NOTE:: - This method just exists to make some general algorithms work. - It is not intended that the returned cusp is an additive - neutral element. + This method just exists to make some general algorithms work. + It is not intended that the returned cusp is an additive + neutral element. EXAMPLES:: @@ -387,12 +288,9 @@ def zero(self): sage: kCusps = NFCusps(k) sage: kCusps.zero() Cusp [0: 1] of Number Field in a with defining polynomial x^2 + 5 - """ return self(0) - zero_element = deprecated_function_alias(17694, zero) - def number_field(self): """ Return the number field that this set of cusps is attached to. @@ -406,13 +304,14 @@ def number_field(self): """ return self.__number_field -#************************************************************************** -#* NFCusp class * -#************************************************************************** +# ************************************************************************* +# NFCusp class * +# ************************************************************************* + class NFCusp(Element): r""" - Creates a number field cusp, i.e., an element of `\mathbb{P}^1(k)`. + Create a number field cusp, i.e., an element of `\mathbb{P}^1(k)`. A cusp on a number field is either an element of the field or infinity, i.e., an element of the projective line over the number field. It is @@ -532,7 +431,7 @@ def __init__(self, number_field, a, b=None, parent=None, lreps=None): Element.__init__(self, parent) R = number_field.maximal_order() if b is None: - if not a:#that is cusp "0" + if not a: # that is cusp "0" self.__a = R.zero() self.__b = R.one() return @@ -557,49 +456,49 @@ def __init__(self, number_field, a, b=None, parent=None, lreps=None): elif isinstance(a, (tuple, list)): if len(a) != 2: raise TypeError("unable to convert %r to a cusp \ - of the number field"%a) + of the number field" % a) if a[1].is_zero(): self.__a = R.one() self.__b = R.zero() elif a[0] in R and a[1] in R: self.__a = R(a[0]) self.__b = R(a[1]) - elif isinstance(a[0], NFCusp):#we know that a[1] is not zero + elif isinstance(a[0], NFCusp): # we know that a[1] is not zero if a[1] == 1: self.__a = a[0].__a self.__b = a[0].__b else: r = a[0].__a / (a[0].__b * a[1]) self.__b = R(r.denominator()) - self.__a = R(r*self.__b) + self.__a = R(r * self.__b) else: try: - r = number_field(a[0]/a[1]) + r = number_field(a[0] / a[1]) self.__b = R(r.denominator()) self.__a = R(r * self.__b) except (ValueError, TypeError): - raise TypeError("unable to convert %r to a cusp \ - of the number field"%a) + raise TypeError("unable to convert %r to a cusp " + "of the number field" % a) else: try: r = number_field(a) self.__b = R(r.denominator()) self.__a = R(r * self.__b) except (ValueError, TypeError): - raise TypeError("unable to convert %r to a cusp \ - of the number field"%a) - else:#'b' is given + raise TypeError("unable to convert %r to a cusp " + "of the number field" % a) + else: # 'b' is given if is_InfinityElement(b): if is_InfinityElement(a) or (isinstance(a, NFCusp) and a.is_infinity()): - raise TypeError("unable to convert (%r, %r) \ - to a cusp of the number field" % (a, b)) + raise TypeError("unable to convert (%r, %r) " + "to a cusp of the number field" % (a, b)) self.__a = R.zero() self.__b = R.one() return elif not b: if not a: - raise TypeError("unable to convert (%r, %r) \ - to a cusp of the number field" % (a, b)) + raise TypeError("unable to convert (%r, %r) " + "to a cusp of the number field" % (a, b)) self.__a = R.one() self.__b = R.zero() return @@ -638,15 +537,15 @@ def __init__(self, number_field, a, b=None, parent=None, lreps=None): to a cusp of the number field" % (a, b)) self.__b = R(r.denominator()) self.__a = R(r * self.__b) - if not lreps is None: - # Changes the representative of the cusp so the ideal associated - # to the cusp is one of the ideals of the given list lreps. - # Note: the trivial class is always represented by (1). + if lreps is not None: + # Changes the representative of the cusp so the ideal associated + # to the cusp is one of the ideals of the given list lreps. + # Note: the trivial class is always represented by (1). I = self.ideal() for J in lreps: - if (J/I).is_principal(): + if (J / I).is_principal(): newI = J - l = (newI/I).gens_reduced()[0] + l = (newI / I).gens_reduced()[0] self.__a = R(l * self.__a) self.__b = R(l * self.__b) @@ -672,10 +571,9 @@ def _repr_(self): return "Cusp [%s: %s] of %s" % (self.__a, self.__b, self.parent().number_field()) - def number_field(self): """ - Returns the number field of definition of the cusp ``self`` + Return the number field of definition of the cusp ``self``. EXAMPLES:: @@ -688,7 +586,7 @@ def number_field(self): def is_infinity(self): """ - Returns ``True`` if this is the cusp infinity. + Return ``True`` if this is the cusp infinity. EXAMPLES:: @@ -720,7 +618,6 @@ def numerator(self): """ return self.__a - def denominator(self): """ Return the denominator of the cusp ``self``. @@ -758,7 +655,6 @@ def _number_field_element_(self): k = self.number_field() return k(self.__a / self.__b) - def _ring_of_integers_element_(self): """ Coerce to an element of the ring of integers of the number field. @@ -775,14 +671,13 @@ def _ring_of_integers_element_(self): """ if self.__b.is_one(): return self.__a - if self.__b.is_zero(): - raise TypeError("%s is not an element of %s" % (self, - self.number_field.ring_of_integers())) R = self.number_field().ring_of_integers() + if self.__b.is_zero(): + raise TypeError("%s is not an element of %s" % (self, R)) try: - return R(self.__a/self.__b) + return R(self.__a / self.__b) except (ValueError, TypeError): - raise TypeError("%s is not an integral element"%self) + raise TypeError("%s is not an integral element" % self) def _latex_(self): r""" @@ -875,9 +770,7 @@ def apply(self, g): A number field cusp, obtained by the action of ``g`` on the cusp ``self``. - EXAMPLES: - - :: + EXAMPLES:: sage: k. = NumberField(x^2 + 23) sage: beta = NFCusp(k, 0, 1) @@ -887,12 +780,12 @@ def apply(self, g): Cusp [a: 1] of Number Field in a with defining polynomial x^2 + 23 """ k = self.number_field() - return NFCusp(k, g[0]*self.__a + g[1]*self.__b, \ - g[2]*self.__a + g[3]*self.__b) + return NFCusp(k, g[0] * self.__a + g[1] * self.__b, + g[2] * self.__a + g[3] * self.__b) def ideal(self): """ - Returns the ideal associated to the cusp ``self``. + Return the ideal associated to the cusp ``self``. EXAMPLES:: @@ -908,7 +801,7 @@ def ideal(self): def ABmatrix(self): """ - Returns AB-matrix associated to the cusp ``self``. + Return AB-matrix associated to the cusp ``self``. Given R a Dedekind domain and A, B ideals of R in inverse classes, an AB-matrix is a matrix realizing the isomorphism between R+R and A+B. @@ -943,7 +836,6 @@ def ABmatrix(self): sage: M[0] == alpha.numerator() and M[2]==alpha.denominator() True - An AB-matrix associated to a cusp alpha will send Infinity to alpha: :: @@ -968,26 +860,26 @@ def ABmatrix(self): if A.is_principal(): B = k.ideal(1) else: - B = k.ideal(A.gens_reduced()[1])/A - assert (A*B).is_principal() + B = k.ideal(A.gens_reduced()[1]) / A + assert (A * B).is_principal() a1 = self.__a a2 = self.__b - g = (A*B).gens_reduced()[0] + g = (A * B).gens_reduced()[0] Ainv = A**(-1) - A1 = a1*Ainv - A2 = a2*Ainv + A1 = a1 * Ainv + A2 = a2 * Ainv r = A1.element_1_mod(A2) - b1 = -(1-r)/a2*g - b2 = (r/a1)*g + b1 = -(1 - r) / a2 * g + b2 = (r / a1) * g ABM = [a1, b1, a2, b2] return ABM def is_Gamma0_equivalent(self, other, N, Transformation=False): r""" - Checks if cusps ``self`` and ``other`` are `\Gamma_0(N)`- equivalent. + Check if cusps ``self`` and ``other`` are `\Gamma_0(N)`- equivalent. INPUT: @@ -1038,7 +930,7 @@ def is_Gamma0_equivalent(self, other, N, Transformation=False): """ k = self.number_field() other = NFCusp(k, other) - if not (self.ideal()/other.ideal()).is_principal(): + if not (self.ideal() / other.ideal()).is_principal(): if not Transformation: return False else: @@ -1049,7 +941,7 @@ def is_Gamma0_equivalent(self, other, N, Transformation=False): alpha2 = NFCusp(k, other, lreps=reps) delta = k.ideal(alpha1.__b) + N - if (k.ideal(alpha2.__b) + N)!= delta: + if (k.ideal(alpha2.__b) + N) != delta: if not Transformation: return False else: @@ -1061,30 +953,30 @@ def is_Gamma0_equivalent(self, other, N, Transformation=False): A = alpha1.ideal() B = k.ideal(M1[1], M1[3]) - ABdelta = A*B*delta*delta + ABdelta = A * B * delta * delta units = units_mod_ideal(ABdelta) for u in units: - if (M2[2]*M1[3] - u*M1[2]*M2[3]) in ABdelta: + if (M2[2] * M1[3] - u * M1[2] * M2[3]) in ABdelta: if not Transformation: return True else: AuxCoeff = [1, 0, 0, 1] - Aux = M2[2]*M1[3] - u*M1[2]*M2[3] - if Aux in A*B*N: - if not u==1: + Aux = M2[2] * M1[3] - u * M1[2] * M2[3] + if Aux in A * B * N: + if u != 1: AuxCoeff[3] = u else: - A1 = (A*B*N)/ABdelta - A2 = B*k.ideal(M1[2]*M2[2])/(A*ABdelta) + A1 = (A * B * N) / ABdelta + A2 = B * k.ideal(M1[2] * M2[2]) / (A * ABdelta) f = A1.element_1_mod(A2) - w = ((1 - f)*Aux)/(M1[2]*M2[2]) + w = ((1 - f) * Aux) / (M1[2] * M2[2]) AuxCoeff[3] = u AuxCoeff[1] = w from sage.matrix.all import Matrix Maux = Matrix(k, 2, AuxCoeff) M1inv = Matrix(k, 2, M1).inverse() - Mtrans = Matrix(k, 2, M2)*Maux*M1inv + Mtrans = Matrix(k, 2, M2) * Maux * M1inv assert Mtrans[1][0] in N return True, Mtrans.list() if not Transformation: @@ -1092,18 +984,19 @@ def is_Gamma0_equivalent(self, other, N, Transformation=False): else: return False, 0 -#************************************************************************** +# ************************************************************************* # Global functions: # - Gamma0_NFCusps --compute list of inequivalent cusps # Internal use only: # - number_of_Gamma0_NFCusps -- useful to test Gamma0_NFCusps # - NFCusps_ideal_reps_for_levelN -- lists of reps for ideal classes # - units_mod_ideal -- needed to check Gamma0(N)-equiv of cusps -#************************************************************************** +# ************************************************************************* + def Gamma0_NFCusps(N): r""" - Returns a list of inequivalent cusps for `\Gamma_0(N)`, i.e., a set of + Return a list of inequivalent cusps for `\Gamma_0(N)`, i.e., a set of representatives for the orbits of ``self`` on `\mathbb{P}^1(k)`. INPUT: @@ -1151,56 +1044,58 @@ def Gamma0_NFCusps(N): # We create L a list of three lists, which are different and each a list of # prime ideals, coprime to N, representing the ideal classes of k L = NFCusps_ideal_reps_for_levelN(N, nlists=3) - Laux = L[1]+L[2] + Laux = L[1] + L[2] Lreps = list_of_representatives(N) Lcusps = [] k = N.number_field() for A in L[0]: - #find B in inverse class: + # find B in inverse class: if A.is_trivial(): B = k.ideal(1) - #B = k.unit_ideal() produces an error because we need fract ideal + # B = k.unit_ideal() produces an error because we need fract ideal g = 1 else: - Lbs = [P for P in Laux if (P*A).is_principal()] + Lbs = [P for P in Laux if (P * A).is_principal()] B = Lbs[0] - g = (A*B).gens_reduced()[0] + g = (A * B).gens_reduced()[0] - #for every divisor of N we have to find cusps + # for every divisor of N we have to find cusps from sage.arith.all import divisors for d in divisors(N): - #find delta prime coprime to B in inverse class of d*A - #by searching in our list of auxiliary prime ideals - Lds = [P for P in Laux if (P*d*A).is_principal() and P.is_coprime(B)] + # find delta prime coprime to B in inverse class of d*A + # by searching in our list of auxiliary prime ideals + Lds = [P for P in Laux + if (P * d * A).is_principal() and P.is_coprime(B)] deltap = Lds[0] - a = (deltap*d*A).gens_reduced()[0] - I = d + N/d - #especial case: A=B=d=<1>: + a = (deltap * d * A).gens_reduced()[0] + I = d + N / d + # special case: A=B=d=<1>: if a.is_one() and I.is_trivial(): Lcusps.append(NFCusp(k, 0, 1, lreps=Lreps)) else: u = k.unit_group().gens() for b in I.invertible_residues_mod(u): - #Note: if I trivial, invertible_residues_mod returns [1] - #lift b to (R/a)star - #we need the part of d which is coprime to I, call it M + # Note: if I trivial, invertible_residues_mod returns [1] + # lift b to (R/a)star + # we need the part of d which is coprime to I, call it M M = d.prime_to_idealM_part(I) - deltAM = deltap*A*M - u = (B*deltAM).element_1_mod(I) - v = (I*B).element_1_mod(deltAM) - newb = u*b + v - #build AB-matrix: - #----> extended gcd for k.ideal(a), k.ideal(newb) + deltAM = deltap * A * M + u = (B * deltAM).element_1_mod(I) + v = (I * B).element_1_mod(deltAM) + newb = u * b + v + # build AB-matrix: + # ----> extended gcd for k.ideal(a), k.ideal(newb) Y = k.ideal(newb).element_1_mod(k.ideal(a)) # if xa + yb = 1, cusp = y*g /a - Lcusps.append(NFCusp(k, Y*g, a, lreps=Lreps)) + Lcusps.append(NFCusp(k, Y * g, a, lreps=Lreps)) return Lcusps + def number_of_Gamma0_NFCusps(N): """ - Returns the total number of orbits of cusps under the action of the + Return the total number of orbits of cusps under the action of the congruence subgroup `\\Gamma_0(N)`. INPUT: @@ -1233,13 +1128,15 @@ def number_of_Gamma0_NFCusps(N): # The number of Gamma0(N)-sub-orbits for each Gamma-orbit: from sage.arith.all import divisors Ugens = [k(u) for u in k.unit_group().gens()] - s = sum([len((d+N/d).invertible_residues_mod(Ugens)) for d in divisors(N)]) + s = sum([len((d + N / d).invertible_residues_mod(Ugens)) + for d in divisors(N)]) # There are h Gamma-orbits, with h class number of underlying number field. - return s*k.class_number() + return s * k.class_number() + def NFCusps_ideal_reps_for_levelN(N, nlists=1): """ - Returns a list of lists (``nlists`` different lists) of prime ideals, + Return a list of lists (``nlists`` different lists) of prime ideals, coprime to ``N``, representing every ideal class of the number field. INPUT: @@ -1276,7 +1173,7 @@ def NFCusps_ideal_reps_for_levelN(N, nlists=1): Fractional ideal (127, a + 48), Fractional ideal (157, a - 19))] sage: L = NFCusps_ideal_reps_for_levelN(N, 5) - sage: all([len(L[i])==k.class_number() for i in range(len(L))]) + sage: all(len(L[i]) == k.class_number() for i in range(len(L))) True """ k = N.number_field() @@ -1289,16 +1186,17 @@ def NFCusps_ideal_reps_for_levelN(N, nlists=1): check = 0 if not I.is_principal(): Iinv = (I.ideal())**(-1) - while check = NumberField(x^2 + 5) - sage: Ck = NFCusps(k) - sage: Ck.coerce_map_from_c(k) + sage: A = J0(33) + sage: A.coerce_map_from_c(QuadraticField(3)) doctest:...: DeprecationWarning: coerce_map_from_c is deprecated See https://trac.sagemath.org/25236 for details. @@ -306,14 +305,14 @@ cdef class Parent(parent.Parent): def has_coerce_map_from_c(self, S): """ - Return True if there is a natural map from S to self. - Otherwise, return False. + Return ``True`` if there is a natural map from ``S`` to ``self``. + + Otherwise, return ``False``. TESTS:: - sage: k. = NumberField(x^2 + 5) - sage: Ck = NFCusps(k) - sage: Ck.has_coerce_map_from_c(k) + sage: A = J0(33) + sage: A.has_coerce_map_from_c(QuadraticField(3)) doctest:...: DeprecationWarning: has_coerce_map_from_c is deprecated See https://trac.sagemath.org/25236 for details. False From 483ca730a525ddad32407740248c044213e2f2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 13 Sep 2018 14:30:25 +0200 Subject: [PATCH 260/264] remove deprecated keyword in character_art + pep cleanup --- src/sage/typeset/character_art.py | 53 +++++++++++++++++-------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/sage/typeset/character_art.py b/src/sage/typeset/character_art.py index 1d01193ac05..9819ba80245 100644 --- a/src/sage/typeset/character_art.py +++ b/src/sage/typeset/character_art.py @@ -10,7 +10,7 @@ 7-bit ascii, the other uses all unicode code points. """ -#******************************************************************************* +# ****************************************************************************** # Copyright (C) 2013 Jean-Baptiste Priez , # # Distributed under the terms of the GNU General Public License (GPL) @@ -22,23 +22,25 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#******************************************************************************* +# https://www.gnu.org/licenses/ +# ****************************************************************************** from __future__ import print_function -import os, sys +import os +import sys from sage.structure.sage_object import SageObject ################################################################################ -### Global variable use to compute the maximal length allows for ascii art -### object. +# Global variable use to compute the maximal length allows for ascii art +# object. MAX_WIDTH = None ################################################################################ + class CharacterArt(SageObject): - def __init__(self, lines=[], breakpoints=[], baseline=None, atomic=None): + def __init__(self, lines=[], breakpoints=[], baseline=None): r""" Abstract base class for character art @@ -70,9 +72,6 @@ def __init__(self, lines=[], breakpoints=[], baseline=None, atomic=None): * * ***** """ - if atomic is not None: - from sage.misc.superseded import deprecation - deprecation(18357, "the argument atomic is deprecated and will be ignored") self._matrix = lines self._breakpoints = breakpoints self._baseline = baseline if baseline is not None else 0 @@ -144,7 +143,7 @@ def _repr_(self): return self._split_repr_(hsize) ######### output = "" - if len(self._matrix) > 0: + if self._matrix: for i in range(len(self._matrix) - 1): output += self._matrix[i] + "\n" return output + self._matrix[len(self._matrix) - 1] @@ -224,10 +223,10 @@ def get_breakpoints(self): def _isatty(self): """ - Test whether stdout is a TTY + Test whether ``stdout`` is a TTY. - If this test succeeds, you can assume that stdout is directly - connected to a terminal. Otherwise you should treat stdout as + If this test succeeds, you can assume that ``stdout`` is directly + connected to a terminal. Otherwise you should treat ``stdout`` as being redirected to a file. OUTPUT: @@ -262,7 +261,9 @@ def _terminal_width(self): """ if not self._isatty(): return 80 - import fcntl, termios, struct + import fcntl + import termios + import struct rc = fcntl.ioctl(int(0), termios.TIOCGWINSZ, struct.pack('HHHH', sys.stdout.fileno(), 0, 0, 0)) h, w, hp, wp = struct.unpack('HHHH', rc) @@ -287,14 +288,15 @@ def _split_repr_(self, size): * * ] ***** ] """ - f_split = self._breakpoints[0]; i = 1 + f_split = self._breakpoints[0] + i = 1 while i < len(self._breakpoints) and self._breakpoints[i] < size: f_split = self._breakpoints[i] i += 1 if size <= f_split: import warnings - warnings.warn("the console size is smaller than the pretty" + - "representation of the object") + warnings.warn("the console size is smaller than the pretty " + "representation of the object") top, bottom = self.split(f_split) return repr(top * self.empty()) + "\n" + repr(bottom) @@ -318,11 +320,13 @@ def split(self, pos): * * ] ***** ] """ - left = []; right = [] + left = [] + right = [] for line in self: left.append(line[:pos]) right.append(line[pos:]) - l_bp = []; r_bp = [] + l_bp = [] + r_bp = [] for bp in self._breakpoints: if bp < pos: l_bp.append(bp) @@ -639,7 +643,7 @@ def __add__(self, Nelt): # | if new_h - new_baseline > self._h - self._baseline: for _ in range((new_h - new_baseline) - (self._h - self._baseline)): - new_matrix.insert(0, " " * self._l) + new_matrix.insert(0, " " * self._l) # right treatement i = 0 @@ -651,16 +655,17 @@ def __add__(self, Nelt): # || # || # | - i = max(new_h - new_baseline - Nelt._h + Nelt._baseline , 0) + i = max(new_h - new_baseline - Nelt._h + Nelt._baseline, 0) for j in range(Nelt._h): - new_matrix[i+j] += Nelt._matrix[j] + new_matrix[i + j] += Nelt._matrix[j] else: for line in self._matrix: new_matrix.append(line + " " * (self._l - len(line))) for i, line_i in enumerate(Nelt._matrix): if i == len(new_matrix): new_matrix.append(" " * self._l + line_i) - else: new_matrix[i] += line_i + else: + new_matrix[i] += line_i # breakpoint new_breakpoints = list(self._breakpoints) From 694fe31ac3289eb1acac580d15ed984fa5a0a08b Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Thu, 13 Sep 2018 11:59:39 -0700 Subject: [PATCH 261/264] trac 26277: sort the generators in _stanley_reisner_base_ring. --- src/sage/homology/simplicial_complex.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sage/homology/simplicial_complex.py b/src/sage/homology/simplicial_complex.py index f7d0cc3b4ac..62808e8c6d5 100644 --- a/src/sage/homology/simplicial_complex.py +++ b/src/sage/homology/simplicial_complex.py @@ -3317,9 +3317,14 @@ def _stanley_reisner_base_ring(self, base_ring=ZZ): Multivariate Polynomial Ring in x0, x1, x2, x3 over Integer Ring sage: Y = SimplicialComplex([['a', 'b', 'c']]) sage: Y._stanley_reisner_base_ring(base_ring=QQ) - Multivariate Polynomial Ring in a, c, b over Rational Field + Multivariate Polynomial Ring in a, b, c over Rational Field """ - return PolynomialRing(base_ring, list(self._gen_dict.values())) + verts = self._gen_dict.values() + try: + verts = sorted(verts) + except TypeError: + verts = sorted(verts, key=str) + return PolynomialRing(base_ring, verts) def stanley_reisner_ring(self, base_ring=ZZ): """ From 47e82d1eab375eb53f9c9a247d9627541e48db77 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Fri, 14 Sep 2018 08:20:59 +0000 Subject: [PATCH 262/264] remove the deprecated TermOrder.compare_tuples methods (deprecated since Sage 7.5) --- src/sage/rings/polynomial/term_order.py | 192 +----------------------- 1 file changed, 1 insertion(+), 191 deletions(-) diff --git a/src/sage/rings/polynomial/term_order.py b/src/sage/rings/polynomial/term_order.py index a8cb2489ecf..3f0605ed6d7 100644 --- a/src/sage/rings/polynomial/term_order.py +++ b/src/sage/rings/polynomial/term_order.py @@ -366,7 +366,6 @@ import re from sage.structure.sage_object import SageObject -from sage.misc.superseded import deprecation print_name_mapping = { 'lex' : 'Lexicographic', @@ -828,42 +827,18 @@ def __getattr__(self,name): """ Return the correct ``compare_tuples/greater_tuple/sortkey function``. - Note that the ``compare_tuples`` methods have been deprecated in - :trac:`21766`. - EXAMPLES:: - sage: TermOrder('lex').compare_tuples - sage: TermOrder('lex').sortkey - sage: TermOrder('deglex').compare_tuples - """ - if name == 'compare_tuples': - return getattr(self, 'compare_tuples_' + self._name) - elif name == 'greater_tuple': + if name == 'greater_tuple': return getattr(self, 'greater_tuple_' + self._name) elif name == 'sortkey': return getattr(self, 'sortkey_' + self._name) else: raise AttributeError(name) - def compare_tuples_matrix(self, f, g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - for row in self._matrix: - sf = sum(l*r for (l,r) in zip(row,f)) - sg = sum(l*r for (l,r) in zip(row,g)) - - if sf > sg: - return 1 - elif sf < sg: - return -1 - return 0 - def sortkey_matrix(self, f): """ Return the sortkey of an exponent tuple with respect to the matrix @@ -884,18 +859,6 @@ def sortkey_matrix(self, f): return tuple(sum(l * r for l, r in zip(row, f)) for row in self._matrix) - def compare_tuples_lex(self,f,g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - if f > g: - return 1 - elif f < g: - return -1 - else: - return 0 - def sortkey_lex(self, f): """ Return the sortkey of an exponent tuple with respect to the @@ -915,13 +878,6 @@ def sortkey_lex(self, f): """ return f - def compare_tuples_invlex(self,f,g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - return self.compare_tuples_lex(f.reversed(),g.reversed()) - def sortkey_invlex(self, f): """ Return the sortkey of an exponent tuple with respect to the inversed @@ -941,20 +897,6 @@ def sortkey_invlex(self, f): """ return f.reversed() - def compare_tuples_deglex(self,f,g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - sf = sum(f.nonzero_values(sort=False)) - sg = sum(g.nonzero_values(sort=False)) - if sf > sg: - return 1 - elif sf < sg: - return -1 - elif sf == sg: - return self.compare_tuples_lex(f,g) - def sortkey_deglex(self, f): """ Return the sortkey of an exponent tuple with respect to the degree @@ -975,20 +917,6 @@ def sortkey_deglex(self, f): """ return (sum(f.nonzero_values(sort=False)), f) - def compare_tuples_degrevlex(self,f,g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - sf = sum(f.nonzero_values(sort=False)) - sg = sum(g.nonzero_values(sort=False)) - if sf > sg: - return 1 - elif sf < sg: - return -1 - elif sf == sg: - return -self.compare_tuples_lex(f.reversed(), g.reversed()) - def sortkey_degrevlex(self, f): """ Return the sortkey of an exponent tuple with respect to the @@ -1010,13 +938,6 @@ def sortkey_degrevlex(self, f): return (sum(f.nonzero_values(sort=False)), tuple(-v for v in f.reversed())) - def compare_tuples_neglex(self,f,g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - return -self.compare_tuples_lex(f,g) - def sortkey_neglex(self, f): """ Return the sortkey of an exponent tuple with respect to the negative @@ -1036,20 +957,6 @@ def sortkey_neglex(self, f): """ return tuple(-v for v in f) - def compare_tuples_negdegrevlex(self,f,g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - sf = sum(f.nonzero_values(sort=False)) - sg = sum(g.nonzero_values(sort=False)) - if sf > sg: - return -1 - elif sf < sg: - return 1 - elif sf == sg: - return -self.compare_tuples_lex(f.reversed(), g.reversed()) - def sortkey_negdegrevlex(self, f): """ Return the sortkey of an exponent tuple with respect to the @@ -1070,20 +977,6 @@ def sortkey_negdegrevlex(self, f): return (-sum(f.nonzero_values(sort=False)), tuple(-v for v in f.reversed())) - def compare_tuples_negdeglex(self,f,g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - sf = sum(f.nonzero_values(sort=False)) - sg = sum(g.nonzero_values(sort=False)) - if sf > sg: - return -1 - elif sf < sg: - return 1 - elif sf == sg: - return self.compare_tuples_lex(f,g) - def sortkey_negdeglex(self, f): """ Return the sortkey of an exponent tuple with respect to the @@ -1103,20 +996,6 @@ def sortkey_negdeglex(self, f): """ return (-sum(f.nonzero_values(sort=False)), f) - def compare_tuples_degneglex(self,f,g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - sf = sum(f.nonzero_values(sort=False)) - sg = sum(g.nonzero_values(sort=False)) - if sf < sg: - return -1 - elif sf > sg: - return 1 - elif sf == sg: - return self.compare_tuples_neglex(f,g) - def sortkey_degneglex(self, f): """ Return the sortkey of an exponent tuple with respect to the @@ -1136,20 +1015,6 @@ def sortkey_degneglex(self, f): """ return (sum(f.nonzero_values(sort=False)), tuple(-v for v in f)) - def compare_tuples_wdegrevlex(self,f,g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - sf = sum(l*r for (l,r) in zip(f,self._weights)) - sg = sum(l*r for (l,r) in zip(g,self._weights)) - if sf > sg: - return 1 - elif sf < sg: - return -1 - elif sf == sg: - return -self.compare_tuples_lex(f.reversed(), g.reversed()) - def sortkey_wdegrevlex(self, f): """ Return the sortkey of an exponent tuple with respect to the @@ -1171,20 +1036,6 @@ def sortkey_wdegrevlex(self, f): return (sum(l * r for (l, r) in zip(f, self._weights)), tuple(-v for v in f.reversed())) - def compare_tuples_wdeglex(self,f,g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - sf = sum(l*r for (l,r) in zip(f,self._weights)) - sg = sum(l*r for (l,r) in zip(g,self._weights)) - if sf > sg: - return 1 - elif sf < sg: - return -1 - elif sf == sg: - return self.compare_tuples_lex(f,g) - def sortkey_wdeglex(self, f): """ Return the sortkey of an exponent tuple with respect to the @@ -1205,20 +1056,6 @@ def sortkey_wdeglex(self, f): """ return (sum(l * r for (l, r) in zip(f, self._weights)), f) - def compare_tuples_negwdeglex(self,f,g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - sf = sum(l*r for (l,r) in zip(f,self._weights)) - sg = sum(l*r for (l,r) in zip(g,self._weights)) - if sf > sg: - return -1 - elif sf < sg: - return 1 - elif sf == sg: - return self.compare_tuples_lex(f,g) - def sortkey_negwdeglex(self, f): """ Return the sortkey of an exponent tuple with respect to the @@ -1239,20 +1076,6 @@ def sortkey_negwdeglex(self, f): """ return (-sum(l * r for (l, r) in zip(f, self._weights)), f) - def compare_tuples_negwdegrevlex(self,f,g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - sf = sum(l*r for (l,r) in zip(f,self._weights)) - sg = sum(l*r for (l,r) in zip(g,self._weights)) - if sf > sg: - return -1 - elif sf < sg: - return 1 - elif sf == sg: - return -self.compare_tuples_lex(f.reversed(), g.reversed()) - def sortkey_negwdegrevlex(self, f): """ Return the sortkey of an exponent tuple with respect to the @@ -1274,19 +1097,6 @@ def sortkey_negwdegrevlex(self, f): return (-sum(l * r for (l, r) in zip(f, self._weights)), tuple(-v for v in f.reversed())) - def compare_tuples_block(self, f,g): - """ - DEPRECATED in :trac:`21766` - """ - deprecation(21766, 'sorting of polynomials now uses sortkey instead') - n = 0 - for block in self: - r = getattr(block,"compare_tuples_" + block.name())(f[n:n+len(block)],g[n:n+len(block)]) - if r != 0: - return r - n += len(block) - return 0 - def sortkey_block(self, f): """ Return the sortkey of an exponent tuple with respect to the From 91ea150057f19384f9148f2fc3c090643b087395 Mon Sep 17 00:00:00 2001 From: "Erik M. Bray" Date: Fri, 14 Sep 2018 08:40:06 +0000 Subject: [PATCH 263/264] replace this __getattr__ with properties --- src/sage/rings/polynomial/term_order.py | 38 ++++++++++++++++++------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/sage/rings/polynomial/term_order.py b/src/sage/rings/polynomial/term_order.py index 3f0605ed6d7..5c520cf3b2a 100644 --- a/src/sage/rings/polynomial/term_order.py +++ b/src/sage/rings/polynomial/term_order.py @@ -823,21 +823,39 @@ def __copy(self, other): """ self.__dict__ = other.__dict__.copy() - def __getattr__(self,name): + @property + def sortkey(self): """ - Return the correct ``compare_tuples/greater_tuple/sortkey function``. + The default ``sortkey`` method for this term order. EXAMPLES:: - sage: TermOrder('lex').sortkey - + sage: O = TermOrder() + sage: O.sortkey.__func__ is O.sortkey_lex.__func__ + True + sage: O = TermOrder('deglex') + sage: O.sortkey.__func__ is O.sortkey_deglex.__func__ + True """ - if name == 'greater_tuple': - return getattr(self, 'greater_tuple_' + self._name) - elif name == 'sortkey': - return getattr(self, 'sortkey_' + self._name) - else: - raise AttributeError(name) + + return getattr(self, 'sortkey_' + self._name) + + @property + def greater_tuple(self): + """ + The default ``greater_tuple`` method for this term order. + + EXAMPLES:: + + sage: O = TermOrder() + sage: O.greater_tuple.__func__ is O.greater_tuple_lex.__func__ + True + sage: O = TermOrder('deglex') + sage: O.greater_tuple.__func__ is O.greater_tuple_deglex.__func__ + True + """ + + return getattr(self, 'greater_tuple_' + self._name) def sortkey_matrix(self, f): """ From ca26fcca0faebd89ac56f907f911608e81a4c294 Mon Sep 17 00:00:00 2001 From: Volker Braun Date: Sat, 15 Sep 2018 14:30:57 +0200 Subject: [PATCH 264/264] Updated SageMath version to 8.4.beta5 --- VERSION.txt | 2 +- build/pkgs/configure/checksums.ini | 6 +++--- build/pkgs/configure/package-version.txt | 2 +- src/bin/sage-version.sh | 6 +++--- src/sage/version.py | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index b35044f9a9a..f6310824981 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 8.4.beta4, Release Date: 2018-09-06 +SageMath version 8.4.beta5, Release Date: 2018-09-15 diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index b3e693258fe..81522cdc668 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=0fbdda7f9b9a2181f32cd257147f6bd4712697fa -md5=7a95b74489dc74b49f2a36a68b40ba6b -cksum=2018224632 +sha1=d37563de2152d075765145f2d50568182972cf94 +md5=df46180857c9b8bb2fbccfaa8405214b +cksum=492825525 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 1473a88f559..e01062f1f81 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -281 +282 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 5eae00130a8..85325a96529 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -1,5 +1,5 @@ # Sage version information for shell scripts # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='8.4.beta4' -SAGE_RELEASE_DATE='2018-09-06' -SAGE_VERSION_BANNER='SageMath version 8.4.beta4, Release Date: 2018-09-06' +SAGE_VERSION='8.4.beta5' +SAGE_RELEASE_DATE='2018-09-15' +SAGE_VERSION_BANNER='SageMath version 8.4.beta5, Release Date: 2018-09-15' diff --git a/src/sage/version.py b/src/sage/version.py index 086807abb58..f772939a2ae 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,5 +1,5 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '8.4.beta4' -date = '2018-09-06' -banner = 'SageMath version 8.4.beta4, Release Date: 2018-09-06' +version = '8.4.beta5' +date = '2018-09-15' +banner = 'SageMath version 8.4.beta5, Release Date: 2018-09-15'