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 graph6_to_plot(graph6): """ Return a ``Graphics`` object from a ``graph6`` string. This method constructs a graph from a ``graph6`` string and returns a :class:`sage.plot.graphics.Graphics` object with arguments preset for the :meth:`sage.plot.graphics.Graphics.show` method. INPUT: - ``graph6`` -- a ``graph6`` string EXAMPLES:: sage: from sage.graphs.graph_database import graph6_to_plot sage: type(graph6_to_plot('D??')) <class 'sage.plot.graphics.Graphics'> """ g = Graph(str(graph6)) return g.plot(layout='circular', vertex_size=30, vertex_labels=False, graph_border=False)
def dependence_graph(self): r""" Return graph of dependence relation. OUTPUT: dependence graph with generators as vertices TESTS:: sage: from sage.monoids.trace_monoid import TraceMonoid sage: F.<a,b,c> = FreeMonoid() sage: M.<ai,bi,ci> = TraceMonoid(F, I=((a,c), (c,a))) sage: M.dependence_graph() == Graph({a:[a,b], b:[b], c:[c,b]}) True """ return Graph(set( frozenset((e1, e2)) if e1 != e2 else (e1, e2) for e1, e2 in self.dependence()), loops=True, format="list_of_edges", immutable=True)
def get_graphs_list(self): """ Returns a list of Sage Graph objects that satisfy the query. EXAMPLES:: sage: Q = GraphQuery(display_cols=['graph6','num_vertices','degree_sequence'],num_edges=['<=',5],min_degree=1) sage: L = Q.get_graphs_list() sage: L[0] Graph on 2 vertices sage: len(L) 35 """ from sage.graphs.graph_list import from_graph6 s = self.__query_string__ re.sub('SELECT.*FROM ', 'SELECT graph6 FROM ', s) q = GenericGraphQuery(s, self.__database__, self.__param_tuple__) graph6_list = q.query_results() return [Graph(str(g[0])) for g in graph6_list]
def _check_pbd(B, v, S): r""" Checks that ``B`` is a PBD on `v` points with given block sizes. INPUT: - ``bibd`` -- a list of blocks - ``v`` (integer) -- number of points - ``S`` -- list of integers EXAMPLE:: sage: designs.BalancedIncompleteBlockDesign(40,4).blocks() # indirect doctest [[0, 1, 2, 12], [0, 3, 6, 9], [0, 4, 8, 11], [0, 5, 7, 10], [0, 13, 26, 39], [0, 14, 28, 38], [0, 15, 25, 27], [0, 16, 32, 35], [0, 17, 34, 37], [0, 18, 33, 36], ... """ from itertools import combinations from sage.graphs.graph import Graph if not all(len(X) in S for X in B): raise RuntimeError( "This is not a nice honest PBD from the good old days !") g = Graph() m = 0 for X in B: g.add_edges(list(combinations(X, 2))) if g.size() != m + binomial(len(X), 2): raise RuntimeError( "This is not a nice honest PBD from the good old days !") m = g.size() if not (g.is_clique() and g.vertices() == range(v)): raise RuntimeError( "This is not a nice honest PBD from the good old days !") return B
def DegreeSequence(deg_sequence): """ Returns a graph with the given degree sequence. Raises a NetworkX error if the proposed degree sequence cannot be that of a graph. Graph returned is the one returned by the Havel-Hakimi algorithm, which constructs a simple graph by connecting vertices of highest degree to other vertices of highest degree, resorting the remaining vertices by degree and repeating the process. See Theorem 1.4 in [CL1996]_. INPUT: - ``deg_sequence`` - a list of integers with each entry corresponding to the degree of a different vertex. EXAMPLES:: sage: G = graphs.DegreeSequence([3,3,3,3]) sage: G.edges(labels=False) [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] sage: G.show() # long time :: sage: G = graphs.DegreeSequence([3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]) sage: G.show() # long time :: sage: G = graphs.DegreeSequence([4,4,4,4,4,4,4,4]) sage: G.show() # long time :: sage: G = graphs.DegreeSequence([1,2,3,4,3,4,3,2,3,2,1]) sage: G.show() # long time """ import networkx return Graph(networkx.havel_hakimi_graph([int(i) for i in deg_sequence]))
def copy(self, weighted=None, data_structure=None, sparse=None, immutable=None): r""" This method has been overridden by DiscreteZOO to ensure that a mutable copy will have type ``Graph``. """ if immutable is False or (data_structure is not None and data_structure is not 'static_sparse'): return Graph(self).copy(weighted=weighted, data_structure=data_structure, sparse=sparse, immutable=immutable) else: return Graph.copy(self, weighted=weighted, data_structure=data_structure, sparse=sparse, immutable=immutable)
def DiamondGraph(): """ Return a diamond graph with 4 nodes. A diamond graph is a square with one pair of diagonal nodes connected. PLOTTING: Upon construction, the position dictionary is filled to override the spring-layout algorithm. By convention, the diamond graph is drawn as a diamond, with the first node on top, second on the left, third on the right, and fourth on the bottom; with the second and third node connected. EXAMPLES: Construct and show a diamond graph:: sage: g = graphs.DiamondGraph() sage: g.show() # long time """ pos_dict = {0:(0,1),1:(-1,0),2:(1,0),3:(0,-1)} edges = [(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)] return Graph(edges, pos=pos_dict, name="Diamond Graph")
def coxeter_diagram(self): """ Return the Coxeter diagram associated to ``self``. EXAMPLES:: sage: G = ReflectionGroup(['B',3]) # optional - gap3 sage: sorted(G.coxeter_diagram().edges(labels=True)) # optional - gap3 [(1, 2, 4), (2, 3, 3)] """ from sage.graphs.graph import Graph from itertools import combinations V = self.index_set() S = self.simple_reflections() E = [] for i,j in combinations(V, 2): o = (S[i]*S[j]).order() if o >= 3: E.append((i,j,o)) return Graph([V,E], format='vertices_and_edges', immutable=True)
def DegreeSequenceTree(deg_sequence): """ Returns a tree with the given degree sequence. Raises a NetworkX error if the proposed degree sequence cannot be that of a tree. Since every tree has one more vertex than edge, the degree sequence must satisfy len(deg_sequence) - sum(deg_sequence)/2 == 1. INPUT: - ``deg_sequence`` - a list of integers with each entry corresponding to the expected degree of a different vertex. EXAMPLES:: sage: G = graphs.DegreeSequenceTree([3,1,3,3,1,1,1,2,1]) sage: G.show() # long time """ import networkx return Graph(networkx.degree_sequence_tree([int(i) for i in deg_sequence]))
def coxeter_graph(self): """ Return the Coxeter graph of ``self``. EXAMPLES:: sage: C = CoxeterMatrix(['A',3]) sage: C.coxeter_graph() Graph on 3 vertices sage: C = CoxeterMatrix([['A',3],['A',1]]) sage: C.coxeter_graph() Graph on 4 vertices """ n = self.rank() I = self.index_set() val = lambda x: infinity if x == -1 else x G = Graph([(I[i], I[j], val((self._matrix)[i, j])) for i in range(n) for j in range(i) if self._matrix[i, j] not in [1, 2]]) G.add_vertices(I) return G.copy(immutable=True)
def __init__(self, G): """ Initialize ``self``. INPUT: - ``G`` -- a graph TESTS:: sage: G = RightAngledArtinGroup(graphs.CycleGraph(5)) sage: TestSuite(G).run() """ self._graph = G F = FreeGroup(names=['v{}'.format(v) for v in self._graph.vertices()]) CG = Graph(G).complement() # Make sure it's mutable CG.relabel() # Standardize the labels rels = tuple( F([i + 1, j + 1, -i - 1, -j - 1]) for i, j in CG.edges(False)) # +/- 1 for indexing FinitelyPresentedGroup.__init__(self, F, rels)
def descendant(self, v): """ The descendant :class:`graph <sage.graphs.graph.Graph>` at ``v`` The :mod:`switching class of graphs <sage.combinat.designs.twographs>` corresponding to ``self`` contains a graph ``D`` with ``v`` its own connected component; removing ``v`` from ``D``, one obtains the descendant graph of ``self`` at ``v``, which is constructed by this method. INPUT: - ``v`` -- an element of :meth:`ground_set` EXAMPLES:: sage: p=graphs.PetersenGraph().twograph().descendant(0) sage: p.is_strongly_regular(parameters=True) (9, 4, 1, 2) """ from sage.graphs.graph import Graph return Graph(map(lambda y: filter(lambda z: z != v, y), filter(lambda x: v in x, self.blocks())))
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 __classcall_private__(cls, G, names=None): """ Normalize input to ensure a unique representation. TESTS:: sage: G1 = RightAngledArtinGroup(graphs.CycleGraph(5)) sage: Gamma = Graph([(0,1),(1,2),(2,3),(3,4),(4,0)]) sage: G2 = RightAngledArtinGroup(Gamma) sage: G3 = RightAngledArtinGroup([(0,1),(1,2),(2,3),(3,4),(4,0)]) sage: G4 = RightAngledArtinGroup(Gamma, 'v') sage: G1 is G2 and G2 is G3 and G3 is G4 True Handle the empty graph:: sage: RightAngledArtinGroup(Graph()) Traceback (most recent call last): ... ValueError: the graph must not be empty """ if not isinstance(G, Graph): G = Graph(G, immutable=True) else: G = G.copy(immutable=True) if G.num_verts() == 0: raise ValueError("the graph must not be empty") if names is None: names = 'v' if isinstance(names, six.string_types): if ',' in names: names = [x.strip() for x in names.split(',')] else: names = [names + str(v) for v in G.vertices()] names = tuple(names) if len(names) != G.num_verts(): raise ValueError("the number of generators must match the" " number of vertices of the defining graph") return super(RightAngledArtinGroup, cls).__classcall__(cls, G, names)
def royle_x_graph(): r""" Return a strongly regular graph, as described by Royle [Roy2008]_. INPUT: None. OUTPUT: An object of class ``Graph``, representing Royle's X graph [Roy2008]_. EXAMPLES: :: sage: from boolean_cayley_graphs.royle_x_graph import royle_x_graph sage: g = royle_x_graph() sage: g.is_strongly_regular() True sage: g.is_strongly_regular(parameters=True) (64, 35, 18, 20) REFERENCES: Royle [Roy2008]_. """ n = 8 order = 64 vecs = [vector([1] * n)] for a in Combinations(xsrange(1, n), 4): vecs.append(vector([-1 if x in a else 1 for x in xsrange(n)])) for b in Combinations(xsrange(n), 2): vecs.append(vector([-1 if x in b else 1 for x in xsrange(n)])) return Graph([(i, j) for i in xsrange(order) for j in xsrange(i + 1, order) if vecs[i] * vecs[j] == 0])
def apply(solver, model, structures): g = structures[0] vertices = solver.get_objects_in_model(model, g, g.internal_graph.vertices()) dims = int(math.log(g.order, 2)) #print(vertices) #print(dims) edges = solver.get_objects_in_model(model, g, g.internal_graph.edges(labels=False)) #print(edges) #create temp graph t = Graph() t.add_vertices(vertices) t.add_edges(edges) for i in range(g.order/2): antipod = g.order - 1 - i #print(i, antipod) if i in vertices and antipod in vertices: path = t.shortest_path(i, antipod) if path: #print(path) return (True, [g, path]) print("COUNTER") return (False, [])
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 EmptyGraph(): """ Return an empty graph (0 nodes and 0 edges). This is useful for constructing graphs by adding edges and vertices individually or in a loop. PLOTTING: When plotting, this graph will use the default spring-layout algorithm, unless a position dictionary is specified. EXAMPLES: Add one vertex to an empty graph and then show:: sage: empty1 = graphs.EmptyGraph() sage: empty1.add_vertex() 0 sage: empty1.show() # long time Use for loops to build a graph from an empty graph:: sage: empty2 = graphs.EmptyGraph() sage: for i in range(5): ....: empty2.add_vertex() # add 5 nodes, labeled 0-4 0 1 2 3 4 sage: for i in range(3): ....: empty2.add_edge(i,i+1) # add edges {[0:1],[1:2],[2:3]} sage: for i in range(1, 4): ....: empty2.add_edge(4,i) # add edges {[1:4],[2:4],[3:4]} sage: empty2.show() # long time """ return Graph(sparse=True)
def _construct_object(self, cl, d): r""" Prepare all necessary data and construct the graph. INPUT: - ``cl`` - the class to construct the graph for. - ``d`` - the dictionary of parameters. """ ZooObject.__init__(self, **d) if d["data"] is None: try: d["data"] = self._db_read(cl, kargs=d)["data"] except KeyError as ex: if not d["store"]: raise ex propname = lookup(self._graphprops, "name", default=None) if d["name"]: self._graphprops["name"] = d["name"] elif propname: d["name"] = propname if propname == '': del self._graphprops["name"] if d["vertex_labels"] is not None: d["data"] = Graph(d["data"]).relabel(d["vertex_labels"], inplace=False) if d["loops"] is None: d["loops"] = self._graphprops["number_of_loops"] > 0 elif not d["loops"] and self._graphprops["number_of_loops"] > 0: raise ValueError("the requested graph has loops") if d["multiedges"] is None: d["multiedges"] = self._graphprops["has_multiple_edges"] elif not d["multiedges"] and self._graphprops["has_multiple_edges"]: raise ValueError("the requested graph has multiple edges") construct(Graph, self, d) self._initialized = True
def DegreeSequenceExpected(deg_sequence, seed=None): """ Returns a random graph with expected given degree sequence. Raises a NetworkX error if the proposed degree sequence cannot be that of a graph. One requirement is that the sum of the degrees must be even, since every edge must be incident with two vertices. INPUT: - ``deg_sequence`` - a list of integers with each entry corresponding to the expected degree of a different vertex. - ``seed`` - for the random number generator. EXAMPLES:: sage: G = graphs.DegreeSequenceExpected([1,2,3,2,3]) sage: G.edges(labels=False) [(0, 2), (0, 3), (1, 1), (1, 4), (2, 3), (2, 4), (3, 4), (4, 4)] sage: G.show() # long time REFERENCE: .. [ChungLu2002] Chung, Fan and Lu, L. Connected components in random graphs with given expected degree sequences. Ann. Combinatorics (6), 2002 pp. 125-145. """ if seed is None: seed = current_randstate().long_seed() import networkx return Graph(networkx.expected_degree_graph([int(i) for i in deg_sequence], seed=seed), loops=True)
def DegreeSequenceExpected(deg_sequence, seed=None): """ Returns a random graph with expected given degree sequence. Raises a NetworkX error if the proposed degree sequence cannot be that of a graph. One requirement is that the sum of the degrees must be even, since every edge must be incident with two vertices. INPUT: - ``deg_sequence`` - a list of integers with each entry corresponding to the expected degree of a different vertex. - ``seed`` - a ``random.Random`` seed or a Python ``int`` for the random number generator (default: ``None``). EXAMPLES:: sage: G = graphs.DegreeSequenceExpected([1,2,3,2,3]) sage: G.edges(labels=False) [(0, 3), (1, 3), (1, 4), (4, 4)] # 32-bit [(0, 3), (1, 4), (2, 2), (2, 3), (2, 4), (4, 4)] # 64-bit sage: G.show() # long time REFERENCE: [CL2002]_ """ if seed is None: seed = int(current_randstate().long_seed() % sys.maxsize) import networkx return Graph(networkx.expected_degree_graph([int(i) for i in deg_sequence], seed=seed), loops=True)
def ClawGraph(): """ Return a claw graph. A claw graph is named for its shape. It is actually a complete bipartite graph with ``(n1, n2) = (1, 3)``. PLOTTING: See :meth:`CompleteBipartiteGraph`. EXAMPLES: Show a Claw graph:: sage: (graphs.ClawGraph()).show() # long time Inspect a Claw graph:: sage: G = graphs.ClawGraph() sage: G Claw graph: Graph on 4 vertices """ edge_list = [(0, 1), (0, 2), (0, 3)] pos_dict = {0: (0, 1), 1: (-1, 0), 2: (0, 0), 3: (1, 0)} return Graph(edge_list, pos=pos_dict, name="Claw graph")
def relabel(self, perm=None, inplace=True, return_map=False, check_input=True, complete_partial_function=True, immutable=True): r""" This method has been overridden by DiscreteZOO to ensure that a mutable copy will have type ``Graph``. """ if inplace: raise ValueError("To relabel an immutable graph use inplace=False") G = Graph(self, immutable=False) perm = G.relabel(perm, return_map=True, check_input=check_input, complete_partial_function=complete_partial_function) if immutable is not False: G = self.__class__(self, vertex_labels=perm) if return_map: return G, perm else: return G
def RandomLobster(n, p, q, seed=None): """ Returns a random lobster. A lobster is a tree that reduces to a caterpillar when pruning all leaf vertices. A caterpillar is a tree that reduces to a path when pruning all leaf vertices (q=0). INPUT: - ``n`` - expected number of vertices in the backbone - ``p`` - probability of adding an edge to the backbone - ``q`` - probability of adding an edge (claw) to the arms - ``seed`` - for the random number generator EXAMPLE: We show the edge list of a random graph with 3 backbone nodes and probabilities `p = 0.7` and `q = 0.3`:: sage: graphs.RandomLobster(3, 0.7, 0.3).edges(labels=False) [(0, 1), (1, 2)] :: sage: G = graphs.RandomLobster(9, .6, .3) sage: G.show() # long time """ if seed is None: seed = current_randstate().long_seed() import networkx return Graph(networkx.random_lobster(n, p, q, seed=seed))
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 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 OrthogonalArrayBlockGraph(k, n, OA=None): r""" Return the graph of an `OA(k,n)`. The intersection graph of the blocks of a transversal design with parameters `(k,n)`, or `TD(k,n)` for short, is a strongly regular graph (unless it is a complete graph). Its parameters `(v,k',\lambda,\mu)` are determined by the parameters `k,n` via: .. MATH:: v=n^2, k'=k(n-1), \lambda=(k-1)(k-2)+n-2, \mu=k(k-1) As transversal designs and orthogonal arrays (OA for short) are equivalent objects, this graph can also be built from the blocks of an `OA(k,n)`, two of them being adjacent if one of their coordinates match. For more information on these graphs, see `Andries Brouwer's page on Orthogonal Array graphs <https://www.win.tue.nl/~aeb/graphs/OA.html>`_. .. WARNING:: - Brouwer's website uses the notation `OA(n,k)` instead of `OA(k,n)` - For given parameters `k` and `n` there can be many `OA(k,n)` : the graphs returned are not uniquely defined by their parameters (see the examples below). - If the function is called only with the parameter ``k`` and ``n`` the results might be different with two versions of Sage, or even worse : some could not be available anymore. .. SEEALSO:: :mod:`sage.combinat.designs.orthogonal_arrays` INPUT: - ``k,n`` (integers) - ``OA`` -- An orthogonal array. If set to ``None`` (default) then :func:`~sage.combinat.designs.orthogonal_arrays.orthogonal_array` is called to compute an `OA(k,n)`. EXAMPLES:: sage: G = graphs.OrthogonalArrayBlockGraph(5,5); G OA(5,5): Graph on 25 vertices sage: G.is_strongly_regular(parameters=True) (25, 20, 15, 20) sage: G = graphs.OrthogonalArrayBlockGraph(4,10); G OA(4,10): Graph on 100 vertices sage: G.is_strongly_regular(parameters=True) (100, 36, 14, 12) Two graphs built from different orthogonal arrays are also different:: sage: k=4;n=10 sage: OAa = designs.orthogonal_arrays.build(k,n) sage: OAb = [[(x+1)%n for x in R] for R in OAa] sage: set(map(tuple,OAa)) == set(map(tuple,OAb)) False sage: Ga = graphs.OrthogonalArrayBlockGraph(k,n,OAa) sage: Gb = graphs.OrthogonalArrayBlockGraph(k,n,OAb) sage: Ga == Gb False As ``OAb`` was obtained from ``OAa`` by a relabelling the two graphs are isomorphic:: sage: Ga.is_isomorphic(Gb) True But there are examples of `OA(k,n)` for which the resulting graphs are not isomorphic:: sage: oa0 = [[0, 0, 1], [0, 1, 3], [0, 2, 0], [0, 3, 2], ....: [1, 0, 3], [1, 1, 1], [1, 2, 2], [1, 3, 0], ....: [2, 0, 0], [2, 1, 2], [2, 2, 1], [2, 3, 3], ....: [3, 0, 2], [3, 1, 0], [3, 2, 3], [3, 3, 1]] sage: oa1 = [[0, 0, 1], [0, 1, 0], [0, 2, 3], [0, 3, 2], ....: [1, 0, 3], [1, 1, 2], [1, 2, 0], [1, 3, 1], ....: [2, 0, 0], [2, 1, 1], [2, 2, 2], [2, 3, 3], ....: [3, 0, 2], [3, 1, 3], [3, 2, 1], [3, 3, 0]] sage: g0 = graphs.OrthogonalArrayBlockGraph(3,4,oa0) sage: g1 = graphs.OrthogonalArrayBlockGraph(3,4,oa1) sage: g0.is_isomorphic(g1) False But nevertheless isospectral:: sage: g0.spectrum() [9, 1, 1, 1, 1, 1, 1, 1, 1, 1, -3, -3, -3, -3, -3, -3] sage: g1.spectrum() [9, 1, 1, 1, 1, 1, 1, 1, 1, 1, -3, -3, -3, -3, -3, -3] Note that the graph ``g0`` is actually isomorphic to the affine polar graph `VO^+(4,2)`:: sage: graphs.AffineOrthogonalPolarGraph(4,2,'+').is_isomorphic(g0) True TESTS:: sage: G = graphs.OrthogonalArrayBlockGraph(4,6) Traceback (most recent call last): ... NotImplementedError: I don't know how to build an OA(4,6)! sage: G = graphs.OrthogonalArrayBlockGraph(8,2) Traceback (most recent call last): ... ValueError: There is no OA(8,2). Beware, Brouwer's website uses OA(n,k) instead of OA(k,n) ! """ if n > 1 and k >= n + 2: raise ValueError( "There is no OA({},{}). Beware, Brouwer's website uses OA(n,k) instead of OA(k,n) !" .format(k, n)) from itertools import combinations if OA is None: from sage.combinat.designs.orthogonal_arrays import orthogonal_array OA = orthogonal_array(k, n) else: assert len(OA) == n**2 assert n == 0 or k == len(OA[0]) OA = map(tuple, OA) d = [[[] for j in range(n)] for i in range(k)] for R in OA: for i, x in enumerate(R): d[i][x].append(R) g = Graph() for l in d: for ll in l: g.add_edges(combinations(ll, 2)) g.name("OA({},{})".format(k, n)) 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