def coxeter_diagram(self): """ Return the Coxeter diagram for ``self``. EXAMPLES:: sage: cd = CartanType("A2xB2xF4").coxeter_diagram() sage: cd Graph on 8 vertices sage: cd.edges() [(1, 2, 3), (3, 4, 4), (5, 6, 3), (6, 7, 4), (7, 8, 3)] sage: CartanType("F4xA2").coxeter_diagram().edges() [(1, 2, 3), (2, 3, 4), (3, 4, 3), (5, 6, 3)] sage: cd = CartanType("A1xH3").coxeter_diagram(); cd Graph on 4 vertices sage: cd.edges() [(2, 3, 3), (3, 4, 5)] """ from sage.graphs.graph import Graph relabelling = self._index_relabelling g = Graph(multiedges=False) g.add_vertices(self.index_set()) for i, t in enumerate(self._types): for [e1, e2, l] in t.coxeter_diagram().edges(): g.add_edge(relabelling[i, e1], relabelling[i, e2], label=l) return g
def cycle_elements(self): r""" Returns the list of all elements in a cycle for this endofunction. OUTPUT: list EXAMPLES:: sage: from slabbe import Endofunction sage: L = [6, 5, 7, 2, 1, 1, 2, 6, 2, 4] sage: f = Endofunction(L) sage: f.cycle_elements() # random order [0, 6, 7, 2, 1, 5] .. NOTE:: ``G.cycle_basis()`` is not implemented for directed or multiedge graphs (in Networkx). Hence, the ``cycle_basis`` method is missing the 2-cycles. """ G = Graph(loops=True) for i, a in enumerate(self._list): G.add_edge(i,a) C = G.cycle_basis() L = flatten(C) # adding the missing 2-cycle elements for i in self.two_cycle_elements(): L.append(i) return L
def to_bipartite_graph(self, with_partition=False): r""" Returns the associated bipartite graph INPUT: - with_partition -- boolean (default: False) OUTPUT: - a graph or a pair (graph, partition) EXAMPLES:: sage: H = designs.steiner_triple_system(7).blocks() sage: H = Hypergraph(H) sage: g = H.to_bipartite_graph(); g Graph on 14 vertices sage: g.is_regular() True """ from sage.graphs.graph import Graph G = Graph() domain = list(self.domain()) G.add_vertices(domain) for s in self._sets: for i in s: G.add_edge(s, i) if with_partition: return (G, [domain, list(self._sets)]) else: return G
def coxeter_diagram(self): """ Return the Coxeter diagram for ``self``. EXAMPLES:: sage: cd = CartanType("A2xB2xF4").coxeter_diagram() sage: cd Graph on 8 vertices sage: cd.edges() [(1, 2, 3), (3, 4, 4), (5, 6, 3), (6, 7, 4), (7, 8, 3)] sage: CartanType("F4xA2").coxeter_diagram().edges() [(1, 2, 3), (2, 3, 4), (3, 4, 3), (5, 6, 3)] sage: cd = CartanType("A1xH3").coxeter_diagram(); cd Graph on 4 vertices sage: cd.edges() [(2, 3, 3), (3, 4, 5)] """ from sage.graphs.graph import Graph relabelling = self._index_relabelling g = Graph(multiedges=False) g.add_vertices(self.index_set()) for i,t in enumerate(self._types): for [e1, e2, l] in t.coxeter_diagram().edges(): g.add_edge(relabelling[i,e1], relabelling[i,e2], label=l) return g
def coxeter_diagram(self): """ Returns a Coxeter diagram for type H. EXAMPLES:: sage: ct = CartanType(['H',3]) sage: ct.coxeter_diagram() Graph on 3 vertices sage: sorted(ct.coxeter_diagram().edges()) [(1, 2, 3), (2, 3, 5)] sage: ct.coxeter_matrix() [1 3 2] [3 1 5] [2 5 1] sage: ct = CartanType(['H',4]) sage: ct.coxeter_diagram() Graph on 4 vertices sage: sorted(ct.coxeter_diagram().edges()) [(1, 2, 3), (2, 3, 3), (3, 4, 5)] sage: ct.coxeter_matrix() [1 3 2 2] [3 1 3 2] [2 3 1 5] [2 2 5 1] """ from sage.graphs.graph import Graph n = self.n g = Graph(multiedges=False) for i in range(1, n): g.add_edge(i, i+1, 3) g.set_edge_label(n-1, n, 5) return g
def to_undirected_graph(self): r""" Return the undirected graph obtained from the tree nodes and edges. The graph is endowed with an embedding, so that it will be displayed correctly. EXAMPLES:: sage: t = OrderedTree([]) sage: t.to_undirected_graph() Graph on 1 vertex sage: t = OrderedTree([[[]],[],[]]) sage: t.to_undirected_graph() Graph on 5 vertices If the tree is labelled, we use its labelling to label the graph. This will fail if the labels are not all distinct. Otherwise, we use the graph canonical labelling which means that two different trees can have the same graph. EXAMPLES:: sage: t = OrderedTree([[[]],[],[]]) sage: t.canonical_labelling().to_undirected_graph() Graph on 5 vertices TESTS:: sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph() False sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph() True sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph() False """ from sage.graphs.graph import Graph g = Graph() if self in LabelledOrderedTrees(): relabel = False else: self = self.canonical_labelling() relabel = True roots = [self] g.add_vertex(name=self.label()) emb = {self.label(): []} while roots: node = roots.pop() children = reversed([child.label() for child in node]) emb[node.label()].extend(children) for child in node: g.add_vertex(name=child.label()) emb[child.label()] = [node.label()] g.add_edge(child.label(), node.label()) roots.append(child) g.set_embedding(emb) if relabel: g = g.canonical_label() return g
def is_connected(self, force_computation=False): if not force_computation and self._connected: return True from sage.graphs.graph import Graph G = Graph(loops=True, multiedges=True) for i in range(self._total_darts): if self._active_darts[i]: G.add_edge(i,self._vertices[i]) G.add_edge(i,self._edges[i]) G.add_edge(i,self._faces[i]) return G.is_connected()
def is_connected(self, force_computation=False): if not force_computation and self._connected: return True from sage.graphs.graph import Graph G = Graph(loops=True, multiedges=True) for i in xrange(self._total_darts): if self._active_darts[i]: G.add_edge(i,self._vertices[i]) G.add_edge(i,self._edges[i]) G.add_edge(i,self._faces[i]) return G.is_connected()
def dual_equivalence_graph(self): r""" Return the graph with vertices the orbit of ``self`` and edges given by the action of the cactus group generators. In most implementations the generators `s_{i,i+1}` will act as the identity operators. The usual dual equivalence graphs are given by replacing the label `i,i+2` by `i` and removing edges with other labels. EXAMPLES:: sage: s = path_tableaux.DyckPath([0,1,2,3,2,3,2,1,0]) sage: s.dual_equivalence_graph().adjacency_matrix() [0 1 1 1 0 1 0 1 1 0 0 0 0 0] [1 0 1 1 1 1 1 0 1 0 0 1 1 0] [1 1 0 1 1 1 0 1 0 1 1 1 0 0] [1 1 1 0 1 0 1 1 1 1 0 1 1 0] [0 1 1 1 0 0 1 0 0 1 1 0 1 1] [1 1 1 0 0 0 1 1 1 1 1 0 1 0] [0 1 0 1 1 1 0 1 0 1 1 1 0 1] [1 0 1 1 0 1 1 0 1 1 1 1 1 0] [1 1 0 1 0 1 0 1 0 1 0 1 1 0] [0 0 1 1 1 1 1 1 1 0 0 1 1 1] [0 0 1 0 1 1 1 1 0 0 0 1 1 1] [0 1 1 1 0 0 1 1 1 1 1 0 1 1] [0 1 0 1 1 1 0 1 1 1 1 1 0 1] [0 0 0 0 1 0 1 0 0 1 1 1 1 0] sage: s = path_tableaux.DyckPath([0,1,2,3,2,1,0]) sage: sorted(s.dual_equivalence_graph().edges()) [([0, 1, 0, 1, 0, 1, 0], [0, 1, 0, 1, 2, 1, 0], '4,7'), ([0, 1, 0, 1, 0, 1, 0], [0, 1, 2, 1, 0, 1, 0], '2,5'), ([0, 1, 0, 1, 0, 1, 0], [0, 1, 2, 1, 2, 1, 0], '2,7'), ([0, 1, 0, 1, 2, 1, 0], [0, 1, 2, 1, 0, 1, 0], '2,6'), ([0, 1, 0, 1, 2, 1, 0], [0, 1, 2, 1, 2, 1, 0], '1,4'), ([0, 1, 0, 1, 2, 1, 0], [0, 1, 2, 3, 2, 1, 0], '2,7'), ([0, 1, 2, 1, 0, 1, 0], [0, 1, 2, 1, 2, 1, 0], '4,7'), ([0, 1, 2, 1, 0, 1, 0], [0, 1, 2, 3, 2, 1, 0], '3,7'), ([0, 1, 2, 1, 2, 1, 0], [0, 1, 2, 3, 2, 1, 0], '3,6')] """ from sage.graphs.graph import Graph from itertools import combinations G = Graph() orb = self.orbit() for a in orb: for i,j in combinations(range(1,self.size()+1),2): b = a.cactus(i,j) if a != b: G.add_edge(a,b,"%d,%d" % (i,j)) return G
def CayleyGraph(vspace, gens, directed=False): """ Generate a Cayley Graph over given vector space with edges generated by given generators. The graph is optionally directed. Try e.g. CayleyGraph(VectorSpace(GF(2), 3), [(1,0,0), (0,1,0), (0,0,1)]) """ G = Graph() for v in vspace: G.add_vertex(tuple(v)) for g in gens: g2 = vspace(g) G.add_edge(tuple(v), tuple(v + g2)) return G
def _spring_layout(self): r""" Return a spring layout for the vertices. The layout is computed by creating a graph `G` on the vertices *and* sets of the hypergraph. Each set is then made adjacent in `G` with all vertices it contains before a spring layout is computed for this graph. The position of the vertices in the hypergraph is the position of the same vertices in the graph's layout. .. NOTE:: This method also returns the position of the "fake" vertices, i.e. those representing the sets. EXAMPLES:: sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H Hypergraph on 6 vertices containing 4 sets sage: L = H._spring_layout() sage: L # random {1: (0.238, -0.926), 2: (0.672, -0.518), 3: (0.449, -0.225), 4: (0.782, 0.225), 5: (0.558, 0.518), 6: (0.992, 0.926), {3, 4, 5}: (0.504, 0.173), {2, 3, 4}: (0.727, -0.173), {4, 5, 6}: (0.838, 0.617), {1, 2, 3}: (0.393, -0.617)} sage: all(v in L for v in H.domain()) True sage: all(v in L for v in H._sets) True """ from sage.graphs.graph import Graph g = Graph() for s in self._sets: for x in s: g.add_edge(s, x) _ = g.plot(iterations=50000, save_pos=True) # The values are rounded as TikZ does not like accuracy. return { k: (round(x, 3), round(y, 3)) for k, (x, y) in g.get_pos().items() }
def to_undirected_graph(self): r""" Return the undirected graph obtained from the tree nodes and edges. EXAMPLES:: sage: t = OrderedTree([]) sage: t.to_undirected_graph() Graph on 1 vertex sage: t = OrderedTree([[[]],[],[]]) sage: t.to_undirected_graph() Graph on 5 vertices If the tree is labelled, we use its labelling to label the graph. Otherwise, we use the graph canonical labelling which means that two different trees can have the same graph. EXAMPLES:: sage: t = OrderedTree([[[]],[],[]]) sage: t.canonical_labelling().to_undirected_graph() Graph on 5 vertices sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph() False sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph() True sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph() False """ from sage.graphs.graph import Graph g = Graph() if self in LabelledOrderedTrees(): relabel = False else: self = self.canonical_labelling() relabel = True roots = [self] g.add_vertex(name=self.label()) while len(roots) != 0: node = roots.pop() for child in node: g.add_vertex(name=child.label()) g.add_edge(child.label(), node.label()) roots.append(child) if relabel: g = g.canonical_label() return g
def to_undirected_graph(self): r""" Return the undirected graph obtained from the tree nodes and edges. EXAMPLES:: sage: t = OrderedTree([]) sage: t.to_undirected_graph() Graph on 1 vertex sage: t = OrderedTree([[[]],[],[]]) sage: t.to_undirected_graph() Graph on 5 vertices If the tree is labelled, we use its labelling to label the graph. Otherwise, we use the graph canonical labelling which means that two different trees can have the same graph. EXAMPLES:: sage: t = OrderedTree([[[]],[],[]]) sage: t.canonical_labelling().to_undirected_graph() Graph on 5 vertices sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph() False sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph() True sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph() False """ from sage.graphs.graph import Graph g = Graph() if self in LabelledOrderedTrees(): relabel = False else: self = self.canonical_labelling() relabel = True roots = [self] g.add_vertex(name=self.label()) while len(roots) != 0: node = roots.pop() for child in node: g.add_vertex(name=child.label()) g.add_edge(child.label(), node.label()) roots.append(child) if (relabel): g = g.canonical_label() return g
def _spring_layout(self): r""" Return a spring layout for the vertices. The layout is computed by creating a graph `G` on the vertices *and* sets of the hypergraph. Each set is then made adjacent in `G` with all vertices it contains before a spring layout is computed for this graph. The position of the vertices in the hypergraph is the position of the same vertices in the graph's layout. .. NOTE:: This method also returns the position of the "fake" vertices, i.e. those representing the sets. EXAMPLES:: sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H Hypergraph on 6 vertices containing 4 sets sage: L = H._spring_layout() sage: L # random {1: (0.238, -0.926), 2: (0.672, -0.518), 3: (0.449, -0.225), 4: (0.782, 0.225), 5: (0.558, 0.518), 6: (0.992, 0.926), {3, 4, 5}: (0.504, 0.173), {2, 3, 4}: (0.727, -0.173), {4, 5, 6}: (0.838, 0.617), {1, 2, 3}: (0.393, -0.617)} sage: all(v in L for v in H.domain()) True sage: all(v in L for v in H._sets) True """ from sage.graphs.graph import Graph g = Graph() for s in self._sets: for x in s: g.add_edge(s,x) _ = g.plot(iterations = 50000,save_pos=True) # The values are rounded as TikZ does not like accuracy. return {k:(round(x,3),round(y,3)) for k,(x,y) in g.get_pos().items()}
def _check(self): r""" Check that the data of the Ribbon graph is coherent """ from sage.graphs.graph import Graph G = Graph() if len(self._active_darts) != self._total_darts: raise ValueError, "the length of active darts is not total_darts" if self._active_darts.count(True) != self._num_darts: raise ValueError, "the number of darts do not coincide with active darts" for i in xrange(self._total_darts): if self._active_darts[i]: G.add_edge(i,self._vertices[i]) G.add_edge(i,self._edges[i]) G.add_edge(i,self._faces[i]) if self._vertices[i] is None or self._vertices_inv[i] is None: raise ValueError, "dart %d is active but has no vertex" %i if self._edges[i] is None or self._edges_inv[i] is None: raise ValueError, "dart %d is active but has no edge" %i if self._faces[i] is None or self._faces_inv[i] is None: raise ValueError, "dart %d is active but has no face" %i if self._vertices[self._vertices_inv[i]] != i: raise ValueError, "vertices is not the inverse of vertices_inv" if self._edges[self._edges_inv[i]] != i: raise ValueError, "edges is not the inverse of edges_inv" if self._faces[self._faces_inv[i]] != i: raise ValueError, "faces is not the inverse of faces_inv" if self._faces[self._edges[self._vertices[i]]] != i: raise ValueError, "the Ribbon graph condition vef=() is not satisfied for %d" %i if self._edges[i] == i or self._edges[self._edges[i]] != i: raise ValueError, "edges is not an involution without fixed point for %d" %i else: if self._vertices[i] is not None or self._vertices_inv[i] is not None: raise ValueError, "dart %d is not active but has a vertex" %i if self._edges[i] is not None or self._edges_inv[i] is not None: raise ValueError, "dart %d is not active but has an edge" %i if self._faces[i] is not None or self._faces_inv[i] is not None: raise ValueError, "dart %d is not active but has a face" %i if not G.is_connected(): raise ValueError, "the graph is not connected"
def graph(self): r""" EXAMPLES:: sage: from slabbe import DoubleRootedTree sage: edges = [(0,5),(1,2),(2,6),(3,2),(4,1),(5,7),(7,1),(8,2),(9,4)] sage: D = DoubleRootedTree(edges, 6, 0) sage: D.graph() Graph on 10 vertices """ if self._graph is None: G = Graph() G.add_vertex(self._rootA) G.add_vertex(self._rootB) for a,b in self._edges: G.add_edge(a,b) self._graph = G return self._graph
def mutant_neighborhood_graph(link): G = Graph() isosig = link.exterior().isometry_signature(of_link=True) isosig_to_link_dict = {isosig: link.PD_code()} links_boundary = [link] while len(links_boundary) > 0: new_links_boundary = [] for L in links_boundary: L_iso = L.exterior().isometry_signature(of_link=True) mutants = all_mutants(L) for M in mutants: isosig = M.exterior().isometry_signature(of_link=True) if isosig not in isosig_to_link_dict: new_links_boundary.append(M) isosig_to_link_dict[isosig] = M.PD_code() G.add_edge(L_iso, isosig) links_boundary = new_links_boundary return G, isosig_to_link_dict
def underlying_graph(G): r""" Given a graph `G` with multi-edges, returns a graph where all the multi-edges are replaced with a single edge. EXAMPLES:: sage: from sage.graphs.tutte_polynomial import underlying_graph sage: G = Graph(multiedges=True) sage: G.add_edges([(0,1,'a'),(0,1,'b')]) sage: G.edges() [(0, 1, 'a'), (0, 1, 'b')] sage: underlying_graph(G).edges() [(0, 1, None)] """ g = Graph() g.allow_loops(True) for edge in set(G.edges(labels=False)): g.add_edge(edge) return g
def coxeter_graph(self): """ Return the Coxeter graph of ``self``. EXAMPLES:: sage: W = CoxeterGroup(['H',3], implementation="reflection") sage: G = W.coxeter_graph(); G Graph on 3 vertices sage: G.edges() [(1, 2, None), (2, 3, 5)] sage: CoxeterGroup(G) is W True sage: G = Graph([(0, 1, 3), (1, 2, oo)]) sage: W = CoxeterGroup(G) sage: W.coxeter_graph() == G True sage: CoxeterGroup(W.coxeter_graph()) is W True """ G = Graph() G.add_vertices(self.index_set()) for i, row in enumerate(self._matrix.rows()): for j, val in enumerate(row[i + 1 :]): if val == 3: G.add_edge(self._index_set[i], self._index_set[i + 1 + j]) elif val > 3: G.add_edge(self._index_set[i], self._index_set[i + 1 + j], val) elif val == -1: # FIXME: Hack because there is no ZZ\cup\{\infty\} G.add_edge(self._index_set[i], self._index_set[i + 1 + j], infinity) return G
def coxeter_graph(self): """ Return the Coxeter graph of ``self``. EXAMPLES:: sage: W = CoxeterGroup(['H',3], implementation="reflection") sage: G = W.coxeter_graph(); G Graph on 3 vertices sage: G.edges() [(1, 2, None), (2, 3, 5)] sage: CoxeterGroup(G) is W True sage: G = Graph([(0, 1, 3), (1, 2, oo)]) sage: W = CoxeterGroup(G) sage: W.coxeter_graph() == G True sage: CoxeterGroup(W.coxeter_graph()) is W True """ G = Graph() G.add_vertices(self.index_set()) for i, row in enumerate(self._matrix.rows()): for j, val in enumerate(row[i + 1:]): if val == 3: G.add_edge(self._index_set[i], self._index_set[i + 1 + j]) elif val > 3: G.add_edge(self._index_set[i], self._index_set[i + 1 + j], val) elif val == -1: # FIXME: Hack because there is no ZZ\cup\{\infty\} G.add_edge(self._index_set[i], self._index_set[i + 1 + j], infinity) return G
def to_graph(self): r""" Returns the graph corresponding to the perfect matching. OUTPUT: The realization of ``self`` as a graph. EXAMPLES:: sage: PerfectMatching([[1,3], [4,2]]).to_graph().edges(labels=False) [(1, 3), (2, 4)] sage: PerfectMatching([[1,4], [3,2]]).to_graph().edges(labels=False) [(1, 4), (2, 3)] sage: PerfectMatching([]).to_graph().edges(labels=False) [] """ from sage.graphs.graph import Graph G = Graph() for a, b in self.value: G.add_edge((a, b)) return G
def connection_graph(self): """ Return the graph which has the variables of this system as vertices and edges between two variables if they appear in the same polynomial. EXAMPLE:: sage: B.<x,y,z> = BooleanPolynomialRing() sage: F = Sequence([x*y + y + 1, z + 1]) sage: F.connection_graph() Graph on 3 vertices """ V = sorted(self.variables()) from sage.graphs.graph import Graph g = Graph() g.add_vertices(sorted(V)) for f in self: v = f.variables() a,tail = v[0],v[1:] for b in tail: g.add_edge((a,b)) return g
def decode_fgraph_string(s): data = s.split("_") n = decode_6bits(data[0]) f = [decode_6bits(data[1][i]) for i in range(n)] G = Graph() G.add_vertices(range(n)) # read in the adjacency matrix cur = 0 val = decode_6bits(data[2][cur]) mask = 1 << 5 # start with the high bit for j in range( n): # adj matrix is bit packed in colex order, as in graph6 format for i in range(j): if val & mask != 0: # test whether that bit is nonzero G.add_edge(i, j) mask >>= 1 if mask == 0: # mask has become 0 cur += 1 val = decode_6bits(data[2][cur]) mask = 1 << 5 return G, f
def IntervalGraph(intervals, points_ordered = False): r""" Returns the graph corresponding to the given intervals. An interval graph is built from a list `(a_i,b_i)_{1\leq i \leq n}` of intervals : to each interval of the list is associated one vertex, two vertices being adjacent if the two corresponding (closed) intervals intersect. INPUT: - ``intervals`` -- the list of pairs `(a_i,b_i)` defining the graph. - ``points_ordered`` -- states whether every interval `(a_i,b_i)` of `intervals` satisfies `a_i<b_i`. If satisfied then setting ``points_ordered`` to ``True`` will speed up the creation of the graph. .. NOTE:: * The vertices are named 0, 1, 2, and so on. The intervals used to create the graph are saved with the graph and can be recovered using ``get_vertex()`` or ``get_vertices()``. EXAMPLE: The following line creates the sequence of intervals `(i, i+2)` for i in `[0, ..., 8]`:: sage: intervals = [(i,i+2) for i in range(9)] In the corresponding graph :: sage: g = graphs.IntervalGraph(intervals) sage: g.get_vertex(3) (3, 5) sage: neigh = g.neighbors(3) sage: for v in neigh: print g.get_vertex(v) (1, 3) (2, 4) (4, 6) (5, 7) The is_interval() method verifies that this graph is an interval graph. :: sage: g.is_interval() True The intervals in the list need not be distinct. :: sage: intervals = [ (1,2), (1,2), (1,2), (2,3), (3,4) ] sage: g = graphs.IntervalGraph(intervals,True) sage: g.clique_maximum() [0, 1, 2, 3] sage: g.get_vertices() {0: (1, 2), 1: (1, 2), 2: (1, 2), 3: (2, 3), 4: (3, 4)} The endpoints of the intervals are not ordered we get the same graph (except for the vertex labels). :: sage: rev_intervals = [ (2,1), (2,1), (2,1), (3,2), (4,3) ] sage: h = graphs.IntervalGraph(rev_intervals,False) sage: h.get_vertices() {0: (2, 1), 1: (2, 1), 2: (2, 1), 3: (3, 2), 4: (4, 3)} sage: g.edges() == h.edges() True """ n = len(intervals) g = Graph(n) if points_ordered: for i in xrange(n-1): li,ri = intervals[i] for j in xrange(i+1,n): lj,rj = intervals[j] if ri < lj or rj < li: continue g.add_edge(i,j) else: for i in xrange(n-1): I = intervals[i] for j in xrange(i+1,n): J = intervals[j] if max(I) < min(J) or max(J) < min(I): continue g.add_edge(i,j) rep = dict( zip(range(n),intervals) ) g.set_vertices(rep) return g
def PermutationGraph(second_permutation, first_permutation = None): r""" Build a permutation graph from one permutation or from two lists. Definition: If `\sigma` is a permutation of `\{ 1, 2, \ldots, n \}`, then the permutation graph of `\sigma` is the graph on vertex set `\{ 1, 2, \ldots, n \}` in which two vertices `i` and `j` satisfying `i < j` are connected by an edge if and only if `\sigma^{-1}(i) > \sigma^{-1}(j)`. A visual way to construct this graph is as follows: Take two horizontal lines in the euclidean plane, and mark points `1, ..., n` from left to right on the first of them. On the second one, still from left to right, mark `n` points `\sigma(1), \sigma(2), \ldots, \sigma(n)`. Now, link by a segment the two points marked with `1`, then link together the points marked with `2`, and so on. The permutation graph of `\sigma` is the intersection graph of those segments: there exists a vertex in this graph for each element from `1` to `n`, two vertices `i, j` being adjacent if the segments `i` and `j` cross each other. The set of edges of the permutation graph can thus be identified with the set of inversions of the inverse of the given permutation `\sigma`. A more general notion of permutation graph can be defined as follows: If `S` is a set, and `(a_1, a_2, \ldots, a_n)` and `(b_1, b_2, \ldots, b_n)` are two lists of elements of `S`, each of which lists contains every element of `S` exactly once, then the permutation graph defined by these two lists is the graph on the vertex set `S` in which two vertices `i` and `j` are connected by an edge if and only if the order in which these vertices appear in the list `(a_1, a_2, \ldots, a_n)` is the opposite of the order in which they appear in the list `(b_1, b_2, \ldots, b_n)`. When `(a_1, a_2, \ldots, a_n) = (1, 2, \ldots, n)`, this graph is the permutation graph of the permutation `(b_1, b_2, \ldots, b_n) \in S_n`. Notice that `S` does not have to be a set of integers here, but can be a set of strings, tuples, or anything else. We can still use the above visual description to construct the permutation graph, but now we have to mark points `a_1, a_2, \ldots, a_n` from left to right on the first horizontal line and points `b_1, b_2, \ldots, b_n` from left to right on the second horizontal line. INPUT: - ``second_permutation`` -- the unique permutation/list defining the graph, or the second of the two (if the graph is to be built from two permutations/lists). - ``first_permutation`` (optional) -- the first of the two permutations/lists from which the graph should be built, if it is to be built from two permutations/lists. When ``first_permutation is None`` (default), it is set to be equal to ``sorted(second_permutation)``, which yields the expected ordering when the elements of the graph are integers. .. SEEALSO: - Recognition of Permutation graphs in the :mod:`comparability module <sage.graphs.comparability>`. - Drawings of permutation graphs as intersection graphs of segments is possible through the :meth:`~sage.combinat.permutation.Permutation.show` method of :class:`~sage.combinat.permutation.Permutation` objects. The correct argument to use in this case is ``show(representation = "braid")``. - :meth:`~sage.combinat.permutation.Permutation.inversions` EXAMPLES:: sage: p = Permutations(5).random_element() sage: PG = graphs.PermutationGraph(p) sage: edges = PG.edges(labels=False) sage: set(edges) == set(p.inverse().inversions()) True sage: PG = graphs.PermutationGraph([3,4,5,1,2]) sage: sorted(PG.edges()) [(1, 3, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 4, None), (2, 5, None)] sage: PG = graphs.PermutationGraph([3,4,5,1,2], [1,4,2,5,3]) sage: sorted(PG.edges()) [(1, 3, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None), (3, 4, None), (3, 5, None)] sage: PG = graphs.PermutationGraph([1,4,2,5,3], [3,4,5,1,2]) sage: sorted(PG.edges()) [(1, 3, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None), (3, 4, None), (3, 5, None)] sage: PG = graphs.PermutationGraph(Permutation([1,3,2]), Permutation([1,2,3])) sage: sorted(PG.edges()) [(2, 3, None)] sage: graphs.PermutationGraph([]).edges() [] sage: graphs.PermutationGraph([], []).edges() [] sage: PG = graphs.PermutationGraph("graph", "phrag") sage: sorted(PG.edges()) [('a', 'g', None), ('a', 'h', None), ('a', 'p', None), ('g', 'h', None), ('g', 'p', None), ('g', 'r', None), ('h', 'r', None), ('p', 'r', None)] TESTS:: sage: graphs.PermutationGraph([1, 2, 3], [4, 5, 6]) Traceback (most recent call last): ... ValueError: The two permutations do not contain the same set of elements ... """ if first_permutation is None: first_permutation = sorted(second_permutation) else: if set(second_permutation) != set(first_permutation): raise ValueError("The two permutations do not contain the same "+ "set of elements ! It is going to be pretty "+ "hard to define a permutation graph from that !") vertex_to_index = {} for i, v in enumerate(first_permutation): vertex_to_index[v] = i+1 from sage.combinat.permutation import Permutation p2 = Permutation([vertex_to_index[x] for x in second_permutation]) p2 = p2.inverse() g = Graph(name="Permutation graph for "+str(second_permutation)) g.add_vertices(second_permutation) for u,v in p2.inversions(): g.add_edge(first_permutation[u-1], first_permutation[v-1]) return g
def ChessboardGraphGenerator(dim_list, rook=True, rook_radius=None, bishop=True, bishop_radius=None, knight=True, knight_x=1, knight_y=2, relabel=False): r""" Returns a Graph built on a `d`-dimensional chessboard with prescribed dimensions and interconnections. This function allows to generate many kinds of graphs corresponding to legal movements on a `d`-dimensional chessboard: Queen Graph, King Graph, Knight Graphs, Bishop Graph, and many generalizations. It also allows to avoid redondant code. INPUT: - ``dim_list`` -- an iterable object (list, set, dict) providing the dimensions `n_1, n_2, \ldots, n_d`, with `n_i \geq 1`, of the chessboard. - ``rook`` -- (default: ``True``) boolean value indicating if the chess piece is able to move as a rook, that is at any distance along a dimension. - ``rook_radius`` -- (default: None) integer value restricting the rook-like movements to distance at most `rook_radius`. - ``bishop`` -- (default: ``True``) boolean value indicating if the chess piece is able to move like a bishop, that is along diagonals. - ``bishop_radius`` -- (default: None) integer value restricting the bishop-like movements to distance at most `bishop_radius`. - ``knight`` -- (default: ``True``) boolean value indicating if the chess piece is able to move like a knight. - ``knight_x`` -- (default: 1) integer indicating the number on steps the chess piece moves in one dimension when moving like a knight. - ``knight_y`` -- (default: 2) integer indicating the number on steps the chess piece moves in the second dimension when moving like a knight. - ``relabel`` -- (default: ``False``) a boolean set to ``True`` if vertices must be relabeled as integers. OUTPUT: - A Graph build on a `d`-dimensional chessboard with prescribed dimensions, and with edges according given parameters. - A string encoding the dimensions. This is mainly useful for providing names to graphs. EXAMPLES: A `(2,2)`-King Graph is isomorphic to the complete graph on 4 vertices:: sage: G, _ = graphs.ChessboardGraphGenerator( [2,2] ) sage: G.is_isomorphic( graphs.CompleteGraph(4) ) True A Rook's Graph in 2 dimensions is isomporphic to the Cartesian product of 2 complete graphs:: sage: G, _ = graphs.ChessboardGraphGenerator( [3,4], rook=True, rook_radius=None, bishop=False, knight=False ) sage: H = ( graphs.CompleteGraph(3) ).cartesian_product( graphs.CompleteGraph(4) ) sage: G.is_isomorphic(H) True TESTS: Giving dimensions less than 2:: sage: graphs.ChessboardGraphGenerator( [0, 2] ) Traceback (most recent call last): ... ValueError: The dimensions must be positive integers larger than 1. Giving non integer dimensions:: sage: graphs.ChessboardGraphGenerator( [4.5, 2] ) Traceback (most recent call last): ... ValueError: The dimensions must be positive integers larger than 1. Giving too few dimensions:: sage: graphs.ChessboardGraphGenerator( [2] ) Traceback (most recent call last): ... ValueError: The chessboard must have at least 2 dimensions. Giving a non-iterable object as first parameter:: sage: graphs.ChessboardGraphGenerator( 2, 3 ) Traceback (most recent call last): ... TypeError: The first parameter must be an iterable object. Giving too small rook radius:: sage: graphs.ChessboardGraphGenerator( [2, 3], rook=True, rook_radius=0 ) Traceback (most recent call last): ... ValueError: The rook_radius must be either None or have an integer value >= 1. Giving wrong values for knights movements:: sage: graphs.ChessboardGraphGenerator( [2, 3], rook=False, bishop=False, knight=True, knight_x=1, knight_y=-1 ) Traceback (most recent call last): ... ValueError: The knight_x and knight_y values must be integers of value >= 1. """ from sage.rings.integer_ring import ZZ # We decode the dimensions of the chessboard try: dim = list(dim_list) nb_dim = len(dim) except TypeError: raise TypeError('The first parameter must be an iterable object.') if nb_dim < 2: raise ValueError('The chessboard must have at least 2 dimensions.') if any(a not in ZZ or a < 1 for a in dim): raise ValueError( 'The dimensions must be positive integers larger than 1.') dimstr = str(tuple(dim)) # We check the radius toward neighbors if rook: if rook_radius is None: rook_radius = max(dim) elif not rook_radius in ZZ or rook_radius < 1: raise ValueError( 'The rook_radius must be either None or have an integer value >= 1.' ) if bishop: if bishop_radius is None: bishop_radius = max(dim) elif not bishop_radius in ZZ or bishop_radius < 1: raise ValueError( 'The bishop_radius must be either None or have an integer value >= 1.' ) if knight and (not knight_x in ZZ or not knight_y in ZZ or knight_x < 1 or knight_y < 1): raise ValueError( 'The knight_x and knight_y values must be integers of value >= 1.') # We build the set of vertices of the d-dimensionnal chessboard from itertools import product V = [list(x) for x in list(product(*[range(_) for _ in dim]))] from sage.combinat.combination import Combinations combin = Combinations(range(nb_dim), 2) from sage.graphs.graph import Graph G = Graph() for u in V: uu = tuple(u) G.add_vertex(uu) if rook: # We add edges to vertices we can reach when moving in one dimension for d in xrange(nb_dim): v = u[:] for k in xrange(v[d] + 1, min(dim[d], v[d] + 1 + rook_radius)): v[d] = k G.add_edge(uu, tuple(v)) if bishop or knight: # We add edges to vertices we can reach when moving in two dimensions for dx, dy in combin: n = dim[dx] m = dim[dy] v = u[:] i = u[dx] j = u[dy] if bishop: # Diagonal for k in xrange(1, min(n - i, m - j, bishop_radius + 1)): v[dx] = i + k v[dy] = j + k G.add_edge(uu, tuple(v)) # Anti-diagonal for k in xrange(min(i, m - j - 1, bishop_radius)): v[dx] = i - k - 1 v[dy] = j + k + 1 G.add_edge(uu, tuple(v)) if knight: # Moving knight_x in one dimension and knight_y in another # dimension if i + knight_y < n: if j + knight_x < m: v[dx] = i + knight_y v[dy] = j + knight_x G.add_edge(uu, tuple(v)) if j - knight_x >= 0: v[dx] = i + knight_y v[dy] = j - knight_x G.add_edge(uu, tuple(v)) if j + knight_y < m: if i + knight_x < n: v[dx] = i + knight_x v[dy] = j + knight_y G.add_edge(uu, tuple(v)) if i - knight_x >= 0: v[dx] = i - knight_x v[dy] = j + knight_y G.add_edge(uu, tuple(v)) if relabel: G.relabel(inplace=True) return G, dimstr
def AffineOrthogonalPolarGraph(d, q, sign="+"): r""" Returns the affine polar graph `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`. Affine Polar graphs are built from a `d`-dimensional vector space over `F_q`, and a quadratic form which is hyperbolic, elliptic or parabolic according to the value of ``sign``. Note that `VO^+(d,q),VO^-(d,q)` are strongly regular graphs, while `VO(d,q)` is not. For more information on Affine Polar graphs, see `Affine Polar Graphs page of Andries Brouwer's website <http://www.win.tue.nl/~aeb/graphs/VO.html>`_. INPUT: - ``d`` (integer) -- ``d`` must be even if ``sign != None``, and odd otherwise. - ``q`` (integer) -- a power of a prime number, as `F_q` must exist. - ``sign`` -- must be equal to ``"+"``, ``"-"``, or ``None`` to compute (respectively) `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`. By default ``sign="+"``. .. NOTE:: The graph `VO^\epsilon(d,q)` is the graph induced by the non-neighbors of a vertex in an :meth:`Orthogonal Polar Graph <OrthogonalPolarGraph>` `O^\epsilon(d+2,q)`. EXAMPLES: The :meth:`Brouwer-Haemers graph <BrouwerHaemersGraph>` is isomorphic to `VO^-(4,3)`:: sage: g = graphs.AffineOrthogonalPolarGraph(4,3,"-") sage: g.is_isomorphic(graphs.BrouwerHaemersGraph()) True Some examples from `Brouwer's table or strongly regular graphs <http://www.win.tue.nl/~aeb/graphs/srg/srgtab.html>`_:: sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"-"); g Affine Polar Graph VO^-(6,2): Graph on 64 vertices sage: g.is_strongly_regular(parameters=True) (64, 27, 10, 12) sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"+"); g Affine Polar Graph VO^+(6,2): Graph on 64 vertices sage: g.is_strongly_regular(parameters=True) (64, 35, 18, 20) When ``sign is None``:: sage: g = graphs.AffineOrthogonalPolarGraph(5,2,None); g Affine Polar Graph VO^-(5,2): Graph on 32 vertices sage: g.is_strongly_regular(parameters=True) False sage: g.is_regular() True sage: g.is_vertex_transitive() True """ if sign in ["+", "-"]: s = 1 if sign == "+" else -1 if d % 2 == 1: raise ValueError("d must be even when sign!=None") else: if d % 2 == 0: raise ValueError("d must be odd when sign==None") s = 0 from sage.interfaces.gap import gap from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module import VectorSpace from sage.matrix.constructor import Matrix from sage.libs.gap.libgap import libgap from itertools import combinations M = Matrix( libgap.InvariantQuadraticForm(libgap.GeneralOrthogonalGroup( s, d, q))['matrix']) F = libgap.GF(q).sage() V = list(VectorSpace(F, d)) G = Graph() G.add_vertices([tuple(_) for _ in V]) for x, y in combinations(V, 2): if not (x - y) * M * (x - y): G.add_edge(tuple(x), tuple(y)) G.name("Affine Polar Graph VO^" + str('+' if s == 1 else '-') + "(" + str(d) + "," + str(q) + ")") G.relabel() return G
def PermutationGraph(second_permutation, first_permutation=None): r""" Build a permutation graph from one permutation or from two lists. Definition: If `\sigma` is a permutation of `\{ 1, 2, \ldots, n \}`, then the permutation graph of `\sigma` is the graph on vertex set `\{ 1, 2, \ldots, n \}` in which two vertices `i` and `j` satisfying `i < j` are connected by an edge if and only if `\sigma^{-1}(i) > \sigma^{-1}(j)`. A visual way to construct this graph is as follows: Take two horizontal lines in the euclidean plane, and mark points `1, ..., n` from left to right on the first of them. On the second one, still from left to right, mark `n` points `\sigma(1), \sigma(2), \ldots, \sigma(n)`. Now, link by a segment the two points marked with `1`, then link together the points marked with `2`, and so on. The permutation graph of `\sigma` is the intersection graph of those segments: there exists a vertex in this graph for each element from `1` to `n`, two vertices `i, j` being adjacent if the segments `i` and `j` cross each other. The set of edges of the permutation graph can thus be identified with the set of inversions of the inverse of the given permutation `\sigma`. A more general notion of permutation graph can be defined as follows: If `S` is a set, and `(a_1, a_2, \ldots, a_n)` and `(b_1, b_2, \ldots, b_n)` are two lists of elements of `S`, each of which lists contains every element of `S` exactly once, then the permutation graph defined by these two lists is the graph on the vertex set `S` in which two vertices `i` and `j` are connected by an edge if and only if the order in which these vertices appear in the list `(a_1, a_2, \ldots, a_n)` is the opposite of the order in which they appear in the list `(b_1, b_2, \ldots, b_n)`. When `(a_1, a_2, \ldots, a_n) = (1, 2, \ldots, n)`, this graph is the permutation graph of the permutation `(b_1, b_2, \ldots, b_n) \in S_n`. Notice that `S` does not have to be a set of integers here, but can be a set of strings, tuples, or anything else. We can still use the above visual description to construct the permutation graph, but now we have to mark points `a_1, a_2, \ldots, a_n` from left to right on the first horizontal line and points `b_1, b_2, \ldots, b_n` from left to right on the second horizontal line. INPUT: - ``second_permutation`` -- the unique permutation/list defining the graph, or the second of the two (if the graph is to be built from two permutations/lists). - ``first_permutation`` (optional) -- the first of the two permutations/lists from which the graph should be built, if it is to be built from two permutations/lists. When ``first_permutation is None`` (default), it is set to be equal to ``sorted(second_permutation)``, which yields the expected ordering when the elements of the graph are integers. .. SEEALSO:: - Recognition of Permutation graphs in the :mod:`comparability module <sage.graphs.comparability>`. - Drawings of permutation graphs as intersection graphs of segments is possible through the :meth:`~sage.combinat.permutation.Permutation.show` method of :class:`~sage.combinat.permutation.Permutation` objects. The correct argument to use in this case is ``show(representation = "braid")``. - :meth:`~sage.combinat.permutation.Permutation.inversions` EXAMPLES:: sage: p = Permutations(5).random_element() sage: PG = graphs.PermutationGraph(p) sage: edges = PG.edges(labels=False) sage: set(edges) == set(p.inverse().inversions()) True sage: PG = graphs.PermutationGraph([3,4,5,1,2]) sage: sorted(PG.edges()) [(1, 3, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 4, None), (2, 5, None)] sage: PG = graphs.PermutationGraph([3,4,5,1,2], [1,4,2,5,3]) sage: sorted(PG.edges()) [(1, 3, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None), (3, 4, None), (3, 5, None)] sage: PG = graphs.PermutationGraph([1,4,2,5,3], [3,4,5,1,2]) sage: sorted(PG.edges()) [(1, 3, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None), (3, 4, None), (3, 5, None)] sage: PG = graphs.PermutationGraph(Permutation([1,3,2]), Permutation([1,2,3])) sage: sorted(PG.edges()) [(2, 3, None)] sage: graphs.PermutationGraph([]).edges() [] sage: graphs.PermutationGraph([], []).edges() [] sage: PG = graphs.PermutationGraph("graph", "phrag") sage: sorted(PG.edges()) [('a', 'g', None), ('a', 'h', None), ('a', 'p', None), ('g', 'h', None), ('g', 'p', None), ('g', 'r', None), ('h', 'r', None), ('p', 'r', None)] TESTS:: sage: graphs.PermutationGraph([1, 2, 3], [4, 5, 6]) Traceback (most recent call last): ... ValueError: The two permutations do not contain the same set of elements ... """ if first_permutation is None: first_permutation = sorted(second_permutation) else: if set(second_permutation) != set(first_permutation): raise ValueError("The two permutations do not contain the same " + "set of elements ! It is going to be pretty " + "hard to define a permutation graph from that !") vertex_to_index = {} for i, v in enumerate(first_permutation): vertex_to_index[v] = i + 1 from sage.combinat.permutation import Permutation p2 = Permutation([vertex_to_index[x] for x in second_permutation]) p2 = p2.inverse() g = Graph(name="Permutation graph for " + str(second_permutation)) g.add_vertices(second_permutation) for u, v in p2.inversions(): g.add_edge(first_permutation[u - 1], first_permutation[v - 1]) return g
def RandomBipartite(n1,n2, p): r""" Returns a bipartite graph with `n1+n2` vertices such that any edge from `[n1]` to `[n2]` exists with probability `p`. INPUT: - ``n1,n2`` : Cardinalities of the two sets - ``p`` : Probability for an edge to exist EXAMPLE:: sage: g=graphs.RandomBipartite(5,2,0.5) sage: g.vertices() [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1)] TESTS:: sage: g=graphs.RandomBipartite(5,-3,0.5) Traceback (most recent call last): ... ValueError: n1 and n2 should be integers strictly greater than 0 sage: g=graphs.RandomBipartite(5,3,1.5) Traceback (most recent call last): ... ValueError: Parameter p is a probability, and so should be a real value between 0 and 1 Trac ticket #12155:: sage: graphs.RandomBipartite(5,6,.2).complement() complement(Random bipartite graph of size 5+6 with edge probability 0.200000000000000): Graph on 11 vertices """ if not (p>=0 and p<=1): raise ValueError, "Parameter p is a probability, and so should be a real value between 0 and 1" if not (n1>0 and n2>0): raise ValueError, "n1 and n2 should be integers strictly greater than 0" from numpy.random import uniform g=Graph(name="Random bipartite graph of size "+str(n1) +"+"+str(n2)+" with edge probability "+str(p)) S1=[(0,i) for i in range(n1)] S2=[(1,i) for i in range(n2)] g.add_vertices(S1) g.add_vertices(S2) for w in range(n2): for v in range(n1): if uniform()<=p : g.add_edge((0,v),(1,w)) pos = {} for i in range(n1): pos[(0,i)] = (0, i/(n1-1.0)) for i in range(n2): pos[(1,i)] = (1, i/(n2-1.0)) g.set_pos(pos) return g
def restricted_automorphism_group(self, vertex_labels=None): r""" Return the restricted automorphism group. First, let the linear automorphism group be the subgroup of the Euclidean group `E(d) = GL(d,\RR) \ltimes \RR^d` preserving the `d`-dimensional polyhedron. The Euclidean group acts in the usual way `\vec{x}\mapsto A\vec{x}+b` on the ambient space. The restricted automorphism group is the subgroup of the linear automorphism group generated by permutations of vertices. If the polytope is full-dimensional, it is equal to the full (unrestricted) automorphism group. INPUT: - ``vertex_labels`` -- a tuple or ``None`` (default). The labels of the vertices that will be used in the output permutation group. By default, the vertices are used themselves. OUTPUT: A :class:`PermutationGroup<sage.groups.perm_gps.permgroup.PermutationGroup_generic>` acting on the vertices (or the ``vertex_labels``, if specified). REFERENCES: [BSS2009]_ EXAMPLES:: sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL sage: Z3square = LatticePolytope_PPL((0,0), (1,2), (2,1), (3,3)) sage: Z3square.restricted_automorphism_group(vertex_labels=(1,2,3,4)) Permutation Group with generators [(2,3), (1,2)(3,4), (1,4)] sage: G = Z3square.restricted_automorphism_group(); G Permutation Group with generators [((1,2),(2,1)), ((0,0),(1,2))((2,1),(3,3)), ((0,0),(3,3))] sage: tuple(G.domain()) == Z3square.vertices() True sage: G.orbit(Z3square.vertices()[0]) ((0, 0), (1, 2), (3, 3), (2, 1)) sage: cell24 = LatticePolytope_PPL( ....: (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1),(1,-1,-1,1),(0,0,-1,1), ....: (0,-1,0,1),(-1,0,0,1),(1,0,0,-1),(0,1,0,-1),(0,0,1,-1),(-1,1,1,-1), ....: (1,-1,-1,0),(0,0,-1,0),(0,-1,0,0),(-1,0,0,0),(1,-1,0,0),(1,0,-1,0), ....: (0,1,1,-1),(-1,1,1,0),(-1,1,0,0),(-1,0,1,0),(0,-1,-1,1),(0,0,0,-1)) sage: cell24.restricted_automorphism_group().cardinality() 1152 """ if not self.is_full_dimensional(): return self.affine_lattice_polytope().\ restricted_automorphism_group(vertex_labels=vertex_labels) if vertex_labels is None: vertex_labels = self.vertices() from sage.groups.perm_gps.permgroup import PermutationGroup from sage.graphs.graph import Graph # good coordinates for the vertices v_list = [] for v in self.minimized_generators(): assert v.divisor().is_one() v_coords = (1,) + v.coefficients() v_list.append(vector(v_coords)) # Finally, construct the graph Qinv = sum( v.column() * v.row() for v in v_list ).inverse() G = Graph() for i in range(0,len(v_list)): for j in range(i+1,len(v_list)): v_i = v_list[i] v_j = v_list[j] G.add_edge(vertex_labels[i], vertex_labels[j], v_i * Qinv * v_j) return G.automorphism_group(edge_labels=True)
def rectangular_diagram(gauss): """ Return a rectangular diagram and crossing coordinates. INPUT: - signed Gauss code OUTPUT: - graph whose vertices are the corners of the knot diagram - positions of the horizontal and vertical crossings EXAMPLES:: sage: from sage.knots.gauss_code import rectangular_diagram sage: G = [4,1,5,2,1,3,6,4,2,5,3,6] sage: rectangular_diagram(G) (Graph on 18 vertices, ([(1, 3), (3, 7), (4, 6), (6, 2)], [(4, 3), (6, 5)])) sage: G = [1,2,3,1,2,3] sage: rectangular_diagram(G) (Graph on 10 vertices, ([(1, 3), (2, 2)], [(2, 1)])) TESTS:: sage: rectangular_diagram([]) (Graph on 4 vertices, ([], [])) """ if not gauss: G = Graph() verts = [(0, 0), (1, 0), (1, 1), (0, 1)] for i in range(4): G.add_edge(verts[i], verts[(i + 1) % 4]) G.set_pos({v: v for v in verts}) return G, ([], []) changed, positive, negative, ori = recover_orientations(gauss) n = len(changed) G = Graph() horizontal = [] vertical = [] for a, b in positive: G.add_edge((a, a), (b, a)) G.add_edge((b, a), (b, b)) G.add_edge((a + 1, a + 1), (b + 1, a + 1)) G.add_edge((b + 1, a + 1), (b + 1, b + 1)) letter = changed[a] if b != a + 1: if ori[letter - 1] == -1: horizontal.append((b, a + 1)) else: vertical.append((b, a + 1)) for a, b in negative: G.add_edge((a, a), (a, b)) G.add_edge((a, b), (b, b)) G.add_edge((a + 1, a + 1), (a + 1, b + 1)) G.add_edge((a + 1, b + 1), (b + 1, b + 1)) letter = changed[a] if a + 1 != b: if ori[letter - 1] == 1: horizontal.append((a + 1, b)) else: vertical.append((a + 1, b)) # closing the loop on either side total = positive + negative debut = next(iter(b for a, b in total if a == 0)) fin = next(iter(n - 1 - a for a, b in total if b == n - 1)) if debut > fin: G.add_edge((0, 0), (0, n)) G.add_edge((0, n), (n, n)) else: G.add_edge((0, 0), (n, 0)) G.add_edge((n, 0), (n, n)) # remove useless vertices and double edges for a, b in list(G): (x, y), (xx, yy) = sorted(G.neighbors((a, b))) if x == xx == a: G.delete_vertex((a, b)) G.add_edge((a, y), (a, yy)) elif y == yy == b: G.delete_vertex((a, b)) G.add_edge((x, b), (xx, b)) # renumber lines and columns to remove empty ones lignes = sorted(set(a for a, _ in G)) colonnes = sorted(set(b for _, b in G)) d_lig = {a: i for i, a in enumerate(lignes)} d_col = {b: i for i, b in enumerate(colonnes)} horizontal = sorted((d_lig[a], d_col[b]) for a, b in horizontal) vertical = sorted((d_lig[a], d_col[b]) for a, b in vertical) def standard(ab): a, b = ab return (d_lig[a], d_col[b]) G.relabel(standard) # setting the positions positions = {ab: ab for ab in G} G.set_pos(positions) return G, (horizontal, vertical)
def is_partial_cube(G, certificate=False): r""" Test whether the given graph is a partial cube. A partial cube is a graph that can be isometrically embedded into a hypercube, i.e., its vertices can be labelled with (0,1)-vectors of some fixed length such that the distance between any two vertices in the graph equals the Hamming distance of their labels. Originally written by D. Eppstein for the PADS library (http://www.ics.uci.edu/~eppstein/PADS/), see also [Eppstein2008]_. The algorithm runs in `O(n^2)` time, where `n` is the number of vertices. See the documentation of :mod:`~sage.graphs.partial_cube` for an overview of the algorithm. INPUT: - ``certificate`` (boolean; ``False``) -- The function returns ``True`` or ``False`` according to the graph, when ``certificate = False``. When ``certificate = True`` and the graph is a partial cube, the function returns ``(True, mapping)``, where ``mapping`` is an isometric mapping of the vertices of the graph to the vertices of a hypercube ((0, 1)-strings of a fixed length). When ``certificate = True`` and the graph is not a partial cube, ``(False, None)`` is returned. EXAMPLES: The Petersen graph is not a partial cube:: sage: g = graphs.PetersenGraph() sage: g.is_partial_cube() False All prisms are partial cubes:: sage: g = graphs.CycleGraph(10).cartesian_product(graphs.CompleteGraph(2)) sage: g.is_partial_cube() True TESTS: The returned mapping is an isometric embedding into a hypercube:: sage: g = graphs.DesarguesGraph() sage: _, m = g.is_partial_cube(certificate = True) sage: m # random {0: '00000', 1: '00001', 2: '00011', 3: '01011', 4: '11011', 5: '11111', 6: '11110', 7: '11100', 8: '10100', 9: '00100', 10: '01000', 11: '10001', 12: '00111', 13: '01010', 14: '11001', 15: '10111', 16: '01110', 17: '11000', 18: '10101', 19: '00110'} sage: all(all(g.distance(u, v) == len([i for i in range(len(m[u])) if m[u][i] != m[v][i]]) for v in m) for u in m) True A graph without vertices is trivially a partial cube:: sage: Graph().is_partial_cube(certificate = True) (True, {}) """ G._scream_if_not_simple() if G.order() == 0: if certificate: return (True, {}) else: return True if certificate: fail = (False, None) else: fail = False if not G.is_connected(): return fail n = G.order() # Initial sanity check: are there few enough edges? # Needed so that we don't try to use union-find on a dense # graph and incur superquadratic runtimes. if 1 << (2*G.size()//n) > n: return fail # Check for bipartiteness. # This ensures also that each contraction will be bipartite. if not G.is_bipartite(): return fail # Set up data structures for algorithm: # - contracted: contracted graph at current stage of algorithm # - unionfind: union find data structure representing known edge equivalences # - available: limit on number of remaining available labels from sage.graphs.digraph import DiGraph from sage.graphs.graph import Graph from sage.sets.disjoint_set import DisjointSet contracted = DiGraph({v: {w: (v, w) for w in G[v]} for v in G}) unionfind = DisjointSet(contracted.edges(labels = False)) available = n-1 # Main contraction loop in place of the original algorithm's recursion while contracted.order() > 1: # Find max degree vertex in contracted, and update label limit deg, root = max((contracted.out_degree(v), v) for v in contracted) if deg > available: return fail available -= deg # Set up bitvectors on vertices bitvec = {v:0 for v in contracted} neighbors = {} for i, neighbor in enumerate(contracted[root]): bitvec[neighbor] = 1 << i neighbors[1 << i] = neighbor # Breadth first search to propagate bitvectors to the rest of the graph for level in breadth_first_level_search(contracted, root): for v in level: for w in level[v]: bitvec[w] |= bitvec[v] # Make graph of labeled edges and union them together labeled = Graph([contracted.vertices(), []]) for v, w in contracted.edge_iterator(labels = False): diff = bitvec[v]^bitvec[w] if not diff or bitvec[w] &~ bitvec[v] == 0: continue # zero edge or wrong direction if diff not in neighbors: return fail neighbor = neighbors[diff] unionfind.union(contracted.edge_label(v, w), contracted.edge_label(root, neighbor)) unionfind.union(contracted.edge_label(w, v), contracted.edge_label(neighbor, root)) labeled.add_edge(v, w) # Map vertices to components of labeled-edge graph component = {} for i, SCC in enumerate(labeled.connected_components()): for v in SCC: component[v] = i # generate new compressed subgraph newgraph = DiGraph() for v, w, t in contracted.edge_iterator(): if bitvec[v] == bitvec[w]: vi = component[v] wi = component[w] if vi == wi: return fail if newgraph.has_edge(vi, wi): unionfind.union(newgraph.edge_label(vi, wi), t) else: newgraph.add_edge(vi, wi, t) contracted = newgraph # Make a digraph with edges labeled by the equivalence classes in unionfind g = DiGraph({v: {w: unionfind.find((v, w)) for w in G[v]} for v in G}) # Associates to a vertex the token that acts on it, an check that # no two edges on a single vertex have the same label action = {} for v in g: action[v] = set(t for _, _, t in g.edge_iterator(v)) if len(action[v]) != g.out_degree(v): return fail # Associate every token to its reverse reverse = {} for v, w, t in g.edge_iterator(): rt = g.edge_label(w, v) reverse[t] = rt reverse[rt] = t current = initialState = next(g.vertex_iterator()) # A token T is said to be 'active' for a vertex u if it takes u # one step closer to the source in terms of distance. The 'source' # is initially 'initialState'. See the module's documentation for # more explanations. # Find list of tokens that lead to the initial state activeTokens = set() for level in breadth_first_level_search(g, initialState): for v in level: for w in level[v]: activeTokens.add(g.edge_label(w, v)) for t in activeTokens: if reverse[t] in activeTokens: return fail activeTokens = list(activeTokens) # Rest of data structure: point from states to list and list to states state_to_active_token = {v: -1 for v in g} token_to_states = [[] for i in activeTokens] # (i.e. vertices on which each token acts) def scan(v): """Find the next token that is effective for v.""" a = next(i for i in range(state_to_active_token[v]+1, len(activeTokens)) if activeTokens[i] is not None and activeTokens[i] in action[v]) state_to_active_token[v] = a token_to_states[a].append(v) # Initialize isometric embedding into a hypercube if certificate: dim = 0 tokmap = {} for t in reverse: if t not in tokmap: tokmap[t] = tokmap[reverse[t]] = 1 << dim dim += 1 embed = {initialState: 0} # Set initial active states for v in g: if v != current: try: scan(v) except StopIteration: return fail # Traverse the graph, maintaining active tokens for prev, current, fwd in depth_first_traversal(g, initialState): if not fwd: prev, current = current, prev elif certificate: embed[current] = embed[prev] ^ tokmap[g.edge_label(prev, current)] # Add token to end of list, point to it from old state activeTokens.append(g.edge_label(prev, current)) state_to_active_token[prev] = len(activeTokens) - 1 token_to_states.append([prev]) # Inactivate reverse token, find new token for its states # # (the 'active' token of 'current' is necessarily the label of # (current, previous)) activeTokens[state_to_active_token[current]] = None for v in token_to_states[state_to_active_token[current]]: if v != current: try: scan(v) except StopIteration: return fail # All checks passed, return the result if certificate: format = "{0:0%db}" % dim return (True, {v: format.format(l) for v, l in embed.items()}) else: return True
def RandomTree(n): """ Returns a random tree on `n` nodes numbered `0` through `n-1`. By Cayley's theorem, there are `n^{n-2}` trees with vertex set `\{0,1,...,n-1\}`. This constructor chooses one of these uniformly at random. ALGORITHM: The algoritm works by generating an `(n-2)`-long random sequence of numbers chosen independently and uniformly from `\{0,1,\ldots,n-1\}` and then applies an inverse Prufer transformation. INPUT: - ``n`` - number of vertices in the tree EXAMPLE:: sage: G = graphs.RandomTree(10) sage: G.is_tree() True sage: G.show() # long TESTS: Ensuring that we encounter no unexpected surprise :: sage: all( graphs.RandomTree(10).is_tree() ....: for i in range(100) ) True """ from sage.misc.prandom import randint g = Graph() # create random Prufer code code = [randint(0, n - 1) for i in xrange(n - 2)] # We count the number of symbols of each type. # count[k] is the no. of times k appears in code # # (count[k] is set to -1 when the corresponding vertex is not # available anymore) count = [0 for i in xrange(n)] for k in code: count[k] += 1 g.add_vertices(range(n)) for s in code: for x in range(n): if count[x] == 0: break count[x] = -1 g.add_edge(x, s) count[s] -= 1 # Adding as an edge the last two available vertices last_edge = [v for v in range(n) if count[v] != -1] g.add_edge(last_edge) return g
def _check_pbd(B,v,S): r""" Checks that ``B`` is a PBD on ``v`` points with given block sizes ``S``. The points of the balanced incomplete block design are implicitely assumed to be `\{0, ..., v-1\}`. INPUT: - ``B`` -- a list of blocks - ``v`` (integer) -- number of points - ``S`` -- list of integers `\geq 2`. EXAMPLE:: sage: designs.balanced_incomplete_block_design(40,4).blocks() # indirect doctest [[0, 1, 2, 12], [0, 3, 6, 9], [0, 4, 8, 10], [0, 5, 7, 11], [0, 13, 26, 39], [0, 14, 25, 28], [0, 15, 27, 38], [0, 16, 22, 32], [0, 17, 23, 34], ... sage: from sage.combinat.designs.bibd import _check_pbd sage: _check_pbd([[1],[]],1,[1,0]) Traceback (most recent call last): ... RuntimeError: All integers of S must be >=2 TESTS:: sage: _check_pbd([[1,2]],2,[2]) Traceback (most recent call last): ... RuntimeError: The PBD covers a point 2 which is not in {0, 1} sage: _check_pbd([[1,2]]*2,2,[2]) Traceback (most recent call last): ... RuntimeError: The pair (1,2) is covered more than once sage: _check_pbd([],2,[2]) Traceback (most recent call last): ... RuntimeError: The pair (0,1) is not covered sage: _check_pbd([[1,2],[1]],2,[2]) Traceback (most recent call last): ... RuntimeError: A block has size 1 while S=[2] """ from itertools import combinations from sage.graphs.graph import Graph for X in B: if len(X) not in S: raise RuntimeError("A block has size {} while S={}".format(len(X),S)) if any(x < 2 for x in S): raise RuntimeError("All integers of S must be >=2") if v == 0 or v == 1: if B: raise RuntimeError("A PBD with v<=1 is expected to be empty.") g = Graph() g.add_vertices(range(v)) m = 0 for X in B: for i,j in combinations(X,2): g.add_edge(i,j) m_tmp = g.size() if m_tmp != m+1: raise RuntimeError("The pair ({},{}) is covered more than once".format(i,j)) m = m_tmp if g.vertices() != range(v): from sage.sets.integer_range import IntegerRange p = (set(g.vertices())-set(range(v))).pop() raise RuntimeError("The PBD covers a point {} which is not in {}".format(p,IntegerRange(v))) if not g.is_clique(): for p1 in g: if g.degree(p1) != v-1: break neighbors = g.neighbors(p1)+[p1] p2 = (set(g.vertices())-set(neighbors)).pop() raise RuntimeError("The pair ({},{}) is not covered".format(p1,p2)) return B
def ChessboardGraphGenerator(dim_list, rook = True, rook_radius = None, bishop = True, bishop_radius = None, knight = True, knight_x = 1, knight_y = 2, relabel = False): r""" Returns a Graph built on a `d`-dimensional chessboard with prescribed dimensions and interconnections. This function allows to generate many kinds of graphs corresponding to legal movements on a `d`-dimensional chessboard: Queen Graph, King Graph, Knight Graphs, Bishop Graph, and many generalizations. It also allows to avoid redondant code. INPUT: - ``dim_list`` -- an iterable object (list, set, dict) providing the dimensions `n_1, n_2, \ldots, n_d`, with `n_i \geq 1`, of the chessboard. - ``rook`` -- (default: ``True``) boolean value indicating if the chess piece is able to move as a rook, that is at any distance along a dimension. - ``rook_radius`` -- (default: None) integer value restricting the rook-like movements to distance at most `rook_radius`. - ``bishop`` -- (default: ``True``) boolean value indicating if the chess piece is able to move like a bishop, that is along diagonals. - ``bishop_radius`` -- (default: None) integer value restricting the bishop-like movements to distance at most `bishop_radius`. - ``knight`` -- (default: ``True``) boolean value indicating if the chess piece is able to move like a knight. - ``knight_x`` -- (default: 1) integer indicating the number on steps the chess piece moves in one dimension when moving like a knight. - ``knight_y`` -- (default: 2) integer indicating the number on steps the chess piece moves in the second dimension when moving like a knight. - ``relabel`` -- (default: ``False``) a boolean set to ``True`` if vertices must be relabeled as integers. OUTPUT: - A Graph build on a `d`-dimensional chessboard with prescribed dimensions, and with edges according given parameters. - A string encoding the dimensions. This is mainly useful for providing names to graphs. EXAMPLES: A `(2,2)`-King Graph is isomorphic to the complete graph on 4 vertices:: sage: G, _ = graphs.ChessboardGraphGenerator( [2,2] ) sage: G.is_isomorphic( graphs.CompleteGraph(4) ) True A Rook's Graph in 2 dimensions is isomporphic to the Cartesian product of 2 complete graphs:: sage: G, _ = graphs.ChessboardGraphGenerator( [3,4], rook=True, rook_radius=None, bishop=False, knight=False ) sage: H = ( graphs.CompleteGraph(3) ).cartesian_product( graphs.CompleteGraph(4) ) sage: G.is_isomorphic(H) True TESTS: Giving dimensions less than 2:: sage: graphs.ChessboardGraphGenerator( [0, 2] ) Traceback (most recent call last): ... ValueError: The dimensions must be positive integers larger than 1. Giving non integer dimensions:: sage: graphs.ChessboardGraphGenerator( [4.5, 2] ) Traceback (most recent call last): ... ValueError: The dimensions must be positive integers larger than 1. Giving too few dimensions:: sage: graphs.ChessboardGraphGenerator( [2] ) Traceback (most recent call last): ... ValueError: The chessboard must have at least 2 dimensions. Giving a non-iterable object as first parameter:: sage: graphs.ChessboardGraphGenerator( 2, 3 ) Traceback (most recent call last): ... TypeError: The first parameter must be an iterable object. Giving too small rook radius:: sage: graphs.ChessboardGraphGenerator( [2, 3], rook=True, rook_radius=0 ) Traceback (most recent call last): ... ValueError: The rook_radius must be either None or have an integer value >= 1. Giving wrong values for knights movements:: sage: graphs.ChessboardGraphGenerator( [2, 3], rook=False, bishop=False, knight=True, knight_x=1, knight_y=-1 ) Traceback (most recent call last): ... ValueError: The knight_x and knight_y values must be integers of value >= 1. """ from sage.rings.integer_ring import ZZ # We decode the dimensions of the chessboard try: dim = list(dim_list) nb_dim = len(dim) except TypeError: raise TypeError('The first parameter must be an iterable object.') if nb_dim < 2: raise ValueError('The chessboard must have at least 2 dimensions.') if any(a not in ZZ or a < 1 for a in dim): raise ValueError('The dimensions must be positive integers larger than 1.') dimstr = str(tuple(dim)) # We check the radius toward neighbors if rook: if rook_radius is None: rook_radius = max(dim) elif not rook_radius in ZZ or rook_radius < 1: raise ValueError('The rook_radius must be either None or have an integer value >= 1.') if bishop: if bishop_radius is None: bishop_radius = max(dim) elif not bishop_radius in ZZ or bishop_radius < 1: raise ValueError('The bishop_radius must be either None or have an integer value >= 1.') if knight and ( not knight_x in ZZ or not knight_y in ZZ or knight_x < 1 or knight_y < 1 ): raise ValueError('The knight_x and knight_y values must be integers of value >= 1.') # We build the set of vertices of the d-dimensionnal chessboard from itertools import product V = [list(x) for x in list(product(*[range(_) for _ in dim]))] from sage.combinat.combination import Combinations combin = Combinations(range(nb_dim),2) from sage.graphs.graph import Graph G = Graph() for u in V: uu = tuple(u) G.add_vertex(uu) if rook: # We add edges to vertices we can reach when moving in one dimension for d in xrange(nb_dim): v = u[:] for k in xrange(v[d]+1, min(dim[d],v[d]+1+rook_radius)): v[d] = k G.add_edge( uu, tuple(v) ) if bishop or knight: # We add edges to vertices we can reach when moving in two dimensions for dx,dy in combin: n = dim[dx] m = dim[dy] v = u[:] i = u[dx] j = u[dy] if bishop: # Diagonal for k in xrange(1, min(n-i,m-j,bishop_radius+1)): v[dx] = i+k v[dy] = j+k G.add_edge( uu, tuple(v) ) # Anti-diagonal for k in xrange(min(i, m-j-1, bishop_radius)): v[dx] = i-k-1 v[dy] = j+k+1 G.add_edge( uu, tuple(v) ) if knight: # Moving knight_x in one dimension and knight_y in another # dimension if i+knight_y < n: if j+knight_x < m: v[dx] = i+knight_y v[dy] = j+knight_x G.add_edge( uu, tuple(v) ) if j-knight_x >= 0: v[dx] = i+knight_y v[dy] = j-knight_x G.add_edge( uu, tuple(v) ) if j+knight_y < m: if i+knight_x < n: v[dx] = i+knight_x v[dy] = j+knight_y G.add_edge( uu, tuple(v) ) if i-knight_x >= 0: v[dx] = i-knight_x v[dy] = j+knight_y G.add_edge( uu, tuple(v) ) if relabel: G.relabel( inplace=True ) return G, dimstr
def ToleranceGraph(tolrep): r""" Returns the graph generated by the tolerance representation ``tolrep``. The tolerance representation ``tolrep`` is described by the list `((l_0,r_0,t_0), (l_1,r_1,t_1), ..., (l_k,r_k,t_k))` where `I_i = (l_i,r_i)` denotes a closed interval on the real line with `l_i < r_i` and `t_i` a positive value, called tolerance. This representation generates the tolerance graph with the vertex set {0,1, ..., k} and the edge set `{(i,j): |I_i \cap I_j| \ge \min{t_i, t_j}}` where `|I_i \cap I_j|` denotes the length of the intersection of `I_i` and `I_j`. INPUT: - ``tolrep`` -- list of triples `(l_i,r_i,t_i)` where `(l_i,r_i)` denotes a closed interval on the real line and `t_i` a positive value. .. NOTE:: The vertices are named 0, 1, ..., k. The tolerance representation used to create the graph is saved with the graph and can be recovered using ``get_vertex()`` or ``get_vertices()``. EXAMPLE: The following code creates a tolerance representation ``tolrep``, generates its tolerance graph ``g``, and applies some checks:: sage: tolrep = [(1,4,3),(1,2,1),(2,3,1),(0,3,3)] sage: g = graphs.ToleranceGraph(tolrep) sage: g.get_vertex(3) (0, 3, 3) sage: neigh = g.neighbors(3) sage: for v in neigh: print g.get_vertex(v) (1, 2, 1) (2, 3, 1) sage: g.is_interval() False sage: g.is_weakly_chordal() True The intervals in the list need not be distinct :: sage: tolrep2 = [(0,4,5),(1,2,1),(2,3,1),(0,4,5)] sage: g2 = graphs.ToleranceGraph(tolrep2) sage: g2.get_vertices() {0: (0, 4, 5), 1: (1, 2, 1), 2: (2, 3, 1), 3: (0, 4, 5)} sage: g2.is_isomorphic(g) True Real values are also allowed :: sage: tolrep = [(0.1,3.3,4.4),(1.1,2.5,1.1),(1.4,4.4,3.3)] sage: g = graphs.ToleranceGraph(tolrep) sage: g.is_isomorphic(graphs.PathGraph(3)) True TEST: Giving negative third value:: sage: tolrep = [(0.1,3.3,-4.4),(1.1,2.5,1.1),(1.4,4.4,3.3)] sage: g = graphs.ToleranceGraph(tolrep) Traceback (most recent call last): ... ValueError: Invalid tolerance representation at position 0; third value must be positive! """ n = len(tolrep) for i in xrange(n): if tolrep[i][2] <= 0: raise ValueError("Invalid tolerance representation at position "+str(i)+"; third value must be positive!") g = Graph(n) for i in xrange(n-1): li,ri,ti = tolrep[i] for j in xrange(i+1,n): lj,rj,tj = tolrep[j] if min(ri,rj) - max(li,lj) >= min(ti,tj): g.add_edge(i,j) rep = dict( zip(range(n),tolrep) ) g.set_vertices(rep) return g
def AffineOrthogonalPolarGraph(d,q,sign="+"): r""" Returns the affine polar graph `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`. Affine Polar graphs are built from a `d`-dimensional vector space over `F_q`, and a quadratic form which is hyperbolic, elliptic or parabolic according to the value of ``sign``. Note that `VO^+(d,q),VO^-(d,q)` are strongly regular graphs, while `VO(d,q)` is not. For more information on Affine Polar graphs, see `Affine Polar Graphs page of Andries Brouwer's website <http://www.win.tue.nl/~aeb/graphs/VO.html>`_. INPUT: - ``d`` (integer) -- ``d`` must be even if ``sign != None``, and odd otherwise. - ``q`` (integer) -- a power of a prime number, as `F_q` must exist. - ``sign`` -- must be equal to ``"+"``, ``"-"``, or ``None`` to compute (respectively) `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`. By default ``sign="+"``. .. NOTE:: The graph `VO^\epsilon(d,q)` is the graph induced by the non-neighbors of a vertex in an :meth:`Orthogonal Polar Graph <OrthogonalPolarGraph>` `O^\epsilon(d+2,q)`. EXAMPLES: The :meth:`Brouwer-Haemers graph <BrouwerHaemersGraph>` is isomorphic to `VO^-(4,3)`:: sage: g = graphs.AffineOrthogonalPolarGraph(4,3,"-") sage: g.is_isomorphic(graphs.BrouwerHaemersGraph()) True Some examples from `Brouwer's table or strongly regular graphs <http://www.win.tue.nl/~aeb/graphs/srg/srgtab.html>`_:: sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"-"); g Affine Polar Graph VO^-(6,2): Graph on 64 vertices sage: g.is_strongly_regular(parameters=True) (64, 27, 10, 12) sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"+"); g Affine Polar Graph VO^+(6,2): Graph on 64 vertices sage: g.is_strongly_regular(parameters=True) (64, 35, 18, 20) When ``sign is None``:: sage: g = graphs.AffineOrthogonalPolarGraph(5,2,None); g Affine Polar Graph VO^-(5,2): Graph on 32 vertices sage: g.is_strongly_regular(parameters=True) False sage: g.is_regular() True sage: g.is_vertex_transitive() True """ if sign in ["+","-"]: s = 1 if sign == "+" else -1 if d%2 == 1: raise ValueError("d must be even when sign!=None") else: if d%2 == 0: raise ValueError("d must be odd when sign==None") s = 0 from sage.interfaces.gap import gap from sage.modules.free_module import VectorSpace from sage.matrix.constructor import Matrix from sage.libs.gap.libgap import libgap from itertools import combinations M = Matrix(libgap.InvariantQuadraticForm(libgap.GeneralOrthogonalGroup(s,d,q))['matrix']) F = libgap.GF(q).sage() V = list(VectorSpace(F,d)) G = Graph() G.add_vertices([tuple(_) for _ in V]) for x,y in combinations(V,2): if not (x-y)*M*(x-y): G.add_edge(tuple(x),tuple(y)) G.name("Affine Polar Graph VO^"+str('+' if s == 1 else '-')+"("+str(d)+","+str(q)+")") G.relabel() return G
def CycleGraph(n): r""" Returns a cycle graph with n nodes. A cycle graph is a basic structure which is also typically called an `n`-gon. PLOTTING: Upon construction, the position dictionary is filled to override the spring-layout algorithm. By convention, each cycle graph will be displayed with the first (0) node at the top, with the rest following in a counterclockwise manner. The cycle graph is a good opportunity to compare efficiency of filling a position dictionary vs. using the spring-layout algorithm for plotting. Because the cycle graph is very symmetric, the resulting plots should be similar (in cases of small `n`). Filling the position dictionary in advance adds `O(n)` to the constructor. EXAMPLES: Compare plotting using the predefined layout and networkx:: sage: import networkx sage: n = networkx.cycle_graph(23) sage: spring23 = Graph(n) sage: posdict23 = graphs.CycleGraph(23) sage: spring23.show() # long time sage: posdict23.show() # long time We next view many cycle graphs as a Sage graphics array. First we use the ``CycleGraph`` constructor, which fills in the position dictionary:: sage: g = [] sage: j = [] sage: for i in range(9): ....: k = graphs.CycleGraph(i+3) ....: g.append(k) sage: for i in range(3): ....: n = [] ....: for m in range(3): ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False)) ....: j.append(n) sage: G = sage.plot.graphics.GraphicsArray(j) sage: G.show() # long time Compare to plotting with the spring-layout algorithm:: sage: g = [] sage: j = [] sage: for i in range(9): ....: spr = networkx.cycle_graph(i+3) ....: k = Graph(spr) ....: g.append(k) sage: for i in range(3): ....: n = [] ....: for m in range(3): ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False)) ....: j.append(n) sage: G = sage.plot.graphics.GraphicsArray(j) sage: G.show() # long time TESTS: The input parameter must be a positive integer:: sage: G = graphs.CycleGraph(-1) Traceback (most recent call last): ... ValueError: parameter n must be a positive integer """ if n < 0: raise ValueError("parameter n must be a positive integer") G = Graph(n, name="Cycle graph") if n == 1: G.set_pos({0: (0, 0)}) elif n == 2: G.add_edge(0, 1) G.set_pos({0: (0, 1), 1: (0, -1)}) else: pos_dict = {} for i in range(n): x = float(cos((pi / 2) + ((2 * pi) / n) * i)) y = float(sin((pi / 2) + ((2 * pi) / n) * i)) pos_dict[i] = (x, y) G.set_pos(pos_dict) G.add_cycle(list(range(n))) return G
def GridGraph(dim_list): """ Returns an n-dimensional grid graph. INPUT: - ``dim_list`` - a list of integers representing the number of nodes to extend in each dimension. PLOTTING: When plotting, this graph will use the default spring-layout algorithm, unless a position dictionary is specified. EXAMPLES:: sage: G = graphs.GridGraph([2,3,4]) sage: G.show() # long time :: sage: C = graphs.CubeGraph(4) sage: G = graphs.GridGraph([2,2,2,2]) sage: C.show() # long time sage: G.show() # long time TESTS: The graph name contains the dimension:: sage: g = graphs.GridGraph([5, 7]) sage: g.name() 'Grid Graph for [5, 7]' sage: g = graphs.GridGraph([2, 3, 4]) sage: g.name() 'Grid Graph for [2, 3, 4]' sage: g = graphs.GridGraph([2, 4, 3]) sage: g.name() 'Grid Graph for [2, 4, 3]' One dimensional grids (i.e., path) have simple vertex labels:: sage: g = graphs.GridGraph([5]) sage: g.vertices() [0, 1, 2, 3, 4] The graph is correct:: sage: dim = [randint(1,4) for i in range(4)] sage: g = graphs.GridGraph(dim) sage: import networkx sage: h = Graph( networkx.grid_graph(list(dim)) ) sage: g.is_isomorphic(h) True Trivial cases:: sage: g = graphs.GridGraph([]); g; g.vertices() Grid Graph for []: Graph on 0 vertices [] sage: g = graphs.GridGraph([1]); g; g.vertices() Grid Graph for [1]: Graph on 1 vertex [0] sage: g = graphs.GridGraph([2]); g; g.vertices() Grid Graph for [2]: Graph on 2 vertices [0, 1] sage: g = graphs.GridGraph([1,1]); g; g.vertices() Grid Graph for [1, 1]: Graph on 1 vertex [(0, 0)] sage: g = graphs.GridGraph([1, 1, 1]); g; g.vertices() Grid Graph for [1, 1, 1]: Graph on 1 vertex [(0, 0, 0)] sage: g = graphs.GridGraph([1,1,2]); g; g.vertices() Grid Graph for [1, 1, 2]: Graph on 2 vertices [(0, 0, 0), (0, 0, 1)] All dimensions must be positive integers:: sage: g = graphs.GridGraph([2,-1,3]) Traceback (most recent call last): ... ValueError: All dimensions must be positive integers ! """ dim = [int(a) for a in dim_list] if any(a <= 0 for a in dim): raise ValueError("All dimensions must be positive integers !") g = Graph() n_dim = len(dim) if n_dim==1: # Vertices are labeled from 0 to dim[0]-1 g = PathGraph(dim[0]) elif n_dim==2: # We use the Grid2dGraph generator to also get the positions g = Grid2dGraph(*dim) elif n_dim>2: # Vertices are tuples of dimension n_dim, and the graph contains at # least vertex (0, 0, ..., 0) g.add_vertex(tuple([0]*n_dim)) import itertools for u in itertools.product(*[range(d) for d in dim]): for i in range(n_dim): if u[i]+1<dim[i]: v = list(u) v[i] = u[i]+1 g.add_edge(u, tuple(v)) g.name("Grid Graph for {}".format(dim)) return g
def RandomBicubicPlanar(n): """ Return the graph of a random bipartite cubic map with `3 n` edges. INPUT: `n` -- an integer (at least `1`) OUTPUT: a graph with multiple edges (no embedding is provided) The algorithm used is described in [Schaeffer99]_. This samples a random rooted bipartite cubic map, chosen uniformly at random. First one creates a random binary tree with `n` vertices. Next one turns this into a blossoming tree (at random) and reads the contour word of this blossoming tree. Then one performs a rotation on this word so that this becomes a balanced word. There are three ways to do that, one is picked at random. Then a graph is build from the balanced word by iterated closure (adding edges). In the returned graph, the three edges incident to any given vertex are colored by the integers 0, 1 and 2. .. SEEALSO:: the auxiliary method :func:`blossoming_contour` EXAMPLES:: sage: n = randint(200, 300) sage: G = graphs.RandomBicubicPlanar(n) sage: G.order() == 2*n True sage: G.size() == 3*n True sage: G.is_bipartite() and G.is_planar() and G.is_regular(3) True sage: dic = {'red':[v for v in G.vertices() if v[0] == 'n'], ....: 'blue': [v for v in G.vertices() if v[0] != 'n']} sage: G.plot(vertex_labels=False,vertex_size=20,vertex_colors=dic) Graphics object consisting of ... graphics primitives .. PLOT:: :width: 300 px G = graphs.RandomBicubicPlanar(200) V0 = [v for v in G.vertices() if v[0] == 'n'] V1 = [v for v in G.vertices() if v[0] != 'n'] dic = {'red': V0, 'blue': V1} sphinx_plot(G.plot(vertex_labels=False,vertex_colors=dic)) REFERENCES: .. [Schaeffer99] Gilles Schaeffer, *Random Sampling of Large Planar Maps and Convex Polyhedra*, Annual ACM Symposium on Theory of Computing (Atlanta, GA, 1999) """ from sage.combinat.binary_tree import BinaryTrees from sage.rings.finite_rings.integer_mod_ring import Zmod if not n: raise ValueError("n must be at least 1") # first pick a random binary tree t = BinaryTrees(n).random_element() # next pick a random blossoming of this tree, compute its contour contour = blossoming_contour(t) + [('xb',)] # adding the final xb # first step : rotate the contour word to one of 3 balanced N = len(contour) double_contour = contour + contour pile = [] not_touched = [i for i in range(N) if contour[i][0] in ['x', 'xb']] for i, w in enumerate(double_contour): if w[0] == 'x' and i < N: pile.append(i) elif w[0] == 'xb' and (i % N) in not_touched: if pile: j = pile.pop() not_touched.remove(i % N) not_touched.remove(j) # random choice among 3 possibilities for a balanced word idx = not_touched[randint(0, 2)] w = contour[idx + 1:] + contour[:idx + 1] # second step : create the graph by closure from the balanced word G = Graph(multiedges=True) pile = [] Z3 = Zmod(3) colour = Z3.zero() not_touched = [i for i, v in enumerate(w) if v[0] in ['x', 'xb']] for i, v in enumerate(w): # internal edges if v[0] == 'i': colour += 1 if w[i + 1][0] == 'n': G.add_edge((w[i], w[i + 1], colour)) elif v[0] == 'n': colour += 2 elif v[0] == 'x': pile.append(i) elif v[0] == 'xb' and i in not_touched: if pile: j = pile.pop() G.add_edge((w[i + 1], w[j - 1], colour)) not_touched.remove(i) not_touched.remove(j) # there remains to add three edges to elements of "not_touched" # from a new vertex labelled "n" for i in not_touched: taken_colours = [edge[2] for edge in G.edges_incident(w[i - 1])] colour = [u for u in Z3 if u not in taken_colours][0] G.add_edge((('n', -1), w[i - 1], colour)) return G
def RandomTree(n): """ Returns a random tree on `n` nodes numbered `0` through `n-1`. By Cayley's theorem, there are `n^{n-2}` trees with vertex set `\{0,1,...,n-1\}`. This constructor chooses one of these uniformly at random. ALGORITHM: The algoritm works by generating an `(n-2)`-long random sequence of numbers chosen independently and uniformly from `\{0,1,\ldots,n-1\}` and then applies an inverse Prufer transformation. INPUT: - ``n`` - number of vertices in the tree EXAMPLE:: sage: G = graphs.RandomTree(10) sage: G.is_tree() True sage: G.show() # long TESTS: Ensuring that we encounter no unexpected surprise :: sage: all( graphs.RandomTree(10).is_tree() ....: for i in range(100) ) True """ from sage.misc.prandom import randint g = Graph() # create random Prufer code code = [ randint(0,n-1) for i in xrange(n-2) ] # We count the number of symbols of each type. # count[k] is the no. of times k appears in code # # (count[k] is set to -1 when the corresponding vertex is not # available anymore) count = [ 0 for i in xrange(n) ] for k in code: count[k] += 1 g.add_vertices(range(n)) for s in code: for x in range(n): if count[x] == 0: break count[x] = -1 g.add_edge(x,s) count[s] -= 1 # Adding as an edge the last two available vertices last_edge = [ v for v in range(n) if count[v] != -1 ] g.add_edge(last_edge) return g
def ToleranceGraph(tolrep): r""" Return the graph generated by the tolerance representation ``tolrep``. The tolerance representation ``tolrep`` is described by the list `((l_0,r_0,t_0), (l_1,r_1,t_1), ..., (l_k,r_k,t_k))` where `I_i = (l_i,r_i)` denotes a closed interval on the real line with `l_i < r_i` and `t_i` a positive value, called tolerance. This representation generates the tolerance graph with the vertex set {0,1, ..., k} and the edge set `{(i,j): |I_i \cap I_j| \ge \min{t_i, t_j}}` where `|I_i \cap I_j|` denotes the length of the intersection of `I_i` and `I_j`. INPUT: - ``tolrep`` -- list of triples `(l_i,r_i,t_i)` where `(l_i,r_i)` denotes a closed interval on the real line and `t_i` a positive value. .. NOTE:: The vertices are named 0, 1, ..., k. The tolerance representation used to create the graph is saved with the graph and can be recovered using ``get_vertex()`` or ``get_vertices()``. EXAMPLES: The following code creates a tolerance representation ``tolrep``, generates its tolerance graph ``g``, and applies some checks:: sage: tolrep = [(1,4,3),(1,2,1),(2,3,1),(0,3,3)] sage: g = graphs.ToleranceGraph(tolrep) sage: g.get_vertex(3) (0, 3, 3) sage: neigh = g.neighbors(3) sage: for v in neigh: print(g.get_vertex(v)) (1, 2, 1) (2, 3, 1) sage: g.is_interval() False sage: g.is_weakly_chordal() True The intervals in the list need not be distinct :: sage: tolrep2 = [(0,4,5),(1,2,1),(2,3,1),(0,4,5)] sage: g2 = graphs.ToleranceGraph(tolrep2) sage: g2.get_vertices() {0: (0, 4, 5), 1: (1, 2, 1), 2: (2, 3, 1), 3: (0, 4, 5)} sage: g2.is_isomorphic(g) True Real values are also allowed :: sage: tolrep = [(0.1,3.3,4.4),(1.1,2.5,1.1),(1.4,4.4,3.3)] sage: g = graphs.ToleranceGraph(tolrep) sage: g.is_isomorphic(graphs.PathGraph(3)) True TESTS: Giving negative third value:: sage: tolrep = [(0.1,3.3,-4.4),(1.1,2.5,1.1),(1.4,4.4,3.3)] sage: g = graphs.ToleranceGraph(tolrep) Traceback (most recent call last): ... ValueError: Invalid tolerance representation at position 0; third value must be positive! """ n = len(tolrep) for i in range(n): if tolrep[i][2] <= 0: raise ValueError("Invalid tolerance representation at position " + str(i) + "; third value must be positive!") g = Graph(n) for i in range(n - 1): li, ri, ti = tolrep[i] for j in range(i + 1, n): lj, rj, tj = tolrep[j] if min(ri, rj) - max(li, lj) >= min(ti, tj): g.add_edge(i, j) rep = dict(zip(range(n), tolrep)) g.set_vertices(rep) return g
def IntervalGraph(intervals, points_ordered=False): r""" Return the graph corresponding to the given intervals. An interval graph is built from a list `(a_i,b_i)_{1\leq i \leq n}` of intervals : to each interval of the list is associated one vertex, two vertices being adjacent if the two corresponding (closed) intervals intersect. INPUT: - ``intervals`` -- the list of pairs `(a_i,b_i)` defining the graph. - ``points_ordered`` -- states whether every interval `(a_i,b_i)` of `intervals` satisfies `a_i<b_i`. If satisfied then setting ``points_ordered`` to ``True`` will speed up the creation of the graph. .. NOTE:: * The vertices are named 0, 1, 2, and so on. The intervals used to create the graph are saved with the graph and can be recovered using ``get_vertex()`` or ``get_vertices()``. EXAMPLES: The following line creates the sequence of intervals `(i, i+2)` for i in `[0, ..., 8]`:: sage: intervals = [(i,i+2) for i in range(9)] In the corresponding graph :: sage: g = graphs.IntervalGraph(intervals) sage: g.get_vertex(3) (3, 5) sage: neigh = g.neighbors(3) sage: for v in neigh: print(g.get_vertex(v)) (1, 3) (2, 4) (4, 6) (5, 7) The is_interval() method verifies that this graph is an interval graph. :: sage: g.is_interval() True The intervals in the list need not be distinct. :: sage: intervals = [ (1,2), (1,2), (1,2), (2,3), (3,4) ] sage: g = graphs.IntervalGraph(intervals,True) sage: g.clique_maximum() [0, 1, 2, 3] sage: g.get_vertices() {0: (1, 2), 1: (1, 2), 2: (1, 2), 3: (2, 3), 4: (3, 4)} The endpoints of the intervals are not ordered we get the same graph (except for the vertex labels). :: sage: rev_intervals = [ (2,1), (2,1), (2,1), (3,2), (4,3) ] sage: h = graphs.IntervalGraph(rev_intervals,False) sage: h.get_vertices() {0: (2, 1), 1: (2, 1), 2: (2, 1), 3: (3, 2), 4: (4, 3)} sage: g.edges() == h.edges() True """ intervals = list(intervals) n = len(intervals) g = Graph(n) if points_ordered: for i in range(n - 1): li, ri = intervals[i] for j in range(i + 1, n): lj, rj = intervals[j] if ri < lj or rj < li: continue g.add_edge(i, j) else: for i in range(n - 1): I = intervals[i] for j in range(i + 1, n): J = intervals[j] if max(I) < min(J) or max(J) < min(I): continue g.add_edge(i, j) rep = dict(zip(range(n), intervals)) g.set_vertices(rep) return g
def RandomBicubicPlanar(n): """ Return the graph of a random bipartite cubic map with `3 n` edges. INPUT: `n` -- an integer (at least `1`) OUTPUT: a graph with multiple edges (no embedding is provided) The algorithm used is described in [Schaeffer99]_. This samples a random rooted bipartite cubic map, chosen uniformly at random. First one creates a random binary tree with `n` vertices. Next one turns this into a blossoming tree (at random) and reads the contour word of this blossoming tree. Then one performs a rotation on this word so that this becomes a balanced word. There are three ways to do that, one is picked at random. Then a graph is build from the balanced word by iterated closure (adding edges). In the returned graph, the three edges incident to any given vertex are colored by the integers 0, 1 and 2. .. SEEALSO:: the auxiliary method :func:`blossoming_contour` EXAMPLES:: sage: n = randint(200, 300) sage: G = graphs.RandomBicubicPlanar(n) sage: G.order() == 2*n True sage: G.size() == 3*n True sage: G.is_bipartite() and G.is_planar() and G.is_regular(3) True sage: dic = {'red':[v for v in G.vertices() if v[0] == 'n'], ....: 'blue': [v for v in G.vertices() if v[0] != 'n']} sage: G.plot(vertex_labels=False,vertex_size=20,vertex_colors=dic) Graphics object consisting of ... graphics primitives .. PLOT:: :width: 300 px G = graphs.RandomBicubicPlanar(200) V0 = [v for v in G.vertices() if v[0] == 'n'] V1 = [v for v in G.vertices() if v[0] != 'n'] dic = {'red': V0, 'blue': V1} sphinx_plot(G.plot(vertex_labels=False,vertex_colors=dic)) REFERENCES: .. [Schaeffer99] Gilles Schaeffer, *Random Sampling of Large Planar Maps and Convex Polyhedra*, Annual ACM Symposium on Theory of Computing (Atlanta, GA, 1999) """ from sage.combinat.binary_tree import BinaryTrees from sage.rings.finite_rings.integer_mod_ring import Zmod if not n: raise ValueError("n must be at least 1") # first pick a random binary tree t = BinaryTrees(n).random_element() # next pick a random blossoming of this tree, compute its contour contour = blossoming_contour(t) + [('xb', )] # adding the final xb # first step : rotate the contour word to one of 3 balanced N = len(contour) double_contour = contour + contour pile = [] not_touched = [i for i in range(N) if contour[i][0] in ['x', 'xb']] for i, w in enumerate(double_contour): if w[0] == 'x' and i < N: pile.append(i) elif w[0] == 'xb' and (i % N) in not_touched: if pile: j = pile.pop() not_touched.remove(i % N) not_touched.remove(j) # random choice among 3 possibilities for a balanced word idx = not_touched[randint(0, 2)] w = contour[idx + 1:] + contour[:idx + 1] # second step : create the graph by closure from the balanced word G = Graph(multiedges=True) pile = [] Z3 = Zmod(3) colour = Z3.zero() not_touched = [i for i, v in enumerate(w) if v[0] in ['x', 'xb']] for i, v in enumerate(w): # internal edges if v[0] == 'i': colour += 1 if w[i + 1][0] == 'n': G.add_edge((w[i], w[i + 1], colour)) elif v[0] == 'n': colour += 2 elif v[0] == 'x': pile.append(i) elif v[0] == 'xb' and i in not_touched: if pile: j = pile.pop() G.add_edge((w[i + 1], w[j - 1], colour)) not_touched.remove(i) not_touched.remove(j) # there remains to add three edges to elements of "not_touched" # from a new vertex labelled "n" for i in not_touched: taken_colours = [edge[2] for edge in G.edges_incident(w[i - 1])] colour = [u for u in Z3 if u not in taken_colours][0] G.add_edge((('n', -1), w[i - 1], colour)) return G
def restricted_automorphism_group(self, vertex_labels=None): r""" Return the restricted automorphism group. First, let the linear automorphism group be the subgroup of the Euclidean group `E(d) = GL(d,\RR) \ltimes \RR^d` preserving the `d`-dimensional polyhedron. The Euclidean group acts in the usual way `\vec{x}\mapsto A\vec{x}+b` on the ambient space. The restricted automorphism group is the subgroup of the linear automorphism group generated by permutations of vertices. If the polytope is full-dimensional, it is equal to the full (unrestricted) automorphism group. INPUT: - ``vertex_labels`` -- a tuple or ``None`` (default). The labels of the vertices that will be used in the output permutation group. By default, the vertices are used themselves. OUTPUT: A :class:`PermutationGroup<sage.groups.perm_gps.permgroup.PermutationGroup_generic>` acting on the vertices (or the ``vertex_labels``, if specified). REFERENCES: [BSS2009]_ EXAMPLES:: sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL sage: Z3square = LatticePolytope_PPL((0,0), (1,2), (2,1), (3,3)) sage: Z3square.restricted_automorphism_group(vertex_labels=(1,2,3,4)) == PermutationGroup([[(2,3)],[(1,2),(3,4)]]) True sage: G = Z3square.restricted_automorphism_group() sage: G == PermutationGroup([[((1,2),(2,1))],[((0,0),(1,2)),((2,1),(3,3))],[((0,0),(3,3))]]) True sage: set(G.domain()) == set(Z3square.vertices()) True sage: set(map(tuple,G.orbit(Z3square.vertices()[0]))) == set([(0, 0), (1, 2), (3, 3), (2, 1)]) True sage: cell24 = LatticePolytope_PPL( ....: (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1),(1,-1,-1,1),(0,0,-1,1), ....: (0,-1,0,1),(-1,0,0,1),(1,0,0,-1),(0,1,0,-1),(0,0,1,-1),(-1,1,1,-1), ....: (1,-1,-1,0),(0,0,-1,0),(0,-1,0,0),(-1,0,0,0),(1,-1,0,0),(1,0,-1,0), ....: (0,1,1,-1),(-1,1,1,0),(-1,1,0,0),(-1,0,1,0),(0,-1,-1,1),(0,0,0,-1)) sage: cell24.restricted_automorphism_group().cardinality() 1152 """ if not self.is_full_dimensional(): return self.affine_lattice_polytope().\ restricted_automorphism_group(vertex_labels=vertex_labels) if vertex_labels is None: vertex_labels = self.vertices() from sage.graphs.graph import Graph # good coordinates for the vertices v_list = [] for v in self.minimized_generators(): assert v.divisor() == 1 v_coords = (1,) + tuple(Integer(mpz) for mpz in v.coefficients()) v_list.append(vector(v_coords)) # Finally, construct the graph Qinv = sum( v.column() * v.row() for v in v_list ).inverse() G = Graph() for i in range(len(v_list)): for j in range(i+1,len(v_list)): v_i = v_list[i] v_j = v_list[j] G.add_edge(vertex_labels[i], vertex_labels[j], v_i * Qinv * v_j) return G.automorphism_group(edge_labels=True)
def PermutationGraph(second_permutation, first_permutation = None): r""" Builds a permutation graph from one (or two) permutations. General definition A Permutation Graph can be encoded by a permutation `\sigma` of `1, ..., n`. It is then built in the following way : Take two horizontal lines in the euclidean plane, and mark points `1, ..., n` from left to right on the first of them. On the second one, still from left to right, mark point in the order in which they appear in `\sigma`. Now, link by a segment the two points marked with 1, then link together the points marked with 2, and so on. The permutation graph defined by the permutation is the intersection graph of those segments : there exists a point in this graph for each element from `1` to `n`, two vertices `i, j` being adjacent if the segments `i` and `j` cross each other. The set of edges of the resulting graph is equal to the set of inversions of the inverse of the given permutation. INPUT: - ``second_permutation`` -- the permutation from which the graph should be built. It corresponds to the ordering of the elements on the second line (see previous definition) - ``first_permutation`` (optional) -- the ordering of the elements on the *first* line. This is useful when the elements have no natural ordering, for instance when they are strings, or tuples, or anything else. When ``first_permutation == None`` (default), it is set to be equal to ``sorted(second_permutation)``, which just yields the expected ordering when the elements of the graph are integers. .. SEEALSO: - Recognition of Permutation graphs in the :mod:`comparability module <sage.graphs.comparability>`. - Drawings of permutation graphs as intersection graphs of segments is possible through the :meth:`~sage.combinat.permutation.Permutation.show` method of :class:`~sage.combinat.permutation.Permutation` objects. The correct argument to use in this case is ``show(representation = "braid")``. - :meth:`~sage.combinat.permutation.Permutation.inversions` EXAMPLE:: sage: p = Permutations(5).random_element() sage: edges = graphs.PermutationGraph(p).edges(labels =False) sage: set(edges) == set(p.inverse().inversions()) True TESTS:: sage: graphs.PermutationGraph([1, 2, 3], [4, 5, 6]) Traceback (most recent call last): ... ValueError: The two permutations do not contain the same set of elements ... """ if first_permutation == None: first_permutation = sorted(second_permutation) else: if set(second_permutation) != set(first_permutation): raise ValueError("The two permutations do not contain the same "+ "set of elements ! It is going to be pretty "+ "hard to define a permutation graph from that !") vertex_to_index = {} for i, v in enumerate(first_permutation): vertex_to_index[v] = i+1 from sage.combinat.permutation import Permutation p2 = Permutation(map(lambda x:vertex_to_index[x], second_permutation)) p1 = Permutation(map(lambda x:vertex_to_index[x], first_permutation)) p2 = p2 * p1.inverse() p2 = p2.inverse() g = Graph(name="Permutation graph for "+str(second_permutation)) g.add_vertices(second_permutation) for u,v in p2.inversions(): g.add_edge(first_permutation[u-1], first_permutation[v-1]) return g
def is_partial_cube(G, certificate=False): r""" Test whether the given graph is a partial cube. A partial cube is a graph that can be isometrically embedded into a hypercube, i.e., its vertices can be labelled with (0,1)-vectors of some fixed length such that the distance between any two vertices in the graph equals the Hamming distance of their labels. Originally written by D. Eppstein for the PADS library (http://www.ics.uci.edu/~eppstein/PADS/), see also [Epp2008]_. The algorithm runs in `O(n^2)` time, where `n` is the number of vertices. See the documentation of :mod:`~sage.graphs.partial_cube` for an overview of the algorithm. INPUT: - ``certificate`` -- boolean (default: ``False``); this function returns ``True`` or ``False`` according to the graph, when ``certificate = False``. When ``certificate = True`` and the graph is a partial cube, the function returns ``(True, mapping)``, where ``mapping`` is an isometric mapping of the vertices of the graph to the vertices of a hypercube ((0, 1)-strings of a fixed length). When ``certificate = True`` and the graph is not a partial cube, ``(False, None)`` is returned. EXAMPLES: The Petersen graph is not a partial cube:: sage: g = graphs.PetersenGraph() sage: g.is_partial_cube() False All prisms are partial cubes:: sage: g = graphs.CycleGraph(10).cartesian_product(graphs.CompleteGraph(2)) sage: g.is_partial_cube() True TESTS: The returned mapping is an isometric embedding into a hypercube:: sage: g = graphs.DesarguesGraph() sage: _, m = g.is_partial_cube(certificate=True) sage: m # random {0: '00000', 1: '00001', 2: '00011', 3: '01011', 4: '11011', 5: '11111', 6: '11110', 7: '11100', 8: '10100', 9: '00100', 10: '01000', 11: '10001', 12: '00111', 13: '01010', 14: '11001', 15: '10111', 16: '01110', 17: '11000', 18: '10101', 19: '00110'} sage: all(all(g.distance(u, v) == len([i for i in range(len(m[u])) if m[u][i] != m[v][i]]) for v in m) for u in m) True A graph without vertices is trivially a partial cube:: sage: Graph().is_partial_cube(certificate=True) (True, {}) """ G._scream_if_not_simple() if not G.order(): if certificate: return (True, {}) else: return True if certificate: fail = (False, None) else: fail = False if not G.is_connected(): return fail n = G.order() # Initial sanity check: are there few enough edges? # Needed so that we don't try to use union-find on a dense # graph and incur superquadratic runtimes. if 1 << (2 * G.size() // n) > n: return fail # Check for bipartiteness. # This ensures also that each contraction will be bipartite. if not G.is_bipartite(): return fail # Set up data structures for algorithm: # - contracted: contracted graph at current stage of algorithm # - unionfind: union find data structure representing known edge equivalences # - available: limit on number of remaining available labels from sage.graphs.digraph import DiGraph from sage.graphs.graph import Graph from sage.sets.disjoint_set import DisjointSet contracted = DiGraph({v: {w: (v, w) for w in G[v]} for v in G}) unionfind = DisjointSet(contracted.edges(labels=False)) available = n - 1 # Main contraction loop in place of the original algorithm's recursion while contracted.order() > 1: # Find max degree vertex in contracted, and update label limit deg, root = max([(contracted.out_degree(v), v) for v in contracted], key=lambda x: x[0]) if deg > available: return fail available -= deg # Set up bitvectors on vertices bitvec = {v: 0 for v in contracted} neighbors = {} for i, neighbor in enumerate(contracted[root]): bitvec[neighbor] = 1 << i neighbors[1 << i] = neighbor # Breadth first search to propagate bitvectors to the rest of the graph for level in breadth_first_level_search(contracted, root): for v in level: for w in level[v]: bitvec[w] |= bitvec[v] # Make graph of labeled edges and union them together labeled = Graph([contracted.vertices(), []]) for v, w in contracted.edge_iterator(labels=False): diff = bitvec[v] ^ bitvec[w] if not diff or not bitvec[w] & ~bitvec[v]: continue # zero edge or wrong direction if diff not in neighbors: return fail neighbor = neighbors[diff] unionfind.union(contracted.edge_label(v, w), contracted.edge_label(root, neighbor)) unionfind.union(contracted.edge_label(w, v), contracted.edge_label(neighbor, root)) labeled.add_edge(v, w) # Map vertices to components of labeled-edge graph component = {} for i, SCC in enumerate(labeled.connected_components()): for v in SCC: component[v] = i # generate new compressed subgraph newgraph = DiGraph() for v, w, t in contracted.edge_iterator(): if bitvec[v] == bitvec[w]: vi = component[v] wi = component[w] if vi == wi: return fail if newgraph.has_edge(vi, wi): unionfind.union(newgraph.edge_label(vi, wi), t) else: newgraph.add_edge(vi, wi, t) contracted = newgraph # Make a digraph with edges labeled by the equivalence classes in unionfind g = DiGraph({v: {w: unionfind.find((v, w)) for w in G[v]} for v in G}) # Associates to a vertex the token that acts on it, an check that # no two edges on a single vertex have the same label action = {} for v in g: action[v] = set(t for _, _, t in g.edge_iterator(v)) if len(action[v]) != g.out_degree(v): return fail # Associate every token to its reverse reverse = {} for v, w, t in g.edge_iterator(): rt = g.edge_label(w, v) reverse[t] = rt reverse[rt] = t current = initialState = next(g.vertex_iterator()) # A token T is said to be 'active' for a vertex u if it takes u # one step closer to the source in terms of distance. The 'source' # is initially 'initialState'. See the module's documentation for # more explanations. # Find list of tokens that lead to the initial state activeTokens = set() for level in breadth_first_level_search(g, initialState): for v in level: for w in level[v]: activeTokens.add(g.edge_label(w, v)) for t in activeTokens: if reverse[t] in activeTokens: return fail activeTokens = list(activeTokens) # Rest of data structure: point from states to list and list to states state_to_active_token = {v: -1 for v in g} token_to_states = [[] for i in activeTokens ] # (i.e. vertices on which each token acts) def scan(v): """Find the next token that is effective for v.""" a = next( i for i in range(state_to_active_token[v] + 1, len(activeTokens)) if activeTokens[i] is not None and activeTokens[i] in action[v]) state_to_active_token[v] = a token_to_states[a].append(v) # Initialize isometric embedding into a hypercube if certificate: dim = 0 tokmap = {} for t in reverse: if t not in tokmap: tokmap[t] = tokmap[reverse[t]] = 1 << dim dim += 1 embed = {initialState: 0} # Set initial active states for v in g: if v != current: try: scan(v) except StopIteration: return fail # Traverse the graph, maintaining active tokens for prev, current, fwd in depth_first_traversal(g, initialState): if not fwd: prev, current = current, prev elif certificate: embed[current] = embed[prev] ^ tokmap[g.edge_label(prev, current)] # Add token to end of list, point to it from old state activeTokens.append(g.edge_label(prev, current)) state_to_active_token[prev] = len(activeTokens) - 1 token_to_states.append([prev]) # Inactivate reverse token, find new token for its states # # (the 'active' token of 'current' is necessarily the label of # (current, previous)) activeTokens[state_to_active_token[current]] = None for v in token_to_states[state_to_active_token[current]]: if v != current: try: scan(v) except StopIteration: return fail # All checks passed, return the result if certificate: format = "{0:0%db}" % dim return (True, {v: format.format(l) for v, l in embed.items()}) else: return True
def RandomBipartite(n1, n2, p): r""" Returns a bipartite graph with `n1+n2` vertices such that any edge from `[n1]` to `[n2]` exists with probability `p`. INPUT: - ``n1,n2`` : Cardinalities of the two sets - ``p`` : Probability for an edge to exist EXAMPLE:: sage: g=graphs.RandomBipartite(5,2,0.5) sage: g.vertices() [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1)] TESTS:: sage: g=graphs.RandomBipartite(5,-3,0.5) Traceback (most recent call last): ... ValueError: n1 and n2 should be integers strictly greater than 0 sage: g=graphs.RandomBipartite(5,3,1.5) Traceback (most recent call last): ... ValueError: Parameter p is a probability, and so should be a real value between 0 and 1 Trac ticket #12155:: sage: graphs.RandomBipartite(5,6,.2).complement() complement(Random bipartite graph of size 5+6 with edge probability 0.200000000000000): Graph on 11 vertices """ if not (p >= 0 and p <= 1): raise ValueError, "Parameter p is a probability, and so should be a real value between 0 and 1" if not (n1 > 0 and n2 > 0): raise ValueError, "n1 and n2 should be integers strictly greater than 0" from numpy.random import uniform from sage.graphs.all import Graph g = Graph(name="Random bipartite graph of size " + str(n1) + "+" + str(n2) + " with edge probability " + str(p)) S1 = [(0, i) for i in range(n1)] S2 = [(1, i) for i in range(n2)] g.add_vertices(S1) g.add_vertices(S2) for w in range(n2): for v in range(n1): if uniform() <= p: g.add_edge((0, v), (1, w)) pos = {} for i in range(n1): pos[(0, i)] = (0, i / (n1 - 1.0)) for i in range(n2): pos[(1, i)] = (1, i / (n2 - 1.0)) g.set_pos(pos) return g
def CycleGraph(n): r""" Returns a cycle graph with n nodes. A cycle graph is a basic structure which is also typically called an `n`-gon. PLOTTING: Upon construction, the position dictionary is filled to override the spring-layout algorithm. By convention, each cycle graph will be displayed with the first (0) node at the top, with the rest following in a counterclockwise manner. The cycle graph is a good opportunity to compare efficiency of filling a position dictionary vs. using the spring-layout algorithm for plotting. Because the cycle graph is very symmetric, the resulting plots should be similar (in cases of small `n`). Filling the position dictionary in advance adds `O(n)` to the constructor. EXAMPLES: Compare plotting using the predefined layout and networkx:: sage: import networkx sage: n = networkx.cycle_graph(23) sage: spring23 = Graph(n) sage: posdict23 = graphs.CycleGraph(23) sage: spring23.show() # long time sage: posdict23.show() # long time We next view many cycle graphs as a Sage graphics array. First we use the ``CycleGraph`` constructor, which fills in the position dictionary:: sage: g = [] sage: j = [] sage: for i in range(9): ....: k = graphs.CycleGraph(i+3) ....: g.append(k) sage: for i in range(3): ....: n = [] ....: for m in range(3): ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False)) ....: j.append(n) sage: G = sage.plot.graphics.GraphicsArray(j) sage: G.show() # long time Compare to plotting with the spring-layout algorithm:: sage: g = [] sage: j = [] sage: for i in range(9): ....: spr = networkx.cycle_graph(i+3) ....: k = Graph(spr) ....: g.append(k) sage: for i in range(3): ....: n = [] ....: for m in range(3): ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False)) ....: j.append(n) sage: G = sage.plot.graphics.GraphicsArray(j) sage: G.show() # long time TESTS: The input parameter must be a positive integer:: sage: G = graphs.CycleGraph(-1) Traceback (most recent call last): ... ValueError: parameter n must be a positive integer """ if n < 0: raise ValueError("parameter n must be a positive integer") G = Graph(n, name="Cycle graph") if n == 1: G.set_pos({0:(0, 0)}) elif n == 2: G.add_edge(0, 1) G.set_pos({0:(0, 1), 1:(0, -1)}) else: pos_dict = {} for i in range(n): x = float(cos((pi/2) + ((2*pi)/n)*i)) y = float(sin((pi/2) + ((2*pi)/n)*i)) pos_dict[i] = (x, y) G.set_pos(pos_dict) G.add_cycle(list(range(n))) return G