def IntersectionGraph(S): r""" Returns the intersection graph of the family `S` The intersection graph of a family `S` is a graph `G` with `V(G)=S` such that two elements `s_1,s_2\in S` are adjacent in `G` if and only if `s_1\cap s_2\neq \emptyset`. INPUT: - ``S`` -- a list of sets/tuples/iterables .. NOTE:: The elements of `S` must be finite, hashable, and the elements of any `s\in S` must be hashable too. EXAMPLES:: sage: graphs.IntersectionGraph([(1,2,3),(3,4,5),(5,6,7)]) Intersection Graph: Graph on 3 vertices TESTS:: sage: graphs.IntersectionGraph([(1,2,[1])]) Traceback (most recent call last): ... TypeError: The elements of S must be hashable, and this one is not: (1, 2, [1]) """ from itertools import combinations for s in S: try: hash(s) except TypeError: raise TypeError( "The elements of S must be hashable, and this one is not: {}". format(s)) ground_set_to_sets = {} for s in S: for x in s: if x not in ground_set_to_sets: ground_set_to_sets[x] = [] ground_set_to_sets[x].append(s) g = Graph(name="Intersection Graph") g.add_vertices(S) for clique in itervalues(ground_set_to_sets): g.add_clique(set(clique)) return g
def IntersectionGraph(S): r""" Returns the intersection graph of the family `S` The intersection graph of a family `S` is a graph `G` with `V(G)=S` such that two elements `s_1,s_2\in S` are adjacent in `G` if and only if `s_1\cap s_2\neq \emptyset`. INPUT: - ``S`` -- a list of sets/tuples/iterables .. NOTE:: The elements of `S` must be finite, hashable, and the elements of any `s\in S` must be hashable too. EXAMPLES:: sage: graphs.IntersectionGraph([(1,2,3),(3,4,5),(5,6,7)]) Intersection Graph: Graph on 3 vertices TESTS:: sage: graphs.IntersectionGraph([(1,2,[1])]) Traceback (most recent call last): ... TypeError: The elements of S must be hashable, and this one is not: (1, 2, [1]) """ from itertools import combinations for s in S: try: hash(s) except TypeError: raise TypeError("The elements of S must be hashable, and this one is not: {}".format(s)) ground_set_to_sets = {} for s in S: for x in s: if x not in ground_set_to_sets: ground_set_to_sets[x] = [] ground_set_to_sets[x].append(s) g = Graph(name="Intersection Graph") g.add_vertices(S) for clique in itervalues(ground_set_to_sets): g.add_clique(set(clique)) return g
def CompleteMultipartiteGraph(l): r""" Return a complete multipartite graph. INPUT: - ``l`` -- a list of integers; the respective sizes of the components PLOTTING: Produce a layout of the vertices so that vertices in the same vertex set are adjacent and clearly separated from vertices in other vertex sets. This is done by calculating the vertices of an `r`-gon then calculating the slope between adjacent vertices. We then 'walk' around the `r`-gon placing graph vertices in regular intervals between adjacent vertices of the `r`-gon. Makes a nicely organized graph like in this picture: https://commons.wikimedia.org/wiki/File:Turan_13-4.svg EXAMPLES: A complete tripartite graph with sets of sizes `5, 6, 8`:: sage: g = graphs.CompleteMultipartiteGraph([5, 6, 8]); g Multipartite Graph with set sizes [5, 6, 8]: Graph on 19 vertices It clearly has a chromatic number of 3:: sage: g.chromatic_number() 3 """ r = len(l) # getting the number of partitions name = "Multipartite Graph with set sizes {}".format(l) if not r: g = Graph() elif r == 1: g = Graph(l[0]) g._line_embedding(range(l[0]), first=(0, 0), last=(l[0], 0)) elif r == 2: g = CompleteBipartiteGraph(l[0], l[1]) g.name(name) else: # This position code gives bad results on bipartite or isolated graphs points = [(cos(2 * pi * i / r), sin(2 * pi * i / r)) for i in range(r)] slopes = [(points[(i + 1) % r][0] - points[i % r][0], points[(i + 1) % r][1] - points[i % r][1]) for i in range(r)] counter = 0 positions = {} for i in range(r): vertex_set_size = l[i] + 1 for j in range(1, vertex_set_size): x = points[i][0] + slopes[i][0] * j / vertex_set_size y = points[i][1] + slopes[i][1] * j / vertex_set_size positions[counter] = (x, y) counter += 1 g = Graph(sum(l)) s = 0 for i in l: g.add_clique(range(s, s + i)) s += i g = g.complement() g.set_pos(positions) g.name(name) return g
def RandomBlockGraph(m, k, kmax=None, incidence_structure=False): r""" Return a Random Block Graph. A block graph is a connected graph in which every biconnected component (block) is a clique. .. SEEALSO:: - :wikipedia:`Block_graph` for more details on these graphs - :meth:`~sage.graphs.graph.Graph.is_block_graph` -- test if a graph is a block graph - :meth:`~sage.graphs.generic_graph.GenericGraph.blocks_and_cut_vertices` - :meth:`~sage.graphs.generic_graph.GenericGraph.blocks_and_cuts_tree` - :meth:`~sage.combinat.designs.incidence_structures.IncidenceStructure` INPUT: - ``m`` -- integer; number of blocks (at least one). - ``k`` -- integer; minimum number of vertices of a block (at least two). - ``kmax`` -- integer (default: ``None``) By default, each block has `k` vertices. When the parameter `kmax` is specified (with `kmax \geq k`), the number of vertices of each block is randomly chosen between `k` and `kmax`. - ``incidence_structure`` -- boolean (default: ``False``) when set to ``True``, the incidence structure of the graphs is returned instead of the graph itself, that is the list of the lists of vertices in each block. This is useful for the creation of some hypergraphs. OUTPUT: A Graph when ``incidence_structure==False`` (default), and otherwise an incidence structure. EXAMPLES: A block graph with a single block is a clique:: sage: B = graphs.RandomBlockGraph(1, 4) sage: B.is_clique() True A block graph with blocks of order 2 is a tree:: sage: B = graphs.RandomBlockGraph(10, 2) sage: B.is_tree() True Every biconnected component of a block graph is a clique:: sage: B = graphs.RandomBlockGraph(5, 3, kmax=6) sage: blocks,cuts = B.blocks_and_cut_vertices() sage: all(B.is_clique(block) for block in blocks) True A block graph with blocks of order `k` has `m*(k-1)+1` vertices:: sage: m, k = 6, 4 sage: B = graphs.RandomBlockGraph(m, k) sage: B.order() == m*(k-1)+1 True Test recognition methods:: sage: B = graphs.RandomBlockGraph(6, 2, kmax=6) sage: B.is_block_graph() True sage: B in graph_classes.Block True Asking for the incidence structure:: sage: m, k = 6, 4 sage: IS = graphs.RandomBlockGraph(m, k, incidence_structure=True) sage: from sage.combinat.designs.incidence_structures import IncidenceStructure sage: IncidenceStructure(IS) Incidence structure with 19 points and 6 blocks sage: m*(k-1)+1 19 TESTS: A block graph has at least one block, so `m\geq 1`:: sage: B = graphs.RandomBlockGraph(0, 1) Traceback (most recent call last): ... ValueError: the number `m` of blocks must be >= 1 A block has at least 2 vertices, so `k\geq 2`:: sage: B = graphs.RandomBlockGraph(1, 1) Traceback (most recent call last): ... ValueError: the minimum number `k` of vertices in a block must be >= 2 The maximum size of a block is at least its minimum size, so `k\leq kmax`:: sage: B = graphs.RandomBlockGraph(1, 3, kmax=2) Traceback (most recent call last): ... ValueError: the maximum number `kmax` of vertices in a block must be >= `k` """ import itertools from sage.misc.prandom import choice from sage.sets.disjoint_set import DisjointSet if m < 1: raise ValueError("the number `m` of blocks must be >= 1") if k < 2: raise ValueError( "the minimum number `k` of vertices in a block must be >= 2") if kmax is None: kmax = k elif kmax < k: raise ValueError( "the maximum number `kmax` of vertices in a block must be >= `k`") if m == 1: # A block graph with a single block is a clique IS = [list(range(randint(k, kmax)))] elif kmax == 2: # A block graph with blocks of order 2 is a tree IS = [list(e) for e in RandomTree(m + 1).edges(labels=False)] else: # We start with a random tree of order m T = RandomTree(m) # We create a block of order in range [k,kmax] per vertex of the tree B = {u: [(u, i) for i in range(randint(k, kmax))] for u in T} # For each edge of the tree, we choose 1 vertex in each of the # corresponding blocks and we merge them. We use a disjoint set data # structure to keep a unique identifier per merged vertices DS = DisjointSet([i for u in B for i in B[u]]) for u, v in T.edges(labels=0): DS.union(choice(B[u]), choice(B[v])) # We relabel vertices in the range [0, m*(k-1)] and build the incidence # structure new_label = { root: i for i, root in enumerate(DS.root_to_elements_dict()) } IS = [[new_label[DS.find(v)] for v in B[u]] for u in B] if incidence_structure: return IS # We finally build the block graph if k == kmax: BG = Graph( name="Random Block Graph with {} blocks of order {}".format(m, k)) else: BG = Graph( name="Random Block Graph with {} blocks of order {} to {}".format( m, k, kmax)) for block in IS: BG.add_clique(block) return BG
def RandomBlockGraph(m, k, kmax=None, incidence_structure=False): r""" Return a Random Block Graph. A block graph is a connected graph in which every biconnected component (block) is a clique. .. SEEALSO:: - :wikipedia:`Block_graph` for more details on these graphs - :meth:`~sage.graphs.graph.Graph.is_block_graph` -- test if a graph is a block graph - :meth:`~sage.graphs.generic_graph.GenericGraph.blocks_and_cut_vertices` - :meth:`~sage.graphs.generic_graph.GenericGraph.blocks_and_cuts_tree` - :meth:`~sage.combinat.designs.incidence_structures.IncidenceStructure` INPUT: - ``m`` -- integer; number of blocks (at least one). - ``k`` -- integer; minimum number of vertices of a block (at least two). - ``kmax`` -- integer (default: ``None``) By default, each block has `k` vertices. When the parameter `kmax` is specified (with `kmax \geq k`), the number of vertices of each block is randomly chosen between `k` and `kmax`. - ``incidence_structure`` -- boolean (default: ``False``) when set to ``True``, the incidence structure of the graphs is returned instead of the graph itself, that is the list of the lists of vertices in each block. This is useful for the creation of some hypergraphs. OUTPUT: A Graph when ``incidence_structure==False`` (default), and otherwise an incidence structure. EXAMPLES: A block graph with a single block is a clique:: sage: B = graphs.RandomBlockGraph(1, 4) sage: B.is_clique() True A block graph with blocks of order 2 is a tree:: sage: B = graphs.RandomBlockGraph(10, 2) sage: B.is_tree() True Every biconnected component of a block graph is a clique:: sage: B = graphs.RandomBlockGraph(5, 3, kmax=6) sage: blocks,cuts = B.blocks_and_cut_vertices() sage: all(B.is_clique(block) for block in blocks) True A block graph with blocks of order `k` has `m*(k-1)+1` vertices:: sage: m, k = 6, 4 sage: B = graphs.RandomBlockGraph(m, k) sage: B.order() == m*(k-1)+1 True Test recognition methods:: sage: B = graphs.RandomBlockGraph(6, 2, kmax=6) sage: B.is_block_graph() True sage: B in graph_classes.Block True Asking for the incidence structure:: sage: m, k = 6, 4 sage: IS = graphs.RandomBlockGraph(m, k, incidence_structure=True) sage: from sage.combinat.designs.incidence_structures import IncidenceStructure sage: IncidenceStructure(IS) Incidence structure with 19 points and 6 blocks sage: m*(k-1)+1 19 TESTS: A block graph has at least one block, so `m\geq 1`:: sage: B = graphs.RandomBlockGraph(0, 1) Traceback (most recent call last): ... ValueError: the number `m` of blocks must be >= 1 A block has at least 2 vertices, so `k\geq 2`:: sage: B = graphs.RandomBlockGraph(1, 1) Traceback (most recent call last): ... ValueError: the minimum number `k` of vertices in a block must be >= 2 The maximum size of a block is at least its minimum size, so `k\leq kmax`:: sage: B = graphs.RandomBlockGraph(1, 3, kmax=2) Traceback (most recent call last): ... ValueError: the maximum number `kmax` of vertices in a block must be >= `k` """ import itertools from sage.misc.prandom import choice from sage.sets.disjoint_set import DisjointSet if m < 1: raise ValueError("the number `m` of blocks must be >= 1") if k < 2: raise ValueError("the minimum number `k` of vertices in a block must be >= 2") if kmax is None: kmax = k elif kmax < k: raise ValueError("the maximum number `kmax` of vertices in a block must be >= `k`") if m == 1: # A block graph with a single block is a clique IS = [ list(range(randint(k, kmax))) ] elif kmax == 2: # A block graph with blocks of order 2 is a tree IS = [ list(e) for e in RandomTree(m+1).edges(labels=False) ] else: # We start with a random tree of order m T = RandomTree(m) # We create a block of order in range [k,kmax] per vertex of the tree B = {u:[(u,i) for i in range(randint(k, kmax))] for u in T} # For each edge of the tree, we choose 1 vertex in each of the # corresponding blocks and we merge them. We use a disjoint set data # structure to keep a unique identifier per merged vertices DS = DisjointSet([i for u in B for i in B[u]]) for u,v in T.edges(labels=0): DS.union(choice(B[u]), choice(B[v])) # We relabel vertices in the range [0, m*(k-1)] and build the incidence # structure new_label = {root:i for i,root in enumerate(DS.root_to_elements_dict())} IS = [ [new_label[DS.find(v)] for v in B[u]] for u in B ] if incidence_structure: return IS # We finally build the block graph if k == kmax: BG = Graph(name = "Random Block Graph with {} blocks of order {}".format(m, k)) else: BG = Graph(name = "Random Block Graph with {} blocks of order {} to {}".format(m, k, kmax)) for block in IS: BG.add_clique( block ) return BG