Undirected graphs

This module implements functions and operations involving undirected graphs.

Algorithmically hard stuff

convexity_properties()

Return a ConvexityProperties object corresponding to self.

has_homomorphism_to()

Check whether there is a homomorphism between two graphs.

independent_set()

Return a maximum independent set.

independent_set_of_representatives()

Return an independent set of representatives.

is_perfect()

Test whether the graph is perfect.

matching_polynomial()

Compute the matching polynomial of the graph \(G\).

minor()

Return the vertices of a minor isomorphic to \(H\) in the current graph.

pathwidth()

Compute the pathwidth of self (and provides a decomposition).

rank_decomposition()

Compute an optimal rank-decomposition of the given graph.

topological_minor()

Return a topological \(H\)-minor from self if one exists.

treelength()

Compute the treelength of \(G\) (and provide a decomposition).

treewidth()

Compute the treewidth of \(g\) (and provide a decomposition).

tutte_polynomial()

Return the Tutte polynomial of the graph \(G\).

vertex_cover()

Return a minimum vertex cover of self represented by a set of vertices.

Basic methods

bipartite_color()

Return a dictionary with vertices as the keys and the color class as the values.

bipartite_double()

Return the (extended) bipartite double of this graph.

bipartite_sets()

Return \((X,Y)\) where \(X\) and \(Y\) are the nodes in each bipartite set of graph \(G\).

graph6_string()

Return the graph6 representation of the graph as an ASCII string.

is_directed()

Since graph is undirected, returns False.

join()

Return the join of self and other.

sparse6_string()

Return the sparse6 representation of the graph as an ASCII string.

to_directed()

Return a directed version of the graph.

to_undirected()

Since the graph is already undirected, simply returns a copy of itself.

write_to_eps()

Write a plot of the graph to filename in eps format.

Clique-related methods

all_cliques()

Iterator over the cliques in graph.

atoms_and_clique_separators()

Return the atoms of the decomposition of \(G\) by clique minimal separators.

clique_complex()

Return the clique complex of self.

clique_maximum()

Return the vertex set of a maximal order complete subgraph.

clique_number()

Return the order of the largest clique of the graph.

clique_polynomial()

Return the clique polynomial of self.

cliques_containing_vertex()

Return the cliques containing each vertex, represented as a dictionary of lists of lists, keyed by vertex.

cliques_get_clique_bipartite()

Return the vertex-clique bipartite graph of self.

cliques_get_max_clique_graph()

Return the clique graph.

cliques_maximal()

Return the list of all maximal cliques.

cliques_maximum()

Return the vertex sets of ALL the maximum complete subgraphs.

cliques_number_of()

Return a dictionary of the number of maximal cliques containing each vertex, keyed by vertex.

cliques_vertex_clique_number()

Return a dictionary of sizes of the largest maximal cliques containing each vertex, keyed by vertex.

fractional_clique_number()

Return the fractional clique number of the graph.

Coloring

chromatic_index()

Return the chromatic index of the graph.

chromatic_number()

Return the minimal number of colors needed to color the vertices of the graph.

chromatic_polynomial()

Compute the chromatic polynomial of the graph G.

chromatic_quasisymmetric_function()

Return the chromatic quasisymmetric function of self.

chromatic_symmetric_function()

Return the chromatic symmetric function of self.

coloring()

Return the first (optimal) proper vertex-coloring found.

fractional_chromatic_index()

Return the fractional chromatic index of the graph.

fractional_chromatic_number()

Return the fractional chromatic number of the graph.

tutte_symmetric_function()

Return the Tutte symmetric function of self.

Connectivity, orientations, trees

acyclic_orientations()

Return an iterator over all acyclic orientations of an undirected graph \(G\).

bounded_outdegree_orientation()

Return an orientation of \(G\) such that every vertex \(v\) has out-degree less than \(b(v)\).

bridges()

Return an iterator over the bridges (or cut edges).

cleave()

Return the connected subgraphs separated by the input vertex cut.

degree_constrained_subgraph()

Return a degree-constrained subgraph.

ear_decomposition()

Return an Ear decomposition of the graph.

eulerian_orientation()

Return an Eulerian orientation of the graph \(G\).

gomory_hu_tree()

Return a Gomory-Hu tree of self.

is_triconnected()

Check whether the graph is triconnected.

minimal_separators()

Return an iterator over the minimal separators of G.

minimum_outdegree_orientation()

Return an orientation of \(G\) with the smallest possible maximum outdegree.

orient()

Return an oriented version of \(G\) according the input function \(f\).

orientations()

Return an iterator over orientations of \(G\).

random_orientation()

Return a random orientation of a graph \(G\).

random_spanning_tree()

Return a random spanning tree of the graph.

spanning_trees()

Return an iterator over all spanning trees of the graph \(g\).

spqr_tree()

Return an SPQR-tree representing the triconnected components of the graph.

strong_orientation()

Return a strongly connected orientation of the graph \(G\).

strong_orientations_iterator()

Return an iterator over all strong orientations of a graph \(G\).

Distances

center()

Return the set of vertices in the center of the graph.

centrality_degree()

Return the degree centrality of a vertex.

diameter()

Return the diameter of the graph.

distance_graph()

Return the graph on the same vertex set as the original graph but vertices are adjacent in the returned graph if and only if they are at specified distances in the original graph.

eccentricity()

Return the eccentricity of vertex (or vertices) v.

hyperbolicity()

Return the hyperbolicity of the graph or an approximation of this value.

periphery()

Return the set of vertices in the periphery of the graph.

radius()

Return the radius of the graph.

Domination

is_dominating()

Check whether dom is a dominating set of G.

is_redundant()

Check whether dom has redundant vertices.

minimal_dominating_sets()

Return an iterator over the minimal dominating sets of a graph.

private_neighbors()

Return the private neighbors of a vertex with respect to other vertices.

Expansion properties

cheeger_constant()

Return the cheeger constant of the graph.

edge_isoperimetric_number()

Return the edge-isoperimetric number of the graph.

vertex_isoperimetric_number()

Return the vertex-isoperimetric number of the graph.

Graph properties

apex_vertices()

Return the list of apex vertices.

is_antipodal()

Check whether this graph is antipodal.

is_apex()

Test if the graph is apex.

is_arc_transitive()

Check if self is an arc-transitive graph.

is_asteroidal_triple_free()

Test if the input graph is asteroidal triple-free.

is_biconnected()

Test if the graph is biconnected.

is_block_graph()

Return whether this graph is a block graph.

is_cactus()

Check whether the graph is cactus graph.

is_cartesian_product()

Test whether the graph is a Cartesian product.

is_circumscribable()

Test whether the graph is the graph of a circumscribed polyhedron.

is_cograph()

Check whether the graph is cograph.

is_comparability()

Check whether the graph is a comparability graph.

is_distance_regular()

Test if the graph is distance-regular.

is_edge_transitive()

Check if self is an edge transitive graph.

is_even_hole_free()

Test whether self contains an induced even hole.

is_forest()

Test if the graph is a forest, i.e. a disjoint union of trees.

is_half_transitive()

Check if self is a half-transitive graph.

is_inscribable()

Test whether the graph is the graph of an inscribed polyhedron.

is_line_graph()

Check whether the graph \(g\) is a line graph.

is_long_antihole_free()

Test whether the given graph contains an induced subgraph that is isomorphic to the complement of a cycle of length at least 5.

is_long_hole_free()

Test whether g contains an induced cycle of length at least 5.

is_odd_hole_free()

Test whether self contains an induced odd hole.

is_overfull()

Test whether the current graph is overfull.

is_partial_cube()

Test whether the given graph is a partial cube.

is_path()

Check whether self is a path.

is_permutation()

Check whether the graph is a permutation graph.

is_polyhedral()

Check whether the graph is the graph of the polyhedron.

is_prime()

Test whether the current graph is prime.

is_semi_symmetric()

Check if self is semi-symmetric.

is_split()

Return True if the graph is a Split graph, False otherwise.

is_strongly_regular()

Check whether the graph is strongly regular.

is_tree()

Test if the graph is a tree.

is_triangle_free()

Check whether self is triangle-free.

is_weakly_chordal()

Test whether the given graph is weakly chordal, i.e., the graph and its complement have no induced cycle of length at least 5.

Leftovers

antipodal_graph()

Return the antipodal graph of self.

arboricity()

Return the arboricity of the graph and an optional certificate.

common_neighbors_matrix()

Return a matrix of numbers of common neighbors between each pairs.

cores()

Return the core number for each vertex in an ordered list.

effective_resistance()

Return the effective resistance between nodes \(i\) and \(j\).

effective_resistance_matrix()

Return a matrix whose (\(i\) , \(j\)) entry gives the effective resistance between vertices \(i\) and \(j\).

folded_graph()

Return the antipodal fold of this graph.

geodetic_closure()

Return the geodetic closure of the set of vertices \(S\) in \(G\).

ihara_zeta_function_inverse()

Compute the inverse of the Ihara zeta function of the graph.

kirchhoff_symanzik_polynomial()

Return the Kirchhoff-Symanzik polynomial of a graph.

least_effective_resistance()

Return a list of pairs of nodes with the least effective resistance.

lovasz_theta()

Return the value of Lovász theta-function of graph.

magnitude_function()

Return the magnitude function of the graph as a rational function.

maximum_average_degree()

Return the Maximum Average Degree (MAD) of the current graph.

most_common_neighbors()

Return vertex pairs with maximal number of common neighbors.

seidel_adjacency_matrix()

Return the Seidel adjacency matrix of self.

seidel_switching()

Return the Seidel switching of self w.r.t. subset of vertices s.

two_factor_petersen()

Return a decomposition of the graph into 2-factors.

twograph()

Return the two-graph of self.

Matching

has_perfect_matching()

Return whether the graph has a perfect matching.

is_bicritical()

Check if the graph is bicritical.

is_factor_critical()

Check whether the graph is factor-critical.

is_matching_covered()

Check if the graph is matching covered.

matching()

Return a maximum weighted matching of the graph represented by the list of its edges.

perfect_matchings()

Return an iterator over all perfect matchings of the graph.

Modules

is_module()

Check whether vertices is a module of self.

modular_decomposition()

Return the modular decomposition of the current graph.

Traversals

lex_M()

Return an ordering of the vertices according the LexM graph traversal.

maximum_cardinality_search()

Return an ordering of the vertices according a maximum cardinality search.

maximum_cardinality_search_M()

Return the ordering and the edges of the triangulation produced by MCS-M.

Unsorted

bandwidth()

Compute the bandwidth of an undirected graph.

cutwidth()

Return the cutwidth of the graph and the corresponding vertex ordering.

slice_decomposition()

Compute a slice decomposition of the simple undirected graph

AUTHORS:

  • Robert L. Miller (2006-10-22): initial version

  • William Stein (2006-12-05): Editing

  • Robert L. Miller (2007-01-13): refactoring, adjusting for NetworkX-0.33, fixed

    plotting bugs (2007-01-23): basic tutorial, edge labels, loops, multiple edges and arcs (2007-02-07): graph6 and sparse6 formats, matrix input

  • Emily Kirkmann (2007-02-11): added graph_border option to plot and show

  • Robert L. Miller (2007-02-12): vertex color-maps, graph boundaries, graph6

    helper functions in Cython

  • Robert L. Miller Sage Days 3 (2007-02-17-21): 3d plotting in Tachyon

  • Robert L. Miller (2007-02-25): display a partition

  • Robert L. Miller (2007-02-28): associate arbitrary objects to vertices, edge

    and arc label display (in 2d), edge coloring

  • Robert L. Miller (2007-03-21): Automorphism group, isomorphism check,

    canonical label

  • Robert L. Miller (2007-06-07-09): NetworkX function wrapping

  • Michael W. Hansen (2007-06-09): Topological sort generation

  • Emily Kirkman, Robert L. Miller Sage Days 4: Finished wrapping NetworkX

  • Emily Kirkman (2007-07-21): Genus (including circular planar, all embeddings

    and all planar embeddings), all paths, interior paths

  • Bobby Moretti (2007-08-12): fixed up plotting of graphs with edge colors

    differentiated by label

  • Jason Grout (2007-09-25): Added functions, bug fixes, and general enhancements

  • Robert L. Miller (Sage Days 7): Edge labeled graph isomorphism

  • Tom Boothby (Sage Days 7): Miscellaneous awesomeness

  • Tom Boothby (2008-01-09): Added graphviz output

  • David Joyner (2009-2): Fixed docstring bug related to GAP.

  • Stephen Hartke (2009-07-26): Fixed bug in blocks_and_cut_vertices() that

    caused an incorrect result when the vertex 0 was a cut vertex.

  • Stephen Hartke (2009-08-22): Fixed bug in blocks_and_cut_vertices() where the

    list of cut_vertices is not treated as a set.

  • Anders Jonsson (2009-10-10): Counting of spanning trees and out-trees added.

  • Nathann Cohen (2009-09)Cliquer, Connectivity, Flows and everything that

    uses Linear Programming and class numerical.MIP

  • Nicolas M. Thiery (2010-02): graph layout code refactoring, dot2tex/graphviz interface

  • David Coudert (2012-04) : Reduction rules in vertex_cover.

  • Birk Eisermann (2012-06): added recognition of weakly chordal graphs and

    long-hole-free / long-antihole-free graphs

  • Alexandre P. Zuge (2013-07): added join operation.

  • Amritanshu Prasad (2014-08): added clique polynomial

  • Julian Rüth (2018-06-21): upgrade to NetworkX 2

  • David Coudert (2018-10-07): cleaning

  • Amanda Francis, Caitlin Lienkaemper, Kate Collins, Rajat Mittal (2019-03-10): methods for computing effective resistance

  • Amanda Francis, Caitlin Lienkaemper, Kate Collins, Rajat Mittal (2019-03-19): most_common_neighbors and common_neighbors_matrix added.

  • Jean-Florent Raymond (2019-04): is_redundant, is_dominating,

    private_neighbors

  • Cyril Bouvier (2024-11): is_module

Graph Format

Supported formats

Sage Graphs can be created from a wide range of inputs. A few examples are covered here.

  • NetworkX dictionary format:

    sage: d = {0: [1,4,5], 1: [2,6], 2: [3,7], 3: [4,8], 4: [9], \
    ....: 5: [7, 8], 6: [8,9], 7: [9]}
    sage: G = Graph(d); G
    Graph on 10 vertices
    sage: G.plot().show()    # or G.show()                                           # needs sage.plot
    
    >>> from sage.all import *
    >>> d = {Integer(0): [Integer(1),Integer(4),Integer(5)], Integer(1): [Integer(2),Integer(6)], Integer(2): [Integer(3),Integer(7)], Integer(3): [Integer(4),Integer(8)], Integer(4): [Integer(9)], Integer(5): [Integer(7), Integer(8)], Integer(6): [Integer(8),Integer(9)], Integer(7): [Integer(9)]}
    >>> G = Graph(d); G
    Graph on 10 vertices
    >>> G.plot().show()    # or G.show()                                           # needs sage.plot
    
  • A NetworkX graph:

    sage: # needs networkx
    sage: import networkx
    sage: K = networkx.complete_bipartite_graph(12,7)
    sage: G = Graph(K)
    sage: G.degree()
    [7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 12, 12, 12, 12, 12, 12, 12]
    
    >>> from sage.all import *
    >>> # needs networkx
    >>> import networkx
    >>> K = networkx.complete_bipartite_graph(Integer(12),Integer(7))
    >>> G = Graph(K)
    >>> G.degree()
    [7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 12, 12, 12, 12, 12, 12, 12]
    
  • graph6 or sparse6 format:

    sage: s = ':I`AKGsaOs`cI]Gb~'
    sage: G = Graph(s, sparse=True); G
    Looped multi-graph on 10 vertices
    sage: G.plot().show()    # or G.show()                                           # needs sage.plot
    
    >>> from sage.all import *
    >>> s = ':I`AKGsaOs`cI]Gb~'
    >>> G = Graph(s, sparse=True); G
    Looped multi-graph on 10 vertices
    >>> G.plot().show()    # or G.show()                                           # needs sage.plot
    

    Note that the \ character is an escape character in Python, and also a character used by graph6 strings:

    sage: G = Graph('Ihe\n@GUA')
    Traceback (most recent call last):
    ...
    RuntimeError: the string (Ihe) seems corrupt: for n = 10, the string is too short
    
    >>> from sage.all import *
    >>> G = Graph('Ihe\n@GUA')
    Traceback (most recent call last):
    ...
    RuntimeError: the string (Ihe) seems corrupt: for n = 10, the string is too short
    

    In Python, the escaped character \ is represented by \\:

    sage: G = Graph('Ihe\\n@GUA')
    sage: G.plot().show()    # or G.show()                                           # needs sage.plot
    
    >>> from sage.all import *
    >>> G = Graph('Ihe\\n@GUA')
    >>> G.plot().show()    # or G.show()                                           # needs sage.plot
    
  • adjacency matrix: In an adjacency matrix, each column and each row represent a

    vertex. If a 1 shows up in row \(i\), column \(j\), there is an edge \((i,j)\).

    sage: # needs sage.modules
    sage: M = Matrix([(0,1,0,0,1,1,0,0,0,0), (1,0,1,0,0,0,1,0,0,0),
    ....:             (0,1,0,1,0,0,0,1,0,0), (0,0,1,0,1,0,0,0,1,0),
    ....:             (1,0,0,1,0,0,0,0,0,1), (1,0,0,0,0,0,0,1,1,0), (0,1,0,0,0,0,0,0,1,1),
    ....:             (0,0,1,0,0,1,0,0,0,1), (0,0,0,1,0,1,1,0,0,0), (0,0,0,0,1,0,1,1,0,0)])
    sage: M
    [0 1 0 0 1 1 0 0 0 0]
    [1 0 1 0 0 0 1 0 0 0]
    [0 1 0 1 0 0 0 1 0 0]
    [0 0 1 0 1 0 0 0 1 0]
    [1 0 0 1 0 0 0 0 0 1]
    [1 0 0 0 0 0 0 1 1 0]
    [0 1 0 0 0 0 0 0 1 1]
    [0 0 1 0 0 1 0 0 0 1]
    [0 0 0 1 0 1 1 0 0 0]
    [0 0 0 0 1 0 1 1 0 0]
    sage: G = Graph(M); G
    Graph on 10 vertices
    sage: G.plot().show()    # or G.show()                                           # needs sage.plot
    
    >>> from sage.all import *
    >>> # needs sage.modules
    >>> M = Matrix([(Integer(0),Integer(1),Integer(0),Integer(0),Integer(1),Integer(1),Integer(0),Integer(0),Integer(0),Integer(0)), (Integer(1),Integer(0),Integer(1),Integer(0),Integer(0),Integer(0),Integer(1),Integer(0),Integer(0),Integer(0)),
    ...             (Integer(0),Integer(1),Integer(0),Integer(1),Integer(0),Integer(0),Integer(0),Integer(1),Integer(0),Integer(0)), (Integer(0),Integer(0),Integer(1),Integer(0),Integer(1),Integer(0),Integer(0),Integer(0),Integer(1),Integer(0)),
    ...             (Integer(1),Integer(0),Integer(0),Integer(1),Integer(0),Integer(0),Integer(0),Integer(0),Integer(0),Integer(1)), (Integer(1),Integer(0),Integer(0),Integer(0),Integer(0),Integer(0),Integer(0),Integer(1),Integer(1),Integer(0)), (Integer(0),Integer(1),Integer(0),Integer(0),Integer(0),Integer(0),Integer(0),Integer(0),Integer(1),Integer(1)),
    ...             (Integer(0),Integer(0),Integer(1),Integer(0),Integer(0),Integer(1),Integer(0),Integer(0),Integer(0),Integer(1)), (Integer(0),Integer(0),Integer(0),Integer(1),Integer(0),Integer(1),Integer(1),Integer(0),Integer(0),Integer(0)), (Integer(0),Integer(0),Integer(0),Integer(0),Integer(1),Integer(0),Integer(1),Integer(1),Integer(0),Integer(0))])
    >>> M
    [0 1 0 0 1 1 0 0 0 0]
    [1 0 1 0 0 0 1 0 0 0]
    [0 1 0 1 0 0 0 1 0 0]
    [0 0 1 0 1 0 0 0 1 0]
    [1 0 0 1 0 0 0 0 0 1]
    [1 0 0 0 0 0 0 1 1 0]
    [0 1 0 0 0 0 0 0 1 1]
    [0 0 1 0 0 1 0 0 0 1]
    [0 0 0 1 0 1 1 0 0 0]
    [0 0 0 0 1 0 1 1 0 0]
    >>> G = Graph(M); G
    Graph on 10 vertices
    >>> G.plot().show()    # or G.show()                                           # needs sage.plot
    
  • incidence matrix: In an incidence matrix, each row represents a vertex and

    each column represents an edge.

    sage: # needs sage.modules
    sage: M = Matrix([(-1, 0, 0, 0, 1, 0, 0, 0, 0, 0,-1, 0, 0, 0, 0),
    ....:             ( 1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 0),
    ....:             ( 0, 1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0),
    ....:             ( 0, 0, 1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0),
    ....:             ( 0, 0, 0, 1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1),
    ....:             ( 0, 0, 0, 0, 0,-1, 0, 0, 0, 1, 1, 0, 0, 0, 0),
    ....:             ( 0, 0, 0, 0, 0, 0, 0, 1,-1, 0, 0, 1, 0, 0, 0),
    ....:             ( 0, 0, 0, 0, 0, 1,-1, 0, 0, 0, 0, 0, 1, 0, 0),
    ....:             ( 0, 0, 0, 0, 0, 0, 0, 0, 1,-1, 0, 0, 0, 1, 0),
    ....:             ( 0, 0, 0, 0, 0, 0, 1,-1, 0, 0, 0, 0, 0, 0, 1)])
    sage: M
    [-1  0  0  0  1  0  0  0  0  0 -1  0  0  0  0]
    [ 1 -1  0  0  0  0  0  0  0  0  0 -1  0  0  0]
    [ 0  1 -1  0  0  0  0  0  0  0  0  0 -1  0  0]
    [ 0  0  1 -1  0  0  0  0  0  0  0  0  0 -1  0]
    [ 0  0  0  1 -1  0  0  0  0  0  0  0  0  0 -1]
    [ 0  0  0  0  0 -1  0  0  0  1  1  0  0  0  0]
    [ 0  0  0  0  0  0  0  1 -1  0  0  1  0  0  0]
    [ 0  0  0  0  0  1 -1  0  0  0  0  0  1  0  0]
    [ 0  0  0  0  0  0  0  0  1 -1  0  0  0  1  0]
    [ 0  0  0  0  0  0  1 -1  0  0  0  0  0  0  1]
    sage: G = Graph(M); G
    Graph on 10 vertices
    sage: G.plot().show()    # or G.show()                                           # needs sage.plot
    sage: DiGraph(matrix(2, [0,0,-1,1]), format='incidence_matrix')
    Traceback (most recent call last):
    ...
    ValueError: there must be two nonzero entries (-1 & 1) per column
    
    >>> from sage.all import *
    >>> # needs sage.modules
    >>> M = Matrix([(-Integer(1), Integer(0), Integer(0), Integer(0), Integer(1), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0),-Integer(1), Integer(0), Integer(0), Integer(0), Integer(0)),
    ...             ( Integer(1),-Integer(1), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0),-Integer(1), Integer(0), Integer(0), Integer(0)),
    ...             ( Integer(0), Integer(1),-Integer(1), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0),-Integer(1), Integer(0), Integer(0)),
    ...             ( Integer(0), Integer(0), Integer(1),-Integer(1), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0),-Integer(1), Integer(0)),
    ...             ( Integer(0), Integer(0), Integer(0), Integer(1),-Integer(1), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0),-Integer(1)),
    ...             ( Integer(0), Integer(0), Integer(0), Integer(0), Integer(0),-Integer(1), Integer(0), Integer(0), Integer(0), Integer(1), Integer(1), Integer(0), Integer(0), Integer(0), Integer(0)),
    ...             ( Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(1),-Integer(1), Integer(0), Integer(0), Integer(1), Integer(0), Integer(0), Integer(0)),
    ...             ( Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(1),-Integer(1), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(1), Integer(0), Integer(0)),
    ...             ( Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(1),-Integer(1), Integer(0), Integer(0), Integer(0), Integer(1), Integer(0)),
    ...             ( Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(1),-Integer(1), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(1))])
    >>> M
    [-1  0  0  0  1  0  0  0  0  0 -1  0  0  0  0]
    [ 1 -1  0  0  0  0  0  0  0  0  0 -1  0  0  0]
    [ 0  1 -1  0  0  0  0  0  0  0  0  0 -1  0  0]
    [ 0  0  1 -1  0  0  0  0  0  0  0  0  0 -1  0]
    [ 0  0  0  1 -1  0  0  0  0  0  0  0  0  0 -1]
    [ 0  0  0  0  0 -1  0  0  0  1  1  0  0  0  0]
    [ 0  0  0  0  0  0  0  1 -1  0  0  1  0  0  0]
    [ 0  0  0  0  0  1 -1  0  0  0  0  0  1  0  0]
    [ 0  0  0  0  0  0  0  0  1 -1  0  0  0  1  0]
    [ 0  0  0  0  0  0  1 -1  0  0  0  0  0  0  1]
    >>> G = Graph(M); G
    Graph on 10 vertices
    >>> G.plot().show()    # or G.show()                                           # needs sage.plot
    >>> DiGraph(matrix(Integer(2), [Integer(0),Integer(0),-Integer(1),Integer(1)]), format='incidence_matrix')
    Traceback (most recent call last):
    ...
    ValueError: there must be two nonzero entries (-1 & 1) per column
    
  • a list of edges:

    sage: g = Graph([(1, 3), (3, 8), (5, 2)]); g
    Graph on 5 vertices
    
    >>> from sage.all import *
    >>> g = Graph([(Integer(1), Integer(3)), (Integer(3), Integer(8)), (Integer(5), Integer(2))]); g
    Graph on 5 vertices
    
  • an igraph Graph:

    sage: import igraph                                 # optional - python_igraph
    sage: g = Graph(igraph.Graph([(1,3),(3,2),(0,2)]))  # optional - python_igraph
    sage: g                                             # optional - python_igraph
    Graph on 4 vertices
    
    >>> from sage.all import *
    >>> import igraph                                 # optional - python_igraph
    >>> g = Graph(igraph.Graph([(Integer(1),Integer(3)),(Integer(3),Integer(2)),(Integer(0),Integer(2))]))  # optional - python_igraph
    >>> g                                             # optional - python_igraph
    Graph on 4 vertices
    

Generators

Use graphs(n) to iterate through all non-isomorphic graphs of given size:

sage: for g in graphs(4):
....:     print(g.degree_sequence())
[0, 0, 0, 0]
[1, 1, 0, 0]
[2, 1, 1, 0]
[3, 1, 1, 1]
[1, 1, 1, 1]
[2, 2, 1, 1]
[2, 2, 2, 0]
[3, 2, 2, 1]
[2, 2, 2, 2]
[3, 3, 2, 2]
[3, 3, 3, 3]
>>> from sage.all import *
>>> for g in graphs(Integer(4)):
...     print(g.degree_sequence())
[0, 0, 0, 0]
[1, 1, 0, 0]
[2, 1, 1, 0]
[3, 1, 1, 1]
[1, 1, 1, 1]
[2, 2, 1, 1]
[2, 2, 2, 0]
[3, 2, 2, 1]
[2, 2, 2, 2]
[3, 3, 2, 2]
[3, 3, 3, 3]

Similarly graphs() will iterate through all graphs. The complete graph of 4 vertices is of course the smallest graph with chromatic number bigger than three:

sage: for g in graphs():
....:     if g.chromatic_number() > 3:
....:         break
sage: g.is_isomorphic(graphs.CompleteGraph(4))
True
>>> from sage.all import *
>>> for g in graphs():
...     if g.chromatic_number() > Integer(3):
...         break
>>> g.is_isomorphic(graphs.CompleteGraph(Integer(4)))
True

For some commonly used graphs to play with, type:

sage: graphs.[tab]          # not tested
>>> from sage.all import *
>>> graphs.[tab]          # not tested

and hit {tab}. Most of these graphs come with their own custom plot, so you can see how people usually visualize these graphs.

sage: G = graphs.PetersenGraph()
sage: G.plot().show()    # or G.show()                                              # needs sage.plot
sage: G.degree_histogram()
[0, 0, 0, 10]
sage: G.adjacency_matrix()                                                          # needs sage.modules
[0 1 0 0 1 1 0 0 0 0]
[1 0 1 0 0 0 1 0 0 0]
[0 1 0 1 0 0 0 1 0 0]
[0 0 1 0 1 0 0 0 1 0]
[1 0 0 1 0 0 0 0 0 1]
[1 0 0 0 0 0 0 1 1 0]
[0 1 0 0 0 0 0 0 1 1]
[0 0 1 0 0 1 0 0 0 1]
[0 0 0 1 0 1 1 0 0 0]
[0 0 0 0 1 0 1 1 0 0]
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> G.plot().show()    # or G.show()                                              # needs sage.plot
>>> G.degree_histogram()
[0, 0, 0, 10]
>>> G.adjacency_matrix()                                                          # needs sage.modules
[0 1 0 0 1 1 0 0 0 0]
[1 0 1 0 0 0 1 0 0 0]
[0 1 0 1 0 0 0 1 0 0]
[0 0 1 0 1 0 0 0 1 0]
[1 0 0 1 0 0 0 0 0 1]
[1 0 0 0 0 0 0 1 1 0]
[0 1 0 0 0 0 0 0 1 1]
[0 0 1 0 0 1 0 0 0 1]
[0 0 0 1 0 1 1 0 0 0]
[0 0 0 0 1 0 1 1 0 0]

sage: S = G.subgraph([0,1,2,3])
sage: S.plot().show()    # or S.show()                                              # needs sage.plot
sage: S.density()
1/2
>>> from sage.all import *
>>> S = G.subgraph([Integer(0),Integer(1),Integer(2),Integer(3)])
>>> S.plot().show()    # or S.show()                                              # needs sage.plot
>>> S.density()
1/2

sage: G = GraphQuery(display_cols=['graph6'], num_vertices=7, diameter=5)
sage: L = G.get_graphs_list()
sage: graphs_list.show_graphs(L)                                                    # needs sage.plot
>>> from sage.all import *
>>> G = GraphQuery(display_cols=['graph6'], num_vertices=Integer(7), diameter=Integer(5))
>>> L = G.get_graphs_list()
>>> graphs_list.show_graphs(L)                                                    # needs sage.plot

Labels

Each vertex can have any hashable object as a label. These are things like strings, numbers, and tuples. Each edge is given a default label of None, but if specified, edges can have any label at all. Edges between vertices \(u\) and \(v\) are represented typically as (u, v, l), where l is the label for the edge.

Note that vertex labels themselves cannot be mutable items:

sage: M = Matrix([[0,0], [0,0]])                                                    # needs sage.modules
sage: G = Graph({ 0 : { M : None } })                                               # needs sage.modules
Traceback (most recent call last):
...
TypeError: mutable matrices are unhashable
>>> from sage.all import *
>>> M = Matrix([[Integer(0),Integer(0)], [Integer(0),Integer(0)]])                                                    # needs sage.modules
>>> G = Graph({ Integer(0) : { M : None } })                                               # needs sage.modules
Traceback (most recent call last):
...
TypeError: mutable matrices are unhashable

However, if one wants to define a dictionary, with the same keys and arbitrary objects for entries, one can make that association:

sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), \
....: 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() }
sage: d[2]
Moebius-Kantor Graph: Graph on 16 vertices
sage: T = graphs.TetrahedralGraph()
sage: T.vertices(sort=True)
[0, 1, 2, 3]
sage: T.set_vertices(d)
sage: T.get_vertex(1)
Flower Snark: Graph on 20 vertices
>>> from sage.all import *
>>> d = {Integer(0) : graphs.DodecahedralGraph(), Integer(1) : graphs.FlowerSnark(), Integer(2) : graphs.MoebiusKantorGraph(), Integer(3) : graphs.PetersenGraph() }
>>> d[Integer(2)]
Moebius-Kantor Graph: Graph on 16 vertices
>>> T = graphs.TetrahedralGraph()
>>> T.vertices(sort=True)
[0, 1, 2, 3]
>>> T.set_vertices(d)
>>> T.get_vertex(Integer(1))
Flower Snark: Graph on 20 vertices

Database

There is a database available for searching for graphs that satisfy a certain set of parameters, including number of vertices and edges, density, maximum and minimum degree, diameter, radius, and connectivity. To see a list of all search parameter keywords broken down by their designated table names, type

sage: graph_db_info()
{...}
>>> from sage.all import *
>>> graph_db_info()
{...}

For more details on data types or keyword input, enter

sage: GraphQuery?    # not tested
>>> from sage.all import *
>>> GraphQuery?    # not tested

The results of a query can be viewed with the show method, or can be viewed individually by iterating through the results

sage: Q = GraphQuery(display_cols=['graph6'],num_vertices=7, diameter=5)
sage: Q.show()
Graph6
--------------------
F?`po
F?gqg
F@?]O
F@OKg
F@R@o
FA_pW
FEOhW
FGC{o
FIAHo
>>> from sage.all import *
>>> Q = GraphQuery(display_cols=['graph6'],num_vertices=Integer(7), diameter=Integer(5))
>>> Q.show()
Graph6
--------------------
F?`po
F?gqg
F@?]O
F@OKg
F@R@o
FA_pW
FEOhW
FGC{o
FIAHo

Show each graph as you iterate through the results:

sage: for g in Q:                                                                   # needs sage.plot
....:     show(g)
>>> from sage.all import *
>>> for g in Q:                                                                   # needs sage.plot
...     show(g)

Visualization

To see a graph \(G\) you are working with, there are three main options. You can view the graph in two dimensions via matplotlib with show().

sage: G = graphs.RandomGNP(15,.3)
sage: G.show()                                                                      # needs sage.plot
>>> from sage.all import *
>>> G = graphs.RandomGNP(Integer(15),RealNumber('.3'))
>>> G.show()                                                                      # needs sage.plot

And you can view it in three dimensions with show3d().

sage: G.show3d()                                                                    # needs sage.plot
>>> from sage.all import *
>>> G.show3d()                                                                    # needs sage.plot

Or it can be rendered with \(\LaTeX\). This requires the right additions to a standard \(\mbox{\rm\TeX}\) installation. Then standard Sage commands, such as view(G) will display the graph, or latex(G) will produce a string suitable for inclusion in a \(\LaTeX\) document. More details on this are at the sage.graphs.graph_latex module.

sage: from sage.graphs.graph_latex import check_tkz_graph
sage: check_tkz_graph()  # random - depends on TeX installation
sage: latex(G)
\begin{tikzpicture}
...
\end{tikzpicture}
>>> from sage.all import *
>>> from sage.graphs.graph_latex import check_tkz_graph
>>> check_tkz_graph()  # random - depends on TeX installation
>>> latex(G)
\begin{tikzpicture}
...
\end{tikzpicture}

Mutability

Graphs are mutable, and thus unusable as dictionary keys, unless data_structure="static_sparse" is used:

sage: G = graphs.PetersenGraph()
sage: {G:1}[G]
Traceback (most recent call last):
...
TypeError: This graph is mutable, and thus not hashable.
Create an immutable copy by `g.copy(immutable=True)`
sage: G_immutable = Graph(G, immutable=True)
sage: G_immutable == G
True
sage: {G_immutable:1}[G_immutable]
1
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> {G:Integer(1)}[G]
Traceback (most recent call last):
...
TypeError: This graph is mutable, and thus not hashable.
Create an immutable copy by `g.copy(immutable=True)`
>>> G_immutable = Graph(G, immutable=True)
>>> G_immutable == G
True
>>> {G_immutable:Integer(1)}[G_immutable]
1

Methods

class sage.graphs.graph.Graph(data=None, pos=None, loops=None, format=None, weighted=None, data_structure='sparse', vertex_labels=True, name=None, multiedges=None, convert_empty_dict_labels_to_None=None, sparse=True, immutable=False, hash_labels=None)[source]

Bases: GenericGraph

Undirected graph.

A graph is a set of vertices connected by edges. See the Wikipedia article Graph_(mathematics) for more information. For a collection of pre-defined graphs, see the graph_generators module.

A Graph object has many methods whose list can be obtained by typing g.<tab> (i.e. hit the Tab key) or by reading the documentation of graph, generic_graph, and digraph.

INPUT:

By default, a Graph object is simple (i.e. no loops nor multiple edges) and unweighted. This can be easily tuned with the appropriate flags (see below).

  • data – can be any of the following (see the format argument):

    1. Graph() – build a graph on 0 vertices.

    2. Graph(5) – return an edgeless graph on the 5 vertices 0,…,4.

    3. Graph([list_of_vertices, list_of_edges]) – returns a graph with given vertices/edges.

      To bypass auto-detection, prefer the more explicit Graph([V, E], format='vertices_and_edges').

    4. Graph(list_of_edges) – return a graph with a given list of edges (see documentation of add_edges()).

      To bypass auto-detection, prefer the more explicit Graph(L, format='list_of_edges').

    5. Graph({1: [2, 3, 4], 3: [4]}) – return a graph by associating to each vertex the list of its neighbors.

      To bypass auto-detection, prefer the more explicit Graph(D, format='dict_of_lists').

    6. Graph({1: {2: 'a', 3:'b'} ,3:{2:'c'}}) – return a graph by associating a list of neighbors to each vertex and providing its edge label.

      To bypass auto-detection, prefer the more explicit Graph(D, format='dict_of_dicts').

      For graphs with multiple edges, you can provide a list of labels instead, e.g.: Graph({1: {2: ['a1', 'a2'], 3:['b']} ,3:{2:['c']}}).

    7. Graph(a_symmetric_matrix) – return a graph with given (weighted) adjacency matrix (see documentation of adjacency_matrix()).

      To bypass auto-detection, prefer the more explicit Graph(M, format='adjacency_matrix'). To take weights into account, use format='weighted_adjacency_matrix' instead.

    8. Graph(a_nonsymmetric_matrix) – return a graph with given incidence matrix (see documentation of incidence_matrix()).

      To bypass auto-detection, prefer the more explicit Graph(M, format='incidence_matrix').

    9. Graph([V, f]) – return a graph from a vertex set V and a symmetric function f. The graph contains an edge \(u,v\) whenever f(u,v) is True.. Example: Graph([ [1..10], lambda x,y: abs(x-y).is_square()])

    10. Graph(':I`ES@obGkqegW~') – return a graph from a graph6 or sparse6 string (see documentation of graph6_string() or sparse6_string()).

    11. Graph(a_seidel_matrix, format='seidel_adjacency_matrix') – return a graph with a given Seidel adjacency matrix (see documentation of seidel_adjacency_matrix()).

    12. Graph(another_graph) – return a graph from a Sage (di)graph, pygraphviz graph, NetworkX graph, or igraph graph.

  • pos – a positioning dictionary (cf. documentation of layout()). For example, to draw 4 vertices on a square:

    {0: [-1,-1],
     1: [ 1,-1],
     2: [ 1, 1],
     3: [-1, 1]}
    
  • name – (must be an explicitly named parameter, i.e.,

    name='complete') gives the graph a name

  • loops – boolean (default: None); whether to allow loops (ignored if data is an instance of the Graph class)

  • multiedges – boolean (default: None); whether to allow multiple edges (ignored if data is an instance of the Graph class)

  • weighted – boolean (default: None); whether graph thinks of itself as weighted or not. See weighted().

  • format – if set to None (default), Graph tries to guess input’s format. To avoid this possibly time-consuming step, one of the following values can be specified (see description above): 'int', 'graph6', 'sparse6', 'rule', 'list_of_edges', 'dict_of_lists', 'dict_of_dicts', 'adjacency_matrix', 'weighted_adjacency_matrix', 'seidel_adjacency_matrix', 'incidence_matrix', "NX", 'igraph'.

  • sparse – boolean (default: True); sparse=True is an alias for data_structure="sparse", and sparse=False is an alias for data_structure="dense".

  • data_structure – one of the following (for more information, see overview)

    • 'dense' – selects the dense_graph backend.

    • 'sparse' – selects the sparse_graph backend.

    • 'static_sparse' – selects the static_sparse_backend (this backend is faster than the sparse backend and smaller in memory, and it is immutable, so that the resulting graphs can be used as dictionary keys).

  • immutable – boolean (default: False); whether to create a immutable graph. Note that immutable=True is actually a shortcut for data_structure='static_sparse'. Set to False by default.

  • hash_labels – boolean (default: None); whether to include edge labels during hashing. This parameter defaults to True if the graph is weighted. This parameter is ignored if the graph is mutable. Beware that trying to hash unhashable labels will raise an error.

  • vertex_labels – boolean (default: True); whether to allow any object as a vertex (slower), or only the integers \(0,...,n-1\), where \(n\) is the number of vertices.

  • convert_empty_dict_labels_to_None – this arguments sets the default edge labels used by NetworkX (empty dictionaries) to be replaced by None, the default Sage edge label. It is set to True iff a NetworkX graph is on the input.

EXAMPLES:

We illustrate the first seven input formats (the other two involve packages that are currently not standard in Sage):

  1. An integer giving the number of vertices:

    sage: g = Graph(5); g
    Graph on 5 vertices
    sage: g.vertices(sort=True)
    [0, 1, 2, 3, 4]
    sage: g.edges(sort=False)
    []
    
    >>> from sage.all import *
    >>> g = Graph(Integer(5)); g
    Graph on 5 vertices
    >>> g.vertices(sort=True)
    [0, 1, 2, 3, 4]
    >>> g.edges(sort=False)
    []
    
  2. A dictionary of dictionaries:

    sage: g = Graph({0:{1:'x',2:'z',3:'a'}, 2:{5:'out'}}); g
    Graph on 5 vertices
    
    >>> from sage.all import *
    >>> g = Graph({Integer(0):{Integer(1):'x',Integer(2):'z',Integer(3):'a'}, Integer(2):{Integer(5):'out'}}); g
    Graph on 5 vertices
    

    The labels (‘x’, ‘z’, ‘a’, ‘out’) are labels for edges. For example, ‘out’ is the label for the edge on 2 and 5. Labels can be used as weights, if all the labels share some common parent.:

    sage: a, b, c, d, e, f = sorted(SymmetricGroup(3))                              # needs sage.groups
    sage: Graph({b: {d: 'c', e: 'p'}, c: {d: 'p', e: 'c'}})                         # needs sage.groups
    Graph on 4 vertices
    
    >>> from sage.all import *
    >>> a, b, c, d, e, f = sorted(SymmetricGroup(Integer(3)))                              # needs sage.groups
    >>> Graph({b: {d: 'c', e: 'p'}, c: {d: 'p', e: 'c'}})                         # needs sage.groups
    Graph on 4 vertices
    
  3. A dictionary of lists:

    sage: g = Graph({0:[1,2,3], 2:[4]}); g
    Graph on 5 vertices
    
    >>> from sage.all import *
    >>> g = Graph({Integer(0):[Integer(1),Integer(2),Integer(3)], Integer(2):[Integer(4)]}); g
    Graph on 5 vertices
    
  4. A list of vertices and a function describing adjacencies. Note that the list of vertices and the function must be enclosed in a list (i.e., [list of vertices, function]).

    Construct the Paley graph over GF(13).:

    sage: g = Graph([GF(13), lambda i,j: i!=j and (i-j).is_square()])             # needs sage.rings.finite_rings
    sage: g.vertices(sort=True)                                                   # needs sage.rings.finite_rings
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
    sage: g.adjacency_matrix()                                                    # needs sage.modules sage.rings.finite_rings
    [0 1 0 1 1 0 0 0 0 1 1 0 1]
    [1 0 1 0 1 1 0 0 0 0 1 1 0]
    [0 1 0 1 0 1 1 0 0 0 0 1 1]
    [1 0 1 0 1 0 1 1 0 0 0 0 1]
    [1 1 0 1 0 1 0 1 1 0 0 0 0]
    [0 1 1 0 1 0 1 0 1 1 0 0 0]
    [0 0 1 1 0 1 0 1 0 1 1 0 0]
    [0 0 0 1 1 0 1 0 1 0 1 1 0]
    [0 0 0 0 1 1 0 1 0 1 0 1 1]
    [1 0 0 0 0 1 1 0 1 0 1 0 1]
    [1 1 0 0 0 0 1 1 0 1 0 1 0]
    [0 1 1 0 0 0 0 1 1 0 1 0 1]
    [1 0 1 1 0 0 0 0 1 1 0 1 0]
    
    >>> from sage.all import *
    >>> g = Graph([GF(Integer(13)), lambda i,j: i!=j and (i-j).is_square()])             # needs sage.rings.finite_rings
    >>> g.vertices(sort=True)                                                   # needs sage.rings.finite_rings
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
    >>> g.adjacency_matrix()                                                    # needs sage.modules sage.rings.finite_rings
    [0 1 0 1 1 0 0 0 0 1 1 0 1]
    [1 0 1 0 1 1 0 0 0 0 1 1 0]
    [0 1 0 1 0 1 1 0 0 0 0 1 1]
    [1 0 1 0 1 0 1 1 0 0 0 0 1]
    [1 1 0 1 0 1 0 1 1 0 0 0 0]
    [0 1 1 0 1 0 1 0 1 1 0 0 0]
    [0 0 1 1 0 1 0 1 0 1 1 0 0]
    [0 0 0 1 1 0 1 0 1 0 1 1 0]
    [0 0 0 0 1 1 0 1 0 1 0 1 1]
    [1 0 0 0 0 1 1 0 1 0 1 0 1]
    [1 1 0 0 0 0 1 1 0 1 0 1 0]
    [0 1 1 0 0 0 0 1 1 0 1 0 1]
    [1 0 1 1 0 0 0 0 1 1 0 1 0]
    

    Construct the line graph of a complete graph.:

    sage: g = graphs.CompleteGraph(4)
    sage: line_graph = Graph([g.edges(sort=True, labels=false),
    ....:                     lambda i,j: len(set(i).intersection(set(j)))>0],
    ....:                    loops=False)
    sage: line_graph.vertices(sort=True)
    [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
    sage: line_graph.adjacency_matrix()                                           # needs sage.modules
    [0 1 1 1 1 0]
    [1 0 1 1 0 1]
    [1 1 0 0 1 1]
    [1 1 0 0 1 1]
    [1 0 1 1 0 1]
    [0 1 1 1 1 0]
    
    >>> from sage.all import *
    >>> g = graphs.CompleteGraph(Integer(4))
    >>> line_graph = Graph([g.edges(sort=True, labels=false),
    ...                     lambda i,j: len(set(i).intersection(set(j)))>Integer(0)],
    ...                    loops=False)
    >>> line_graph.vertices(sort=True)
    [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
    >>> line_graph.adjacency_matrix()                                           # needs sage.modules
    [0 1 1 1 1 0]
    [1 0 1 1 0 1]
    [1 1 0 0 1 1]
    [1 1 0 0 1 1]
    [1 0 1 1 0 1]
    [0 1 1 1 1 0]
    
  5. A graph6 or sparse6 string: Sage automatically recognizes whether a string is in graph6 or sparse6 format:

    sage: s = ':I`AKGsaOs`cI]Gb~'
    sage: Graph(s, sparse=True)
    Looped multi-graph on 10 vertices
    
    >>> from sage.all import *
    >>> s = ':I`AKGsaOs`cI]Gb~'
    >>> Graph(s, sparse=True)
    Looped multi-graph on 10 vertices
    

    sage: G = Graph('G?????')
    sage: G = Graph("G'?G?C")
    Traceback (most recent call last):
    ...
    RuntimeError: the string seems corrupt: valid characters are
    ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
    sage: G = Graph('G??????')
    Traceback (most recent call last):
    ...
    RuntimeError: the string (G??????) seems corrupt: for n = 8, the string is too long
    
    >>> from sage.all import *
    >>> G = Graph('G?????')
    >>> G = Graph("G'?G?C")
    Traceback (most recent call last):
    ...
    RuntimeError: the string seems corrupt: valid characters are
    ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
    >>> G = Graph('G??????')
    Traceback (most recent call last):
    ...
    RuntimeError: the string (G??????) seems corrupt: for n = 8, the string is too long
    

    sage: G = Graph(":I'AKGsaOs`cI]Gb~")
    Traceback (most recent call last):
    ...
    RuntimeError: the string seems corrupt: valid characters are
    ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
    
    >>> from sage.all import *
    >>> G = Graph(":I'AKGsaOs`cI]Gb~")
    Traceback (most recent call last):
    ...
    RuntimeError: the string seems corrupt: valid characters are
    ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
    

    There are also list functions to take care of lists of graphs:

    sage: s = ':IgMoqoCUOqeb\n:I`AKGsaOs`cI]Gb~\n:I`EDOAEQ?PccSsge\\N\n'
    sage: graphs_list.from_sparse6(s)
    [Looped multi-graph on 10 vertices,
     Looped multi-graph on 10 vertices,
     Looped multi-graph on 10 vertices]
    
    >>> from sage.all import *
    >>> s = ':IgMoqoCUOqeb\n:I`AKGsaOs`cI]Gb~\n:I`EDOAEQ?PccSsge\\N\n'
    >>> graphs_list.from_sparse6(s)
    [Looped multi-graph on 10 vertices,
     Looped multi-graph on 10 vertices,
     Looped multi-graph on 10 vertices]
    
  6. A Sage matrix: Note: If format is not specified, then Sage assumes a symmetric square matrix is an adjacency matrix, otherwise an incidence matrix.

    • an adjacency matrix:

      sage: M = graphs.PetersenGraph().am(); M                                    # needs sage.modules
      [0 1 0 0 1 1 0 0 0 0]
      [1 0 1 0 0 0 1 0 0 0]
      [0 1 0 1 0 0 0 1 0 0]
      [0 0 1 0 1 0 0 0 1 0]
      [1 0 0 1 0 0 0 0 0 1]
      [1 0 0 0 0 0 0 1 1 0]
      [0 1 0 0 0 0 0 0 1 1]
      [0 0 1 0 0 1 0 0 0 1]
      [0 0 0 1 0 1 1 0 0 0]
      [0 0 0 0 1 0 1 1 0 0]
      sage: Graph(M)                                                              # needs sage.modules
      Graph on 10 vertices
      
      >>> from sage.all import *
      >>> M = graphs.PetersenGraph().am(); M                                    # needs sage.modules
      [0 1 0 0 1 1 0 0 0 0]
      [1 0 1 0 0 0 1 0 0 0]
      [0 1 0 1 0 0 0 1 0 0]
      [0 0 1 0 1 0 0 0 1 0]
      [1 0 0 1 0 0 0 0 0 1]
      [1 0 0 0 0 0 0 1 1 0]
      [0 1 0 0 0 0 0 0 1 1]
      [0 0 1 0 0 1 0 0 0 1]
      [0 0 0 1 0 1 1 0 0 0]
      [0 0 0 0 1 0 1 1 0 0]
      >>> Graph(M)                                                              # needs sage.modules
      Graph on 10 vertices
      

      sage: Graph(matrix([[1,2], [2,4]]), loops=True, sparse=True)                # needs sage.modules
      Looped multi-graph on 2 vertices
      
      sage: M = Matrix([[0,1,-1], [1,0,-1/2], [-1,-1/2,0]]); M                    # needs sage.modules
      [   0    1   -1]
      [   1    0 -1/2]
      [  -1 -1/2    0]
      sage: G = Graph(M, sparse=True); G                                          # needs sage.modules
      Graph on 3 vertices
      sage: G.weighted()                                                          # needs sage.modules
      True
      
      >>> from sage.all import *
      >>> Graph(matrix([[Integer(1),Integer(2)], [Integer(2),Integer(4)]]), loops=True, sparse=True)                # needs sage.modules
      Looped multi-graph on 2 vertices
      
      >>> M = Matrix([[Integer(0),Integer(1),-Integer(1)], [Integer(1),Integer(0),-Integer(1)/Integer(2)], [-Integer(1),-Integer(1)/Integer(2),Integer(0)]]); M                    # needs sage.modules
      [   0    1   -1]
      [   1    0 -1/2]
      [  -1 -1/2    0]
      >>> G = Graph(M, sparse=True); G                                          # needs sage.modules
      Graph on 3 vertices
      >>> G.weighted()                                                          # needs sage.modules
      True
      
    • an incidence matrix:

      sage: M = Matrix(6, [-1,0,0,0,1, 1,-1,0,0,0, 0,1,-1,0,0,                    # needs sage.modules
      ....:                0,0,1,-1,0, 0,0,0,1,-1, 0,0,0,0,0]); M
      [-1  0  0  0  1]
      [ 1 -1  0  0  0]
      [ 0  1 -1  0  0]
      [ 0  0  1 -1  0]
      [ 0  0  0  1 -1]
      [ 0  0  0  0  0]
      sage: Graph(M)                                                              # needs sage.modules
      Graph on 6 vertices
      
      sage: Graph(Matrix([[1],[1],[1]]))                                          # needs sage.modules
      Traceback (most recent call last):
      ...
      ValueError: there must be one or two nonzero entries per column
      in an incidence matrix, got entries [1, 1, 1] in column 0
      sage: Graph(Matrix([[1],[1],[0]]))                                          # needs sage.modules
      Graph on 3 vertices
      
      sage: M = Matrix([[0,1,-1], [1,0,-1], [-1,-1,0]]); M                        # needs sage.modules
      [ 0  1 -1]
      [ 1  0 -1]
      [-1 -1  0]
      sage: Graph(M, sparse=True)                                                 # needs sage.modules
      Graph on 3 vertices
      
      sage: M = Matrix([[0,1,1], [1,0,1], [-1,-1,0]]); M                          # needs sage.modules
      [ 0  1  1]
      [ 1  0  1]
      [-1 -1  0]
      sage: Graph(M)                                                              # needs sage.modules
      Traceback (most recent call last):
      ...
      ValueError: there must be one or two nonzero entries per column
      in an incidence matrix, got entries [1, 1] in column 2
      
      >>> from sage.all import *
      >>> M = Matrix(Integer(6), [-Integer(1),Integer(0),Integer(0),Integer(0),Integer(1), Integer(1),-Integer(1),Integer(0),Integer(0),Integer(0), Integer(0),Integer(1),-Integer(1),Integer(0),Integer(0),                    # needs sage.modules
      ...                Integer(0),Integer(0),Integer(1),-Integer(1),Integer(0), Integer(0),Integer(0),Integer(0),Integer(1),-Integer(1), Integer(0),Integer(0),Integer(0),Integer(0),Integer(0)]); M
      [-1  0  0  0  1]
      [ 1 -1  0  0  0]
      [ 0  1 -1  0  0]
      [ 0  0  1 -1  0]
      [ 0  0  0  1 -1]
      [ 0  0  0  0  0]
      >>> Graph(M)                                                              # needs sage.modules
      Graph on 6 vertices
      
      >>> Graph(Matrix([[Integer(1)],[Integer(1)],[Integer(1)]]))                                          # needs sage.modules
      Traceback (most recent call last):
      ...
      ValueError: there must be one or two nonzero entries per column
      in an incidence matrix, got entries [1, 1, 1] in column 0
      >>> Graph(Matrix([[Integer(1)],[Integer(1)],[Integer(0)]]))                                          # needs sage.modules
      Graph on 3 vertices
      
      >>> M = Matrix([[Integer(0),Integer(1),-Integer(1)], [Integer(1),Integer(0),-Integer(1)], [-Integer(1),-Integer(1),Integer(0)]]); M                        # needs sage.modules
      [ 0  1 -1]
      [ 1  0 -1]
      [-1 -1  0]
      >>> Graph(M, sparse=True)                                                 # needs sage.modules
      Graph on 3 vertices
      
      >>> M = Matrix([[Integer(0),Integer(1),Integer(1)], [Integer(1),Integer(0),Integer(1)], [-Integer(1),-Integer(1),Integer(0)]]); M                          # needs sage.modules
      [ 0  1  1]
      [ 1  0  1]
      [-1 -1  0]
      >>> Graph(M)                                                              # needs sage.modules
      Traceback (most recent call last):
      ...
      ValueError: there must be one or two nonzero entries per column
      in an incidence matrix, got entries [1, 1] in column 2
      

    Check that Issue #9714 is fixed:

    sage: # needs sage.modules
    sage: MA = Matrix([[1,2,0], [0,2,0], [0,0,1]])
    sage: GA = Graph(MA, format='adjacency_matrix')
    sage: MI = GA.incidence_matrix(oriented=False); MI
    [2 1 1 0 0 0]
    [0 1 1 2 2 0]
    [0 0 0 0 0 2]
    sage: Graph(MI).edges(sort=True, labels=None)
    [(0, 0), (0, 1), (0, 1), (1, 1), (1, 1), (2, 2)]
    
    sage: M = Matrix([[1], [-1]]); M                                            # needs sage.modules
    [ 1]
    [-1]
    sage: Graph(M).edges(sort=True)                                             # needs sage.modules
    [(0, 1, None)]
    
    >>> from sage.all import *
    >>> # needs sage.modules
    >>> MA = Matrix([[Integer(1),Integer(2),Integer(0)], [Integer(0),Integer(2),Integer(0)], [Integer(0),Integer(0),Integer(1)]])
    >>> GA = Graph(MA, format='adjacency_matrix')
    >>> MI = GA.incidence_matrix(oriented=False); MI
    [2 1 1 0 0 0]
    [0 1 1 2 2 0]
    [0 0 0 0 0 2]
    >>> Graph(MI).edges(sort=True, labels=None)
    [(0, 0), (0, 1), (0, 1), (1, 1), (1, 1), (2, 2)]
    
    >>> M = Matrix([[Integer(1)], [-Integer(1)]]); M                                            # needs sage.modules
    [ 1]
    [-1]
    >>> Graph(M).edges(sort=True)                                             # needs sage.modules
    [(0, 1, None)]
    
  7. A Seidel adjacency matrix:

    sage: from sage.combinat.matrices.hadamard_matrix import (                    # needs sage.combinat sage.modules
    ....:  regular_symmetric_hadamard_matrix_with_constant_diagonal as rshcd)
    sage: m = rshcd(16,1) - matrix.identity(16)                                   # needs sage.combinat sage.modules
    sage: Graph(m,                                                                # needs sage.combinat sage.modules
    ....:       format='seidel_adjacency_matrix').is_strongly_regular(parameters=True)
    (16, 6, 2, 2)
    
    >>> from sage.all import *
    >>> from sage.combinat.matrices.hadamard_matrix import (                    # needs sage.combinat sage.modules
    ...  regular_symmetric_hadamard_matrix_with_constant_diagonal as rshcd)
    >>> m = rshcd(Integer(16),Integer(1)) - matrix.identity(Integer(16))                                   # needs sage.combinat sage.modules
    >>> Graph(m,                                                                # needs sage.combinat sage.modules
    ...       format='seidel_adjacency_matrix').is_strongly_regular(parameters=True)
    (16, 6, 2, 2)
    
  8. List of edges, or labelled edges:

    sage: g = Graph([(1, 3), (3, 8), (5, 2)]); g
    Graph on 5 vertices
    
    sage: g = Graph([(1, 2, "Peace"), (7, -9, "and"), (77, 2, "Love")]); g
    Graph on 5 vertices
    sage: g = Graph([(0, 2, '0'), (0, 2, '1'), (3, 3, '2')],
    ....:           loops=True, multiedges=True)
    sage: g.loops()
    [(3, 3, '2')]
    
    >>> from sage.all import *
    >>> g = Graph([(Integer(1), Integer(3)), (Integer(3), Integer(8)), (Integer(5), Integer(2))]); g
    Graph on 5 vertices
    
    >>> g = Graph([(Integer(1), Integer(2), "Peace"), (Integer(7), -Integer(9), "and"), (Integer(77), Integer(2), "Love")]); g
    Graph on 5 vertices
    >>> g = Graph([(Integer(0), Integer(2), '0'), (Integer(0), Integer(2), '1'), (Integer(3), Integer(3), '2')],
    ...           loops=True, multiedges=True)
    >>> g.loops()
    [(3, 3, '2')]
    
  9. A NetworkX MultiGraph:

    sage: import networkx                                                         # needs networkx
    sage: g = networkx.MultiGraph({0:[1,2,3], 2:[4]})                             # needs networkx
    sage: Graph(g)                                                                # needs networkx
    Multi-graph on 5 vertices
    
    >>> from sage.all import *
    >>> import networkx                                                         # needs networkx
    >>> g = networkx.MultiGraph({Integer(0):[Integer(1),Integer(2),Integer(3)], Integer(2):[Integer(4)]})                             # needs networkx
    >>> Graph(g)                                                                # needs networkx
    Multi-graph on 5 vertices
    
  10. A NetworkX graph:

    sage: import networkx                                                        # needs networkx
    sage: g = networkx.Graph({0:[1,2,3], 2:[4]})                                 # needs networkx
    sage: DiGraph(g)                                                             # needs networkx
    Digraph on 5 vertices
    
    >>> from sage.all import *
    >>> import networkx                                                        # needs networkx
    >>> g = networkx.Graph({Integer(0):[Integer(1),Integer(2),Integer(3)], Integer(2):[Integer(4)]})                                 # needs networkx
    >>> DiGraph(g)                                                             # needs networkx
    Digraph on 5 vertices
    
  11. An igraph Graph (see also igraph_graph()):

    sage: import igraph                       # optional - python_igraph
    sage: g = igraph.Graph([(0, 1), (0, 2)])  # optional - python_igraph
    sage: Graph(g)                            # optional - python_igraph
    Graph on 3 vertices
    
    >>> from sage.all import *
    >>> import igraph                       # optional - python_igraph
    >>> g = igraph.Graph([(Integer(0), Integer(1)), (Integer(0), Integer(2))])  # optional - python_igraph
    >>> Graph(g)                            # optional - python_igraph
    Graph on 3 vertices
    

    If vertex_labels is True, the names of the vertices are given by the vertex attribute 'name', if available:

    sage: # optional - python_igraph
    sage: g = igraph.Graph([(0,1),(0,2)], vertex_attrs={'name':['a','b','c']})
    sage: Graph(g).vertices(sort=True)
    ['a', 'b', 'c']
    sage: g = igraph.Graph([(0,1),(0,2)], vertex_attrs={'label':['a','b','c']})
    sage: Graph(g).vertices(sort=True)
    [0, 1, 2]
    
    >>> from sage.all import *
    >>> # optional - python_igraph
    >>> g = igraph.Graph([(Integer(0),Integer(1)),(Integer(0),Integer(2))], vertex_attrs={'name':['a','b','c']})
    >>> Graph(g).vertices(sort=True)
    ['a', 'b', 'c']
    >>> g = igraph.Graph([(Integer(0),Integer(1)),(Integer(0),Integer(2))], vertex_attrs={'label':['a','b','c']})
    >>> Graph(g).vertices(sort=True)
    [0, 1, 2]
    

    If the igraph Graph has edge attributes, they are used as edge labels:

    sage: g = igraph.Graph([(0, 1), (0, 2)],                             # optional - python_igraph
    ....:                  edge_attrs={'name': ['a', 'b'], 'weight': [1, 3]})
    sage: Graph(g).edges(sort=True)                                      # optional - python_igraph
    [(0, 1, {'name': 'a', 'weight': 1}), (0, 2, {'name': 'b', 'weight': 3})]
    
    >>> from sage.all import *
    >>> g = igraph.Graph([(Integer(0), Integer(1)), (Integer(0), Integer(2))],                             # optional - python_igraph
    ...                  edge_attrs={'name': ['a', 'b'], 'weight': [Integer(1), Integer(3)]})
    >>> Graph(g).edges(sort=True)                                      # optional - python_igraph
    [(0, 1, {'name': 'a', 'weight': 1}), (0, 2, {'name': 'b', 'weight': 3})]
    

When defining an undirected graph from a function f, it is very important that f be symmetric. If it is not, anything can happen:

sage: f_sym = lambda x,y: abs(x-y) == 1
sage: f_nonsym = lambda x,y: (x-y) == 1
sage: G_sym = Graph([[4,6,1,5,3,7,2,0], f_sym])
sage: G_sym.is_isomorphic(graphs.PathGraph(8))
True
sage: G_nonsym = Graph([[4,6,1,5,3,7,2,0], f_nonsym])
sage: G_nonsym.size()
4
sage: G_nonsym.is_isomorphic(G_sym)
False
>>> from sage.all import *
>>> f_sym = lambda x,y: abs(x-y) == Integer(1)
>>> f_nonsym = lambda x,y: (x-y) == Integer(1)
>>> G_sym = Graph([[Integer(4),Integer(6),Integer(1),Integer(5),Integer(3),Integer(7),Integer(2),Integer(0)], f_sym])
>>> G_sym.is_isomorphic(graphs.PathGraph(Integer(8)))
True
>>> G_nonsym = Graph([[Integer(4),Integer(6),Integer(1),Integer(5),Integer(3),Integer(7),Integer(2),Integer(0)], f_nonsym])
>>> G_nonsym.size()
4
>>> G_nonsym.is_isomorphic(G_sym)
False

By default, graphs are mutable and can thus not be used as a dictionary key:

sage: G = graphs.PetersenGraph()
sage: {G:1}[G]
Traceback (most recent call last):
...
TypeError: This graph is mutable, and thus not hashable.
Create an immutable copy by `g.copy(immutable=True)`
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> {G:Integer(1)}[G]
Traceback (most recent call last):
...
TypeError: This graph is mutable, and thus not hashable.
Create an immutable copy by `g.copy(immutable=True)`

When providing the optional arguments data_structure="static_sparse" or immutable=True (both mean the same), then an immutable graph results:

sage: G_imm = Graph(G, immutable=True)
sage: H_imm = Graph(G, data_structure='static_sparse')
sage: G_imm == H_imm == G
True
sage: {G_imm:1}[H_imm]
1
>>> from sage.all import *
>>> G_imm = Graph(G, immutable=True)
>>> H_imm = Graph(G, data_structure='static_sparse')
>>> G_imm == H_imm == G
True
>>> {G_imm:Integer(1)}[H_imm]
1
acyclic_orientations(G)[source]

Return an iterator over all acyclic orientations of an undirected graph \(G\).

ALGORITHM:

The algorithm is based on [Sq1998]. It presents an efficient algorithm for listing the acyclic orientations of a graph. The algorithm is shown to require O(n) time per acyclic orientation generated, making it the most efficient known algorithm for generating acyclic orientations.

The function uses a recursive approach to generate acyclic orientations of the graph. It reorders the vertices and edges of the graph, creating a new graph with updated labels. Then, it iteratively generates acyclic orientations by considering subsets of edges and checking whether they form upsets in a corresponding poset.

INPUT:

  • G – an undirected graph

OUTPUT: an iterator over all acyclic orientations of the input graph

Note

The function assumes that the input graph is undirected and the edges are unlabelled.

EXAMPLES:

To count the number of acyclic orientations for a graph:

sage: g = Graph([(0, 3), (0, 4), (3, 4), (1, 3), (1, 2), (2, 3), (2, 4)])
sage: it = g.acyclic_orientations()
sage: len(list(it))
54
>>> from sage.all import *
>>> g = Graph([(Integer(0), Integer(3)), (Integer(0), Integer(4)), (Integer(3), Integer(4)), (Integer(1), Integer(3)), (Integer(1), Integer(2)), (Integer(2), Integer(3)), (Integer(2), Integer(4))])
>>> it = g.acyclic_orientations()
>>> len(list(it))
54

Test for arbitrary vertex labels:

sage: g_str = Graph([('abc', 'def'), ('ghi', 'def'), ('xyz', 'abc'),
....:                ('xyz', 'uvw'), ('uvw', 'abc'), ('uvw', 'ghi')])
sage: it = g_str.acyclic_orientations()
sage: len(list(it))
42
>>> from sage.all import *
>>> g_str = Graph([('abc', 'def'), ('ghi', 'def'), ('xyz', 'abc'),
...                ('xyz', 'uvw'), ('uvw', 'abc'), ('uvw', 'ghi')])
>>> it = g_str.acyclic_orientations()
>>> len(list(it))
42

Check that the method returns properly relabeled acyclic digraphs:

sage: g = Graph([(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)])
sage: orientations = set([frozenset(d.edges(labels=false)) for d in g.acyclic_orientations()])
sage: len(orientations)
18
sage: all(d.is_directed_acyclic() for d in g.acyclic_orientations())
True
>>> from sage.all import *
>>> g = Graph([(Integer(0), Integer(1)), (Integer(1), Integer(2)), (Integer(2), Integer(3)), (Integer(3), Integer(0)), (Integer(0), Integer(2))])
>>> orientations = set([frozenset(d.edges(labels=false)) for d in g.acyclic_orientations()])
>>> len(orientations)
18
>>> all(d.is_directed_acyclic() for d in g.acyclic_orientations())
True
all_cliques(graph, min_size=0, max_size=0)[source]

Iterator over the cliques in graph.

A clique is an induced complete subgraph. This method is an iterator over all the cliques with size in between min_size and max_size. By default, this method returns only maximum cliques. Each yielded clique is represented by a list of vertices.

Note

Currently only implemented for undirected graphs. Use to_undirected() to convert a digraph to an undirected graph.

INPUT:

  • min_size – integer (default: 0); minimum size of reported cliques. When set to 0 (default), this method searches for maximum cliques. In such case, parameter max_size must also be set to 0.

  • max_size – integer (default: 0); maximum size of reported cliques. When set to 0 (default), the maximum size of the cliques is unbounded. When min_size is set to 0, this parameter must be set to 0.

ALGORITHM:

This function is based on Cliquer [NO2003].

EXAMPLES:

sage: G = graphs.CompleteGraph(5)
sage: list(sage.graphs.cliquer.all_cliques(G))
[[0, 1, 2, 3, 4]]
sage: list(sage.graphs.cliquer.all_cliques(G, 2, 3))
[[3, 4],
 [2, 3],
 [2, 3, 4],
 [2, 4],
 [1, 2],
 [1, 2, 3],
 [1, 2, 4],
 [1, 3],
 [1, 3, 4],
 [1, 4],
 [0, 1],
 [0, 1, 2],
 [0, 1, 3],
 [0, 1, 4],
 [0, 2],
 [0, 2, 3],
 [0, 2, 4],
 [0, 3],
 [0, 3, 4],
 [0, 4]]
sage: G.delete_edge([1,3])
sage: list(sage.graphs.cliquer.all_cliques(G))
[[0, 2, 3, 4], [0, 1, 2, 4]]
>>> from sage.all import *
>>> G = graphs.CompleteGraph(Integer(5))
>>> list(sage.graphs.cliquer.all_cliques(G))
[[0, 1, 2, 3, 4]]
>>> list(sage.graphs.cliquer.all_cliques(G, Integer(2), Integer(3)))
[[3, 4],
 [2, 3],
 [2, 3, 4],
 [2, 4],
 [1, 2],
 [1, 2, 3],
 [1, 2, 4],
 [1, 3],
 [1, 3, 4],
 [1, 4],
 [0, 1],
 [0, 1, 2],
 [0, 1, 3],
 [0, 1, 4],
 [0, 2],
 [0, 2, 3],
 [0, 2, 4],
 [0, 3],
 [0, 3, 4],
 [0, 4]]
>>> G.delete_edge([Integer(1),Integer(3)])
>>> list(sage.graphs.cliquer.all_cliques(G))
[[0, 2, 3, 4], [0, 1, 2, 4]]

Todo

Use the re-entrant functionality of Cliquer [NO2003] to avoid storing all cliques.

antipodal_graph()[source]

Return the antipodal graph of self.

The antipodal graph of a graph \(G\) has the same vertex set of \(G\) and two vertices are adjacent if their distance in \(G\) is equal to the diameter of \(G\).

OUTPUT: a new graph. self is not touched

EXAMPLES:

sage: G = graphs.JohnsonGraph(10, 5)
sage: G.antipodal_graph()
Antipodal graph of Johnson graph with parameters 10,5: Graph on 252 vertices
sage: G = graphs.HammingGraph(8, 2)
sage: G.antipodal_graph()
Antipodal graph of Hamming Graph with parameters 8,2: Graph on 256 vertices
>>> from sage.all import *
>>> G = graphs.JohnsonGraph(Integer(10), Integer(5))
>>> G.antipodal_graph()
Antipodal graph of Johnson graph with parameters 10,5: Graph on 252 vertices
>>> G = graphs.HammingGraph(Integer(8), Integer(2))
>>> G.antipodal_graph()
Antipodal graph of Hamming Graph with parameters 8,2: Graph on 256 vertices

The antipodal graph of a disconnected graph is its complement:

sage: G = Graph(5)
sage: H = G.antipodal_graph()
sage: H.is_isomorphic(G.complement())
True
>>> from sage.all import *
>>> G = Graph(Integer(5))
>>> H = G.antipodal_graph()
>>> H.is_isomorphic(G.complement())
True
apex_vertices(k=None)[source]

Return the list of apex vertices.

A graph is apex if it can be made planar by the removal of a single vertex. The deleted vertex is called an apex of the graph, and a graph may have more than one apex. For instance, in the minimal nonplanar graphs \(K_5\) or \(K_{3,3}\), every vertex is an apex. The apex graphs include graphs that are themselves planar, in which case again every vertex is an apex. The null graph is also counted as an apex graph even though it has no vertex to remove. If the graph is not connected, we say that it is apex if it has at most one non planar connected component and that this component is apex. See the Wikipedia article Apex_graph for more information.

INPUT:

  • k – integer (default: None); when set to None, the method returns the list of all apex of the graph, possibly empty if the graph is not apex. When set to a positive integer, the method ends as soon as \(k\) apex vertices are found.

OUTPUT:

By default, the method returns the list of all apex of the graph. When parameter k is set to a positive integer, the returned list is bounded to \(k\) apex vertices.

EXAMPLES:

\(K_5\) and \(K_{3,3}\) are apex graphs, and each of their vertices is an apex:

sage: G = graphs.CompleteGraph(5)
sage: G.apex_vertices()
[0, 1, 2, 3, 4]
sage: G = graphs.CompleteBipartiteGraph(3,3)
sage: G.is_apex()
True
sage: G.apex_vertices()
[0, 1, 2, 3, 4, 5]
sage: G.apex_vertices(k=3)
[0, 1, 2]
>>> from sage.all import *
>>> G = graphs.CompleteGraph(Integer(5))
>>> G.apex_vertices()
[0, 1, 2, 3, 4]
>>> G = graphs.CompleteBipartiteGraph(Integer(3),Integer(3))
>>> G.is_apex()
True
>>> G.apex_vertices()
[0, 1, 2, 3, 4, 5]
>>> G.apex_vertices(k=Integer(3))
[0, 1, 2]

A \(4\\times 4\)-grid is apex and each of its vertices is an apex. When adding a universal vertex, the resulting graph is apex and the universal vertex is the unique apex vertex

sage: G = graphs.Grid2dGraph(4,4)
sage: set(G.apex_vertices()) == set(G.vertices(sort=False))
True
sage: G.add_edges([('universal',v) for v in G])
sage: G.apex_vertices()
['universal']
>>> from sage.all import *
>>> G = graphs.Grid2dGraph(Integer(4),Integer(4))
>>> set(G.apex_vertices()) == set(G.vertices(sort=False))
True
>>> G.add_edges([('universal',v) for v in G])
>>> G.apex_vertices()
['universal']

The Petersen graph is not apex:

sage: G = graphs.PetersenGraph()
sage: G.apex_vertices()
[]
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> G.apex_vertices()
[]

A graph is apex if all its connected components are apex, but at most one is not planar:

sage: M = graphs.Grid2dGraph(3,3)
sage: K5 = graphs.CompleteGraph(5)
sage: (M+K5).apex_vertices()
[9, 10, 11, 12, 13]
sage: (M+K5+K5).apex_vertices()
[]
>>> from sage.all import *
>>> M = graphs.Grid2dGraph(Integer(3),Integer(3))
>>> K5 = graphs.CompleteGraph(Integer(5))
>>> (M+K5).apex_vertices()
[9, 10, 11, 12, 13]
>>> (M+K5+K5).apex_vertices()
[]

Neighbors of an apex of degree 2 are apex:

sage: G = graphs.Grid2dGraph(5,5)
sage: v = (666, 666)
sage: G.add_path([(1, 1), v, (3, 3)])
sage: G.is_planar()
False
sage: G.degree(v)
2
sage: sorted(G.apex_vertices())
[(1, 1), (2, 2), (3, 3), (666, 666)]
>>> from sage.all import *
>>> G = graphs.Grid2dGraph(Integer(5),Integer(5))
>>> v = (Integer(666), Integer(666))
>>> G.add_path([(Integer(1), Integer(1)), v, (Integer(3), Integer(3))])
>>> G.is_planar()
False
>>> G.degree(v)
2
>>> sorted(G.apex_vertices())
[(1, 1), (2, 2), (3, 3), (666, 666)]
arboricity(certificate=False)[source]

Return the arboricity of the graph and an optional certificate.

The arboricity is the minimum number of forests that covers the graph.

See Wikipedia article Arboricity

INPUT:

  • certificate – boolean (default: False); whether to return a certificate

OUTPUT:

When certificate = True, then the function returns \((a, F)\) where \(a\) is the arboricity and \(F\) is a list of \(a\) disjoint forests that partitions the edge set of \(g\). The forests are represented as subgraphs of the original graph.

If certificate = False, the function returns just a integer indicating the arboricity.

ALGORITHM:

Represent the graph as a graphical matroid, then apply matroid sage.matroid.partition() algorithm from the matroids module.

EXAMPLES:

sage: G = graphs.PetersenGraph()
sage: a, F = G.arboricity(True)                                             # needs sage.modules
sage: a                                                                     # needs sage.modules
2
sage: all([f.is_forest() for f in F])                                       # needs sage.modules
True
sage: len(set.union(*[set(f.edges(sort=False)) for f in F])) == G.size()    # needs sage.modules
True
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> a, F = G.arboricity(True)                                             # needs sage.modules
>>> a                                                                     # needs sage.modules
2
>>> all([f.is_forest() for f in F])                                       # needs sage.modules
True
>>> len(set.union(*[set(f.edges(sort=False)) for f in F])) == G.size()    # needs sage.modules
True
atoms_and_clique_separators(G, tree=False, rooted_tree=False, separators=False)[source]

Return the atoms of the decomposition of \(G\) by clique minimal separators.

Let \(G = (V, E)\) be a graph. A set \(S \subset V\) is a clique separator if \(G[S]\) is a clique and the graph \(G \setminus S\) has at least 2 connected components. Let \(C \subset V\) be the vertices of a connected component of \(G \setminus S\). The graph \(G[C + S]\) is an atom if it has no clique separator.

This method implements the algorithm proposed in [BPS2010], that improves upon the algorithm proposed in [TY1984], for computing the atoms and the clique minimal separators of a graph. This algorithm is based on the maximum_cardinality_search_M() graph traversal and has time complexity in \(O(|V|\cdot|E|)\).

If the graph is not connected, we insert empty separators between the lists of separators of each connected components. See the examples below for more details.

INPUT:

  • G – a Sage graph

  • tree – boolean (default: False); whether to return the result as a directed tree in which internal nodes are clique separators and leaves are the atoms of the decomposition. Since a clique separator is repeated when its removal partition the graph into 3 or more connected components, vertices are labels by tuples \((i, S)\), where \(S\) is the set of vertices of the atom or the clique separator, and \(0 \leq i \leq |T|\).

  • rooted_tree – boolean (default: False); whether to return the result as a LabelledRootedTree. When tree is True, this parameter is ignored.

  • separators – boolean (default: False); whether to also return the complete list of separators considered during the execution of the algorithm. When tree or rooted_tree is True, this parameter is ignored.

OUTPUT:

  • By default, return a tuple \((A, S_c)\), where \(A\) is the list of atoms of the graph in the order of discovery, and \(S_c\) is the list of clique separators, with possible repetitions, in the order the separator has been considered. If furthermore separators is True, return a tuple \((A, S_h, S_c)\), where \(S_c\) is the list of considered separators of the graph in the order they have been considered.

  • When tree is True, format the result as a directed tree

  • When rooted_tree is True and tree is False, format the output as a LabelledRootedTree

EXAMPLES:

Example of [BPS2010]:

sage: G = Graph({'a': ['b', 'k'], 'b': ['c'], 'c': ['d', 'j', 'k'],
....:            'd': ['e', 'f', 'j', 'k'], 'e': ['g'],
....:            'f': ['g', 'j', 'k'], 'g': ['j', 'k'], 'h': ['i', 'j'],
....:            'i': ['k'], 'j': ['k']})
sage: atoms, cliques = G.atoms_and_clique_separators()
sage: sorted(sorted(a) for a in atoms)
[['a', 'b', 'c', 'k'],
 ['c', 'd', 'j', 'k'],
 ['d', 'e', 'f', 'g', 'j', 'k'],
 ['h', 'i', 'j', 'k']]
sage: sorted(sorted(c) for c in cliques)
[['c', 'k'], ['d', 'j', 'k'], ['j', 'k']]
sage: T = G.atoms_and_clique_separators(tree=True)
sage: T.is_tree()
True
sage: T.diameter() == len(atoms)
True
sage: all(u[1] in atoms for u in T if T.degree(u) == 1)
True
sage: all(u[1] in cliques for u in T if T.degree(u) != 1)
True
>>> from sage.all import *
>>> G = Graph({'a': ['b', 'k'], 'b': ['c'], 'c': ['d', 'j', 'k'],
...            'd': ['e', 'f', 'j', 'k'], 'e': ['g'],
...            'f': ['g', 'j', 'k'], 'g': ['j', 'k'], 'h': ['i', 'j'],
...            'i': ['k'], 'j': ['k']})
>>> atoms, cliques = G.atoms_and_clique_separators()
>>> sorted(sorted(a) for a in atoms)
[['a', 'b', 'c', 'k'],
 ['c', 'd', 'j', 'k'],
 ['d', 'e', 'f', 'g', 'j', 'k'],
 ['h', 'i', 'j', 'k']]
>>> sorted(sorted(c) for c in cliques)
[['c', 'k'], ['d', 'j', 'k'], ['j', 'k']]
>>> T = G.atoms_and_clique_separators(tree=True)
>>> T.is_tree()
True
>>> T.diameter() == len(atoms)
True
>>> all(u[Integer(1)] in atoms for u in T if T.degree(u) == Integer(1))
True
>>> all(u[Integer(1)] in cliques for u in T if T.degree(u) != Integer(1))
True

A graph without clique separator:

sage: G = graphs.CompleteGraph(5)
sage: G.atoms_and_clique_separators()
([{0, 1, 2, 3, 4}], [])
sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
{0, 1, 2, 3, 4}
>>> from sage.all import *
>>> G = graphs.CompleteGraph(Integer(5))
>>> G.atoms_and_clique_separators()
([{0, 1, 2, 3, 4}], [])
>>> ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
{0, 1, 2, 3, 4}

Graphs with several biconnected components:

sage: G = graphs.PathGraph(4)
sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
  ____{2}____
 /          /
{2, 3}   __{1}__
        /      /
       {1, 2} {0, 1}

sage: G = graphs.WindmillGraph(3, 4)
sage: G.atoms_and_clique_separators()
([{0, 1, 2}, {0, 3, 4}, {0, 5, 6}, {0, 8, 7}], [{0}, {0}, {0}])
sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
  ________{0}________
 /                  /
{0, 1, 2}   _______{0}______
           /               /
          {0, 3, 4}   ____{0}___
                     /         /
                    {0, 8, 7} {0, 5, 6}
>>> from sage.all import *
>>> G = graphs.PathGraph(Integer(4))
>>> ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
  ____{2}____
 /          /
{2, 3}   __{1}__
        /      /
       {1, 2} {0, 1}

>>> G = graphs.WindmillGraph(Integer(3), Integer(4))
>>> G.atoms_and_clique_separators()
([{0, 1, 2}, {0, 3, 4}, {0, 5, 6}, {0, 8, 7}], [{0}, {0}, {0}])
>>> ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
  ________{0}________
 /                  /
{0, 1, 2}   _______{0}______
           /               /
          {0, 3, 4}   ____{0}___
                     /         /
                    {0, 8, 7} {0, 5, 6}

When the removal of a clique separator results in \(k > 2\) connected components, this separator is repeated \(k - 1\) times, but the repetitions are not necessarily contiguous:

sage: G = Graph(2)
sage: for i in range(5):
....:     G.add_cycle([0, 1, G.add_vertex()])
sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
  _________{0, 1}_____
 /                   /
{0, 1, 4}   ________{0, 1}_____
           /                  /
          {0, 1, 2}   _______{0, 1}___
                     /               /
                    {0, 1, 3}   ____{0, 1}
                               /         /
                              {0, 1, 5} {0, 1, 6}

sage: G = graphs.StarGraph(3)
sage: G.subdivide_edges(G.edges(sort=False), 2)
sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
  ______{5}______
 /              /
{1, 5}   ______{7}______
        /              /
       {2, 7}   ______{9}______
               /              /
              {9, 3}   ______{6}______
                      /              /
                     {6, 7}   ______{4}_____
                             /             /
                            {4, 5}   _____{0}_____
                                    /            /
                                   {0, 6}   ____{8}____
                                           /          /
                                          {8, 9}   __{0}__
                                                  /      /
                                                 {0, 8} {0, 4}
>>> from sage.all import *
>>> G = Graph(Integer(2))
>>> for i in range(Integer(5)):
...     G.add_cycle([Integer(0), Integer(1), G.add_vertex()])
>>> ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
  _________{0, 1}_____
 /                   /
{0, 1, 4}   ________{0, 1}_____
           /                  /
          {0, 1, 2}   _______{0, 1}___
                     /               /
                    {0, 1, 3}   ____{0, 1}
                               /         /
                              {0, 1, 5} {0, 1, 6}

>>> G = graphs.StarGraph(Integer(3))
>>> G.subdivide_edges(G.edges(sort=False), Integer(2))
>>> ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
  ______{5}______
 /              /
{1, 5}   ______{7}______
        /              /
       {2, 7}   ______{9}______
               /              /
              {9, 3}   ______{6}______
                      /              /
                     {6, 7}   ______{4}_____
                             /             /
                            {4, 5}   _____{0}_____
                                    /            /
                                   {0, 6}   ____{8}____
                                           /          /
                                          {8, 9}   __{0}__
                                                  /      /
                                                 {0, 8} {0, 4}

If the graph is not connected, we insert empty separators between the lists of separators of each connected components. For instance, let \(G\) be a graph with 3 connected components. The method returns the list \(S_c = [S_0,\cdots,S_{i},\ldots, S_{j},\ldots,S_{k-1}]\) of \(k\) clique separators, where \(i\) and \(j\) are the indexes of the inserted empty separators and \(0 \leq i < j < k - 1\). The method also returns the list \(A = [A_0,\ldots,S_{k}]\) of the \(k + 1\) atoms, with \(k + 1 \geq 3\). The lists of atoms and clique separators of each of the connected components are respectively \([A_0,\ldots,A_{i}]\) and \([S_0,\ldots,S_{i-1}]\), \([A_{i+1},\ldots,A_{j}]\) and \([S_{i+1},\ldots,S_{j-1}]\), and \([A_{j+1},\ldots,A_{k}]\) and \([S_{j+1},\ldots,S_{k-1}]\). One can check that for each connected component, we get one atom more than clique separators:

sage: G = graphs.PathGraph(3) * 3
sage: A, Sc = G.atoms_and_clique_separators()
sage: A
[{1, 2}, {0, 1}, {4, 5}, {3, 4}, {8, 7}, {6, 7}]
sage: Sc
[{1}, {}, {4}, {}, {7}]
sage: i , j = [i for i, s in enumerate(Sc) if not s]
sage: i, j
(1, 3)
sage: A[:i+1], Sc[:i]
([{1, 2}, {0, 1}], [{1}])
sage: A[i+1:j+1], Sc[i+1:j]
([{4, 5}, {3, 4}], [{4}])
sage: A[j+1:], Sc[j+1:]
([{8, 7}, {6, 7}], [{7}])
sage: I = [-1, i, j, len(Sc)]
sage: for i, j in zip(I[:-1], I[1:]):
....:     print(A[i+1:j+1], Sc[i+1:j])
[{1, 2}, {0, 1}] [{1}]
[{4, 5}, {3, 4}] [{4}]
[{8, 7}, {6, 7}] [{7}]
sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
  ______{1}______
 /              /
{1, 2}   ______{}______
        /             /
       {0, 1}   _____{4}_____
               /            /
              {4, 5}   ____{}_____
                      /          /
                     {3, 4}   __{7}__
                             /      /
                            {6, 7} {8, 7}
>>> from sage.all import *
>>> G = graphs.PathGraph(Integer(3)) * Integer(3)
>>> A, Sc = G.atoms_and_clique_separators()
>>> A
[{1, 2}, {0, 1}, {4, 5}, {3, 4}, {8, 7}, {6, 7}]
>>> Sc
[{1}, {}, {4}, {}, {7}]
>>> i , j = [i for i, s in enumerate(Sc) if not s]
>>> i, j
(1, 3)
>>> A[:i+Integer(1)], Sc[:i]
([{1, 2}, {0, 1}], [{1}])
>>> A[i+Integer(1):j+Integer(1)], Sc[i+Integer(1):j]
([{4, 5}, {3, 4}], [{4}])
>>> A[j+Integer(1):], Sc[j+Integer(1):]
([{8, 7}, {6, 7}], [{7}])
>>> I = [-Integer(1), i, j, len(Sc)]
>>> for i, j in zip(I[:-Integer(1)], I[Integer(1):]):
...     print(A[i+Integer(1):j+Integer(1)], Sc[i+Integer(1):j])
[{1, 2}, {0, 1}] [{1}]
[{4, 5}, {3, 4}] [{4}]
[{8, 7}, {6, 7}] [{7}]
>>> ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
  ______{1}______
 /              /
{1, 2}   ______{}______
        /             /
       {0, 1}   _____{4}_____
               /            /
              {4, 5}   ____{}_____
                      /          /
                     {3, 4}   __{7}__
                             /      /
                            {6, 7} {8, 7}

Loops and multiple edges are ignored:

sage: G.allow_loops(True)
sage: G.add_edges([(u, u) for u in G])
sage: G.allow_multiple_edges(True)
sage: G.add_edges(G.edges(sort=False))
sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
  ______{1}______
 /              /
{1, 2}   ______{}______
        /             /
       {0, 1}   _____{4}_____
               /            /
              {4, 5}   ____{}_____
                      /          /
                     {3, 4}   __{7}__
                             /      /
                            {6, 7} {8, 7}
>>> from sage.all import *
>>> G.allow_loops(True)
>>> G.add_edges([(u, u) for u in G])
>>> G.allow_multiple_edges(True)
>>> G.add_edges(G.edges(sort=False))
>>> ascii_art(G.atoms_and_clique_separators(rooted_tree=True))
  ______{1}______
 /              /
{1, 2}   ______{}______
        /             /
       {0, 1}   _____{4}_____
               /            /
              {4, 5}   ____{}_____
                      /          /
                     {3, 4}   __{7}__
                             /      /
                            {6, 7} {8, 7}

We can check that the returned list of separators is valid:

sage: G = graphs.RandomGNP(50, .1)
sage: while not G.is_connected():
....:     G = graphs.RandomGNP(50, .1)
sage: _, separators, _ = G.atoms_and_clique_separators(separators=True)
sage: for S in separators:
....:     H = G.copy()
....:     H.delete_vertices(S)
....:     if H.is_connected():
....:         raise ValueError("something goes wrong")
>>> from sage.all import *
>>> G = graphs.RandomGNP(Integer(50), RealNumber('.1'))
>>> while not G.is_connected():
...     G = graphs.RandomGNP(Integer(50), RealNumber('.1'))
>>> _, separators, _ = G.atoms_and_clique_separators(separators=True)
>>> for S in separators:
...     H = G.copy()
...     H.delete_vertices(S)
...     if H.is_connected():
...         raise ValueError("something goes wrong")
bandwidth(G, k=None)[source]

Compute the bandwidth of an undirected graph.

For a definition of the bandwidth of a graph, see the documentation of the bandwidth module.

INPUT:

  • G – a graph

  • k – integer (default: None); set to an integer value to test whether \(bw(G)\leq k\), or to None (default) to compute \(bw(G)\)

OUTPUT:

When \(k\) is an integer value, the function returns either False or an ordering of cost \(\leq k\).

When \(k\) is equal to None, the function returns a pair (bw, ordering).

See also

sage.graphs.generic_graph.GenericGraph.adjacency_matrix() – return the adjacency matrix from an ordering of the vertices.

EXAMPLES:

sage: from sage.graphs.graph_decompositions.bandwidth import bandwidth
sage: G = graphs.PetersenGraph()
sage: bandwidth(G,3)
False
sage: bandwidth(G)
(5, [0, 4, 5, 8, 1, 9, 3, 7, 6, 2])
sage: G.adjacency_matrix(vertices=[0, 4, 5, 8, 1, 9, 3, 7, 6, 2])               # needs sage.modules
[0 1 1 0 1 0 0 0 0 0]
[1 0 0 0 0 1 1 0 0 0]
[1 0 0 1 0 0 0 1 0 0]
[0 0 1 0 0 0 1 0 1 0]
[1 0 0 0 0 0 0 0 1 1]
[0 1 0 0 0 0 0 1 1 0]
[0 1 0 1 0 0 0 0 0 1]
[0 0 1 0 0 1 0 0 0 1]
[0 0 0 1 1 1 0 0 0 0]
[0 0 0 0 1 0 1 1 0 0]
sage: G = graphs.ChvatalGraph()
sage: bandwidth(G)
(6, [0, 5, 9, 4, 10, 1, 6, 11, 3, 8, 7, 2])
sage: G.adjacency_matrix(vertices=[0, 5, 9, 4, 10, 1, 6, 11, 3, 8, 7, 2])       # needs sage.modules
[0 0 1 1 0 1 1 0 0 0 0 0]
[0 0 0 1 1 1 0 1 0 0 0 0]
[1 0 0 0 1 0 0 1 1 0 0 0]
[1 1 0 0 0 0 0 0 1 1 0 0]
[0 1 1 0 0 0 1 0 0 1 0 0]
[1 1 0 0 0 0 0 0 0 0 1 1]
[1 0 0 0 1 0 0 1 0 0 0 1]
[0 1 1 0 0 0 1 0 0 0 1 0]
[0 0 1 1 0 0 0 0 0 0 1 1]
[0 0 0 1 1 0 0 0 0 0 1 1]
[0 0 0 0 0 1 0 1 1 1 0 0]
[0 0 0 0 0 1 1 0 1 1 0 0]
>>> from sage.all import *
>>> from sage.graphs.graph_decompositions.bandwidth import bandwidth
>>> G = graphs.PetersenGraph()
>>> bandwidth(G,Integer(3))
False
>>> bandwidth(G)
(5, [0, 4, 5, 8, 1, 9, 3, 7, 6, 2])
>>> G.adjacency_matrix(vertices=[Integer(0), Integer(4), Integer(5), Integer(8), Integer(1), Integer(9), Integer(3), Integer(7), Integer(6), Integer(2)])               # needs sage.modules
[0 1 1 0 1 0 0 0 0 0]
[1 0 0 0 0 1 1 0 0 0]
[1 0 0 1 0 0 0 1 0 0]
[0 0 1 0 0 0 1 0 1 0]
[1 0 0 0 0 0 0 0 1 1]
[0 1 0 0 0 0 0 1 1 0]
[0 1 0 1 0 0 0 0 0 1]
[0 0 1 0 0 1 0 0 0 1]
[0 0 0 1 1 1 0 0 0 0]
[0 0 0 0 1 0 1 1 0 0]
>>> G = graphs.ChvatalGraph()
>>> bandwidth(G)
(6, [0, 5, 9, 4, 10, 1, 6, 11, 3, 8, 7, 2])
>>> G.adjacency_matrix(vertices=[Integer(0), Integer(5), Integer(9), Integer(4), Integer(10), Integer(1), Integer(6), Integer(11), Integer(3), Integer(8), Integer(7), Integer(2)])       # needs sage.modules
[0 0 1 1 0 1 1 0 0 0 0 0]
[0 0 0 1 1 1 0 1 0 0 0 0]
[1 0 0 0 1 0 0 1 1 0 0 0]
[1 1 0 0 0 0 0 0 1 1 0 0]
[0 1 1 0 0 0 1 0 0 1 0 0]
[1 1 0 0 0 0 0 0 0 0 1 1]
[1 0 0 0 1 0 0 1 0 0 0 1]
[0 1 1 0 0 0 1 0 0 0 1 0]
[0 0 1 1 0 0 0 0 0 0 1 1]
[0 0 0 1 1 0 0 0 0 0 1 1]
[0 0 0 0 0 1 0 1 1 1 0 0]
[0 0 0 0 0 1 1 0 1 1 0 0]
bipartite_color()[source]

Return a dictionary with vertices as the keys and the color class as the values.

Fails with an error if the graph is not bipartite.

EXAMPLES:

sage: graphs.CycleGraph(4).bipartite_color()
{0: 1, 1: 0, 2: 1, 3: 0}
sage: graphs.CycleGraph(5).bipartite_color()
Traceback (most recent call last):
...
RuntimeError: Graph is not bipartite.
>>> from sage.all import *
>>> graphs.CycleGraph(Integer(4)).bipartite_color()
{0: 1, 1: 0, 2: 1, 3: 0}
>>> graphs.CycleGraph(Integer(5)).bipartite_color()
Traceback (most recent call last):
...
RuntimeError: Graph is not bipartite.
bipartite_double(extended=False)[source]

Return the (extended) bipartite double of this graph.

The bipartite double of a graph \(G\) has vertex set \(\{ (v,0), (v,1) : v \in G\}\) and for any edge \((u, v)\) in \(G\) it has edges \(((u,0),(v,1))\) and \(((u,1),(v,0))\). Note that this is the tensor product of \(G\) with \(K_2\).

The extended bipartite double of \(G\) is the bipartite double of \(G\) after added all edges \(((v,0),(v,1))\) for all vertices \(v\).

INPUT:

  • extended – boolean (default: False); whether to return the extended bipartite double, or only the bipartite double (default)

OUTPUT: a graph; self is left untouched

EXAMPLES:

sage: G = graphs.PetersenGraph()
sage: H = G.bipartite_double()
sage: G == graphs.PetersenGraph()  # G is left invariant
True
sage: H.order() == 2 * G.order()
True
sage: H.size() == 2 * G.size()
True
sage: H.is_bipartite()
True
sage: H.bipartite_sets() == (set([(v, 0) for v in G]),
....: set([(v, 1) for v in G]))
True
sage: H.is_isomorphic(G.tensor_product(graphs.CompleteGraph(2)))
True
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> H = G.bipartite_double()
>>> G == graphs.PetersenGraph()  # G is left invariant
True
>>> H.order() == Integer(2) * G.order()
True
>>> H.size() == Integer(2) * G.size()
True
>>> H.is_bipartite()
True
>>> H.bipartite_sets() == (set([(v, Integer(0)) for v in G]),
... set([(v, Integer(1)) for v in G]))
True
>>> H.is_isomorphic(G.tensor_product(graphs.CompleteGraph(Integer(2))))
True

Behaviour with disconnected graphs:

sage: G1 = graphs.PetersenGraph()
sage: G2 = graphs.HoffmanGraph()
sage: G = G1.disjoint_union(G2)
sage: H = G.bipartite_double()
sage: H1 = G1.bipartite_double()
sage: H2 = G2.bipartite_double()
sage: H.is_isomorphic(H1.disjoint_union(H2))
True
>>> from sage.all import *
>>> G1 = graphs.PetersenGraph()
>>> G2 = graphs.HoffmanGraph()
>>> G = G1.disjoint_union(G2)
>>> H = G.bipartite_double()
>>> H1 = G1.bipartite_double()
>>> H2 = G2.bipartite_double()
>>> H.is_isomorphic(H1.disjoint_union(H2))
True

See also

Wikipedia article Bipartite_double_cover, WolframAlpha Bipartite Double, [VDKT2016] p. 20 for the extended bipartite double.

bipartite_sets()[source]

Return \((X,Y)\) where \(X\) and \(Y\) are the nodes in each bipartite set of graph \(G\).

Fails with an error if graph is not bipartite.

EXAMPLES:

sage: graphs.CycleGraph(4).bipartite_sets()
({0, 2}, {1, 3})
sage: graphs.CycleGraph(5).bipartite_sets()
Traceback (most recent call last):
...
RuntimeError: Graph is not bipartite.
>>> from sage.all import *
>>> graphs.CycleGraph(Integer(4)).bipartite_sets()
({0, 2}, {1, 3})
>>> graphs.CycleGraph(Integer(5)).bipartite_sets()
Traceback (most recent call last):
...
RuntimeError: Graph is not bipartite.
bounded_outdegree_orientation(G, bound, solver, verbose=None, integrality_tolerance=False)[source]

Return an orientation of \(G\) such that every vertex \(v\) has out-degree less than \(b(v)\).

INPUT:

  • G – an undirected graph

  • bound – maximum bound on the out-degree. Can be of three different types :

    • An integer \(k\). In this case, computes an orientation whose maximum out-degree is less than \(k\).

    • A dictionary associating to each vertex its associated maximum out-degree.

    • A function associating to each vertex its associated maximum out-degree.

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

OUTPUT:

A DiGraph representing the orientation if it exists. A ValueError exception is raised otherwise.

ALGORITHM:

The problem is solved through a maximum flow :

Given a graph \(G\), we create a DiGraph \(D\) defined on \(E(G)\cup V(G)\cup \{s,t\}\). We then link \(s\) to all of \(V(G)\) (these edges having a capacity equal to the bound associated to each element of \(V(G)\)), and all the elements of \(E(G)\) to \(t\) . We then link each \(v \in V(G)\) to each of its incident edges in \(G\). A maximum integer flow of value \(|E(G)|\) corresponds to an admissible orientation of \(G\). Otherwise, none exists.

EXAMPLES:

There is always an orientation of a graph \(G\) such that a vertex \(v\) has out-degree at most \(\lceil \frac {d(v)} 2 \rceil\):

sage: g = graphs.RandomGNP(40, .4)
sage: b = lambda v: integer_ceil(g.degree(v)/2)
sage: D = g.bounded_outdegree_orientation(b)
sage: all(D.out_degree(v) <= b(v) for v in g)
True
>>> from sage.all import *
>>> g = graphs.RandomGNP(Integer(40), RealNumber('.4'))
>>> b = lambda v: integer_ceil(g.degree(v)/Integer(2))
>>> D = g.bounded_outdegree_orientation(b)
>>> all(D.out_degree(v) <= b(v) for v in g)
True

Chvatal’s graph, being 4-regular, can be oriented in such a way that its maximum out-degree is 2:

sage: g = graphs.ChvatalGraph()
sage: D = g.bounded_outdegree_orientation(2)
sage: max(D.out_degree())
2
>>> from sage.all import *
>>> g = graphs.ChvatalGraph()
>>> D = g.bounded_outdegree_orientation(Integer(2))
>>> max(D.out_degree())
2

For any graph \(G\), it is possible to compute an orientation such that the maximum out-degree is at most the maximum average degree of \(G\) divided by 2. Anything less, though, is impossible.

sage: g = graphs.RandomGNP(40, .4) sage: mad = g.maximum_average_degree() # needs sage.numerical.mip

Hence this is possible

sage: d = g.bounded_outdegree_orientation(integer_ceil(mad/2))              # needs sage.numerical.mip
>>> from sage.all import *
>>> d = g.bounded_outdegree_orientation(integer_ceil(mad/Integer(2)))              # needs sage.numerical.mip

While this is not:

sage: try:                                                                  # needs sage.numerical.mip
....:     g.bounded_outdegree_orientation(integer_ceil(mad/2-1))
....:     print("Error")
....: except ValueError:
....:     pass
>>> from sage.all import *
>>> try:                                                                  # needs sage.numerical.mip
...     g.bounded_outdegree_orientation(integer_ceil(mad/Integer(2)-Integer(1)))
...     print("Error")
... except ValueError:
...     pass

The bounds can be specified in different ways:

sage: g = graphs.PetersenGraph()
sage: b = lambda v: integer_ceil(g.degree(v)/2)
sage: D = g.bounded_outdegree_orientation(b)
sage: b_dict = {u: b(u) for u in g}
sage: D = g.bounded_outdegree_orientation(b_dict)
sage: unique_bound = 2
sage: D = g.bounded_outdegree_orientation(unique_bound)
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> b = lambda v: integer_ceil(g.degree(v)/Integer(2))
>>> D = g.bounded_outdegree_orientation(b)
>>> b_dict = {u: b(u) for u in g}
>>> D = g.bounded_outdegree_orientation(b_dict)
>>> unique_bound = Integer(2)
>>> D = g.bounded_outdegree_orientation(unique_bound)
bridges(G, labels=True)[source]

Return an iterator over the bridges (or cut edges).

A bridge is an edge whose deletion disconnects the undirected graph. A disconnected graph has no bridge.

INPUT:

  • labels – boolean (default: True); if False, each bridge is a tuple \((u, v)\) of vertices

EXAMPLES:

sage: from sage.graphs.connectivity import bridges
sage: from sage.graphs.connectivity import is_connected
sage: g = 2 * graphs.PetersenGraph()
sage: g.add_edge(1, 10)
sage: is_connected(g)
True
sage: list(bridges(g))
[(1, 10, None)]
sage: list(g.bridges())
[(1, 10, None)]
>>> from sage.all import *
>>> from sage.graphs.connectivity import bridges
>>> from sage.graphs.connectivity import is_connected
>>> g = Integer(2) * graphs.PetersenGraph()
>>> g.add_edge(Integer(1), Integer(10))
>>> is_connected(g)
True
>>> list(bridges(g))
[(1, 10, None)]
>>> list(g.bridges())
[(1, 10, None)]

Every edge of a tree is a bridge:

sage: g = graphs.RandomTree(100)
sage: sum(1 for _ in g.bridges()) == 99
True
>>> from sage.all import *
>>> g = graphs.RandomTree(Integer(100))
>>> sum(Integer(1) for _ in g.bridges()) == Integer(99)
True
center(by_weight=False, algorithm=None, weight_function=None, check_weight=True)[source]

Return the set of vertices in the center of the graph.

The center is the set of vertices whose eccentricity is equal to the radius of the graph, i.e., achieving the minimum eccentricity.

For more information and examples on how to use input variables, see shortest_paths() and eccentricity()

INPUT:

  • by_weight – boolean (default: False); if True, edge weights are taken into account; if False, all edges have weight 1

  • algorithm – string (default: None); see method eccentricity() for the list of available algorithms

  • weight_function – function (default: None); a function that takes as input an edge (u, v, l) and outputs its weight. If not None, by_weight is automatically set to True. If None and by_weight is True, we use the edge label l as a weight, if l is not None, else 1 as a weight.

  • check_weight – boolean (default: True); if True, we check that the weight_function outputs a number for each edge

EXAMPLES:

Is Central African Republic in the center of Africa in graph theoretic sense? Yes:

sage: A = graphs.AfricaMap(continental=True)
sage: sorted(A.center())
['Cameroon', 'Central Africa']
>>> from sage.all import *
>>> A = graphs.AfricaMap(continental=True)
>>> sorted(A.center())
['Cameroon', 'Central Africa']

Some other graphs. Center can be the whole graph:

sage: G = graphs.DiamondGraph()
sage: G.center()
[1, 2]
sage: P = graphs.PetersenGraph()
sage: P.subgraph(P.center()) == P
True
sage: S = graphs.StarGraph(19)
sage: S.center()
[0]
>>> from sage.all import *
>>> G = graphs.DiamondGraph()
>>> G.center()
[1, 2]
>>> P = graphs.PetersenGraph()
>>> P.subgraph(P.center()) == P
True
>>> S = graphs.StarGraph(Integer(19))
>>> S.center()
[0]
centrality_degree(v=None)[source]

Return the degree centrality of a vertex.

The degree centrality of a vertex \(v\) is its degree, divided by \(|V(G)|-1\). For more information, see the Wikipedia article Centrality.

INPUT:

  • v – a vertex (default: None); set to None (default) to get a dictionary associating each vertex with its centrality degree

EXAMPLES:

sage: (graphs.ChvatalGraph()).centrality_degree()
{0: 4/11, 1: 4/11, 2: 4/11, 3: 4/11,  4: 4/11,  5: 4/11,
 6: 4/11, 7: 4/11, 8: 4/11, 9: 4/11, 10: 4/11, 11: 4/11}
sage: D = graphs.DiamondGraph()
sage: D.centrality_degree()
{0: 2/3, 1: 1, 2: 1, 3: 2/3}
sage: D.centrality_degree(v=1)
1
>>> from sage.all import *
>>> (graphs.ChvatalGraph()).centrality_degree()
{0: 4/11, 1: 4/11, 2: 4/11, 3: 4/11,  4: 4/11,  5: 4/11,
 6: 4/11, 7: 4/11, 8: 4/11, 9: 4/11, 10: 4/11, 11: 4/11}
>>> D = graphs.DiamondGraph()
>>> D.centrality_degree()
{0: 2/3, 1: 1, 2: 1, 3: 2/3}
>>> D.centrality_degree(v=Integer(1))
1
cheeger_constant(g)[source]

Return the cheeger constant of the graph.

The Cheeger constant of a graph \(G = (V,E)\) is the minimum of \(|\partial S| / |Vol(S)|\) where \(Vol(S)\) is the sum of degrees of element in \(S\), \(\partial S\) is the edge boundary of \(S\) (number of edges with one end in \(S\) and one end in \(V \setminus S\)) and the minimum is taken over all non-empty subsets \(S\) of vertices so that \(|Vol(S)| \leq |E|\).

See also

Alternative but similar quantities can be obtained via the methods edge_isoperimetric_number() and vertex_isoperimetric_number().

EXAMPLES:

sage: graphs.PetersenGraph().cheeger_constant()
1/3
>>> from sage.all import *
>>> graphs.PetersenGraph().cheeger_constant()
1/3

The Cheeger constant of a cycle on \(n\) vertices is \(1/\lfloor n/2 \rfloor\):

sage: [graphs.CycleGraph(k).cheeger_constant() for k in range(2,10)]
[1, 1, 1/2, 1/2, 1/3, 1/3, 1/4, 1/4]
>>> from sage.all import *
>>> [graphs.CycleGraph(k).cheeger_constant() for k in range(Integer(2),Integer(10))]
[1, 1, 1/2, 1/2, 1/3, 1/3, 1/4, 1/4]

The Cheeger constant of a complete graph on \(n\) vertices is \(\lceil n/2 \rceil / (n-1)\):

sage: [graphs.CompleteGraph(k).cheeger_constant() for k in range(2,10)]
[1, 1, 2/3, 3/4, 3/5, 2/3, 4/7, 5/8]
>>> from sage.all import *
>>> [graphs.CompleteGraph(k).cheeger_constant() for k in range(Integer(2),Integer(10))]
[1, 1, 2/3, 3/4, 3/5, 2/3, 4/7, 5/8]

For complete bipartite:

sage: [graphs.CompleteBipartiteGraph(i,j).cheeger_constant() for i in range(2,7) for j in range(2, i)]
[3/5, 1/2, 3/5, 5/9, 4/7, 5/9, 1/2, 5/9, 1/2, 5/9]
>>> from sage.all import *
>>> [graphs.CompleteBipartiteGraph(i,j).cheeger_constant() for i in range(Integer(2),Integer(7)) for j in range(Integer(2), i)]
[3/5, 1/2, 3/5, 5/9, 4/7, 5/9, 1/2, 5/9, 1/2, 5/9]

More examples:

sage: G = Graph([(0, 1), (0, 3), (0, 8), (1, 4), (1, 6), (2, 4), (2, 7), (2, 9),
....:            (3, 6), (3, 8), (4, 9), (5, 6), (5, 7), (5, 8), (7, 9)])
sage: G.cheeger_constant()
1/6

sage: G = Graph([(0, 1), (0, 2), (1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (3, 4), (3, 5)])
sage: G.cheeger_constant()
1/2

sage: Graph([[1,2,3,4],[(1,2),(3,4)]]).cheeger_constant()
0
>>> from sage.all import *
>>> G = Graph([(Integer(0), Integer(1)), (Integer(0), Integer(3)), (Integer(0), Integer(8)), (Integer(1), Integer(4)), (Integer(1), Integer(6)), (Integer(2), Integer(4)), (Integer(2), Integer(7)), (Integer(2), Integer(9)),
...            (Integer(3), Integer(6)), (Integer(3), Integer(8)), (Integer(4), Integer(9)), (Integer(5), Integer(6)), (Integer(5), Integer(7)), (Integer(5), Integer(8)), (Integer(7), Integer(9))])
>>> G.cheeger_constant()
1/6

>>> G = Graph([(Integer(0), Integer(1)), (Integer(0), Integer(2)), (Integer(1), Integer(2)), (Integer(1), Integer(3)), (Integer(1), Integer(4)), (Integer(1), Integer(5)), (Integer(2), Integer(3)), (Integer(3), Integer(4)), (Integer(3), Integer(5))])
>>> G.cheeger_constant()
1/2

>>> Graph([[Integer(1),Integer(2),Integer(3),Integer(4)],[(Integer(1),Integer(2)),(Integer(3),Integer(4))]]).cheeger_constant()
0
chromatic_index(solver, verbose=None, integrality_tolerance=0)[source]

Return the chromatic index of the graph.

The chromatic index is the minimal number of colors needed to properly color the edges of the graph.

INPUT:

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

This method is a frontend for method sage.graphs.graph_coloring.edge_coloring() that uses a mixed integer-linear programming formulation to compute the chromatic index.

EXAMPLES:

The clique \(K_n\) has chromatic index \(n\) when \(n\) is odd and \(n-1\) when \(n\) is even:

sage: graphs.CompleteGraph(4).chromatic_index()
3
sage: graphs.CompleteGraph(5).chromatic_index()
5
sage: graphs.CompleteGraph(6).chromatic_index()
5
>>> from sage.all import *
>>> graphs.CompleteGraph(Integer(4)).chromatic_index()
3
>>> graphs.CompleteGraph(Integer(5)).chromatic_index()
5
>>> graphs.CompleteGraph(Integer(6)).chromatic_index()
5

The path \(P_n\) with \(n \geq 2\) has chromatic index 2:

sage: graphs.PathGraph(5).chromatic_index()                                 # needs sage.numerical.mip
2
>>> from sage.all import *
>>> graphs.PathGraph(Integer(5)).chromatic_index()                                 # needs sage.numerical.mip
2

The windmill graph with parameters \(k,n\) has chromatic index \((k-1)n\):

sage: k,n = 3,4
sage: G = graphs.WindmillGraph(k,n)
sage: G.chromatic_index() == (k-1)*n                                        # needs sage.numerical.mip
True
>>> from sage.all import *
>>> k,n = Integer(3),Integer(4)
>>> G = graphs.WindmillGraph(k,n)
>>> G.chromatic_index() == (k-Integer(1))*n                                        # needs sage.numerical.mip
True
chromatic_number(algorithm, solver='DLX', verbose=None, integrality_tolerance=0)[source]

Return the minimal number of colors needed to color the vertices of the graph.

INPUT:

  • algorithm – string (default: 'DLX'); one of the following algorithms:

    • 'DLX' (default): the chromatic number is computed using the dancing link algorithm. It is inefficient speedwise to compute the chromatic number through the dancing link algorithm because this algorithm computes all the possible colorings to check that one exists.

    • 'CP': the chromatic number is computed using the coefficients of the chromatic polynomial. Again, this method is inefficient in terms of speed and it only useful for small graphs.

    • 'MILP': the chromatic number is computed using a mixed integer linear program. The performance of this implementation is affected by whether optional MILP solvers have been installed (see the MILP module, or Sage’s tutorial on Linear Programming).

    • 'parallel': all the above algorithms are executed in parallel and the result is returned as soon as one algorithm ends. Observe that the speed of the above algorithms depends on the size and structure of the graph.

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

See also

For more functions related to graph coloring, see the module sage.graphs.graph_coloring.

EXAMPLES:

sage: G = Graph({0: [1, 2, 3], 1: [2]})
sage: G.chromatic_number(algorithm='DLX')
3
sage: G.chromatic_number(algorithm='MILP')
3
sage: G.chromatic_number(algorithm='CP')                                    # needs sage.libs.flint
3
sage: G.chromatic_number(algorithm='parallel')
3
>>> from sage.all import *
>>> G = Graph({Integer(0): [Integer(1), Integer(2), Integer(3)], Integer(1): [Integer(2)]})
>>> G.chromatic_number(algorithm='DLX')
3
>>> G.chromatic_number(algorithm='MILP')
3
>>> G.chromatic_number(algorithm='CP')                                    # needs sage.libs.flint
3
>>> G.chromatic_number(algorithm='parallel')
3

A bipartite graph has (by definition) chromatic number 2:

sage: graphs.RandomBipartite(50,50,0.7).chromatic_number()                  # needs numpy
2
>>> from sage.all import *
>>> graphs.RandomBipartite(Integer(50),Integer(50),RealNumber('0.7')).chromatic_number()                  # needs numpy
2

A complete multipartite graph with \(k\) parts has chromatic number \(k\):

sage: all(graphs.CompleteMultipartiteGraph([5]*i).chromatic_number() == i
....:     for i in range(2, 5))
True
>>> from sage.all import *
>>> all(graphs.CompleteMultipartiteGraph([Integer(5)]*i).chromatic_number() == i
...     for i in range(Integer(2), Integer(5)))
True

The complete graph has the largest chromatic number from all the graphs of order \(n\). Namely its chromatic number is \(n\):

sage: all(graphs.CompleteGraph(i).chromatic_number() == i for i in range(10))
True
>>> from sage.all import *
>>> all(graphs.CompleteGraph(i).chromatic_number() == i for i in range(Integer(10)))
True

The Kneser graph with parameters \((n, 2)\) for \(n > 3\) has chromatic number \(n-2\):

sage: all(graphs.KneserGraph(i,2).chromatic_number() == i-2 for i in range(4,6))
True
>>> from sage.all import *
>>> all(graphs.KneserGraph(i,Integer(2)).chromatic_number() == i-Integer(2) for i in range(Integer(4),Integer(6)))
True

The Flower Snark graph has chromatic index 4 hence its line graph has chromatic number 4:

sage: graphs.FlowerSnark().line_graph().chromatic_number()
4
>>> from sage.all import *
>>> graphs.FlowerSnark().line_graph().chromatic_number()
4
chromatic_polynomial(G, return_tree_basis=False, algorithm='C', cache=None)[source]

Compute the chromatic polynomial of the graph G.

The algorithm used is a recursive one, based on the following observations of Read:

  • The chromatic polynomial of a tree on n vertices is x(x-1)^(n-1).

  • If e is an edge of G, G’ is the result of deleting the edge e, and G’’ is the result of contracting e, then the chromatic polynomial of G is equal to that of G’ minus that of G’’.

INPUT:

  • G – a Sage graph

  • return_tree_basis – boolean (default: False); not used yet

  • algorithm – string (default: 'C'); the algorithm to use among

    • 'C', an implementation in C by Robert Miller and Gordon Royle.

    • 'Python', an implementation in Python using caching to avoid recomputing the chromatic polynomial of a graph that has already been seen. This seems faster on some dense graphs.

  • cache – dictionary (default: None); this parameter is used only for algorithm 'Python'. It is a dictionary keyed by canonical labelings of graphs and used to cache the chromatic polynomials of the graphs generated by the algorithm. In other words, it avoids computing twice the chromatic polynomial of isometric graphs. One will be created automatically if not provided.

EXAMPLES:

sage: graphs.CycleGraph(4).chromatic_polynomial()
x^4 - 4*x^3 + 6*x^2 - 3*x
sage: graphs.CycleGraph(3).chromatic_polynomial()
x^3 - 3*x^2 + 2*x
sage: graphs.CubeGraph(3).chromatic_polynomial()
x^8 - 12*x^7 + 66*x^6 - 214*x^5 + 441*x^4 - 572*x^3 + 423*x^2 - 133*x
sage: graphs.PetersenGraph().chromatic_polynomial()
x^10 - 15*x^9 + 105*x^8 - 455*x^7 + 1353*x^6 - 2861*x^5 + 4275*x^4 - 4305*x^3 + 2606*x^2 - 704*x
sage: graphs.CompleteBipartiteGraph(3,3).chromatic_polynomial()
x^6 - 9*x^5 + 36*x^4 - 75*x^3 + 78*x^2 - 31*x
sage: for i in range(2,7):
....:     graphs.CompleteGraph(i).chromatic_polynomial().factor()
(x - 1) * x
(x - 2) * (x - 1) * x
(x - 3) * (x - 2) * (x - 1) * x
(x - 4) * (x - 3) * (x - 2) * (x - 1) * x
(x - 5) * (x - 4) * (x - 3) * (x - 2) * (x - 1) * x
sage: graphs.CycleGraph(5).chromatic_polynomial().factor()
(x - 2) * (x - 1) * x * (x^2 - 2*x + 2)
sage: graphs.OctahedralGraph().chromatic_polynomial().factor()
(x - 2) * (x - 1) * x * (x^3 - 9*x^2 + 29*x - 32)
sage: graphs.WheelGraph(5).chromatic_polynomial().factor()
(x - 2) * (x - 1) * x * (x^2 - 5*x + 7)
sage: graphs.WheelGraph(6).chromatic_polynomial().factor()
(x - 3) * (x - 2) * (x - 1) * x * (x^2 - 4*x + 5)
sage: C(x)=graphs.LCFGraph(24, [12,7,-7], 8).chromatic_polynomial()  # long time (6s on sage.math, 2011)
sage: C(2)  # long time
0
>>> from sage.all import *
>>> graphs.CycleGraph(Integer(4)).chromatic_polynomial()
x^4 - 4*x^3 + 6*x^2 - 3*x
>>> graphs.CycleGraph(Integer(3)).chromatic_polynomial()
x^3 - 3*x^2 + 2*x
>>> graphs.CubeGraph(Integer(3)).chromatic_polynomial()
x^8 - 12*x^7 + 66*x^6 - 214*x^5 + 441*x^4 - 572*x^3 + 423*x^2 - 133*x
>>> graphs.PetersenGraph().chromatic_polynomial()
x^10 - 15*x^9 + 105*x^8 - 455*x^7 + 1353*x^6 - 2861*x^5 + 4275*x^4 - 4305*x^3 + 2606*x^2 - 704*x
>>> graphs.CompleteBipartiteGraph(Integer(3),Integer(3)).chromatic_polynomial()
x^6 - 9*x^5 + 36*x^4 - 75*x^3 + 78*x^2 - 31*x
>>> for i in range(Integer(2),Integer(7)):
...     graphs.CompleteGraph(i).chromatic_polynomial().factor()
(x - 1) * x
(x - 2) * (x - 1) * x
(x - 3) * (x - 2) * (x - 1) * x
(x - 4) * (x - 3) * (x - 2) * (x - 1) * x
(x - 5) * (x - 4) * (x - 3) * (x - 2) * (x - 1) * x
>>> graphs.CycleGraph(Integer(5)).chromatic_polynomial().factor()
(x - 2) * (x - 1) * x * (x^2 - 2*x + 2)
>>> graphs.OctahedralGraph().chromatic_polynomial().factor()
(x - 2) * (x - 1) * x * (x^3 - 9*x^2 + 29*x - 32)
>>> graphs.WheelGraph(Integer(5)).chromatic_polynomial().factor()
(x - 2) * (x - 1) * x * (x^2 - 5*x + 7)
>>> graphs.WheelGraph(Integer(6)).chromatic_polynomial().factor()
(x - 3) * (x - 2) * (x - 1) * x * (x^2 - 4*x + 5)
>>> __tmp__=var("x"); C = symbolic_expression(graphs.LCFGraph(Integer(24), [Integer(12),Integer(7),-Integer(7)], Integer(8)).chromatic_polynomial()  ).function(x)# long time (6s on sage.math, 2011)
>>> C(Integer(2))  # long time
0

By definition, the chromatic number of a graph G is the least integer k such that the chromatic polynomial of G is strictly positive at k:

sage: G = graphs.PetersenGraph()
sage: P = G.chromatic_polynomial()
sage: min(i for i in range(11) if P(i) > 0) == G.chromatic_number()
True

sage: G = graphs.RandomGNP(10,0.7)
sage: P = G.chromatic_polynomial()
sage: min(i for i in range(11) if P(i) > 0) == G.chromatic_number()
True
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> P = G.chromatic_polynomial()
>>> min(i for i in range(Integer(11)) if P(i) > Integer(0)) == G.chromatic_number()
True

>>> G = graphs.RandomGNP(Integer(10),RealNumber('0.7'))
>>> P = G.chromatic_polynomial()
>>> min(i for i in range(Integer(11)) if P(i) > Integer(0)) == G.chromatic_number()
True

Check that algorithms 'C' and 'Python' return the same results:

sage: G = graphs.RandomGNP(8, randint(1, 9)*0.1)
sage: c = G.chromatic_polynomial(algorithm='C')
sage: p = G.chromatic_polynomial(algorithm='Python')
sage: c == p
True
>>> from sage.all import *
>>> G = graphs.RandomGNP(Integer(8), randint(Integer(1), Integer(9))*RealNumber('0.1'))
>>> c = G.chromatic_polynomial(algorithm='C')
>>> p = G.chromatic_polynomial(algorithm='Python')
>>> c == p
True
chromatic_quasisymmetric_function(t=None, R=None)[source]

Return the chromatic quasisymmetric function of self.

Let \(G\) be a graph whose vertex set is totally ordered. The chromatic quasisymmetric function \(X_G(t)\) was first described in [SW2012]. We use the equivalent definition given in [BC2018]:

\[X_G(t) = \sum_{\sigma=(\sigma_1,\ldots,\sigma_n)} t^{\operatorname{asc}(\sigma)} M_{|\sigma_1|,\ldots,|\sigma_n|},\]

where we sum over all ordered set partitions of the vertex set of \(G\) such that each block \(\sigma_i\) is an independent (i.e., stable) set of \(G\), and where \(\operatorname{asc}(\sigma)\) denotes the number of edges \(\{u, v\}\) of \(G\) such that \(u < v\) and \(v\) appears in a later part of \(\sigma\) than \(u\).

INPUT:

  • t – (optional) the parameter \(t\); uses the variable \(t\) in \(\ZZ[t]\) by default

  • R – (optional) the base ring for the quasisymmetric functions; uses the parent of \(t\) by default

EXAMPLES:

sage: # needs sage.combinat sage.modules
sage: G = Graph([[1,2,3], [[1,3], [2,3]]])
sage: G.chromatic_quasisymmetric_function()
(2*t^2+2*t+2)*M[1, 1, 1] + M[1, 2] + t^2*M[2, 1]
sage: G = graphs.PathGraph(4)
sage: XG = G.chromatic_quasisymmetric_function(); XG
(t^3+11*t^2+11*t+1)*M[1, 1, 1, 1] + (3*t^2+3*t)*M[1, 1, 2]
 + (3*t^2+3*t)*M[1, 2, 1] + (3*t^2+3*t)*M[2, 1, 1]
 + (t^2+t)*M[2, 2]
sage: XG.to_symmetric_function()
(t^3+11*t^2+11*t+1)*m[1, 1, 1, 1] + (3*t^2+3*t)*m[2, 1, 1]
 + (t^2+t)*m[2, 2]
sage: G = graphs.CompleteGraph(4)
sage: G.chromatic_quasisymmetric_function()
(t^6+3*t^5+5*t^4+6*t^3+5*t^2+3*t+1)*M[1, 1, 1, 1]
>>> from sage.all import *
>>> # needs sage.combinat sage.modules
>>> G = Graph([[Integer(1),Integer(2),Integer(3)], [[Integer(1),Integer(3)], [Integer(2),Integer(3)]]])
>>> G.chromatic_quasisymmetric_function()
(2*t^2+2*t+2)*M[1, 1, 1] + M[1, 2] + t^2*M[2, 1]
>>> G = graphs.PathGraph(Integer(4))
>>> XG = G.chromatic_quasisymmetric_function(); XG
(t^3+11*t^2+11*t+1)*M[1, 1, 1, 1] + (3*t^2+3*t)*M[1, 1, 2]
 + (3*t^2+3*t)*M[1, 2, 1] + (3*t^2+3*t)*M[2, 1, 1]
 + (t^2+t)*M[2, 2]
>>> XG.to_symmetric_function()
(t^3+11*t^2+11*t+1)*m[1, 1, 1, 1] + (3*t^2+3*t)*m[2, 1, 1]
 + (t^2+t)*m[2, 2]
>>> G = graphs.CompleteGraph(Integer(4))
>>> G.chromatic_quasisymmetric_function()
(t^6+3*t^5+5*t^4+6*t^3+5*t^2+3*t+1)*M[1, 1, 1, 1]

Not all chromatic quasisymmetric functions are symmetric:

sage: G = Graph([[1,2], [1,5], [3,4], [3,5]])
sage: G.chromatic_quasisymmetric_function().is_symmetric()                  # needs sage.combinat sage.modules
False
>>> from sage.all import *
>>> G = Graph([[Integer(1),Integer(2)], [Integer(1),Integer(5)], [Integer(3),Integer(4)], [Integer(3),Integer(5)]])
>>> G.chromatic_quasisymmetric_function().is_symmetric()                  # needs sage.combinat sage.modules
False

We check that at \(t = 1\), we recover the usual chromatic symmetric function:

sage: p = SymmetricFunctions(QQ).p()                                        # needs sage.combinat sage.modules
sage: G = graphs.CycleGraph(5)
sage: XG = G.chromatic_quasisymmetric_function(t=1); XG                     # needs sage.combinat sage.modules
120*M[1, 1, 1, 1, 1] + 30*M[1, 1, 1, 2] + 30*M[1, 1, 2, 1]
 + 30*M[1, 2, 1, 1] + 10*M[1, 2, 2] + 30*M[2, 1, 1, 1]
 + 10*M[2, 1, 2] + 10*M[2, 2, 1]
sage: p(XG.to_symmetric_function())                                         # needs sage.combinat sage.modules
p[1, 1, 1, 1, 1] - 5*p[2, 1, 1, 1] + 5*p[2, 2, 1]
 + 5*p[3, 1, 1] - 5*p[3, 2] - 5*p[4, 1] + 4*p[5]

sage: G = graphs.ClawGraph()
sage: XG = G.chromatic_quasisymmetric_function(t=1); XG                     # needs sage.combinat sage.modules
24*M[1, 1, 1, 1] + 6*M[1, 1, 2] + 6*M[1, 2, 1] + M[1, 3]
 + 6*M[2, 1, 1] + M[3, 1]
sage: p(XG.to_symmetric_function())                                         # needs sage.combinat sage.modules
p[1, 1, 1, 1] - 3*p[2, 1, 1] + 3*p[3, 1] - p[4]
>>> from sage.all import *
>>> p = SymmetricFunctions(QQ).p()                                        # needs sage.combinat sage.modules
>>> G = graphs.CycleGraph(Integer(5))
>>> XG = G.chromatic_quasisymmetric_function(t=Integer(1)); XG                     # needs sage.combinat sage.modules
120*M[1, 1, 1, 1, 1] + 30*M[1, 1, 1, 2] + 30*M[1, 1, 2, 1]
 + 30*M[1, 2, 1, 1] + 10*M[1, 2, 2] + 30*M[2, 1, 1, 1]
 + 10*M[2, 1, 2] + 10*M[2, 2, 1]
>>> p(XG.to_symmetric_function())                                         # needs sage.combinat sage.modules
p[1, 1, 1, 1, 1] - 5*p[2, 1, 1, 1] + 5*p[2, 2, 1]
 + 5*p[3, 1, 1] - 5*p[3, 2] - 5*p[4, 1] + 4*p[5]

>>> G = graphs.ClawGraph()
>>> XG = G.chromatic_quasisymmetric_function(t=Integer(1)); XG                     # needs sage.combinat sage.modules
24*M[1, 1, 1, 1] + 6*M[1, 1, 2] + 6*M[1, 2, 1] + M[1, 3]
 + 6*M[2, 1, 1] + M[3, 1]
>>> p(XG.to_symmetric_function())                                         # needs sage.combinat sage.modules
p[1, 1, 1, 1] - 3*p[2, 1, 1] + 3*p[3, 1] - p[4]
chromatic_symmetric_function(R=None)[source]

Return the chromatic symmetric function of self.

Let \(G\) be a graph. The chromatic symmetric function \(X_G\) was described in [Sta1995], specifically Theorem 2.5 states that

\[X_G = \sum_{F \subseteq E(G)} (-1)^{|F|} p_{\lambda(F)},\]

where \(\lambda(F)\) is the partition of the sizes of the connected components of the subgraph induced by the edges \(F\) and \(p_{\mu}\) is the powersum symmetric function.

INPUT:

  • R – (optional) the base ring for the symmetric functions; this uses \(\ZZ\) by default

ALGORITHM:

We traverse a binary tree whose leaves correspond to subsets of edges, and whose internal vertices at depth \(d\) correspond to a choice of whether to include the \(d\)-th edge in a given subset. The components of the induced subgraph are incrementally updated using a disjoint-set forest. If the next edge would introduce a cycle to the subset, we prune the branch as the terms produced by the two subtrees cancel in this case.

EXAMPLES:

sage: s = SymmetricFunctions(ZZ).s()                                        # needs sage.combinat sage.modules
sage: G = graphs.CycleGraph(5)
sage: XG = G.chromatic_symmetric_function(); XG                             # needs sage.combinat sage.modules
p[1, 1, 1, 1, 1] - 5*p[2, 1, 1, 1] + 5*p[2, 2, 1]
 + 5*p[3, 1, 1] - 5*p[3, 2] - 5*p[4, 1] + 4*p[5]
sage: s(XG)                                                                 # needs sage.combinat sage.modules
30*s[1, 1, 1, 1, 1] + 10*s[2, 1, 1, 1] + 10*s[2, 2, 1]
>>> from sage.all import *
>>> s = SymmetricFunctions(ZZ).s()                                        # needs sage.combinat sage.modules
>>> G = graphs.CycleGraph(Integer(5))
>>> XG = G.chromatic_symmetric_function(); XG                             # needs sage.combinat sage.modules
p[1, 1, 1, 1, 1] - 5*p[2, 1, 1, 1] + 5*p[2, 2, 1]
 + 5*p[3, 1, 1] - 5*p[3, 2] - 5*p[4, 1] + 4*p[5]
>>> s(XG)                                                                 # needs sage.combinat sage.modules
30*s[1, 1, 1, 1, 1] + 10*s[2, 1, 1, 1] + 10*s[2, 2, 1]

Not all graphs have a positive Schur expansion:

sage: G = graphs.ClawGraph()
sage: XG = G.chromatic_symmetric_function(); XG                             # needs sage.combinat sage.modules
p[1, 1, 1, 1] - 3*p[2, 1, 1] + 3*p[3, 1] - p[4]
sage: s(XG)                                                                 # needs sage.combinat sage.modules
8*s[1, 1, 1, 1] + 5*s[2, 1, 1] - s[2, 2] + s[3, 1]
>>> from sage.all import *
>>> G = graphs.ClawGraph()
>>> XG = G.chromatic_symmetric_function(); XG                             # needs sage.combinat sage.modules
p[1, 1, 1, 1] - 3*p[2, 1, 1] + 3*p[3, 1] - p[4]
>>> s(XG)                                                                 # needs sage.combinat sage.modules
8*s[1, 1, 1, 1] + 5*s[2, 1, 1] - s[2, 2] + s[3, 1]

We show that given a triangle \(\{e_1, e_2, e_3\}\), we have \(X_G = X_{G - e_1} + X_{G - e_2} - X_{G - e_1 - e_2}\):

sage: # needs sage.combinat sage.modules
sage: G = Graph([[1,2],[1,3],[2,3]])
sage: XG = G.chromatic_symmetric_function()
sage: G1 = copy(G)
sage: G1.delete_edge([1,2])
sage: XG1 = G1.chromatic_symmetric_function()
sage: G2 = copy(G)
sage: G2.delete_edge([1,3])
sage: XG2 = G2.chromatic_symmetric_function()
sage: G3 = copy(G1)
sage: G3.delete_edge([1,3])
sage: XG3 = G3.chromatic_symmetric_function()
sage: XG == XG1 + XG2 - XG3
True
>>> from sage.all import *
>>> # needs sage.combinat sage.modules
>>> G = Graph([[Integer(1),Integer(2)],[Integer(1),Integer(3)],[Integer(2),Integer(3)]])
>>> XG = G.chromatic_symmetric_function()
>>> G1 = copy(G)
>>> G1.delete_edge([Integer(1),Integer(2)])
>>> XG1 = G1.chromatic_symmetric_function()
>>> G2 = copy(G)
>>> G2.delete_edge([Integer(1),Integer(3)])
>>> XG2 = G2.chromatic_symmetric_function()
>>> G3 = copy(G1)
>>> G3.delete_edge([Integer(1),Integer(3)])
>>> XG3 = G3.chromatic_symmetric_function()
>>> XG == XG1 + XG2 - XG3
True
cleave(G, cut_vertices=None, virtual_edges=True, solver=None, verbose=0, integrality_tolerance=0.001)[source]

Return the connected subgraphs separated by the input vertex cut.

Given a connected (multi)graph \(G\) and a vertex cut \(X\), this method computes the list of subgraphs of \(G\) induced by each connected component \(c\) of \(G\setminus X\) plus \(X\), i.e., \(G[c\cup X]\).

INPUT:

  • G – a Graph

  • cut_vertices – iterable container of vertices (default: None); a set of vertices representing a vertex cut of G. If no vertex cut is given, the method will compute one via a call to vertex_connectivity().

  • virtual_edges – boolean (default: True); whether to add virtual edges to the sides of the cut or not. A virtual edge is an edge between a pair of vertices of the cut that are not connected by an edge in G.

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

OUTPUT: a triple \((S, C, f)\), where

  • \(S\) is a list of the graphs that are sides of the vertex cut.

  • \(C\) is the graph of the cocycles. For each pair of vertices of the cut, if there exists an edge between them, \(C\) has one copy of each edge connecting them in G per sides of the cut plus one extra copy. Furthermore, when virtual_edges == True, if a pair of vertices of the cut is not connected by an edge in G, then it has one virtual edge between them per sides of the cut.

  • \(f\) is the complement of the subgraph of G induced by the vertex cut. Hence, its vertex set is the vertex cut, and its edge set is the set of virtual edges (i.e., edges between pairs of vertices of the cut that are not connected by an edge in G). When virtual_edges == False, the edge set is empty.

EXAMPLES:

If there is an edge between cut vertices:

sage: from sage.graphs.connectivity import cleave
sage: G = Graph(2)
sage: for _ in range(3):
....:     G.add_clique([0, 1, G.add_vertex(), G.add_vertex()])
sage: S1,C1,f1 = cleave(G, cut_vertices=[0, 1])
sage: [g.order() for g in S1]
[4, 4, 4]
sage: C1.order(), C1.size()
(2, 4)
sage: f1.vertices(sort=True), f1.edges(sort=True)
([0, 1], [])
>>> from sage.all import *
>>> from sage.graphs.connectivity import cleave
>>> G = Graph(Integer(2))
>>> for _ in range(Integer(3)):
...     G.add_clique([Integer(0), Integer(1), G.add_vertex(), G.add_vertex()])
>>> S1,C1,f1 = cleave(G, cut_vertices=[Integer(0), Integer(1)])
>>> [g.order() for g in S1]
[4, 4, 4]
>>> C1.order(), C1.size()
(2, 4)
>>> f1.vertices(sort=True), f1.edges(sort=True)
([0, 1], [])

If virtual_edges == False and there is an edge between cut vertices:

sage: G.subgraph([0, 1]).complement() == Graph([[0, 1], []])
True
sage: S2,C2,f2 = cleave(G, cut_vertices=[0, 1], virtual_edges=False)
sage: (S1 == S2, C1 == C2, f1 == f2)
(True, True, True)
>>> from sage.all import *
>>> G.subgraph([Integer(0), Integer(1)]).complement() == Graph([[Integer(0), Integer(1)], []])
True
>>> S2,C2,f2 = cleave(G, cut_vertices=[Integer(0), Integer(1)], virtual_edges=False)
>>> (S1 == S2, C1 == C2, f1 == f2)
(True, True, True)

If cut vertices doesn’t have edge between them:

sage: G.delete_edge(0, 1)
sage: S1,C1,f1 = cleave(G, cut_vertices=[0, 1])
sage: [g.order() for g in S1]
[4, 4, 4]
sage: C1.order(), C1.size()
(2, 3)
sage: f1.vertices(sort=True), f1.edges(sort=True)
([0, 1], [(0, 1, None)])
>>> from sage.all import *
>>> G.delete_edge(Integer(0), Integer(1))
>>> S1,C1,f1 = cleave(G, cut_vertices=[Integer(0), Integer(1)])
>>> [g.order() for g in S1]
[4, 4, 4]
>>> C1.order(), C1.size()
(2, 3)
>>> f1.vertices(sort=True), f1.edges(sort=True)
([0, 1], [(0, 1, None)])

If virtual_edges == False and the cut vertices are not connected by an edge:

sage: G.subgraph([0, 1]).complement() == Graph([[0, 1], []])
False
sage: S2,C2,f2 = cleave(G, cut_vertices=[0, 1], virtual_edges=False)
sage: [g.order() for g in S2]
[4, 4, 4]
sage: C2.order(), C2.size()
(2, 0)
sage: f2.vertices(sort=True), f2.edges(sort=True)
([0, 1], [])
sage: (S1 == S2, C1 == C2, f1 == f2)
(False, False, False)
>>> from sage.all import *
>>> G.subgraph([Integer(0), Integer(1)]).complement() == Graph([[Integer(0), Integer(1)], []])
False
>>> S2,C2,f2 = cleave(G, cut_vertices=[Integer(0), Integer(1)], virtual_edges=False)
>>> [g.order() for g in S2]
[4, 4, 4]
>>> C2.order(), C2.size()
(2, 0)
>>> f2.vertices(sort=True), f2.edges(sort=True)
([0, 1], [])
>>> (S1 == S2, C1 == C2, f1 == f2)
(False, False, False)

If \(G\) is a biconnected multigraph:

sage: G = graphs.CompleteBipartiteGraph(2, 3)
sage: G.add_edge(2, 3)
sage: G.allow_multiple_edges(True)
sage: G.add_edges(G.edge_iterator())
sage: G.add_edges([(0, 1), (0, 1), (0, 1)])
sage: S,C,f = cleave(G, cut_vertices=[0, 1])
sage: for g in S:
....:     print(g.edges(sort=True, labels=0))
[(0, 1), (0, 1), (0, 1), (0, 2), (0, 2), (0, 3), (0, 3), (1, 2), (1, 2), (1, 3), (1, 3), (2, 3), (2, 3)]
[(0, 1), (0, 1), (0, 1), (0, 4), (0, 4), (1, 4), (1, 4)]
>>> from sage.all import *
>>> G = graphs.CompleteBipartiteGraph(Integer(2), Integer(3))
>>> G.add_edge(Integer(2), Integer(3))
>>> G.allow_multiple_edges(True)
>>> G.add_edges(G.edge_iterator())
>>> G.add_edges([(Integer(0), Integer(1)), (Integer(0), Integer(1)), (Integer(0), Integer(1))])
>>> S,C,f = cleave(G, cut_vertices=[Integer(0), Integer(1)])
>>> for g in S:
...     print(g.edges(sort=True, labels=Integer(0)))
[(0, 1), (0, 1), (0, 1), (0, 2), (0, 2), (0, 3), (0, 3), (1, 2), (1, 2), (1, 3), (1, 3), (2, 3), (2, 3)]
[(0, 1), (0, 1), (0, 1), (0, 4), (0, 4), (1, 4), (1, 4)]
clique_complex()[source]

Return the clique complex of self.

This is the largest simplicial complex on the vertices of self whose 1-skeleton is self.

This is only makes sense for undirected simple graphs.

EXAMPLES:

sage: g = Graph({0:[1,2],1:[2],4:[]})
sage: g.clique_complex()
Simplicial complex with vertex set (0, 1, 2, 4) and facets {(4,), (0, 1, 2)}

sage: h = Graph({0:[1,2,3,4],1:[2,3,4],2:[3]})
sage: x = h.clique_complex()
sage: x
Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 4), (0, 1, 2, 3)}
sage: i = x.graph()
sage: i==h
True
sage: x==i.clique_complex()
True
>>> from sage.all import *
>>> g = Graph({Integer(0):[Integer(1),Integer(2)],Integer(1):[Integer(2)],Integer(4):[]})
>>> g.clique_complex()
Simplicial complex with vertex set (0, 1, 2, 4) and facets {(4,), (0, 1, 2)}

>>> h = Graph({Integer(0):[Integer(1),Integer(2),Integer(3),Integer(4)],Integer(1):[Integer(2),Integer(3),Integer(4)],Integer(2):[Integer(3)]})
>>> x = h.clique_complex()
>>> x
Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 4), (0, 1, 2, 3)}
>>> i = x.graph()
>>> i==h
True
>>> x==i.clique_complex()
True
clique_maximum(algorithm, solver='Cliquer', verbose=None, integrality_tolerance=0)[source]

Return the vertex set of a maximal order complete subgraph.

INPUT:

  • algorithm – the algorithm to be used :

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

Parameters solver and verbose are used only when algorithm="MILP".

Note

Currently only implemented for undirected graphs. Use to_undirected to convert a digraph to an undirected graph.

ALGORITHM:

This function is based on Cliquer [NO2003].

EXAMPLES:

Using Cliquer (default):

sage: C = graphs.PetersenGraph()
sage: C.clique_maximum()
[7, 9]
sage: C = Graph('DJ{')
sage: C.clique_maximum()
[1, 2, 3, 4]
>>> from sage.all import *
>>> C = graphs.PetersenGraph()
>>> C.clique_maximum()
[7, 9]
>>> C = Graph('DJ{')
>>> C.clique_maximum()
[1, 2, 3, 4]

Through a Linear Program:

sage: len(C.clique_maximum(algorithm='MILP'))
4
>>> from sage.all import *
>>> len(C.clique_maximum(algorithm='MILP'))
4
clique_number(algorithm, cliques='Cliquer', solver=None, verbose=None, integrality_tolerance=0)[source]

Return the order of the largest clique of the graph.

This is also called as the clique number.

Note

Currently only implemented for undirected graphs. Use to_undirected to convert a digraph to an undirected graph.

INPUT:

  • algorithm – the algorithm to be used :

    • If algorithm = "Cliquer", wraps the C program Cliquer [NO2003].

    • If algorithm = "networkx", uses the NetworkX’s implementation of the Bron and Kerbosch Algorithm [BK1973].

    • If algorithm = "MILP", the problem is solved through a Mixed Integer Linear Program.

      (see MixedIntegerLinearProgram)

    • If algorithm = "mcqd", uses the MCQD solver (http://insilab.org/maxclique/). Note that the MCQD package must be installed.

  • cliques – an optional list of cliques that can be input if already computed. Ignored unless algorithm=="networkx".

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

ALGORITHM:

This function is based on Cliquer [NO2003] and [BK1973].

EXAMPLES:

sage: C = Graph('DJ{')
sage: C.clique_number()
4
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
sage: G.show(figsize=[2,2])                                                 # needs sage.plot
sage: G.clique_number()
3
>>> from sage.all import *
>>> C = Graph('DJ{')
>>> C.clique_number()
4
>>> G = Graph({Integer(0):[Integer(1),Integer(2),Integer(3)], Integer(1):[Integer(2)], Integer(3):[Integer(0),Integer(1)]})
>>> G.show(figsize=[Integer(2),Integer(2)])                                                 # needs sage.plot
>>> G.clique_number()
3

By definition the clique number of a complete graph is its order:

sage: all(graphs.CompleteGraph(i).clique_number() == i for i in range(1,15))
True
>>> from sage.all import *
>>> all(graphs.CompleteGraph(i).clique_number() == i for i in range(Integer(1),Integer(15)))
True

A non-empty graph without edges has a clique number of 1:

sage: all((i*graphs.CompleteGraph(1)).clique_number() == 1
....:     for i in range(1,15))
True
>>> from sage.all import *
>>> all((i*graphs.CompleteGraph(Integer(1))).clique_number() == Integer(1)
...     for i in range(Integer(1),Integer(15)))
True

A complete multipartite graph with k parts has clique number k:

sage: all((i*graphs.CompleteMultipartiteGraph(i*[5])).clique_number() == i
....:     for i in range(1,6))
True
>>> from sage.all import *
>>> all((i*graphs.CompleteMultipartiteGraph(i*[Integer(5)])).clique_number() == i
...     for i in range(Integer(1),Integer(6)))
True
clique_polynomial(t=None)[source]

Return the clique polynomial of self.

This is the polynomial where the coefficient of \(t^n\) is the number of cliques in the graph with \(n\) vertices. The constant term of the clique polynomial is always taken to be one.

EXAMPLES:

sage: g = Graph()
sage: g.clique_polynomial()
1
sage: g = Graph({0:[1]})
sage: g.clique_polynomial()
t^2 + 2*t + 1
sage: g = graphs.CycleGraph(4)
sage: g.clique_polynomial()
4*t^2 + 4*t + 1
>>> from sage.all import *
>>> g = Graph()
>>> g.clique_polynomial()
1
>>> g = Graph({Integer(0):[Integer(1)]})
>>> g.clique_polynomial()
t^2 + 2*t + 1
>>> g = graphs.CycleGraph(Integer(4))
>>> g.clique_polynomial()
4*t^2 + 4*t + 1
cliques_containing_vertex(vertices=None, cliques=None)[source]

Return the cliques containing each vertex, represented as a dictionary of lists of lists, keyed by vertex.

Returns a single list if only one input vertex.

Note

Currently only implemented for undirected graphs. Use to_undirected to convert a digraph to an undirected graph.

INPUT:

  • vertices – the vertices to inspect (default: entire graph)

  • cliques – list of cliques (if already computed)

EXAMPLES:

sage: # needs networkx
sage: C = Graph('DJ{')
sage: C.cliques_containing_vertex()
{0: [[0, 4]],
 1: [[1, 2, 3, 4]],
 2: [[1, 2, 3, 4]],
 3: [[1, 2, 3, 4]],
 4: [[0, 4], [1, 2, 3, 4]]}
sage: C.cliques_containing_vertex(4)
[[0, 4], [1, 2, 3, 4]]
sage: C.cliques_containing_vertex([0, 1])
{0: [[0, 4]], 1: [[1, 2, 3, 4]]}
sage: E = C.cliques_maximal(); E
[[0, 4], [1, 2, 3, 4]]
sage: C.cliques_containing_vertex(cliques=E)
{0: [[0, 4]],
 1: [[1, 2, 3, 4]],
 2: [[1, 2, 3, 4]],
 3: [[1, 2, 3, 4]],
 4: [[0, 4], [1, 2, 3, 4]]}

sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
sage: G.show(figsize=[2,2])                                                 # needs sage.plot
sage: G.cliques_containing_vertex()                                         # needs networkx
{0: [[0, 1, 2], [0, 1, 3]],
 1: [[0, 1, 2], [0, 1, 3]],
 2: [[0, 1, 2]],
 3: [[0, 1, 3]]}
>>> from sage.all import *
>>> # needs networkx
>>> C = Graph('DJ{')
>>> C.cliques_containing_vertex()
{0: [[0, 4]],
 1: [[1, 2, 3, 4]],
 2: [[1, 2, 3, 4]],
 3: [[1, 2, 3, 4]],
 4: [[0, 4], [1, 2, 3, 4]]}
>>> C.cliques_containing_vertex(Integer(4))
[[0, 4], [1, 2, 3, 4]]
>>> C.cliques_containing_vertex([Integer(0), Integer(1)])
{0: [[0, 4]], 1: [[1, 2, 3, 4]]}
>>> E = C.cliques_maximal(); E
[[0, 4], [1, 2, 3, 4]]
>>> C.cliques_containing_vertex(cliques=E)
{0: [[0, 4]],
 1: [[1, 2, 3, 4]],
 2: [[1, 2, 3, 4]],
 3: [[1, 2, 3, 4]],
 4: [[0, 4], [1, 2, 3, 4]]}

>>> G = Graph({Integer(0):[Integer(1),Integer(2),Integer(3)], Integer(1):[Integer(2)], Integer(3):[Integer(0),Integer(1)]})
>>> G.show(figsize=[Integer(2),Integer(2)])                                                 # needs sage.plot
>>> G.cliques_containing_vertex()                                         # needs networkx
{0: [[0, 1, 2], [0, 1, 3]],
 1: [[0, 1, 2], [0, 1, 3]],
 2: [[0, 1, 2]],
 3: [[0, 1, 3]]}

Since each clique of a 2 dimensional grid corresponds to an edge, the number of cliques in which a vertex is involved equals its degree:

sage: # needs networkx
sage: F = graphs.Grid2dGraph(2,3)
sage: d = F.cliques_containing_vertex()
sage: all(F.degree(u) == len(cliques) for u,cliques in d.items())
True
sage: d = F.cliques_containing_vertex(vertices=[(0, 1)])
sage: list(d)
[(0, 1)]
sage: sorted(sorted(x for x in L) for L in d[(0, 1)])
[[(0, 0), (0, 1)], [(0, 1), (0, 2)], [(0, 1), (1, 1)]]
>>> from sage.all import *
>>> # needs networkx
>>> F = graphs.Grid2dGraph(Integer(2),Integer(3))
>>> d = F.cliques_containing_vertex()
>>> all(F.degree(u) == len(cliques) for u,cliques in d.items())
True
>>> d = F.cliques_containing_vertex(vertices=[(Integer(0), Integer(1))])
>>> list(d)
[(0, 1)]
>>> sorted(sorted(x for x in L) for L in d[(Integer(0), Integer(1))])
[[(0, 0), (0, 1)], [(0, 1), (0, 2)], [(0, 1), (1, 1)]]
cliques_get_clique_bipartite(**kwds)[source]

Return the vertex-clique bipartite graph of self.

In the returned bipartite graph, the left vertices are the vertices of self and the right vertices represent the maximal cliques of self. There is an edge from vertex \(v\) to clique \(C\) in the bipartite graph if and only if \(v\) belongs to \(C\).

Note

Currently only implemented for undirected graphs. Use to_undirected to convert a digraph to an undirected graph.

EXAMPLES:

sage: CBG = graphs.ChvatalGraph().cliques_get_clique_bipartite(); CBG
Bipartite graph on 36 vertices
sage: CBG.show(figsize=[2,2], vertex_size=20, vertex_labels=False)          # needs sage.plot
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
sage: G.show(figsize=[2,2])                                                 # needs sage.plot
sage: G.cliques_get_clique_bipartite()
Bipartite graph on 6 vertices
sage: G.cliques_get_clique_bipartite().show(figsize=[2,2])                  # needs sage.plot
>>> from sage.all import *
>>> CBG = graphs.ChvatalGraph().cliques_get_clique_bipartite(); CBG
Bipartite graph on 36 vertices
>>> CBG.show(figsize=[Integer(2),Integer(2)], vertex_size=Integer(20), vertex_labels=False)          # needs sage.plot
>>> G = Graph({Integer(0):[Integer(1),Integer(2),Integer(3)], Integer(1):[Integer(2)], Integer(3):[Integer(0),Integer(1)]})
>>> G.show(figsize=[Integer(2),Integer(2)])                                                 # needs sage.plot
>>> G.cliques_get_clique_bipartite()
Bipartite graph on 6 vertices
>>> G.cliques_get_clique_bipartite().show(figsize=[Integer(2),Integer(2)])                  # needs sage.plot
cliques_get_max_clique_graph()[source]

Return the clique graph.

Vertices of the result are the maximal cliques of the graph, and edges of the result are between maximal cliques with common members in the original graph.

For more information, see the Wikipedia article Clique_graph.

Note

Currently only implemented for undirected graphs. Use to_undirected to convert a digraph to an undirected graph.

EXAMPLES:

sage: MCG = graphs.ChvatalGraph().cliques_get_max_clique_graph(); MCG
Graph on 24 vertices
sage: MCG.show(figsize=[2,2], vertex_size=20, vertex_labels=False)          # needs sage.plot
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
sage: G.show(figsize=[2,2])                                                 # needs sage.plot
sage: G.cliques_get_max_clique_graph()
Graph on 2 vertices
sage: G.cliques_get_max_clique_graph().show(figsize=[2,2])                  # needs sage.plot
>>> from sage.all import *
>>> MCG = graphs.ChvatalGraph().cliques_get_max_clique_graph(); MCG
Graph on 24 vertices
>>> MCG.show(figsize=[Integer(2),Integer(2)], vertex_size=Integer(20), vertex_labels=False)          # needs sage.plot
>>> G = Graph({Integer(0):[Integer(1),Integer(2),Integer(3)], Integer(1):[Integer(2)], Integer(3):[Integer(0),Integer(1)]})
>>> G.show(figsize=[Integer(2),Integer(2)])                                                 # needs sage.plot
>>> G.cliques_get_max_clique_graph()
Graph on 2 vertices
>>> G.cliques_get_max_clique_graph().show(figsize=[Integer(2),Integer(2)])                  # needs sage.plot
cliques_maximal(algorithm='native')[source]

Return the list of all maximal cliques.

Each clique is represented by a list of vertices. A clique is an induced complete subgraph, and a maximal clique is one not contained in a larger one.

INPUT:

  • algorithm – can be set to 'native' (default) to use Sage’s own implementation, or to "NetworkX" to use NetworkX’ implementation of the Bron and Kerbosch Algorithm [BK1973]

Note

Sage’s implementation of the enumeration of maximal independent sets is not much faster than NetworkX’ (expect a 2x speedup), which is surprising as it is written in Cython. This being said, the algorithm from NetworkX appears to be slightly different from this one, and that would be a good thing to explore if one wants to improve the implementation.

ALGORITHM:

This function is based on NetworkX’s implementation of the Bron and Kerbosch Algorithm [BK1973].

EXAMPLES:

sage: graphs.ChvatalGraph().cliques_maximal()
[[0, 1], [0, 4], [0, 6], [0, 9], [1, 2], [1, 5], [1, 7], [2, 3],
 [2, 6], [2, 8], [3, 4], [3, 7], [3, 9], [4, 5], [4, 8], [5, 10],
 [5, 11], [6, 10], [6, 11], [7, 8], [7, 11], [8, 10], [9, 10], [9, 11]]
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
sage: G.show(figsize=[2, 2])                                                # needs sage.plot
sage: G.cliques_maximal()
[[0, 1, 2], [0, 1, 3]]
sage: C = graphs.PetersenGraph()
sage: C.cliques_maximal()
[[0, 1], [0, 4], [0, 5], [1, 2], [1, 6], [2, 3], [2, 7], [3, 4],
 [3, 8], [4, 9], [5, 7], [5, 8], [6, 8], [6, 9], [7, 9]]
sage: C = Graph('DJ{')
sage: C.cliques_maximal()
[[0, 4], [1, 2, 3, 4]]
>>> from sage.all import *
>>> graphs.ChvatalGraph().cliques_maximal()
[[0, 1], [0, 4], [0, 6], [0, 9], [1, 2], [1, 5], [1, 7], [2, 3],
 [2, 6], [2, 8], [3, 4], [3, 7], [3, 9], [4, 5], [4, 8], [5, 10],
 [5, 11], [6, 10], [6, 11], [7, 8], [7, 11], [8, 10], [9, 10], [9, 11]]
>>> G = Graph({Integer(0):[Integer(1),Integer(2),Integer(3)], Integer(1):[Integer(2)], Integer(3):[Integer(0),Integer(1)]})
>>> G.show(figsize=[Integer(2), Integer(2)])                                                # needs sage.plot
>>> G.cliques_maximal()
[[0, 1, 2], [0, 1, 3]]
>>> C = graphs.PetersenGraph()
>>> C.cliques_maximal()
[[0, 1], [0, 4], [0, 5], [1, 2], [1, 6], [2, 3], [2, 7], [3, 4],
 [3, 8], [4, 9], [5, 7], [5, 8], [6, 8], [6, 9], [7, 9]]
>>> C = Graph('DJ{')
>>> C.cliques_maximal()
[[0, 4], [1, 2, 3, 4]]

Comparing the two implementations:

sage: g = graphs.RandomGNP(20,.7)
sage: s1 = Set(map(Set, g.cliques_maximal(algorithm="NetworkX")))           # needs networkx
sage: s2 = Set(map(Set, g.cliques_maximal(algorithm='native')))
sage: s1 == s2                                                              # needs networkx
True
>>> from sage.all import *
>>> g = graphs.RandomGNP(Integer(20),RealNumber('.7'))
>>> s1 = Set(map(Set, g.cliques_maximal(algorithm="NetworkX")))           # needs networkx
>>> s2 = Set(map(Set, g.cliques_maximal(algorithm='native')))
>>> s1 == s2                                                              # needs networkx
True
cliques_maximum(graph)[source]

Return the vertex sets of ALL the maximum complete subgraphs.

Returns the list of all maximum cliques, with each clique represented by a list of vertices. A clique is an induced complete subgraph, and a maximum clique is one of maximal order.

Note

Currently only implemented for undirected graphs. Use to_undirected() to convert a digraph to an undirected graph.

ALGORITHM:

This function is based on Cliquer [NO2003].

EXAMPLES:

sage: graphs.ChvatalGraph().cliques_maximum() # indirect doctest
[[0, 1], [0, 4], [0, 6], [0, 9], [1, 2], [1, 5], [1, 7], [2, 3],
 [2, 6], [2, 8], [3, 4], [3, 7], [3, 9], [4, 5], [4, 8], [5, 10],
 [5, 11], [6, 10], [6, 11], [7, 8], [7, 11], [8, 10], [9, 10], [9, 11]]
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
sage: G.show(figsize=[2,2])                                                     # needs sage.plot
sage: G.cliques_maximum()
[[0, 1, 2], [0, 1, 3]]
sage: C = graphs.PetersenGraph()
sage: C.cliques_maximum()
[[0, 1], [0, 4], [0, 5], [1, 2], [1, 6], [2, 3], [2, 7], [3, 4],
 [3, 8], [4, 9], [5, 7], [5, 8], [6, 8], [6, 9], [7, 9]]
sage: C = Graph('DJ{')
sage: C.cliques_maximum()
[[1, 2, 3, 4]]
>>> from sage.all import *
>>> graphs.ChvatalGraph().cliques_maximum() # indirect doctest
[[0, 1], [0, 4], [0, 6], [0, 9], [1, 2], [1, 5], [1, 7], [2, 3],
 [2, 6], [2, 8], [3, 4], [3, 7], [3, 9], [4, 5], [4, 8], [5, 10],
 [5, 11], [6, 10], [6, 11], [7, 8], [7, 11], [8, 10], [9, 10], [9, 11]]
>>> G = Graph({Integer(0):[Integer(1),Integer(2),Integer(3)], Integer(1):[Integer(2)], Integer(3):[Integer(0),Integer(1)]})
>>> G.show(figsize=[Integer(2),Integer(2)])                                                     # needs sage.plot
>>> G.cliques_maximum()
[[0, 1, 2], [0, 1, 3]]
>>> C = graphs.PetersenGraph()
>>> C.cliques_maximum()
[[0, 1], [0, 4], [0, 5], [1, 2], [1, 6], [2, 3], [2, 7], [3, 4],
 [3, 8], [4, 9], [5, 7], [5, 8], [6, 8], [6, 9], [7, 9]]
>>> C = Graph('DJ{')
>>> C.cliques_maximum()
[[1, 2, 3, 4]]
cliques_number_of(vertices=None, cliques=None)[source]

Return a dictionary of the number of maximal cliques containing each vertex, keyed by vertex.

This returns a single value if only one input vertex.

Note

Currently only implemented for undirected graphs. Use to_undirected to convert a digraph to an undirected graph.

INPUT:

  • vertices – the vertices to inspect (default: entire graph)

  • cliques – list of cliques (if already computed)

EXAMPLES:

sage: C = Graph('DJ{')
sage: C.cliques_number_of()
{0: 1, 1: 1, 2: 1, 3: 1, 4: 2}
sage: E = C.cliques_maximal(); E
[[0, 4], [1, 2, 3, 4]]
sage: C.cliques_number_of(cliques=E)
{0: 1, 1: 1, 2: 1, 3: 1, 4: 2}
sage: F = graphs.Grid2dGraph(2,3)
sage: F.cliques_number_of()
{(0, 0): 2, (0, 1): 3, (0, 2): 2, (1, 0): 2, (1, 1): 3, (1, 2): 2}
sage: F.cliques_number_of(vertices=[(0, 1), (1, 2)])
{(0, 1): 3, (1, 2): 2}
sage: F.cliques_number_of(vertices=(0, 1))
3
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
sage: G.show(figsize=[2,2])                                                 # needs sage.plot
sage: G.cliques_number_of()
{0: 2, 1: 2, 2: 1, 3: 1}
>>> from sage.all import *
>>> C = Graph('DJ{')
>>> C.cliques_number_of()
{0: 1, 1: 1, 2: 1, 3: 1, 4: 2}
>>> E = C.cliques_maximal(); E
[[0, 4], [1, 2, 3, 4]]
>>> C.cliques_number_of(cliques=E)
{0: 1, 1: 1, 2: 1, 3: 1, 4: 2}
>>> F = graphs.Grid2dGraph(Integer(2),Integer(3))
>>> F.cliques_number_of()
{(0, 0): 2, (0, 1): 3, (0, 2): 2, (1, 0): 2, (1, 1): 3, (1, 2): 2}
>>> F.cliques_number_of(vertices=[(Integer(0), Integer(1)), (Integer(1), Integer(2))])
{(0, 1): 3, (1, 2): 2}
>>> F.cliques_number_of(vertices=(Integer(0), Integer(1)))
3
>>> G = Graph({Integer(0):[Integer(1),Integer(2),Integer(3)], Integer(1):[Integer(2)], Integer(3):[Integer(0),Integer(1)]})
>>> G.show(figsize=[Integer(2),Integer(2)])                                                 # needs sage.plot
>>> G.cliques_number_of()
{0: 2, 1: 2, 2: 1, 3: 1}
cliques_vertex_clique_number(algorithm='cliquer', vertices=None, cliques=None)[source]

Return a dictionary of sizes of the largest maximal cliques containing each vertex, keyed by vertex.

Returns a single value if only one input vertex.

Note

Currently only implemented for undirected graphs. Use to_undirected() to convert a digraph to an undirected graph.

INPUT:

  • algorithm – either cliquer or networkx

    • cliquer – this wraps the C program Cliquer [NO2003]

    • networkx – this function is based on NetworkX’s implementation of the Bron and Kerbosch Algorithm [BK1973]

  • vertices – the vertices to inspect (default: entire graph). Ignored unless algorithm=='networkx'.

  • cliques – list of cliques (if already computed). Ignored unless algorithm=='networkx'.

EXAMPLES:

sage: C = Graph('DJ{')
sage: C.cliques_vertex_clique_number()                                      # needs sage.plot
{0: 2, 1: 4, 2: 4, 3: 4, 4: 4}
sage: E = C.cliques_maximal(); E
[[0, 4], [1, 2, 3, 4]]
sage: C.cliques_vertex_clique_number(cliques=E, algorithm='networkx')       # needs networkx
{0: 2, 1: 4, 2: 4, 3: 4, 4: 4}

sage: F = graphs.Grid2dGraph(2,3)
sage: F.cliques_vertex_clique_number(algorithm='networkx')                  # needs networkx
{(0, 0): 2, (0, 1): 2, (0, 2): 2, (1, 0): 2, (1, 1): 2, (1, 2): 2}
sage: F.cliques_vertex_clique_number(vertices=[(0, 1), (1, 2)])             # needs sage.plot
{(0, 1): 2, (1, 2): 2}

sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
sage: G.show(figsize=[2,2])                                                 # needs sage.plot
sage: G.cliques_vertex_clique_number()                                      # needs sage.plot
{0: 3, 1: 3, 2: 3, 3: 3}
>>> from sage.all import *
>>> C = Graph('DJ{')
>>> C.cliques_vertex_clique_number()                                      # needs sage.plot
{0: 2, 1: 4, 2: 4, 3: 4, 4: 4}
>>> E = C.cliques_maximal(); E
[[0, 4], [1, 2, 3, 4]]
>>> C.cliques_vertex_clique_number(cliques=E, algorithm='networkx')       # needs networkx
{0: 2, 1: 4, 2: 4, 3: 4, 4: 4}

>>> F = graphs.Grid2dGraph(Integer(2),Integer(3))
>>> F.cliques_vertex_clique_number(algorithm='networkx')                  # needs networkx
{(0, 0): 2, (0, 1): 2, (0, 2): 2, (1, 0): 2, (1, 1): 2, (1, 2): 2}
>>> F.cliques_vertex_clique_number(vertices=[(Integer(0), Integer(1)), (Integer(1), Integer(2))])             # needs sage.plot
{(0, 1): 2, (1, 2): 2}

>>> G = Graph({Integer(0):[Integer(1),Integer(2),Integer(3)], Integer(1):[Integer(2)], Integer(3):[Integer(0),Integer(1)]})
>>> G.show(figsize=[Integer(2),Integer(2)])                                                 # needs sage.plot
>>> G.cliques_vertex_clique_number()                                      # needs sage.plot
{0: 3, 1: 3, 2: 3, 3: 3}
coloring(algorithm, hex_colors='DLX', solver=False, verbose=None, integrality_tolerance=0)[source]

Return the first (optimal) proper vertex-coloring found.

INPUT:

  • algorithm – select an algorithm from the following supported algorithms:

    • If algorithm="DLX" (default), the coloring is computed using the dancing link algorithm.

    • If algorithm="MILP", the coloring is computed using a mixed integer linear program. The performance of this implementation is affected by whether optional MILP solvers have been installed (see the MILP module).

  • hex_colors – boolean (default: False); if True, return a dictionary which can easily be used for plotting

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

See also

For more functions related to graph coloring, see the module sage.graphs.graph_coloring.

EXAMPLES:

sage: G = Graph("Fooba")
sage: P = G.coloring(algorithm='MILP')
sage: Q = G.coloring(algorithm='DLX')
sage: def are_equal_colorings(A, B):
....:     return Set(map(Set, A)) == Set(map(Set, B))
sage: are_equal_colorings(P, [[1, 2, 3], [0, 5, 6], [4]])
True
sage: are_equal_colorings(P, Q)
True

sage: # needs sage.plot
sage: G.plot(partition=P)
Graphics object consisting of 16 graphics primitives
sage: G.coloring(hex_colors=True, algorithm='MILP')
{'#0000ff': [4], '#00ff00': [0, 6, 5], '#ff0000': [2, 1, 3]}
sage: H = G.coloring(hex_colors=True, algorithm='DLX'); H
{'#0000ff': [4], '#00ff00': [1, 2, 3], '#ff0000': [0, 5, 6]}
sage: G.plot(vertex_colors=H)
Graphics object consisting of 16 graphics primitives
>>> from sage.all import *
>>> G = Graph("Fooba")
>>> P = G.coloring(algorithm='MILP')
>>> Q = G.coloring(algorithm='DLX')
>>> def are_equal_colorings(A, B):
...     return Set(map(Set, A)) == Set(map(Set, B))
>>> are_equal_colorings(P, [[Integer(1), Integer(2), Integer(3)], [Integer(0), Integer(5), Integer(6)], [Integer(4)]])
True
>>> are_equal_colorings(P, Q)
True

>>> # needs sage.plot
>>> G.plot(partition=P)
Graphics object consisting of 16 graphics primitives
>>> G.coloring(hex_colors=True, algorithm='MILP')
{'#0000ff': [4], '#00ff00': [0, 6, 5], '#ff0000': [2, 1, 3]}
>>> H = G.coloring(hex_colors=True, algorithm='DLX'); H
{'#0000ff': [4], '#00ff00': [1, 2, 3], '#ff0000': [0, 5, 6]}
>>> G.plot(vertex_colors=H)
Graphics object consisting of 16 graphics primitives
../../_images/graph-1.svg
common_neighbors_matrix(vertices, nonedgesonly=None, base_ring=True, **kwds)[source]

Return a matrix of numbers of common neighbors between each pairs.

The \((i , j)\) entry of the matrix gives the number of common neighbors between vertices \(i\) and \(j\).

This method is only valid for simple (no loops, no multiple edges) graphs.

INPUT:

  • nonedgesonly – boolean (default: True); if True, assigns \(0\) value to adjacent vertices.

  • vertices – list (default: None); the ordering of the vertices defining how they should appear in the matrix. By default, the ordering given by GenericGraph.vertices() is used.

  • base_ring – a ring (default: None); the base ring of the matrix space to use

  • **kwds – other keywords to pass to matrix()

OUTPUT: matrix

EXAMPLES:

The common neighbors matrix for a straight linear 2-tree counting only non-adjacent vertex pairs

sage: G1 = Graph()
sage: G1.add_edges([(0,1),(0,2),(1,2),(1,3),(3,5),(2,4),(2,3),(3,4),(4,5)])
sage: G1.common_neighbors_matrix(nonedgesonly=True)                         # needs sage.modules
[0 0 0 2 1 0]
[0 0 0 0 2 1]
[0 0 0 0 0 2]
[2 0 0 0 0 0]
[1 2 0 0 0 0]
[0 1 2 0 0 0]
>>> from sage.all import *
>>> G1 = Graph()
>>> G1.add_edges([(Integer(0),Integer(1)),(Integer(0),Integer(2)),(Integer(1),Integer(2)),(Integer(1),Integer(3)),(Integer(3),Integer(5)),(Integer(2),Integer(4)),(Integer(2),Integer(3)),(Integer(3),Integer(4)),(Integer(4),Integer(5))])
>>> G1.common_neighbors_matrix(nonedgesonly=True)                         # needs sage.modules
[0 0 0 2 1 0]
[0 0 0 0 2 1]
[0 0 0 0 0 2]
[2 0 0 0 0 0]
[1 2 0 0 0 0]
[0 1 2 0 0 0]

We now show the common neighbors matrix which includes adjacent vertices

sage: G1.common_neighbors_matrix(nonedgesonly=False)                        # needs sage.modules
[0 1 1 2 1 0]
[1 0 2 1 2 1]
[1 2 0 2 1 2]
[2 1 2 0 2 1]
[1 2 1 2 0 1]
[0 1 2 1 1 0]
>>> from sage.all import *
>>> G1.common_neighbors_matrix(nonedgesonly=False)                        # needs sage.modules
[0 1 1 2 1 0]
[1 0 2 1 2 1]
[1 2 0 2 1 2]
[2 1 2 0 2 1]
[1 2 1 2 0 1]
[0 1 2 1 1 0]

The common neighbors matrix for a fan on 6 vertices counting only non-adjacent vertex pairs

sage: H = Graph([(0,1),(0,2),(0,3),(0,4),(0,5),(0,6),(1,2),(2,3),(3,4),(4,5)])
sage: H.common_neighbors_matrix()                                           # needs sage.modules
[0 0 0 0 0 0 0]
[0 0 0 2 1 1 1]
[0 0 0 0 2 1 1]
[0 2 0 0 0 2 1]
[0 1 2 0 0 0 1]
[0 1 1 2 0 0 1]
[0 1 1 1 1 1 0]
>>> from sage.all import *
>>> H = Graph([(Integer(0),Integer(1)),(Integer(0),Integer(2)),(Integer(0),Integer(3)),(Integer(0),Integer(4)),(Integer(0),Integer(5)),(Integer(0),Integer(6)),(Integer(1),Integer(2)),(Integer(2),Integer(3)),(Integer(3),Integer(4)),(Integer(4),Integer(5))])
>>> H.common_neighbors_matrix()                                           # needs sage.modules
[0 0 0 0 0 0 0]
[0 0 0 2 1 1 1]
[0 0 0 0 2 1 1]
[0 2 0 0 0 2 1]
[0 1 2 0 0 0 1]
[0 1 1 2 0 0 1]
[0 1 1 1 1 1 0]

A different base ring:

sage: H.common_neighbors_matrix(base_ring=RDF)                              # needs sage.modules
[0.0 0.0 0.0 0.0 0.0 0.0 0.0]
[0.0 0.0 0.0 2.0 1.0 1.0 1.0]
[0.0 0.0 0.0 0.0 2.0 1.0 1.0]
[0.0 2.0 0.0 0.0 0.0 2.0 1.0]
[0.0 1.0 2.0 0.0 0.0 0.0 1.0]
[0.0 1.0 1.0 2.0 0.0 0.0 1.0]
[0.0 1.0 1.0 1.0 1.0 1.0 0.0]
>>> from sage.all import *
>>> H.common_neighbors_matrix(base_ring=RDF)                              # needs sage.modules
[0.0 0.0 0.0 0.0 0.0 0.0 0.0]
[0.0 0.0 0.0 2.0 1.0 1.0 1.0]
[0.0 0.0 0.0 0.0 2.0 1.0 1.0]
[0.0 2.0 0.0 0.0 0.0 2.0 1.0]
[0.0 1.0 2.0 0.0 0.0 0.0 1.0]
[0.0 1.0 1.0 2.0 0.0 0.0 1.0]
[0.0 1.0 1.0 1.0 1.0 1.0 0.0]

It is an error to input anything other than a simple graph:

sage: G = Graph([(0,0)], loops=True)
sage: G.common_neighbors_matrix()                                           # needs sage.modules
Traceback (most recent call last):
...
ValueError: This method is not known to work on graphs with loops.
Perhaps this method can be updated to handle them, but in the
meantime if you want to use it please disallow loops using
allow_loops().
>>> from sage.all import *
>>> G = Graph([(Integer(0),Integer(0))], loops=True)
>>> G.common_neighbors_matrix()                                           # needs sage.modules
Traceback (most recent call last):
...
ValueError: This method is not known to work on graphs with loops.
Perhaps this method can be updated to handle them, but in the
meantime if you want to use it please disallow loops using
allow_loops().

See also

convexity_properties()[source]

Return a ConvexityProperties object corresponding to self.

This object contains the methods related to convexity in graphs (convex hull, hull number) and caches useful information so that it becomes comparatively cheaper to compute the convex hull of many different sets of the same graph.

See also

In order to know what can be done through this object, please refer to module sage.graphs.convexity_properties.

Note

If you want to compute many convex hulls, keep this object in memory! When it is created, it builds a table of useful information to compute convex hulls. As a result

sage: # needs sage.numerical.mip
sage: g = graphs.PetersenGraph()
sage: g.convexity_properties().hull([1, 3])
[1, 2, 3]
sage: g.convexity_properties().hull([3, 7])
[2, 3, 7]
>>> from sage.all import *
>>> # needs sage.numerical.mip
>>> g = graphs.PetersenGraph()
>>> g.convexity_properties().hull([Integer(1), Integer(3)])
[1, 2, 3]
>>> g.convexity_properties().hull([Integer(3), Integer(7)])
[2, 3, 7]

is a terrible waste of computations, while

sage: # needs sage.numerical.mip
sage: g = graphs.PetersenGraph()
sage: CP = g.convexity_properties()
sage: CP.hull([1, 3])
[1, 2, 3]
sage: CP.hull([3, 7])
[2, 3, 7]
>>> from sage.all import *
>>> # needs sage.numerical.mip
>>> g = graphs.PetersenGraph()
>>> CP = g.convexity_properties()
>>> CP.hull([Integer(1), Integer(3)])
[1, 2, 3]
>>> CP.hull([Integer(3), Integer(7)])
[2, 3, 7]

makes perfect sense.

cores(k=None, with_labels=False)[source]

Return the core number for each vertex in an ordered list.

(for homomorphisms cores, see the Graph.has_homomorphism_to() method)

DEFINITIONS:

  • K-cores in graph theory were introduced by Seidman in 1983 and by Bollobas in 1984 as a method of (destructively) simplifying graph topology to aid in analysis and visualization. They have been more recently defined as the following by Batagelj et al:

    Given a graph `G` with vertices set `V` and edges set `E`, the `k`-core of `G` is the graph obtained from `G` by recursively removing the vertices with degree less than `k`, for as long as there are any.

    This operation can be useful to filter or to study some properties of the graphs. For instance, when you compute the 2-core of graph G, you are cutting all the vertices which are in a tree part of graph. (A tree is a graph with no loops). See the Wikipedia article K-core.

    [PSW1996] defines a \(k\)-core of \(G\) as the largest subgraph (it is unique) of \(G\) with minimum degree at least \(k\).

  • Core number of a vertex

    The core number of a vertex \(v\) is the largest integer \(k\) such that \(v\) belongs to the \(k\)-core of \(G\).

  • Degeneracy

    The degeneracy of a graph \(G\), usually denoted \(\delta^*(G)\), is the smallest integer \(k\) such that the graph \(G\) can be reduced to the empty graph by iteratively removing vertices of degree \(\leq k\). Equivalently, \(\delta^*(G) = k - 1\) if \(k\) is the smallest integer such that the \(k\)-core of \(G\) is empty.

IMPLEMENTATION:

This implementation is based on the NetworkX implementation of the algorithm described in [BZ2003].

INPUT:

  • k – integer (default: None)

    • If k = None (default), returns the core number for each vertex.

    • If k is an integer, returns a pair (ordering, core), where core is the list of vertices in the \(k\)-core of self, and ordering is an elimination order for the other vertices such that each vertex is of degree strictly less than \(k\) when it is to be eliminated from the graph.

  • with_labels – boolean (default: False); when set to False, and k = None, the method returns a list whose \(i\) th element is the core number of the \(i\) th vertex. When set to True, the method returns a dictionary whose keys are vertices, and whose values are the corresponding core numbers.

See also

EXAMPLES:

sage: (graphs.FruchtGraph()).cores()
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
sage: (graphs.FruchtGraph()).cores(with_labels=True)
{0: 3, 1: 3, 2: 3, 3: 3, 4: 3, 5: 3, 6: 3, 7: 3, 8: 3, 9: 3, 10: 3, 11: 3}

sage: # needs sage.modules
sage: set_random_seed(0)
sage: a = random_matrix(ZZ, 20, x=2, sparse=True, density=.1)
sage: b = Graph(20)
sage: b.add_edges(a.nonzero_positions(), loops=False)
sage: cores = b.cores(with_labels=True); cores
{0: 3, 1: 3, 2: 3, 3: 3, 4: 2, 5: 2, 6: 3, 7: 1, 8: 3, 9: 3, 10: 3,
 11: 3, 12: 3, 13: 3, 14: 2, 15: 3, 16: 3, 17: 3, 18: 3, 19: 3}
sage: [v for v,c in cores.items() if c >= 2]  # the vertices in the 2-core
[0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> from sage.all import *
>>> (graphs.FruchtGraph()).cores()
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
>>> (graphs.FruchtGraph()).cores(with_labels=True)
{0: 3, 1: 3, 2: 3, 3: 3, 4: 3, 5: 3, 6: 3, 7: 3, 8: 3, 9: 3, 10: 3, 11: 3}

>>> # needs sage.modules
>>> set_random_seed(Integer(0))
>>> a = random_matrix(ZZ, Integer(20), x=Integer(2), sparse=True, density=RealNumber('.1'))
>>> b = Graph(Integer(20))
>>> b.add_edges(a.nonzero_positions(), loops=False)
>>> cores = b.cores(with_labels=True); cores
{0: 3, 1: 3, 2: 3, 3: 3, 4: 2, 5: 2, 6: 3, 7: 1, 8: 3, 9: 3, 10: 3,
 11: 3, 12: 3, 13: 3, 14: 2, 15: 3, 16: 3, 17: 3, 18: 3, 19: 3}
>>> [v for v,c in cores.items() if c >= Integer(2)]  # the vertices in the 2-core
[0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

Checking the 2-core of a random lobster is indeed the empty set:

sage: g = graphs.RandomLobster(20, .5, .5)                                  # needs networkx
sage: ordering, core = g.cores(2)                                           # needs networkx
sage: len(core) == 0                                                        # needs networkx
True
>>> from sage.all import *
>>> g = graphs.RandomLobster(Integer(20), RealNumber('.5'), RealNumber('.5'))                                  # needs networkx
>>> ordering, core = g.cores(Integer(2))                                           # needs networkx
>>> len(core) == Integer(0)                                                        # needs networkx
True

Checking the cores of a bull graph:

sage: G = graphs.BullGraph()
sage: G.cores(with_labels=True)
{0: 2, 1: 2, 2: 2, 3: 1, 4: 1}
sage: G.cores(k=2)
([3, 4], [0, 1, 2])
>>> from sage.all import *
>>> G = graphs.BullGraph()
>>> G.cores(with_labels=True)
{0: 2, 1: 2, 2: 2, 3: 1, 4: 1}
>>> G.cores(k=Integer(2))
([3, 4], [0, 1, 2])

Graphs with multiple edges:

sage: G.allow_multiple_edges(True)
sage: G.add_edges(G.edges(sort=False))
sage: G.cores(with_labels=True)
{0: 4, 1: 4, 2: 4, 3: 2, 4: 2}
sage: G.cores(k=4)
([3, 4], [0, 1, 2])
>>> from sage.all import *
>>> G.allow_multiple_edges(True)
>>> G.add_edges(G.edges(sort=False))
>>> G.cores(with_labels=True)
{0: 4, 1: 4, 2: 4, 3: 2, 4: 2}
>>> G.cores(k=Integer(4))
([3, 4], [0, 1, 2])
cutwidth(G, algorithm='exponential', cut_off=0, solver=None, verbose=False, integrality_tolerance=0.001)[source]

Return the cutwidth of the graph and the corresponding vertex ordering.

INPUT:

  • G – a Graph or a DiGraph

  • algorithm – string (default: 'exponential'); algorithm to use among:

    • exponential – use an exponential time and space algorithm based on dynamic programming. This algorithm only works on graphs with strictly less than 32 vertices.

    • MILP – use a mixed integer linear programming formulation. This algorithm has no size restriction but could take a very long time

  • cut_off – integer (default: 0); used to stop the search as soon as a solution with width at most cut_off is found, if any. If this bound cannot be reached, the best solution found is returned.

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – boolean (default: False); whether to display information on the computations

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

OUTPUT:

A pair (cost, ordering) representing the optimal ordering of the vertices and its cost.

EXAMPLES:

Cutwidth of a Complete Graph:

sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth
sage: G = graphs.CompleteGraph(5)
sage: cw,L = cutwidth(G); cw
6
sage: K = graphs.CompleteGraph(6)
sage: cw,L = cutwidth(K); cw
9
sage: cw,L = cutwidth(K+K); cw
9
>>> from sage.all import *
>>> from sage.graphs.graph_decompositions.cutwidth import cutwidth
>>> G = graphs.CompleteGraph(Integer(5))
>>> cw,L = cutwidth(G); cw
6
>>> K = graphs.CompleteGraph(Integer(6))
>>> cw,L = cutwidth(K); cw
9
>>> cw,L = cutwidth(K+K); cw
9

The cutwidth of a \(p\times q\) Grid Graph with \(p\leq q\) is \(p+1\):

sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth
sage: G = graphs.Grid2dGraph(3,3)
sage: cw,L = cutwidth(G); cw
4
sage: G = graphs.Grid2dGraph(3,5)
sage: cw,L = cutwidth(G); cw
4
>>> from sage.all import *
>>> from sage.graphs.graph_decompositions.cutwidth import cutwidth
>>> G = graphs.Grid2dGraph(Integer(3),Integer(3))
>>> cw,L = cutwidth(G); cw
4
>>> G = graphs.Grid2dGraph(Integer(3),Integer(5))
>>> cw,L = cutwidth(G); cw
4
degree_constrained_subgraph(bounds, solver, verbose=None, integrality_tolerance=0)[source]

Return a degree-constrained subgraph.

Given a graph \(G\) and two functions \(f, g:V(G)\rightarrow \mathbb Z\) such that \(f \leq g\), a degree-constrained subgraph in \(G\) is a subgraph \(G' \subseteq G\) such that for any vertex \(v \in G\), \(f(v) \leq d_{G'}(v) \leq g(v)\).

INPUT:

  • bounds – (default: None) two possibilities:

    • A dictionary whose keys are the vertices, and values a pair of real values (min,max) corresponding to the values \((f(v),g(v))\).

    • A function associating to each vertex a pair of real values (min,max) corresponding to the values \((f(v),g(v))\).

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

OUTPUT:

  • When a solution exists, this method outputs the degree-constrained subgraph as a Graph object.

  • When no solution exists, returns False.

Note

  • This algorithm computes the degree-constrained subgraph of minimum weight.

  • If the graph’s edges are weighted, these are taken into account.

  • This problem can be solved in polynomial time.

EXAMPLES:

Is there a perfect matching in an even cycle?

sage: g = graphs.CycleGraph(6)
sage: bounds = lambda x: [1,1]
sage: m = g.degree_constrained_subgraph(bounds=bounds)                      # needs sage.numerical.mip
sage: m.size()                                                              # needs sage.numerical.mip
3
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(6))
>>> bounds = lambda x: [Integer(1),Integer(1)]
>>> m = g.degree_constrained_subgraph(bounds=bounds)                      # needs sage.numerical.mip
>>> m.size()                                                              # needs sage.numerical.mip
3
diameter(by_weight=False, algorithm=None, weight_function=None, check_weight=True)[source]

Return the diameter of the graph.

The diameter is defined to be the maximum distance between two vertices. It is infinite if the graph is not connected.

For more information and examples on how to use input variables, see shortest_paths() and eccentricity()

INPUT:

  • by_weight – boolean (default: False); if True, edge weights are taken into account; if False, all edges have weight 1

  • algorithm – string (default: None); one of the following algorithms:

    • 'BFS': the computation is done through a BFS centered on each vertex successively. Works only if by_weight==False.

    • 'Floyd-Warshall-Cython': a Cython implementation of the Floyd-Warshall algorithm. Works only if by_weight==False and v is None.

    • 'Floyd-Warshall-Python': a Python implementation of the Floyd-Warshall algorithm. Works also with weighted graphs, even with negative weights (but no negative cycle is allowed). However, v must be None.

    • 'Dijkstra_NetworkX': the Dijkstra algorithm, implemented in NetworkX. It works with weighted graphs, but no negative weight is allowed.

    • 'DHV' – diameter computation is done using the algorithm proposed in [Dragan2018]. Works only for nonnegative edge weights For more information see method sage.graphs.distances_all_pairs.diameter_DHV() and sage.graphs.base.boost_graph.diameter_DHV().

    • 'standard', '2sweep', 'multi-sweep', 'iFUB': these algorithms are implemented in sage.graphs.distances_all_pairs.diameter() They work only if by_weight==False. See the function documentation for more information.

    • 'Dijkstra_Boost': the Dijkstra algorithm, implemented in Boost (works only with positive weights).

    • 'Johnson_Boost': the Johnson algorithm, implemented in Boost (works also with negative weights, if there is no negative cycle).

    • None (default): Sage chooses the best algorithm: 'iFUB' for unweighted graphs, 'Dijkstra_Boost' if all weights are positive, 'Johnson_Boost' otherwise.

  • weight_function – function (default: None); a function that takes as input an edge (u, v, l) and outputs its weight. If not None, by_weight is automatically set to True. If None and by_weight is True, we use the edge label l as a weight, if l is not None, else 1 as a weight.

  • check_weight – boolean (default: True); if True, we check that the weight_function outputs a number for each edge

EXAMPLES:

The more symmetric a graph is, the smaller (diameter - radius) is:

sage: G = graphs.BarbellGraph(9, 3)
sage: G.radius()
3
sage: G.diameter()
6
>>> from sage.all import *
>>> G = graphs.BarbellGraph(Integer(9), Integer(3))
>>> G.radius()
3
>>> G.diameter()
6

sage: G = graphs.OctahedralGraph()
sage: G.radius()
2
sage: G.diameter()
2
>>> from sage.all import *
>>> G = graphs.OctahedralGraph()
>>> G.radius()
2
>>> G.diameter()
2
distance_graph(dist)[source]

Return the graph on the same vertex set as the original graph but vertices are adjacent in the returned graph if and only if they are at specified distances in the original graph.

INPUT:

  • dist – nonnegative integer or a list of nonnegative integers; specified distance(s) for the connecting vertices. Infinity may be used here to describe vertex pairs in separate components.

OUTPUT:

The returned value is an undirected graph. The vertex set is identical to the calling graph, but edges of the returned graph join vertices whose distance in the calling graph are present in the input dist. Loops will only be present if distance 0 is included. If the original graph has a position dictionary specifying locations of vertices for plotting, then this information is copied over to the distance graph. In some instances this layout may not be the best, and might even be confusing when edges run on top of each other due to symmetries chosen for the layout.

EXAMPLES:

sage: G = graphs.CompleteGraph(3)
sage: H = G.cartesian_product(graphs.CompleteGraph(2))
sage: K = H.distance_graph(2)
sage: K.am()                                                                # needs sage.modules
[0 0 0 1 0 1]
[0 0 1 0 1 0]
[0 1 0 0 0 1]
[1 0 0 0 1 0]
[0 1 0 1 0 0]
[1 0 1 0 0 0]
>>> from sage.all import *
>>> G = graphs.CompleteGraph(Integer(3))
>>> H = G.cartesian_product(graphs.CompleteGraph(Integer(2)))
>>> K = H.distance_graph(Integer(2))
>>> K.am()                                                                # needs sage.modules
[0 0 0 1 0 1]
[0 0 1 0 1 0]
[0 1 0 0 0 1]
[1 0 0 0 1 0]
[0 1 0 1 0 0]
[1 0 1 0 0 0]

To obtain the graph where vertices are adjacent if their distance apart is d or less use a range() command to create the input, using d + 1 as the input to range. Notice that this will include distance 0 and hence place a loop at each vertex. To avoid this, use range(1, d + 1):

sage: G = graphs.OddGraph(4)
sage: d = G.diameter()
sage: n = G.num_verts()
sage: H = G.distance_graph(list(range(d+1)))
sage: H.is_isomorphic(graphs.CompleteGraph(n))
False
sage: H = G.distance_graph(list(range(1,d+1)))
sage: H.is_isomorphic(graphs.CompleteGraph(n))
True
>>> from sage.all import *
>>> G = graphs.OddGraph(Integer(4))
>>> d = G.diameter()
>>> n = G.num_verts()
>>> H = G.distance_graph(list(range(d+Integer(1))))
>>> H.is_isomorphic(graphs.CompleteGraph(n))
False
>>> H = G.distance_graph(list(range(Integer(1),d+Integer(1))))
>>> H.is_isomorphic(graphs.CompleteGraph(n))
True

A complete collection of distance graphs will have adjacency matrices that sum to the matrix of all ones:

sage: P = graphs.PathGraph(20)
sage: all_ones = sum([P.distance_graph(i).am() for i in range(20)])         # needs sage.modules
sage: all_ones == matrix(ZZ, 20, 20, [1]*400)                               # needs sage.modules
True
>>> from sage.all import *
>>> P = graphs.PathGraph(Integer(20))
>>> all_ones = sum([P.distance_graph(i).am() for i in range(Integer(20))])         # needs sage.modules
>>> all_ones == matrix(ZZ, Integer(20), Integer(20), [Integer(1)]*Integer(400))                               # needs sage.modules
True

Four-bit strings differing in one bit is the same as four-bit strings differing in three bits:

sage: G = graphs.CubeGraph(4)
sage: H = G.distance_graph(3)
sage: G.is_isomorphic(H)
True
>>> from sage.all import *
>>> G = graphs.CubeGraph(Integer(4))
>>> H = G.distance_graph(Integer(3))
>>> G.is_isomorphic(H)
True

The graph of eight-bit strings, adjacent if different in an odd number of bits:

sage: # long time, needs sage.symbolic
sage: G = graphs.CubeGraph(8)
sage: H = G.distance_graph([1,3,5,7])
sage: degrees = [0]*sum([binomial(8,j) for j in [1,3,5,7]])
sage: degrees.append(2^8)
sage: degrees == H.degree_histogram()
True
>>> from sage.all import *
>>> # long time, needs sage.symbolic
>>> G = graphs.CubeGraph(Integer(8))
>>> H = G.distance_graph([Integer(1),Integer(3),Integer(5),Integer(7)])
>>> degrees = [Integer(0)]*sum([binomial(Integer(8),j) for j in [Integer(1),Integer(3),Integer(5),Integer(7)]])
>>> degrees.append(Integer(2)**Integer(8))
>>> degrees == H.degree_histogram()
True

An example of using Infinity as the distance in a graph that is not connected:

sage: G = graphs.CompleteGraph(3)
sage: H = G.disjoint_union(graphs.CompleteGraph(2))
sage: L = H.distance_graph(Infinity)
sage: L.am()                                                                # needs sage.modules
[0 0 0 1 1]
[0 0 0 1 1]
[0 0 0 1 1]
[1 1 1 0 0]
[1 1 1 0 0]
sage: L.is_isomorphic(graphs.CompleteBipartiteGraph(3, 2))
True
>>> from sage.all import *
>>> G = graphs.CompleteGraph(Integer(3))
>>> H = G.disjoint_union(graphs.CompleteGraph(Integer(2)))
>>> L = H.distance_graph(Infinity)
>>> L.am()                                                                # needs sage.modules
[0 0 0 1 1]
[0 0 0 1 1]
[0 0 0 1 1]
[1 1 1 0 0]
[1 1 1 0 0]
>>> L.is_isomorphic(graphs.CompleteBipartiteGraph(Integer(3), Integer(2)))
True

AUTHOR:

Rob Beezer, 2009-11-25, Issue #7533

ear_decomposition()[source]

Return an Ear decomposition of the graph.

An ear of an undirected graph \(G\) is a path \(P\) where the two endpoints of the path may coincide (i.e., form a cycle), but where otherwise no repetition of edges or vertices is allowed, so every internal vertex of \(P\) has degree two in \(P\).

An ear decomposition of an undirected graph \(G\) is a partition of its set of edges into a sequence of ears, such that the one or two endpoints of each ear belong to earlier ears in the sequence and such that the internal vertices of each ear do not belong to any earlier ear.

For more information, see the Wikipedia article Ear_decomposition.

This method implements the linear time algorithm presented in [Sch2013].

OUTPUT:

  • A nested list representing the cycles and chains of the ear decomposition of the graph.

EXAMPLES:

Ear decomposition of an outer planar graph of order 13:

sage: g = Graph('LlCG{O@?GBOMW?')
sage: g.ear_decomposition()
[[0, 3, 2, 1, 0],
 [0, 7, 4, 3],
 [0, 11, 9, 8, 7],
 [1, 12, 2],
 [3, 6, 5, 4],
 [4, 6],
 [7, 10, 8],
 [7, 11],
 [8, 11]]
>>> from sage.all import *
>>> g = Graph('LlCG{O@?GBOMW?')
>>> g.ear_decomposition()
[[0, 3, 2, 1, 0],
 [0, 7, 4, 3],
 [0, 11, 9, 8, 7],
 [1, 12, 2],
 [3, 6, 5, 4],
 [4, 6],
 [7, 10, 8],
 [7, 11],
 [8, 11]]

Ear decomposition of a biconnected graph:

sage: g = graphs.CycleGraph(4)
sage: g.ear_decomposition()
[[0, 3, 2, 1, 0]]
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(4))
>>> g.ear_decomposition()
[[0, 3, 2, 1, 0]]

Ear decomposition of a connected but not biconnected graph:

sage: G = Graph()
sage: G.add_cycle([0,1,2])
sage: G.add_edge(0,3)
sage: G.add_cycle([3,4,5,6])
sage: G.ear_decomposition()
[[0, 2, 1, 0], [3, 6, 5, 4, 3]]
>>> from sage.all import *
>>> G = Graph()
>>> G.add_cycle([Integer(0),Integer(1),Integer(2)])
>>> G.add_edge(Integer(0),Integer(3))
>>> G.add_cycle([Integer(3),Integer(4),Integer(5),Integer(6)])
>>> G.ear_decomposition()
[[0, 2, 1, 0], [3, 6, 5, 4, 3]]

The ear decomposition of a multigraph with loops is the same as the ear decomposition of the underlying simple graph:

sage: g = graphs.BullGraph()
sage: g.allow_multiple_edges(True)
sage: g.add_edges(g.edges(sort=False))
sage: g.allow_loops(True)
sage: u = g.random_vertex()
sage: g.add_edge(u, u)
sage: g
Bull graph: Looped multi-graph on 5 vertices
sage: h = g.to_simple()
sage: g.ear_decomposition() == h.ear_decomposition()
True
>>> from sage.all import *
>>> g = graphs.BullGraph()
>>> g.allow_multiple_edges(True)
>>> g.add_edges(g.edges(sort=False))
>>> g.allow_loops(True)
>>> u = g.random_vertex()
>>> g.add_edge(u, u)
>>> g
Bull graph: Looped multi-graph on 5 vertices
>>> h = g.to_simple()
>>> g.ear_decomposition() == h.ear_decomposition()
True
eccentricity(v=None, by_weight=False, algorithm=None, weight_function=None, check_weight=True, dist_dict=None, with_labels=False)[source]

Return the eccentricity of vertex (or vertices) v.

The eccentricity of a vertex is the maximum distance to any other vertex.

For more information and examples on how to use input variables, see shortest_path_all_pairs(), shortest_path_lengths() and shortest_paths()

INPUT:

  • v – either a single vertex or a list of vertices. If it is not specified, then it is taken to be all vertices

  • by_weight – boolean (default: False); if True, edge weights are taken into account; if False, all edges have weight 1

  • algorithm – string (default: None); one of the following algorithms:

    • 'BFS' – the computation is done through a BFS centered on each vertex successively. Works only if by_weight==False

    • 'DHV' – the computation is done using the algorithm proposed in [Dragan2018]. Works only if self has nonnegative edge weights and v is None or v should contain all vertices of self. For more information see method sage.graphs.distances_all_pairs.eccentricity() and sage.graphs.base.boost_graph.eccentricity_DHV().

    • 'Floyd-Warshall-Cython' – a Cython implementation of the Floyd-Warshall algorithm. Works only if by_weight==False and v is None or v should contain all vertices of self.

    • 'Floyd-Warshall-Python' – a Python implementation of the Floyd-Warshall algorithm. Works also with weighted graphs, even with negative weights (but no negative cycle is allowed). However, v must be None or v should contain all vertices of self.

    • 'Dijkstra_NetworkX' – the Dijkstra algorithm, implemented in NetworkX. It works with weighted graphs, but no negative weight is allowed.

    • 'Dijkstra_Boost' – the Dijkstra algorithm, implemented in Boost (works only with positive weights)

    • 'Johnson_Boost' – the Johnson algorithm, implemented in Boost (works also with negative weights, if there is no negative cycle). Works only if v is None or v should contain all vertices of self.

    • 'From_Dictionary' – uses the (already computed) distances, that are provided by input variable dist_dict

    • None (default): Sage chooses the best algorithm: 'From_Dictionary' if dist_dict is not None, 'BFS' for unweighted graphs, 'Dijkstra_Boost' if all weights are positive, 'Johnson_Boost' otherwise.

  • weight_function – function (default: None); a function that takes as input an edge (u, v, l) and outputs its weight. If not None, by_weight is automatically set to True. If None and by_weight is True, we use the edge label l as a weight, if l is not None, else 1 as a weight.

  • check_weight – boolean (default: True); if True, we check that the weight_function outputs a number for each edge

  • dist_dict – dictionary (default: None); a dict of dicts of distances (used only if algorithm=='From_Dictionary')

  • with_labels – boolean (default: False); whether to return a list or a dictionary keyed by vertices

EXAMPLES:

sage: G = graphs.KrackhardtKiteGraph()
sage: G.eccentricity()
[4, 4, 4, 4, 4, 3, 3, 2, 3, 4]
sage: G.vertices(sort=True)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
sage: G.eccentricity(7)
2
sage: G.eccentricity([7,8,9])
[2, 3, 4]
sage: G.eccentricity([7, 8, 9], with_labels=True) == {8: 3, 9: 4, 7: 2}
True
sage: G = Graph({0: [], 1: [], 2: [1]})
sage: G.eccentricity()
[+Infinity, +Infinity, +Infinity]
sage: G = Graph({0:[]})
sage: G.eccentricity(with_labels=True)
{0: 0}
sage: G = Graph({0:[], 1:[]})
sage: G.eccentricity(with_labels=True)
{0: +Infinity, 1: +Infinity}
sage: G = Graph([(0, 1, 1), (1, 2, 1), (0, 2, 3)])
sage: G.eccentricity(algorithm='BFS')
[1, 1, 1]
sage: G.eccentricity(algorithm='Floyd-Warshall-Cython')
[1, 1, 1]
sage: G.eccentricity(by_weight=True, algorithm='Dijkstra_NetworkX')         # needs networkx
[2, 1, 2]
sage: G.eccentricity(by_weight=True, algorithm='Dijkstra_Boost')
[2, 1, 2]
sage: G.eccentricity(by_weight=True, algorithm='Johnson_Boost')
[2, 1, 2]
sage: G.eccentricity(by_weight=True, algorithm='Floyd-Warshall-Python')
[2, 1, 2]
sage: G.eccentricity(dist_dict=G.shortest_path_all_pairs(by_weight=True)[0])
[2, 1, 2]
sage: G.eccentricity(by_weight=False, algorithm='DHV')
[1, 1, 1]
sage: G.eccentricity(by_weight=True, algorithm='DHV')
[2.0, 1.0, 2.0]
>>> from sage.all import *
>>> G = graphs.KrackhardtKiteGraph()
>>> G.eccentricity()
[4, 4, 4, 4, 4, 3, 3, 2, 3, 4]
>>> G.vertices(sort=True)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> G.eccentricity(Integer(7))
2
>>> G.eccentricity([Integer(7),Integer(8),Integer(9)])
[2, 3, 4]
>>> G.eccentricity([Integer(7), Integer(8), Integer(9)], with_labels=True) == {Integer(8): Integer(3), Integer(9): Integer(4), Integer(7): Integer(2)}
True
>>> G = Graph({Integer(0): [], Integer(1): [], Integer(2): [Integer(1)]})
>>> G.eccentricity()
[+Infinity, +Infinity, +Infinity]
>>> G = Graph({Integer(0):[]})
>>> G.eccentricity(with_labels=True)
{0: 0}
>>> G = Graph({Integer(0):[], Integer(1):[]})
>>> G.eccentricity(with_labels=True)
{0: +Infinity, 1: +Infinity}
>>> G = Graph([(Integer(0), Integer(1), Integer(1)), (Integer(1), Integer(2), Integer(1)), (Integer(0), Integer(2), Integer(3))])
>>> G.eccentricity(algorithm='BFS')
[1, 1, 1]
>>> G.eccentricity(algorithm='Floyd-Warshall-Cython')
[1, 1, 1]
>>> G.eccentricity(by_weight=True, algorithm='Dijkstra_NetworkX')         # needs networkx
[2, 1, 2]
>>> G.eccentricity(by_weight=True, algorithm='Dijkstra_Boost')
[2, 1, 2]
>>> G.eccentricity(by_weight=True, algorithm='Johnson_Boost')
[2, 1, 2]
>>> G.eccentricity(by_weight=True, algorithm='Floyd-Warshall-Python')
[2, 1, 2]
>>> G.eccentricity(dist_dict=G.shortest_path_all_pairs(by_weight=True)[Integer(0)])
[2, 1, 2]
>>> G.eccentricity(by_weight=False, algorithm='DHV')
[1, 1, 1]
>>> G.eccentricity(by_weight=True, algorithm='DHV')
[2.0, 1.0, 2.0]
edge_isoperimetric_number(g)[source]

Return the edge-isoperimetric number of the graph.

The edge-isoperimetric number of a graph \(G = (V,E)\) is also sometimes called the isoperimetric number. It is defined as the minimum of \(|\partial S| / |S|\) where \(\partial S\) is the edge boundary of \(S\) (number of edges with one end in \(S\) and one end in \(V \setminus S\)) and the minimum is taken over all subsets of vertices whose cardinality does not exceed half the size \(|V|\) of the graph.

See also

Alternative but similar quantities can be obtained via the methods cheeger_constant() and vertex_isoperimetric_number().

EXAMPLES:

The edge-isoperimetric number of a complete graph on \(n\) vertices is \(\lceil n/2 \rceil\):

sage: [graphs.CompleteGraph(n).edge_isoperimetric_number() for n in range(2,10)]
[1, 2, 2, 3, 3, 4, 4, 5]
>>> from sage.all import *
>>> [graphs.CompleteGraph(n).edge_isoperimetric_number() for n in range(Integer(2),Integer(10))]
[1, 2, 2, 3, 3, 4, 4, 5]

The edge-isoperimetric constant of a cycle on \(n\) vertices is \(2/\lfloor n/2 \rfloor\):

sage: [graphs.CycleGraph(n).edge_isoperimetric_number() for n in range(2,15)]
[1, 2, 1, 1, 2/3, 2/3, 1/2, 1/2, 2/5, 2/5, 1/3, 1/3, 2/7]
>>> from sage.all import *
>>> [graphs.CycleGraph(n).edge_isoperimetric_number() for n in range(Integer(2),Integer(15))]
[1, 2, 1, 1, 2/3, 2/3, 1/2, 1/2, 2/5, 2/5, 1/3, 1/3, 2/7]

In general, for \(d\)-regular graphs the edge-isoperimetric number is \(d\) times larger than the Cheeger constant of the graph:

sage: g = graphs.RandomRegular(3, 10)                                           # needs networkx
sage: g.edge_isoperimetric_number() == g.cheeger_constant() * 3                 # needs networkx
True
>>> from sage.all import *
>>> g = graphs.RandomRegular(Integer(3), Integer(10))                                           # needs networkx
>>> g.edge_isoperimetric_number() == g.cheeger_constant() * Integer(3)                 # needs networkx
True

And the edge-isoperimetric constant of a disconnected graph is \(0\):

sage: Graph([[1,2,3,4],[(1,2),(3,4)]]).edge_isoperimetric_number()
0
>>> from sage.all import *
>>> Graph([[Integer(1),Integer(2),Integer(3),Integer(4)],[(Integer(1),Integer(2)),(Integer(3),Integer(4))]]).edge_isoperimetric_number()
0
effective_resistance(i, j, base_ring)[source]

Return the effective resistance between nodes \(i\) and \(j\).

The resistance distance between vertices \(i\) and \(j\) of a simple connected graph \(G\) is defined as the effective resistance between the two vertices on an electrical network constructed from \(G\) replacing each edge of the graph by a unit (1 ohm) resistor.

See the Wikipedia article Resistance_distance for more information.

INPUT:

  • i, j – vertices of the graph

  • base_ring – a ring (default: None); the base ring of the matrix space to use

OUTPUT: rational number denoting resistance between nodes \(i\) and \(j\)

EXAMPLES:

Effective resistances in a straight linear 2-tree on 6 vertices

sage: # needs sage.modules
sage: G = Graph([(0,1),(0,2),(1,2),(1,3),(3,5),(2,4),(2,3),(3,4),(4,5)])
sage: G.effective_resistance(0,1)
34/55
sage: G.effective_resistance(0,3)
49/55
sage: G.effective_resistance(1,4)
9/11
sage: G.effective_resistance(0,5)
15/11
>>> from sage.all import *
>>> # needs sage.modules
>>> G = Graph([(Integer(0),Integer(1)),(Integer(0),Integer(2)),(Integer(1),Integer(2)),(Integer(1),Integer(3)),(Integer(3),Integer(5)),(Integer(2),Integer(4)),(Integer(2),Integer(3)),(Integer(3),Integer(4)),(Integer(4),Integer(5))])
>>> G.effective_resistance(Integer(0),Integer(1))
34/55
>>> G.effective_resistance(Integer(0),Integer(3))
49/55
>>> G.effective_resistance(Integer(1),Integer(4))
9/11
>>> G.effective_resistance(Integer(0),Integer(5))
15/11

Effective resistances in a fan on 6 vertices

sage: # needs sage.modules
sage: H = Graph([(0,1),(0,2),(0,3),(0,4),(0,5),(0,6),(1,2),(2,3),(3,4),(4,5)])
sage: H.effective_resistance(1,5)
6/5
sage: H.effective_resistance(1,3)
49/55
sage: H.effective_resistance(1,1)
0
>>> from sage.all import *
>>> # needs sage.modules
>>> H = Graph([(Integer(0),Integer(1)),(Integer(0),Integer(2)),(Integer(0),Integer(3)),(Integer(0),Integer(4)),(Integer(0),Integer(5)),(Integer(0),Integer(6)),(Integer(1),Integer(2)),(Integer(2),Integer(3)),(Integer(3),Integer(4)),(Integer(4),Integer(5))])
>>> H.effective_resistance(Integer(1),Integer(5))
6/5
>>> H.effective_resistance(Integer(1),Integer(3))
49/55
>>> H.effective_resistance(Integer(1),Integer(1))
0

Using a different base ring:

sage: H.effective_resistance(1, 5, base_ring=RDF)   # abs tol 1e-14         # needs numpy sage.modules
1.2000000000000000
sage: H.effective_resistance(1, 1, base_ring=RDF)                           # needs sage.modules
0.0
>>> from sage.all import *
>>> H.effective_resistance(Integer(1), Integer(5), base_ring=RDF)   # abs tol 1e-14         # needs numpy sage.modules
1.2000000000000000
>>> H.effective_resistance(Integer(1), Integer(1), base_ring=RDF)                           # needs sage.modules
0.0

See also

effective_resistance_matrix(vertices, nonedgesonly=None, base_ring=True, **kwds)[source]

Return a matrix whose (\(i\) , \(j\)) entry gives the effective resistance between vertices \(i\) and \(j\).

The resistance distance between vertices \(i\) and \(j\) of a simple connected graph \(G\) is defined as the effective resistance between the two vertices on an electrical network constructed from \(G\) replacing each edge of the graph by a unit (1 ohm) resistor.

By default, the matrix returned is over the rationals.

INPUT:

  • nonedgesonly – boolean (default: True); if True assign zero resistance to pairs of adjacent vertices.

  • vertices – list (default: None); the ordering of the vertices defining how they should appear in the matrix. By default, the ordering given by GenericGraph.vertices() is used.

  • base_ring – a ring (default: None); the base ring of the matrix space to use

  • **kwds – other keywords to pass to matrix()

OUTPUT: matrix

EXAMPLES:

The effective resistance matrix for a straight linear 2-tree counting only non-adjacent vertex pairs

sage: G = Graph([(0,1),(0,2),(1,2),(1,3),(3,5),(2,4),(2,3),(3,4),(4,5)])
sage: G.effective_resistance_matrix()                                       # needs sage.modules
[    0     0     0 49/55 59/55 15/11]
[    0     0     0     0  9/11 59/55]
[    0     0     0     0     0 49/55]
[49/55     0     0     0     0     0]
[59/55  9/11     0     0     0     0]
[15/11 59/55 49/55     0     0     0]
>>> from sage.all import *
>>> G = Graph([(Integer(0),Integer(1)),(Integer(0),Integer(2)),(Integer(1),Integer(2)),(Integer(1),Integer(3)),(Integer(3),Integer(5)),(Integer(2),Integer(4)),(Integer(2),Integer(3)),(Integer(3),Integer(4)),(Integer(4),Integer(5))])
>>> G.effective_resistance_matrix()                                       # needs sage.modules
[    0     0     0 49/55 59/55 15/11]
[    0     0     0     0  9/11 59/55]
[    0     0     0     0     0 49/55]
[49/55     0     0     0     0     0]
[59/55  9/11     0     0     0     0]
[15/11 59/55 49/55     0     0     0]

The same effective resistance matrix, this time including adjacent vertices

sage: G.effective_resistance_matrix(nonedgesonly=False)                     # needs sage.modules
[    0 34/55 34/55 49/55 59/55 15/11]
[34/55     0 26/55 31/55  9/11 59/55]
[34/55 26/55     0  5/11 31/55 49/55]
[49/55 31/55  5/11     0 26/55 34/55]
[59/55  9/11 31/55 26/55     0 34/55]
[15/11 59/55 49/55 34/55 34/55     0]
>>> from sage.all import *
>>> G.effective_resistance_matrix(nonedgesonly=False)                     # needs sage.modules
[    0 34/55 34/55 49/55 59/55 15/11]
[34/55     0 26/55 31/55  9/11 59/55]
[34/55 26/55     0  5/11 31/55 49/55]
[49/55 31/55  5/11     0 26/55 34/55]
[59/55  9/11 31/55 26/55     0 34/55]
[15/11 59/55 49/55 34/55 34/55     0]

This example illustrates the common neighbors matrix for a fan on 6 vertices counting only non-adjacent vertex pairs

sage: H = Graph([(0,1),(0,2),(0,3),(0,4),(0,5),(0,6),(1,2),(2,3),(3,4),(4,5)])
sage: H.effective_resistance_matrix()                                       # needs sage.modules
[    0     0     0     0     0     0     0]
[    0     0     0 49/55 56/55   6/5 89/55]
[    0     0     0     0   4/5 56/55 81/55]
[    0 49/55     0     0     0 49/55 16/11]
[    0 56/55   4/5     0     0     0 81/55]
[    0   6/5 56/55 49/55     0     0 89/55]
[    0 89/55 81/55 16/11 81/55 89/55     0]
>>> from sage.all import *
>>> H = Graph([(Integer(0),Integer(1)),(Integer(0),Integer(2)),(Integer(0),Integer(3)),(Integer(0),Integer(4)),(Integer(0),Integer(5)),(Integer(0),Integer(6)),(Integer(1),Integer(2)),(Integer(2),Integer(3)),(Integer(3),Integer(4)),(Integer(4),Integer(5))])
>>> H.effective_resistance_matrix()                                       # needs sage.modules
[    0     0     0     0     0     0     0]
[    0     0     0 49/55 56/55   6/5 89/55]
[    0     0     0     0   4/5 56/55 81/55]
[    0 49/55     0     0     0 49/55 16/11]
[    0 56/55   4/5     0     0     0 81/55]
[    0   6/5 56/55 49/55     0     0 89/55]
[    0 89/55 81/55 16/11 81/55 89/55     0]

A different base ring:

sage: H.effective_resistance_matrix(base_ring=RDF)[0, 0].parent()           # needs numpy sage.modules
Real Double Field
>>> from sage.all import *
>>> H.effective_resistance_matrix(base_ring=RDF)[Integer(0), Integer(0)].parent()           # needs numpy sage.modules
Real Double Field

See also

eulerian_orientation(G)[source]

Return an Eulerian orientation of the graph \(G\).

An Eulerian graph being a graph such that any vertex has an even degree, an Eulerian orientation of a graph is an orientation of its edges such that each vertex \(v\) verifies \(d^+(v)=d^-(v)=d(v)/2\), where \(d^+\) and \(d^-\) respectively represent the out-degree and the in-degree of a vertex.

If the graph is not Eulerian, the orientation verifies for any vertex \(v\) that \(| d^+(v)-d^-(v) | \leq 1\).

INPUT:

  • G – an undirected graph

ALGORITHM:

This algorithm is a random walk through the edges of the graph, which orients the edges according to the walk. When a vertex is reached which has no non-oriented edge (this vertex must have odd degree), the walk resumes at another vertex of odd degree, if any.

This algorithm has time complexity in \(O(n+m)\) for SparseGraph and \(O(n^2)\) for DenseGraph, where \(m\) is the number of edges in the graph and \(n\) is the number of vertices in the graph.

EXAMPLES:

The CubeGraph with parameter 4, which is regular of even degree, has an Eulerian orientation such that \(d^+ = d^-\):

sage: g = graphs.CubeGraph(4)
sage: g.degree()
[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
sage: o = g.eulerian_orientation()
sage: o.in_degree()
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
sage: o.out_degree()
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
>>> from sage.all import *
>>> g = graphs.CubeGraph(Integer(4))
>>> g.degree()
[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
>>> o = g.eulerian_orientation()
>>> o.in_degree()
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
>>> o.out_degree()
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]

Secondly, the Petersen Graph, which is 3 regular has an orientation such that the difference between \(d^+\) and \(d^-\) is at most 1:

sage: g = graphs.PetersenGraph()
sage: o = g.eulerian_orientation()
sage: o.in_degree()
[2, 2, 2, 2, 2, 1, 1, 1, 1, 1]
sage: o.out_degree()
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2]
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> o = g.eulerian_orientation()
>>> o.in_degree()
[2, 2, 2, 2, 2, 1, 1, 1, 1, 1]
>>> o.out_degree()
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2]
folded_graph(check=False)[source]

Return the antipodal fold of this graph.

Given an antipodal graph \(G\) let \(G_d\) be its distance-\(d\) graph. Then the folded graph of \(G\) has a vertex for each maximal clique of \(G_d\) and two cliques are adjacent if there is an edge in \(G\) connecting the two.

See also

sage.graphs.graph.is_antipodal()

INPUT:

  • check – boolean (default: False); whether to check if the graph is antipodal. If check is True and the graph is not antipodal, then return False.

OUTPUT: this function returns a new graph and self is not touched

Note

The input is expected to be an antipodal graph. You can check that a graph is antipodal using sage.graphs.graph.is_antipodal().

EXAMPLES:

sage: G = graphs.JohnsonGraph(10, 5)
sage: H = G.folded_graph(); H
Folded Johnson graph with parameters 10,5: Graph on 126 vertices
sage: Gd = G.distance_graph(G.diameter())
sage: all(i == 1 for i in Gd.degree())
True
sage: H.is_distance_regular(True)
([25, 16, None], [None, 1, 4])
>>> from sage.all import *
>>> G = graphs.JohnsonGraph(Integer(10), Integer(5))
>>> H = G.folded_graph(); H
Folded Johnson graph with parameters 10,5: Graph on 126 vertices
>>> Gd = G.distance_graph(G.diameter())
>>> all(i == Integer(1) for i in Gd.degree())
True
>>> H.is_distance_regular(True)
([25, 16, None], [None, 1, 4])

This method doesn’t check if the graph is antipodal:

sage: G = graphs.PetersenGraph()
sage: G.is_antipodal()
False
sage: G.folded_graph()  # some garbage
Folded Petersen graph: Graph on 2 vertices
sage: G.folded_graph(check=True)
False
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> G.is_antipodal()
False
>>> G.folded_graph()  # some garbage
Folded Petersen graph: Graph on 2 vertices
>>> G.folded_graph(check=True)
False

REFERENCES:

See [BCN1989] p. 438 or [Sam2012] for this definition of folded graph.

fractional_chromatic_index(G, solver='PPL', verbose_constraints=False, verbose=0)[source]

Return the fractional chromatic index of the graph.

The fractional chromatic index is a relaxed version of edge-coloring. An edge coloring of a graph being actually a covering of its edges into the smallest possible number of matchings, the fractional chromatic index of a graph \(G\) is the smallest real value \(\chi_f(G)\) such that there exists a list of matchings \(M_1, \ldots, M_k\) of \(G\) and coefficients \(\alpha_1, \ldots, \alpha_k\) with the property that each edge is covered by the matchings in the following relaxed way

\[\forall e \in E(G), \sum_{e \in M_i} \alpha_i \geq 1.\]

For more information, see the Wikipedia article Fractional_coloring.

ALGORITHM:

The fractional chromatic index is computed through Linear Programming through its dual. The LP solved by sage is actually:

\[\begin{split}\mbox{Maximize : }&\sum_{e\in E(G)} r_{e}\\ \mbox{Such that : }&\\ &\forall M\text{ matching }\subseteq G, \sum_{e\in M}r_{v}\leq 1\\\end{split}\]

INPUT:

  • G – a graph

  • solver – (default: 'PPL') specify a Linear Program (LP) solver to be used. If set to None, the default one is used. For more information on LP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

    Note

    The default solver used here is 'PPL' which provides exact results, i.e. a rational number, although this may be slower that using other solvers. Be aware that this method may loop endlessly when using some non exact solvers as reported in Issue #23658 and Issue #23798.

  • verbose_constraints – boolean (default: False); whether to display which constraints are being generated

  • verbose – integer (default: 0); sets the level of verbosity of the LP solver

EXAMPLES:

The fractional chromatic index of a \(C_5\) is \(5/2\):

sage: g = graphs.CycleGraph(5)
sage: g.fractional_chromatic_index()                                            # needs sage.numerical.mip
5/2
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(5))
>>> g.fractional_chromatic_index()                                            # needs sage.numerical.mip
5/2
fractional_chromatic_number(G, solver='PPL', verbose=0, check_components=True, check_bipartite=True)[source]

Return the fractional chromatic number of the graph.

Fractional coloring is a relaxed version of vertex coloring with several equivalent definitions, such as the optimum value in a linear relaxation of the integer program that gives the usual chromatic number. It is also equal to the fractional clique number by LP-duality.

ALGORITHM:

The fractional chromatic number is computed via the usual Linear Program. The LP solved by sage is essentially,

\[\begin{split}\mbox{Minimize : }&\sum_{I\in \mathcal{I}(G)} x_{I}\\ \mbox{Such that : }&\\ &\forall v\in V(G), \sum_{I\in \mathcal{I}(G),\, v\in I}x_{v}\geq 1\\ &\forall I\in \mathcal{I}(G), x_{I} \geq 0\end{split}\]

where \(\mathcal{I}(G)\) is the set of maximal independent sets of \(G\) (see Section 2.1 of [CFKPR2010] to know why it is sufficient to consider maximal independent sets). As optional optimisations, we construct the LP on each biconnected component of \(G\) (and output the maximum value), and avoid using the LP if G is bipartite (as then the output must be 1 or 2).

Note

Computing the fractional chromatic number can be very slow. Since the variables of the LP are independent sets, in general the LP has size exponential in the order of the graph. In the current implementation a list of all maximal independent sets is created and stored, which can be both slow and memory-hungry.

INPUT:

  • G – a graph

  • solver – (default: 'PPL') specify a Linear Program (LP) solver to be used. If set to None, the default one is used. For more information on LP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

    Note

    The default solver used here is 'PPL' which provides exact results, i.e. a rational number, although this may be slower that using other solvers.

  • verbose – integer (default: 0); sets the level of verbosity of the LP solver

  • check_components – boolean (default: True); whether the method is called on each biconnected component of \(G\)

  • check_bipartite – boolean (default: True); whether the graph is checked for bipartiteness. If the graph is bipartite then we can avoid creating and solving the LP.

EXAMPLES:

The fractional chromatic number of a \(C_5\) is \(5/2\):

sage: g = graphs.CycleGraph(5)
sage: g.fractional_chromatic_number()                                           # needs sage.numerical.mip
5/2
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(5))
>>> g.fractional_chromatic_number()                                           # needs sage.numerical.mip
5/2
fractional_clique_number(solver='PPL', verbose=0, check_components=True, check_bipartite=True)[source]

Return the fractional clique number of the graph.

A fractional clique is a nonnegative weight function on the vertices of a graph such that the sum of the weights over any independent set is at most 1. The fractional clique number is the largest total weight of a fractional clique, which is equal to the fractional chromatic number by LP-duality.

ALGORITHM:

The fractional clique number is computed via the Linear Program for fractional chromatic number, see fractional_chromatic_number

INPUT:

  • solver – (default: 'PPL') specify a Linear Program (LP) solver to be used. If set to None, the default one is used. For more information on LP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

    Note

    The default solver used here is 'PPL' which provides exact results, i.e. a rational number, although this may be slower that using other solvers.

  • verbose – integer (default: 0); sets the level of verbosity of the LP solver

  • check_components – boolean (default: True); whether the method is called on each biconnected component of \(G\)

  • check_bipartite – boolean (default: True); whether the graph is checked for bipartiteness. If the graph is bipartite then we can avoid creating and solving the LP.

EXAMPLES:

The fractional clique number of a \(C_7\) is \(7/3\):

sage: g = graphs.CycleGraph(7)
sage: g.fractional_clique_number()                                          # needs sage.numerical.mip
7/3
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(7))
>>> g.fractional_clique_number()                                          # needs sage.numerical.mip
7/3
geodetic_closure(G, S)[source]

Return the geodetic closure of the set of vertices \(S\) in \(G\).

The geodetic closure \(g(S)\) of a subset of vertices \(S\) of a graph \(G\) is in [HLT1993] as the set of all vertices that lie on a shortest \(u-v\) path for any pair of vertices \(u,v \in S\). We assume that \(g(\emptyset) = \emptyset\) and that \(g(\{u\}) = \{u\}\) for any \(u\) in \(G\).

Warning

This operation is not a closure function. Indeed, a closure function must satisfy the property that \(f(f(X))\) should be equal to \(f(X)\), which is not always the case here. The term closure is used here to follow the terminology of the domain. See for instance [HLT1993].

Here, we implement a simple algorithm to determine this set. Roughly, for each vertex \(u \in S\), the algorithm first performs a breadth first search from \(u\) to get distances, and then identifies the vertices of \(G\) lying on a shortest path from \(u\) to any \(v\in S\) using a reversal traversal from vertices in \(S\). This algorithm has time complexity in \(O(|S|(n + m))\) for SparseGraph, \(O(|S|(n + m) + n^2)\) for DenseGraph and space complexity in \(O(n + m)\).

INPUT:

  • G – a Sage graph

  • S – a subset of vertices of \(G\)

EXAMPLES:

The vertices of the Petersen graph can be obtained by a geodetic closure of four of its vertices:

sage: from sage.graphs.convexity_properties import geodetic_closure
sage: G = graphs.PetersenGraph()
sage: geodetic_closure(G, [0, 2, 8, 9])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> from sage.all import *
>>> from sage.graphs.convexity_properties import geodetic_closure
>>> G = graphs.PetersenGraph()
>>> geodetic_closure(G, [Integer(0), Integer(2), Integer(8), Integer(9)])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

The vertices of a 2D grid can be obtained by a geodetic closure of two vertices:

sage: G = graphs.Grid2dGraph(4, 4)
sage: c = G.geodetic_closure([(0, 0), (3, 3)])
sage: len(c) == G.order()
True
>>> from sage.all import *
>>> G = graphs.Grid2dGraph(Integer(4), Integer(4))
>>> c = G.geodetic_closure([(Integer(0), Integer(0)), (Integer(3), Integer(3))])
>>> len(c) == G.order()
True

If two vertices belong to different connected components of a graph, their geodetic closure is trivial:

sage: G = Graph([(0, 1), (2, 3)])
sage: geodetic_closure(G, [0, 2])
[0, 2]
>>> from sage.all import *
>>> G = Graph([(Integer(0), Integer(1)), (Integer(2), Integer(3))])
>>> geodetic_closure(G, [Integer(0), Integer(2)])
[0, 2]

The geodetic closure does not satisfy the closure function property that \(f(f(X))\) should be equal to \(f(X)\):

sage: G = graphs.DiamondGraph()
sage: G.subdivide_edge((1, 2), 1)
sage: geodetic_closure(G, [0, 3])
[0, 1, 2, 3]
sage: geodetic_closure(G, geodetic_closure(G, [0, 3]))
[0, 1, 2, 3, 4]
>>> from sage.all import *
>>> G = graphs.DiamondGraph()
>>> G.subdivide_edge((Integer(1), Integer(2)), Integer(1))
>>> geodetic_closure(G, [Integer(0), Integer(3)])
[0, 1, 2, 3]
>>> geodetic_closure(G, geodetic_closure(G, [Integer(0), Integer(3)]))
[0, 1, 2, 3, 4]
gomory_hu_tree(algorithm, solver=None, verbose=None, integrality_tolerance=0)[source]

Return a Gomory-Hu tree of self.

Given a tree \(T\) with labeled edges representing capacities, it is very easy to determine the maximum flow between any pair of vertices : it is the minimal label on the edges of the unique path between them.

Given a graph \(G\), a Gomory-Hu tree \(T\) of \(G\) is a tree with the same set of vertices, and such that the maximum flow between any two vertices is the same in \(G\) as in \(T\). See the Wikipedia article Gomory–Hu_tree. Note that, in general, a graph admits more than one Gomory-Hu tree.

See also 15.4 (Gomory-Hu trees) from [Sch2003].

INPUT:

  • algorithm – select the algorithm used by the edge_cut() method. Refer to its documentation for allowed values and default behaviour.

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

    Only useful when algorithm == "LP".

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

    Only useful when algorithm == "LP".

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

    Only useful when algorithm == "LP".

OUTPUT: a graph with labeled edges

EXAMPLES:

Taking the Petersen graph:

sage: g = graphs.PetersenGraph()
sage: t = g.gomory_hu_tree()
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> t = g.gomory_hu_tree()

Obviously, this graph is a tree:

sage: t.is_tree()
True
>>> from sage.all import *
>>> t.is_tree()
True

Note that if the original graph is not connected, then the Gomory-Hu tree is in fact a forest:

sage: (2*g).gomory_hu_tree().is_forest()
True
sage: (2*g).gomory_hu_tree().is_connected()
False
>>> from sage.all import *
>>> (Integer(2)*g).gomory_hu_tree().is_forest()
True
>>> (Integer(2)*g).gomory_hu_tree().is_connected()
False

On the other hand, such a tree has lost nothing of the initial graph connectedness:

sage: all(t.flow(u,v) == g.flow(u,v) for u,v in Subsets(g.vertices(sort=False), 2))
True
>>> from sage.all import *
>>> all(t.flow(u,v) == g.flow(u,v) for u,v in Subsets(g.vertices(sort=False), Integer(2)))
True

Just to make sure, we can check that the same is true for two vertices in a random graph:

sage: g = graphs.RandomGNP(20,.3)
sage: t = g.gomory_hu_tree()
sage: g.flow(0,1) == t.flow(0,1)
True
>>> from sage.all import *
>>> g = graphs.RandomGNP(Integer(20),RealNumber('.3'))
>>> t = g.gomory_hu_tree()
>>> g.flow(Integer(0),Integer(1)) == t.flow(Integer(0),Integer(1))
True

And also the min cut:

sage: g.edge_connectivity() == min(t.edge_labels()) or not g.is_connected()
True
>>> from sage.all import *
>>> g.edge_connectivity() == min(t.edge_labels()) or not g.is_connected()
True
graph6_string()[source]

Return the graph6 representation of the graph as an ASCII string.

This is only valid for simple (no loops, no multiple edges) graphs on at most \(2^{18}-1=262143\) vertices.

Note

As the graph6 format only handles graphs with vertex set \(\{0,...,n-1\}\), a relabelled copy will be encoded, if necessary.

See also

EXAMPLES:

sage: G = graphs.KrackhardtKiteGraph()
sage: G.graph6_string()
'IvUqwK@?G'
>>> from sage.all import *
>>> G = graphs.KrackhardtKiteGraph()
>>> G.graph6_string()
'IvUqwK@?G'
has_homomorphism_to(H, core, solver=False, verbose=None, integrality_tolerance=0)[source]

Check whether there is a homomorphism between two graphs.

A homomorphism from a graph \(G\) to a graph \(H\) is a function \(\phi:V(G)\mapsto V(H)\) such that for any edge \(uv \in E(G)\) the pair \(\phi(u)\phi(v)\) is an edge of \(H\).

Saying that a graph can be \(k\)-colored is equivalent to saying that it has a homomorphism to \(K_k\), the complete graph on \(k\) elements.

For more information, see the Wikipedia article Graph_homomorphism.

INPUT:

  • H – the graph to which self should be sent

  • core – boolean (default: False; whether to minimize the size of the mapping’s image (see note below). This is set to False by default.

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

Note

One can compute the core of a graph (with respect to homomorphism) with this method

sage: g = graphs.CycleGraph(10)
sage: mapping = g.has_homomorphism_to(g, core=True)                      # needs sage.numerical.mip
sage: print("The size of the core is {}".format(len(set(mapping.values()))))         # needs sage.numerical.mip
The size of the core is 2
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(10))
>>> mapping = g.has_homomorphism_to(g, core=True)                      # needs sage.numerical.mip
>>> print("The size of the core is {}".format(len(set(mapping.values()))))         # needs sage.numerical.mip
The size of the core is 2

OUTPUT:

This method returns False when the homomorphism does not exist, and returns the homomorphism otherwise as a dictionary associating a vertex of \(H\) to a vertex of \(G\).

EXAMPLES:

Is Petersen’s graph 3-colorable:

sage: P = graphs.PetersenGraph()
sage: P.has_homomorphism_to(graphs.CompleteGraph(3)) is not False           # needs sage.numerical.mip
True
>>> from sage.all import *
>>> P = graphs.PetersenGraph()
>>> P.has_homomorphism_to(graphs.CompleteGraph(Integer(3))) is not False           # needs sage.numerical.mip
True

An odd cycle admits a homomorphism to a smaller odd cycle, but not to an even cycle:

sage: g = graphs.CycleGraph(9)
sage: g.has_homomorphism_to(graphs.CycleGraph(5)) is not False              # needs sage.numerical.mip
True
sage: g.has_homomorphism_to(graphs.CycleGraph(7)) is not False              # needs sage.numerical.mip
True
sage: g.has_homomorphism_to(graphs.CycleGraph(4)) is not False              # needs sage.numerical.mip
False
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(9))
>>> g.has_homomorphism_to(graphs.CycleGraph(Integer(5))) is not False              # needs sage.numerical.mip
True
>>> g.has_homomorphism_to(graphs.CycleGraph(Integer(7))) is not False              # needs sage.numerical.mip
True
>>> g.has_homomorphism_to(graphs.CycleGraph(Integer(4))) is not False              # needs sage.numerical.mip
False
has_perfect_matching(G, algorithm, solver='Edmonds', verbose=None, integrality_tolerance=0)[source]

Return whether the graph has a perfect matching.

INPUT:

  • algorithm – string (default: 'Edmonds')

    • 'Edmonds' uses Edmonds’ algorithm as implemented in NetworkX to find a matching of maximal cardinality, then check whether this cardinality is half the number of vertices of the graph.

    • 'LP_matching' uses a Linear Program to find a matching of maximal cardinality, then check whether this cardinality is half the number of vertices of the graph.

    • 'LP' uses a Linear Program formulation of the perfect matching problem: put a binary variable b[e] on each edge \(e\), and for each vertex \(v\), require that the sum of the values of the edges incident to \(v\) is 1.

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity: set to 0 by default, which means quiet (only useful when algorithm == "LP_matching" or algorithm == "LP")

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

OUTPUT: boolean

EXAMPLES:

sage: graphs.PetersenGraph().has_perfect_matching()                         # needs networkx
True
sage: graphs.WheelGraph(6).has_perfect_matching()                           # needs networkx
True
sage: graphs.WheelGraph(5).has_perfect_matching()                           # needs networkx
False
sage: graphs.PetersenGraph().has_perfect_matching(algorithm='LP_matching')  # needs sage.numerical.mip
True
sage: graphs.WheelGraph(6).has_perfect_matching(algorithm='LP_matching')    # needs sage.numerical.mip
True
sage: graphs.WheelGraph(5).has_perfect_matching(algorithm='LP_matching')
False
sage: graphs.PetersenGraph().has_perfect_matching(algorithm='LP_matching')  # needs sage.numerical.mip
True
sage: graphs.WheelGraph(6).has_perfect_matching(algorithm='LP_matching')    # needs sage.numerical.mip
True
sage: graphs.WheelGraph(5).has_perfect_matching(algorithm='LP_matching')
False
>>> from sage.all import *
>>> graphs.PetersenGraph().has_perfect_matching()                         # needs networkx
True
>>> graphs.WheelGraph(Integer(6)).has_perfect_matching()                           # needs networkx
True
>>> graphs.WheelGraph(Integer(5)).has_perfect_matching()                           # needs networkx
False
>>> graphs.PetersenGraph().has_perfect_matching(algorithm='LP_matching')  # needs sage.numerical.mip
True
>>> graphs.WheelGraph(Integer(6)).has_perfect_matching(algorithm='LP_matching')    # needs sage.numerical.mip
True
>>> graphs.WheelGraph(Integer(5)).has_perfect_matching(algorithm='LP_matching')
False
>>> graphs.PetersenGraph().has_perfect_matching(algorithm='LP_matching')  # needs sage.numerical.mip
True
>>> graphs.WheelGraph(Integer(6)).has_perfect_matching(algorithm='LP_matching')    # needs sage.numerical.mip
True
>>> graphs.WheelGraph(Integer(5)).has_perfect_matching(algorithm='LP_matching')
False
hyperbolicity(G, algorithm='BCCM', approximation_factor=None, additive_gap=None, verbose=False)[source]

Return the hyperbolicity of the graph or an approximation of this value.

The hyperbolicity of a graph has been defined by Gromov [Gro1987] as follows: Let \(a, b, c, d\) be vertices of the graph, let \(S_1 = dist(a, b) + dist(b, c)\), \(S_2 = dist(a, c) + dist(b, d)\), and \(S_3 = dist(a, d) + dist(b, c)\), and let \(M_1\) and \(M_2\) be the two largest values among \(S_1\), \(S_2\), and \(S_3\). We have \(hyp(a, b, c, d) = |M_1 - M_2|\), and the hyperbolicity of the graph is the maximum over all possible 4-tuples \((a,b, c,d)\) divided by 2. The worst case time complexity is in \(O( n^4 )\).

See the documentation of sage.graphs.hyperbolicity for more information.

INPUT:

  • G – a connected Graph

  • algorithm – (default: 'BCCM') specifies the algorithm to use among:

    • 'basic' is an exhaustive algorithm considering all possible 4-tuples and so have time complexity in \(O(n^4)\).

    • 'CCL' is an exact algorithm proposed in [CCL2015]. It considers the 4-tuples in an ordering allowing to cut the search space as soon as a new lower bound is found (see the module’s documentation). This algorithm can be turned into a approximation algorithm.

    • 'CCL+FA' or 'CCL+' uses the notion of far-apart pairs as proposed in [Sot2011] to significantly reduce the overall computation time of the 'CCL' algorithm.

    • 'BCCM' is an exact algorithm proposed in [BCCM2015]. It improves 'CCL+FA' by cutting several 4-tuples (for more information, see the module’s documentation).

    • 'dom' is an approximation with additive constant four. It computes the hyperbolicity of the vertices of a dominating set of the graph. This is sometimes slower than 'CCL' and sometimes faster. Try it to know if it is interesting for you. The additive_gap and approximation_factor parameters cannot be used in combination with this method and so are ignored.

  • approximation_factor – (default: None) when the approximation factor is set to some value (larger than 1.0), the function stop computations as soon as the ratio between the upper bound and the best found solution is less than the approximation factor. When the approximation factor is 1.0, the problem is solved optimally. This parameter is used only when the chosen algorithm is 'CCL', 'CCL+FA', or 'BCCM'.

  • additive_gap – (default: None) when set to a positive number, the function stop computations as soon as the difference between the upper bound and the best found solution is less than additive gap. When the gap is 0.0, the problem is solved optimally. This parameter is used only when the chosen algorithm is 'CCL' or 'CCL+FA', or 'BCCM'.

  • verbose – boolean (default: False); set to True to display some information during execution: new upper and lower bounds, etc.

OUTPUT:

This function returns the tuple ( delta, certificate, delta_UB ), where:

  • delta – the hyperbolicity of the graph (half-integer value)

  • certificate – is the list of the 4 vertices for which the maximum value has been computed, and so the hyperbolicity of the graph

  • delta_UB – is an upper bound for delta. When delta == delta_UB, the returned solution is optimal. Otherwise, the approximation factor if delta_UB/delta.

EXAMPLES:

Hyperbolicity of a \(3\times 3\) grid:

sage: from sage.graphs.hyperbolicity import hyperbolicity
sage: G = graphs.Grid2dGraph(3, 3)
sage: L,C,U = hyperbolicity(G, algorithm='BCCM'); L,sorted(C),U
(2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2)
sage: L,C,U = hyperbolicity(G, algorithm='CCL'); L,sorted(C),U
(2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2)
sage: L,C,U = hyperbolicity(G, algorithm='basic'); L,sorted(C),U
(2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2)
>>> from sage.all import *
>>> from sage.graphs.hyperbolicity import hyperbolicity
>>> G = graphs.Grid2dGraph(Integer(3), Integer(3))
>>> L,C,U = hyperbolicity(G, algorithm='BCCM'); L,sorted(C),U
(2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2)
>>> L,C,U = hyperbolicity(G, algorithm='CCL'); L,sorted(C),U
(2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2)
>>> L,C,U = hyperbolicity(G, algorithm='basic'); L,sorted(C),U
(2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2)

Hyperbolicity of a PetersenGraph:

sage: from sage.graphs.hyperbolicity import hyperbolicity
sage: G = graphs.PetersenGraph()
sage: L,C,U = hyperbolicity(G, algorithm='BCCM'); L,sorted(C),U
(1/2, [6, 7, 8, 9], 1/2)
sage: L,C,U = hyperbolicity(G, algorithm='CCL'); L,sorted(C),U
(1/2, [0, 1, 2, 3], 1/2)
sage: L,C,U = hyperbolicity(G, algorithm='CCL+'); L,sorted(C),U
(1/2, [0, 1, 2, 3], 1/2)
sage: L,C,U = hyperbolicity(G, algorithm='CCL+FA'); L,sorted(C),U
(1/2, [0, 1, 2, 3], 1/2)
sage: L,C,U = hyperbolicity(G, algorithm='basic'); L,sorted(C),U
(1/2, [0, 1, 2, 3], 1/2)
sage: L,C,U = hyperbolicity(G, algorithm='dom'); L,U
(0, 1)
sage: sorted(C)  # random
[0, 1, 2, 6]
>>> from sage.all import *
>>> from sage.graphs.hyperbolicity import hyperbolicity
>>> G = graphs.PetersenGraph()
>>> L,C,U = hyperbolicity(G, algorithm='BCCM'); L,sorted(C),U
(1/2, [6, 7, 8, 9], 1/2)
>>> L,C,U = hyperbolicity(G, algorithm='CCL'); L,sorted(C),U
(1/2, [0, 1, 2, 3], 1/2)
>>> L,C,U = hyperbolicity(G, algorithm='CCL+'); L,sorted(C),U
(1/2, [0, 1, 2, 3], 1/2)
>>> L,C,U = hyperbolicity(G, algorithm='CCL+FA'); L,sorted(C),U
(1/2, [0, 1, 2, 3], 1/2)
>>> L,C,U = hyperbolicity(G, algorithm='basic'); L,sorted(C),U
(1/2, [0, 1, 2, 3], 1/2)
>>> L,C,U = hyperbolicity(G, algorithm='dom'); L,U
(0, 1)
>>> sorted(C)  # random
[0, 1, 2, 6]

Asking for an approximation in a grid graph:

sage: from sage.graphs.hyperbolicity import hyperbolicity
sage: G = graphs.Grid2dGraph(2, 10)
sage: L,C,U = hyperbolicity(G, algorithm='CCL', approximation_factor=1.5); L,U
(1, 3/2)
sage: L,C,U = hyperbolicity(G, algorithm='CCL+', approximation_factor=1.5); L,U
(1, 1)
sage: L,C,U = hyperbolicity(G, algorithm='CCL', approximation_factor=4); L,U
(1, 4)
sage: L,C,U = hyperbolicity(G, algorithm='CCL', additive_gap=2); L,U
(1, 3)
sage: L,C,U = hyperbolicity(G, algorithm='dom'); L,U
(1, 5)
>>> from sage.all import *
>>> from sage.graphs.hyperbolicity import hyperbolicity
>>> G = graphs.Grid2dGraph(Integer(2), Integer(10))
>>> L,C,U = hyperbolicity(G, algorithm='CCL', approximation_factor=RealNumber('1.5')); L,U
(1, 3/2)
>>> L,C,U = hyperbolicity(G, algorithm='CCL+', approximation_factor=RealNumber('1.5')); L,U
(1, 1)
>>> L,C,U = hyperbolicity(G, algorithm='CCL', approximation_factor=Integer(4)); L,U
(1, 4)
>>> L,C,U = hyperbolicity(G, algorithm='CCL', additive_gap=Integer(2)); L,U
(1, 3)
>>> L,C,U = hyperbolicity(G, algorithm='dom'); L,U
(1, 5)

Asking for an approximation in a cycle graph:

sage: from sage.graphs.hyperbolicity import hyperbolicity
sage: G = graphs.CycleGraph(10)
sage: L,C,U = hyperbolicity(G, algorithm='CCL', approximation_factor=1.5); L,U
(2, 5/2)
sage: L,C,U = hyperbolicity(G, algorithm='CCL+FA', approximation_factor=1.5); L,U
(2, 5/2)
sage: L,C,U = hyperbolicity(G, algorithm='CCL+FA', additive_gap=1); L,U
(2, 5/2)
>>> from sage.all import *
>>> from sage.graphs.hyperbolicity import hyperbolicity
>>> G = graphs.CycleGraph(Integer(10))
>>> L,C,U = hyperbolicity(G, algorithm='CCL', approximation_factor=RealNumber('1.5')); L,U
(2, 5/2)
>>> L,C,U = hyperbolicity(G, algorithm='CCL+FA', approximation_factor=RealNumber('1.5')); L,U
(2, 5/2)
>>> L,C,U = hyperbolicity(G, algorithm='CCL+FA', additive_gap=Integer(1)); L,U
(2, 5/2)

Comparison of results:

sage: from sage.graphs.hyperbolicity import hyperbolicity
sage: for i in range(10):               # long time                             # needs networkx
....:     G = graphs.RandomBarabasiAlbert(100,2)
....:     d1,_,_ = hyperbolicity(G, algorithm='basic')
....:     d2,_,_ = hyperbolicity(G, algorithm='CCL')
....:     d3,_,_ = hyperbolicity(G, algorithm='CCL+')
....:     d4,_,_ = hyperbolicity(G, algorithm='CCL+FA')
....:     d5,_,_ = hyperbolicity(G, algorithm='BCCM')
....:     l3,_,u3 = hyperbolicity(G, approximation_factor=2)
....:     if (not d1==d2==d3==d4==d5) or l3>d1 or u3<d1:
....:        print("That's not good!")

sage: from sage.graphs.hyperbolicity import hyperbolicity
sage: import random
sage: random.seed()
sage: for i in range(10):               # long time                             # needs networkx
....:     n = random.randint(2, 20)
....:     m = random.randint(0, n*(n-1) / 2)
....:     G = graphs.RandomGNM(n, m)
....:     for cc in G.connected_components_subgraphs():
....:         d1,_,_ = hyperbolicity(cc, algorithm='basic')
....:         d2,_,_ = hyperbolicity(cc, algorithm='CCL')
....:         d3,_,_ = hyperbolicity(cc, algorithm='CCL+')
....:         d4,_,_ = hyperbolicity(cc, algorithm='CCL+FA')
....:         d5,_,_ = hyperbolicity(cc, algorithm='BCCM')
....:         l3,_,u3 = hyperbolicity(cc, approximation_factor=2)
....:         if (not d1==d2==d3==d4==d5) or l3>d1 or u3<d1:
....:             print("Error in graph ", cc.edges(sort=True))
>>> from sage.all import *
>>> from sage.graphs.hyperbolicity import hyperbolicity
>>> for i in range(Integer(10)):               # long time                             # needs networkx
...     G = graphs.RandomBarabasiAlbert(Integer(100),Integer(2))
...     d1,_,_ = hyperbolicity(G, algorithm='basic')
...     d2,_,_ = hyperbolicity(G, algorithm='CCL')
...     d3,_,_ = hyperbolicity(G, algorithm='CCL+')
...     d4,_,_ = hyperbolicity(G, algorithm='CCL+FA')
...     d5,_,_ = hyperbolicity(G, algorithm='BCCM')
...     l3,_,u3 = hyperbolicity(G, approximation_factor=Integer(2))
...     if (not d1==d2==d3==d4==d5) or l3>d1 or u3<d1:
...        print("That's not good!")

>>> from sage.graphs.hyperbolicity import hyperbolicity
>>> import random
>>> random.seed()
>>> for i in range(Integer(10)):               # long time                             # needs networkx
...     n = random.randint(Integer(2), Integer(20))
...     m = random.randint(Integer(0), n*(n-Integer(1)) / Integer(2))
...     G = graphs.RandomGNM(n, m)
...     for cc in G.connected_components_subgraphs():
...         d1,_,_ = hyperbolicity(cc, algorithm='basic')
...         d2,_,_ = hyperbolicity(cc, algorithm='CCL')
...         d3,_,_ = hyperbolicity(cc, algorithm='CCL+')
...         d4,_,_ = hyperbolicity(cc, algorithm='CCL+FA')
...         d5,_,_ = hyperbolicity(cc, algorithm='BCCM')
...         l3,_,u3 = hyperbolicity(cc, approximation_factor=Integer(2))
...         if (not d1==d2==d3==d4==d5) or l3>d1 or u3<d1:
...             print("Error in graph ", cc.edges(sort=True))

The hyperbolicity of a graph is the maximum value over all its biconnected components:

sage: from sage.graphs.hyperbolicity import hyperbolicity
sage: G = graphs.PetersenGraph() * 2
sage: G.add_edge(0, 11)
sage: L,C,U = hyperbolicity(G); L,sorted(C),U
(1/2, [6, 7, 8, 9], 1/2)
>>> from sage.all import *
>>> from sage.graphs.hyperbolicity import hyperbolicity
>>> G = graphs.PetersenGraph() * Integer(2)
>>> G.add_edge(Integer(0), Integer(11))
>>> L,C,U = hyperbolicity(G); L,sorted(C),U
(1/2, [6, 7, 8, 9], 1/2)
ihara_zeta_function_inverse()[source]

Compute the inverse of the Ihara zeta function of the graph.

This is a polynomial in one variable with integer coefficients. The Ihara zeta function itself is the inverse of this polynomial.

See the Wikipedia article Ihara zeta function for more information.

ALGORITHM:

This is computed here as the (reversed) characteristic polynomial of a square matrix of size twice the number of edges, related to the adjacency matrix of the line graph, see for example Proposition 9 in [SS2008] and Def. 4.1 in [Ter2011].

The graph is first replaced by its 2-core, as this does not change the Ihara zeta function.

EXAMPLES:

sage: G = graphs.CompleteGraph(4)
sage: factor(G.ihara_zeta_function_inverse())                               # needs sage.libs.pari sage.modules
(2*t - 1) * (t + 1)^2 * (t - 1)^3 * (2*t^2 + t + 1)^3

sage: G = graphs.CompleteGraph(5)
sage: factor(G.ihara_zeta_function_inverse())                               # needs sage.libs.pari sage.modules
(-1) * (3*t - 1) * (t + 1)^5 * (t - 1)^6 * (3*t^2 + t + 1)^4

sage: G = graphs.PetersenGraph()
sage: factor(G.ihara_zeta_function_inverse())                               # needs sage.libs.pari sage.modules
(-1) * (2*t - 1) * (t + 1)^5 * (t - 1)^6 * (2*t^2 + 2*t + 1)^4
* (2*t^2 - t + 1)^5

sage: G = graphs.RandomTree(10)
sage: G.ihara_zeta_function_inverse()                                       # needs sage.libs.pari sage.modules
1
>>> from sage.all import *
>>> G = graphs.CompleteGraph(Integer(4))
>>> factor(G.ihara_zeta_function_inverse())                               # needs sage.libs.pari sage.modules
(2*t - 1) * (t + 1)^2 * (t - 1)^3 * (2*t^2 + t + 1)^3

>>> G = graphs.CompleteGraph(Integer(5))
>>> factor(G.ihara_zeta_function_inverse())                               # needs sage.libs.pari sage.modules
(-1) * (3*t - 1) * (t + 1)^5 * (t - 1)^6 * (3*t^2 + t + 1)^4

>>> G = graphs.PetersenGraph()
>>> factor(G.ihara_zeta_function_inverse())                               # needs sage.libs.pari sage.modules
(-1) * (2*t - 1) * (t + 1)^5 * (t - 1)^6 * (2*t^2 + 2*t + 1)^4
* (2*t^2 - t + 1)^5

>>> G = graphs.RandomTree(Integer(10))
>>> G.ihara_zeta_function_inverse()                                       # needs sage.libs.pari sage.modules
1

REFERENCES:

[HST2001]

independent_set(algorithm, value_only='Cliquer', reduction_rules=False, solver=True, verbose=None, integrality_tolerance=0)[source]

Return a maximum independent set.

An independent set of a graph is a set of pairwise non-adjacent vertices. A maximum independent set is an independent set of maximum cardinality. It induces an empty subgraph.

Equivalently, an independent set is defined as the complement of a vertex cover.

For more information, see the Wikipedia article Independent_set_(graph_theory) and the Wikipedia article Vertex_cover.

INPUT:

  • algorithm – the algorithm to be used

  • value_only – boolean (default: False); if set to True, only the size of a maximum independent set is returned. Otherwise, a maximum independent set is returned as a list of vertices.

  • reduction_rules – (default: True) specify if the reductions rules from kernelization must be applied as pre-processing or not. See [ACFLSS04] for more details. Note that depending on the instance, it might be faster to disable reduction rules.

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

Note

While Cliquer/MCAD are usually (and by far) the most efficient implementations, the MILP formulation sometimes proves faster on very “symmetrical” graphs.

EXAMPLES:

Using Cliquer:

sage: C = graphs.PetersenGraph()
sage: C.independent_set()
[0, 3, 6, 7]
>>> from sage.all import *
>>> C = graphs.PetersenGraph()
>>> C.independent_set()
[0, 3, 6, 7]

As a linear program:

sage: C = graphs.PetersenGraph()
sage: len(C.independent_set(algorithm='MILP'))                              # needs sage.numerical.mip
4
>>> from sage.all import *
>>> C = graphs.PetersenGraph()
>>> len(C.independent_set(algorithm='MILP'))                              # needs sage.numerical.mip
4
../../_images/graph-2.svg
independent_set_of_representatives(family, solver, verbose=None, integrality_tolerance=0)[source]

Return an independent set of representatives.

Given a graph \(G\) and a family \(F=\{F_i:i\in [1,...,k]\}\) of subsets of g.vertices(sort=False), an Independent Set of Representatives (ISR) is an assignation of a vertex \(v_i\in F_i\) to each set \(F_i\) such that \(v_i != v_j\) if \(i<j\) (they are representatives) and the set \(\cup_{i}v_i\) is an independent set in \(G\).

It generalizes, for example, graph coloring and graph list coloring.

(See [ABZ2007] for more information.)

INPUT:

  • family – list of lists defining the family \(F\) (actually, a Family of subsets of G.vertices(sort=False))

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

OUTPUT:

  • A list whose \(i^{\mbox{th}}\) element is the representative of the \(i^{\mbox{th}}\) element of the family list. If there is no ISR, None is returned.

EXAMPLES:

For a bipartite graph missing one edge, the solution is as expected:

sage: g = graphs.CompleteBipartiteGraph(3,3)
sage: g.delete_edge(1,4)
sage: g.independent_set_of_representatives([[0,1,2],[3,4,5]])                # needs sage.numerical.mip
[1, 4]
>>> from sage.all import *
>>> g = graphs.CompleteBipartiteGraph(Integer(3),Integer(3))
>>> g.delete_edge(Integer(1),Integer(4))
>>> g.independent_set_of_representatives([[Integer(0),Integer(1),Integer(2)],[Integer(3),Integer(4),Integer(5)]])                # needs sage.numerical.mip
[1, 4]

The Petersen Graph is 3-colorable, which can be expressed as an independent set of representatives problem : take 3 disjoint copies of the Petersen Graph, each one representing one color. Then take as a partition of the set of vertices the family defined by the three copies of each vertex. The ISR of such a family defines a 3-coloring:

sage: # needs sage.numerical.mip
sage: g = 3 * graphs.PetersenGraph()
sage: n = g.order() / 3
sage: f = [[i, i + n, i + 2*n] for i in range(n)]
sage: isr = g.independent_set_of_representatives(f)
sage: c = [integer_floor(i / n) for i in isr]
sage: color_classes = [[], [], []]
sage: for v, i in enumerate(c):
....:   color_classes[i].append(v)
sage: for classs in color_classes:
....:   g.subgraph(classs).size() == 0
True
True
True
>>> from sage.all import *
>>> # needs sage.numerical.mip
>>> g = Integer(3) * graphs.PetersenGraph()
>>> n = g.order() / Integer(3)
>>> f = [[i, i + n, i + Integer(2)*n] for i in range(n)]
>>> isr = g.independent_set_of_representatives(f)
>>> c = [integer_floor(i / n) for i in isr]
>>> color_classes = [[], [], []]
>>> for v, i in enumerate(c):
...   color_classes[i].append(v)
>>> for classs in color_classes:
...   g.subgraph(classs).size() == Integer(0)
True
True
True
is_antipodal()[source]

Check whether this graph is antipodal.

A graph \(G\) of diameter \(d\) is said to be antipodal if its distance-\(d\) graph is a disjoint union of cliques.

EXAMPLES:

sage: G = graphs.JohnsonGraph(10, 5)
sage: G.is_antipodal()
True
sage: H = G.folded_graph()
sage: H.is_antipodal()
False
>>> from sage.all import *
>>> G = graphs.JohnsonGraph(Integer(10), Integer(5))
>>> G.is_antipodal()
True
>>> H = G.folded_graph()
>>> H.is_antipodal()
False

REFERENCES:

See [BCN1989] p. 438 or [Sam2012] for this definition of antipodal graphs.

is_apex()[source]

Test if the graph is apex.

A graph is apex if it can be made planar by the removal of a single vertex. The deleted vertex is called an apex of the graph, and a graph may have more than one apex. For instance, in the minimal nonplanar graphs \(K_5\) or \(K_{3,3}\), every vertex is an apex. The apex graphs include graphs that are themselves planar, in which case again every vertex is an apex. The null graph is also counted as an apex graph even though it has no vertex to remove. If the graph is not connected, we say that it is apex if it has at most one non planar connected component and that this component is apex. See the Wikipedia article Apex_graph for more information.

EXAMPLES:

\(K_5\) and \(K_{3,3}\) are apex graphs, and each of their vertices is an apex:

sage: G = graphs.CompleteGraph(5)
sage: G.is_apex()
True
sage: G = graphs.CompleteBipartiteGraph(3,3)
sage: G.is_apex()
True
>>> from sage.all import *
>>> G = graphs.CompleteGraph(Integer(5))
>>> G.is_apex()
True
>>> G = graphs.CompleteBipartiteGraph(Integer(3),Integer(3))
>>> G.is_apex()
True

The Petersen graph is not apex:

sage: G = graphs.PetersenGraph()
sage: G.is_apex()
False
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> G.is_apex()
False

A graph is apex if all its connected components are apex, but at most one is not planar:

sage: M = graphs.Grid2dGraph(3,3)
sage: K5 = graphs.CompleteGraph(5)
sage: (M+K5).is_apex()
True
sage: (M+K5+K5).is_apex()
False
>>> from sage.all import *
>>> M = graphs.Grid2dGraph(Integer(3),Integer(3))
>>> K5 = graphs.CompleteGraph(Integer(5))
>>> (M+K5).is_apex()
True
>>> (M+K5+K5).is_apex()
False
is_arc_transitive()[source]

Check if self is an arc-transitive graph.

A graph is arc-transitive if its automorphism group acts transitively on its pairs of adjacent vertices.

Equivalently, if there exists for any pair of edges \(uv,u'v'\in E(G)\) an automorphism \(\phi_1\) of \(G\) such that \(\phi_1(u)=u'\) and \(\phi_1(v)=v'\), as well as another automorphism \(\phi_2\) of \(G\) such that \(\phi_2(u)=v'\) and \(\phi_2(v)=u'\)

EXAMPLES:

sage: P = graphs.PetersenGraph()
sage: P.is_arc_transitive()                                                 # needs sage.libs.gap
True
sage: G = graphs.GrayGraph()                                                # needs networkx
sage: G.is_arc_transitive()                                                 # needs networkx sage.libs.gap
False
>>> from sage.all import *
>>> P = graphs.PetersenGraph()
>>> P.is_arc_transitive()                                                 # needs sage.libs.gap
True
>>> G = graphs.GrayGraph()                                                # needs networkx
>>> G.is_arc_transitive()                                                 # needs networkx sage.libs.gap
False
is_asteroidal_triple_free(G, certificate=False)[source]

Test if the input graph is asteroidal triple-free.

An independent set of three vertices such that each pair is joined by a path that avoids the neighborhood of the third one is called an asteroidal triple. A graph is asteroidal triple-free (AT-free) if it contains no asteroidal triples. See the module's documentation for more details.

This method returns True if the graph is AT-free and False otherwise.

INPUT:

  • G – a Graph

  • certificate – boolean (default: False); by default, this method returns True if the graph is asteroidal triple-free and False otherwise. When certificate==True, this method returns in addition a list of three vertices forming an asteroidal triple if such a triple is found, and the empty list otherwise.

EXAMPLES:

The complete graph is AT-free, as well as its line graph:

sage: G = graphs.CompleteGraph(5)
sage: G.is_asteroidal_triple_free()
True
sage: G.is_asteroidal_triple_free(certificate=True)
(True, [])
sage: LG = G.line_graph()
sage: LG.is_asteroidal_triple_free()
True
sage: LLG = LG.line_graph()
sage: LLG.is_asteroidal_triple_free()
False
>>> from sage.all import *
>>> G = graphs.CompleteGraph(Integer(5))
>>> G.is_asteroidal_triple_free()
True
>>> G.is_asteroidal_triple_free(certificate=True)
(True, [])
>>> LG = G.line_graph()
>>> LG.is_asteroidal_triple_free()
True
>>> LLG = LG.line_graph()
>>> LLG.is_asteroidal_triple_free()
False

The PetersenGraph is not AT-free:

sage: from sage.graphs.asteroidal_triples import *
sage: G = graphs.PetersenGraph()
sage: G.is_asteroidal_triple_free()
False
sage: G.is_asteroidal_triple_free(certificate=True)
(False, [0, 2, 6])
>>> from sage.all import *
>>> from sage.graphs.asteroidal_triples import *
>>> G = graphs.PetersenGraph()
>>> G.is_asteroidal_triple_free()
False
>>> G.is_asteroidal_triple_free(certificate=True)
(False, [0, 2, 6])
is_biconnected()[source]

Test if the graph is biconnected.

A biconnected graph is a connected graph on two or more vertices that is not broken into disconnected pieces by deleting any single vertex.

EXAMPLES:

sage: G = graphs.PetersenGraph()
sage: G.is_biconnected()
True
sage: G.add_path([0,'a','b'])
sage: G.is_biconnected()
False
sage: G.add_edge('b', 1)
sage: G.is_biconnected()
True
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> G.is_biconnected()
True
>>> G.add_path([Integer(0),'a','b'])
>>> G.is_biconnected()
False
>>> G.add_edge('b', Integer(1))
>>> G.is_biconnected()
True
is_bicritical(G, matching, algorithm=None, coNP_certificate='Edmonds', solver=False, verbose=None, integrality_tolerance=0)[source]

Check if the graph is bicritical.

A nontrivial graph \(G\) is bicritical if \(G - u - v\) has a perfect matching for any two distinct vertices \(u\) and \(v\) of \(G\). Bicritical graphs are special kind of matching covered graphs. Each maximal barrier of a bicritical graph is a singleton. Thus, for a bicritical graph, the canonical partition of the vertex set is the set of sets where each set is an indiviudal vertex. Three-connected bicritical graphs, aka bricks, play an important role in the theory of matching covered graphs.

This method implements the algorithm proposed in [LZ2001] and we assume that a connected graph of order two is bicritical, whereas a disconnected graph of the same order is not. The time complexity of the algorithm is \(\mathcal{O}(|V| \cdot |E|)\), if a perfect matching of the graph is given, where \(|V|\) and \(|E|\) are the order and the size of the graph respectively. Otherwise, time complexity may be dominated by the time needed to compute a maximum matching of the graph.

Note that a ValueError is returned if the graph has loops or if the graph is trivial, i.e., it has at most one vertex.

INPUT:

  • matching – (default: None); a perfect matching of the graph, that can be given using any valid input format of Graph.

    If set to None, a matching is computed using the other parameters.

  • algorithm – string (default: 'Edmonds'); the algorithm to be used to compute a maximum matching of the graph among

    • 'Edmonds' selects Edmonds’ algorithm as implemented in NetworkX,

    • 'LP' uses a Linear Program formulation of the matching problem.

  • coNP_certificate – boolean (default: False); if set to True a set of pair of vertices (say \(u\) and \(v\)) is returned such that \(G - u - v\) does not have a perfect matching if \(G\) is not bicritical or otherwise None is returned.

  • solver – string (default: None); specify a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity: set to 0 by default, which means quiet (only useful when algorithm == 'LP').

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

OUTPUT:

  • A boolean indicating whether the graph is bicritical or not.

  • If coNP_certificate is set to True, a set of pair of vertices is returned in case the graph is not bicritical otherwise None is returned.

EXAMPLES:

The Petersen graph is bicritical:

sage: G = graphs.PetersenGraph()
sage: G.is_bicritical()
True
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> G.is_bicritical()
True

A graph (without a self-loop) is bicritical if and only if the underlying simple graph is bicritical:

sage: G = graphs.PetersenGraph()
sage: G.allow_multiple_edges(True)
sage: G.add_edge(0, 5)
sage: G.is_bicritical()
True
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> G.allow_multiple_edges(True)
>>> G.add_edge(Integer(0), Integer(5))
>>> G.is_bicritical()
True

A nontrivial circular ladder graph whose order is not divisible by 4 is bicritical:

sage: G = graphs.CircularLadderGraph(5)
sage: G.is_bicritical()
True
>>> from sage.all import *
>>> G = graphs.CircularLadderGraph(Integer(5))
>>> G.is_bicritical()
True

The graph obtained by splicing two bicritical graph is also bicritical. For instance, \(K_4\) with one extra (multiple) edge (say \(G := K_4^+\)) is bicritical. Let \(H := K_4^+ \odot K_4^+\) such that \(H\) is free of multiple edge. The graph \(H\) is also bicritical:

sage: G = graphs.CompleteGraph(4)
sage: G.allow_multiple_edges(True)
sage: G.add_edge(0, 1)
sage: G.is_bicritical()
True
sage: H = Graph()
sage: H.add_edges([
....:    (0, 1), (0, 2), (0, 3), (0, 4), (1, 2),
....:    (1, 5), (2, 5), (3, 4), (3, 5), (4, 5)
....: ])
sage: H.is_bicritical()
True
>>> from sage.all import *
>>> G = graphs.CompleteGraph(Integer(4))
>>> G.allow_multiple_edges(True)
>>> G.add_edge(Integer(0), Integer(1))
>>> G.is_bicritical()
True
>>> H = Graph()
>>> H.add_edges([
...    (Integer(0), Integer(1)), (Integer(0), Integer(2)), (Integer(0), Integer(3)), (Integer(0), Integer(4)), (Integer(1), Integer(2)),
...    (Integer(1), Integer(5)), (Integer(2), Integer(5)), (Integer(3), Integer(4)), (Integer(3), Integer(5)), (Integer(4), Integer(5))
... ])
>>> H.is_bicritical()
True

A graph (of order more than two) with more that one component is not bicritical:

sage: G = graphs.CycleGraph(4)
sage: G += graphs.CycleGraph(6)
sage: G.connected_components_number()
2
sage: G.is_bicritical()
False
>>> from sage.all import *
>>> G = graphs.CycleGraph(Integer(4))
>>> G += graphs.CycleGraph(Integer(6))
>>> G.connected_components_number()
2
>>> G.is_bicritical()
False

A graph (of order more than two) with a cut-vertex is not bicritical:

sage: G = graphs.CycleGraph(6)
sage: G.add_edges([(5, 6), (5, 7), (6, 7)])
sage: G.is_cut_vertex(5)
True
sage: G.has_perfect_matching()
True
sage: G.is_bicritical()
False
>>> from sage.all import *
>>> G = graphs.CycleGraph(Integer(6))
>>> G.add_edges([(Integer(5), Integer(6)), (Integer(5), Integer(7)), (Integer(6), Integer(7))])
>>> G.is_cut_vertex(Integer(5))
True
>>> G.has_perfect_matching()
True
>>> G.is_bicritical()
False

A connected graph of order two is assumed to be bicritical, whereas the disconnected graph of the same order is not:

sage: G = graphs.CompleteBipartiteGraph(1, 1)
sage: G.is_bicritical()
True
sage: G = graphs.CompleteBipartiteGraph(2, 0)
sage: G.is_bicritical()
False
>>> from sage.all import *
>>> G = graphs.CompleteBipartiteGraph(Integer(1), Integer(1))
>>> G.is_bicritical()
True
>>> G = graphs.CompleteBipartiteGraph(Integer(2), Integer(0))
>>> G.is_bicritical()
False

A bipartite graph of order three or more is not bicritical:

sage: G = graphs.CompleteBipartiteGraph(3, 3)
sage: G.has_perfect_matching()
True
sage: G.is_bicritical()
False
>>> from sage.all import *
>>> G = graphs.CompleteBipartiteGraph(Integer(3), Integer(3))
>>> G.has_perfect_matching()
True
>>> G.is_bicritical()
False

One may specify a matching:

sage: G = graphs.WheelGraph(10)
sage: M = G.matching()
sage: G.is_bicritical(matching=M)
True
sage: H = graphs.HexahedralGraph()
sage: N = H.matching()
sage: H.is_bicritical(matching=N)
False
>>> from sage.all import *
>>> G = graphs.WheelGraph(Integer(10))
>>> M = G.matching()
>>> G.is_bicritical(matching=M)
True
>>> H = graphs.HexahedralGraph()
>>> N = H.matching()
>>> H.is_bicritical(matching=N)
False

One may ask for a co-\(\mathcal{NP}\) certificate:

sage: G = graphs.CompleteGraph(14)
sage: G.is_bicritical(coNP_certificate=True)
(True, None)
sage: H = graphs.CircularLadderGraph(20)
sage: M = H.matching()
sage: H.is_bicritical(matching=M, coNP_certificate=True)
(False, {0, 2})
>>> from sage.all import *
>>> G = graphs.CompleteGraph(Integer(14))
>>> G.is_bicritical(coNP_certificate=True)
(True, None)
>>> H = graphs.CircularLadderGraph(Integer(20))
>>> M = H.matching()
>>> H.is_bicritical(matching=M, coNP_certificate=True)
(False, {0, 2})

REFERENCES:

AUTHORS:

  • Janmenjaya Panda (2024-06-17)

is_block_graph()[source]

Return whether this graph is a block graph.

A block graph is a connected graph in which every biconnected component (block) is a clique.

See also

EXAMPLES:

sage: G = graphs.RandomBlockGraph(6, 2, kmax=4)
sage: G.is_block_graph()
True
sage: from sage.graphs.isgci import graph_classes
sage: G in graph_classes.Block
True
sage: graphs.CompleteGraph(4).is_block_graph()
True
sage: graphs.RandomTree(6).is_block_graph()
True
sage: graphs.PetersenGraph().is_block_graph()
False
sage: Graph(4).is_block_graph()
False
>>> from sage.all import *
>>> G = graphs.RandomBlockGraph(Integer(6), Integer(2), kmax=Integer(4))
>>> G.is_block_graph()
True
>>> from sage.graphs.isgci import graph_classes
>>> G in graph_classes.Block
True
>>> graphs.CompleteGraph(Integer(4)).is_block_graph()
True
>>> graphs.RandomTree(Integer(6)).is_block_graph()
True
>>> graphs.PetersenGraph().is_block_graph()
False
>>> Graph(Integer(4)).is_block_graph()
False
is_cactus()[source]

Check whether the graph is cactus graph.

A graph is called cactus graph if it is connected and every pair of simple cycles have at most one common vertex.

There are other definitions, see the Wikipedia article Cactus_graph.

EXAMPLES:

sage: g = Graph({1: [2], 2: [3, 4], 3: [4, 5, 6, 7], 8: [3, 5], 9: [6, 7]})
sage: g.is_cactus()
True

sage: c6 = graphs.CycleGraph(6)
sage: naphthalene = c6 + c6
sage: naphthalene.is_cactus()  # Not connected
False
sage: naphthalene.merge_vertices([0, 6])
sage: naphthalene.is_cactus()
True
sage: naphthalene.merge_vertices([1, 7])
sage: naphthalene.is_cactus()
False
>>> from sage.all import *
>>> g = Graph({Integer(1): [Integer(2)], Integer(2): [Integer(3), Integer(4)], Integer(3): [Integer(4), Integer(5), Integer(6), Integer(7)], Integer(8): [Integer(3), Integer(5)], Integer(9): [Integer(6), Integer(7)]})
>>> g.is_cactus()
True

>>> c6 = graphs.CycleGraph(Integer(6))
>>> naphthalene = c6 + c6
>>> naphthalene.is_cactus()  # Not connected
False
>>> naphthalene.merge_vertices([Integer(0), Integer(6)])
>>> naphthalene.is_cactus()
True
>>> naphthalene.merge_vertices([Integer(1), Integer(7)])
>>> naphthalene.is_cactus()
False
is_cartesian_product(g, certificate=False, relabeling=False)[source]

Test whether the graph is a Cartesian product.

INPUT:

  • certificate – boolean (default: False); if certificate = False (default) the method only returns True or False answers. If certificate = True, the True answers are replaced by the list of the factors of the graph.

  • relabeling – boolean (default: False); if relabeling = True (implies certificate = True), the method also returns a dictionary associating to each vertex its natural coordinates as a vertex of a product graph. If \(g\) is not a Cartesian product, None is returned instead.

Note

This algorithm may run faster whenever the graph’s vertices are integers (see relabel()). Give it a try if it is too slow !

EXAMPLES:

The Petersen graph is prime:

sage: from sage.graphs.graph_decompositions.graph_products import is_cartesian_product
sage: g = graphs.PetersenGraph()
sage: is_cartesian_product(g)
False
>>> from sage.all import *
>>> from sage.graphs.graph_decompositions.graph_products import is_cartesian_product
>>> g = graphs.PetersenGraph()
>>> is_cartesian_product(g)
False

A 2d grid is the product of paths:

sage: g = graphs.Grid2dGraph(5,5)
sage: p1, p2 = is_cartesian_product(g, certificate = True)
sage: p1.is_isomorphic(graphs.PathGraph(5))
True
sage: p2.is_isomorphic(graphs.PathGraph(5))
True
>>> from sage.all import *
>>> g = graphs.Grid2dGraph(Integer(5),Integer(5))
>>> p1, p2 = is_cartesian_product(g, certificate = True)
>>> p1.is_isomorphic(graphs.PathGraph(Integer(5)))
True
>>> p2.is_isomorphic(graphs.PathGraph(Integer(5)))
True

Forgetting the graph’s labels, then finding them back:

sage: g.relabel()
sage: b,D = g.is_cartesian_product(g, relabeling=True)
sage: b
True
sage: D  # random isomorphism
{0: (20, 0), 1: (20, 1), 2: (20, 2), 3: (20, 3), 4: (20, 4),
 5: (15, 0), 6: (15, 1), 7: (15, 2), 8: (15, 3), 9: (15, 4),
 10: (10, 0), 11: (10, 1), 12: (10, 2), 13: (10, 3), 14: (10, 4),
 15: (5, 0), 16: (5, 1), 17: (5, 2), 18: (5, 3), 19: (5, 4),
 20: (0, 0), 21: (0, 1), 22: (0, 2), 23: (0, 3), 24: (0, 4)}
>>> from sage.all import *
>>> g.relabel()
>>> b,D = g.is_cartesian_product(g, relabeling=True)
>>> b
True
>>> D  # random isomorphism
{0: (20, 0), 1: (20, 1), 2: (20, 2), 3: (20, 3), 4: (20, 4),
 5: (15, 0), 6: (15, 1), 7: (15, 2), 8: (15, 3), 9: (15, 4),
 10: (10, 0), 11: (10, 1), 12: (10, 2), 13: (10, 3), 14: (10, 4),
 15: (5, 0), 16: (5, 1), 17: (5, 2), 18: (5, 3), 19: (5, 4),
 20: (0, 0), 21: (0, 1), 22: (0, 2), 23: (0, 3), 24: (0, 4)}

And of course, we find the factors back when we build a graph from a product:

sage: g = graphs.PetersenGraph().cartesian_product(graphs.CycleGraph(3))
sage: g1, g2 = is_cartesian_product(g, certificate = True)
sage: any( x.is_isomorphic(graphs.PetersenGraph()) for x in [g1,g2])
True
sage: any( x.is_isomorphic(graphs.CycleGraph(3)) for x in [g1,g2])
True
>>> from sage.all import *
>>> g = graphs.PetersenGraph().cartesian_product(graphs.CycleGraph(Integer(3)))
>>> g1, g2 = is_cartesian_product(g, certificate = True)
>>> any( x.is_isomorphic(graphs.PetersenGraph()) for x in [g1,g2])
True
>>> any( x.is_isomorphic(graphs.CycleGraph(Integer(3))) for x in [g1,g2])
True
is_circumscribable(solver='ppl', verbose=0)[source]

Test whether the graph is the graph of a circumscribed polyhedron.

A polyhedron is circumscribed if all of its facets are tangent to a sphere. By a theorem of Rivin ([HRS1993]), this can be checked by solving a linear program that assigns weights between 0 and 1/2 on each edge of the polyhedron, so that the weights on any face add to exactly one and the weights on any non-facial cycle add to more than one. If and only if this can be done, the polyhedron can be circumscribed.

INPUT:

  • solver – (default: 'ppl') specify a Linear Program (LP) solver to be used. If set to None, the default one is used. For more information on LP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

EXAMPLES:

sage: C = graphs.CubeGraph(3)
sage: C.is_circumscribable()                                                # needs sage.numerical.mip
True

sage: O = graphs.OctahedralGraph()
sage: O.is_circumscribable()                                                # needs sage.numerical.mip
True

sage: TT = polytopes.truncated_tetrahedron().graph()                        # needs sage.geometry.polyhedron
sage: TT.is_circumscribable()                                               # needs sage.geometry.polyhedron sage.numerical.mip
False
>>> from sage.all import *
>>> C = graphs.CubeGraph(Integer(3))
>>> C.is_circumscribable()                                                # needs sage.numerical.mip
True

>>> O = graphs.OctahedralGraph()
>>> O.is_circumscribable()                                                # needs sage.numerical.mip
True

>>> TT = polytopes.truncated_tetrahedron().graph()                        # needs sage.geometry.polyhedron
>>> TT.is_circumscribable()                                               # needs sage.geometry.polyhedron sage.numerical.mip
False

Stellating in a face of the octahedral graph is not circumscribable:

sage: f = set(flatten(choice(O.faces())))
sage: O.add_edges([[6, i] for i in f])
sage: O.is_circumscribable()                                                # needs sage.numerical.mip
False
>>> from sage.all import *
>>> f = set(flatten(choice(O.faces())))
>>> O.add_edges([[Integer(6), i] for i in f])
>>> O.is_circumscribable()                                                # needs sage.numerical.mip
False

Todo

Allow the use of other, inexact but faster solvers.

is_cograph()[source]

Check whether the graph is cograph.

A cograph is defined recursively: the single-vertex graph is cograph, complement of cograph is cograph, and disjoint union of two cographs is cograph. There are many other characterizations, see the Wikipedia article Cograph.

EXAMPLES:

sage: graphs.HouseXGraph().is_cograph()
True
sage: graphs.HouseGraph().is_cograph()                                      # needs sage.modules
False
>>> from sage.all import *
>>> graphs.HouseXGraph().is_cograph()
True
>>> graphs.HouseGraph().is_cograph()                                      # needs sage.modules
False

Todo

Implement faster recognition algorithm, as for instance the linear time recognition algorithm using LexBFS proposed in [Bre2008].

is_comparability(g, algorithm='greedy', certificate=False, check=True, solver=None, verbose=0)[source]

Check whether the graph is a comparability graph.

INPUT:

  • g – a graph

  • algorithm – string (default: 'greedy'); choose the implementation used to do the test

    • 'greedy' – a greedy algorithm (see the documentation of the comparability module)

    • 'MILP' – a Mixed Integer Linear Program formulation of the problem. Beware, for this implementation is unable to return negative certificates ! When certificate = True, negative certificates are always equal to None. True certificates are valid, though.

  • certificate – boolean (default: False); whether to return a certificate. Yes-answers the certificate is a transitive orientation of \(G\), and a no certificates is an odd cycle of sequentially forcing edges.

  • check – boolean (default: True); whether to check that the yes-certificates are indeed transitive. As it is very quick compared to the rest of the operation, it is enabled by default.

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

EXAMPLES:

sage: from sage.graphs.comparability import is_comparability
sage: g = graphs.PetersenGraph()
sage: is_comparability(g)
False
sage: is_comparability(graphs.CompleteGraph(5), certificate=True)
(True, Digraph on 5 vertices)
>>> from sage.all import *
>>> from sage.graphs.comparability import is_comparability
>>> g = graphs.PetersenGraph()
>>> is_comparability(g)
False
>>> is_comparability(graphs.CompleteGraph(Integer(5)), certificate=True)
(True, Digraph on 5 vertices)
is_directed()[source]

Since graph is undirected, returns False.

EXAMPLES:

sage: Graph().is_directed()
False
>>> from sage.all import *
>>> Graph().is_directed()
False
is_distance_regular(G, parameters=False)[source]

Test if the graph is distance-regular.

A graph \(G\) is distance-regular if for any integers \(j,k\) the value of \(|\{x:d_G(x,u)=j,x\in V(G)\} \cap \{y:d_G(y,v)=j,y\in V(G)\}|\) is constant for any two vertices \(u,v\in V(G)\) at distance \(i\) from each other. In particular \(G\) is regular, of degree \(b_0\) (see below), as one can take \(u=v\).

Equivalently a graph is distance-regular if there exist integers \(b_i,c_i\) such that for any two vertices \(u,v\) at distance \(i\) we have

  • \(b_i = |\{x:d_G(x,u)=i+1,x\in V(G)\}\cap N_G(v)\}|, \ 0\leq i\leq d-1\)

  • \(c_i = |\{x:d_G(x,u)=i-1,x\in V(G)\}\cap N_G(v)\}|, \ 1\leq i\leq d,\)

where \(d\) is the diameter of the graph. For more information on distance-regular graphs, see the Wikipedia article Distance-regular_graph.

INPUT:

  • parameters – boolean (default: False); if set to True, the function returns the pair (b, c) of lists of integers instead of a boolean answer (see the definition above)

EXAMPLES:

sage: g = graphs.PetersenGraph()
sage: g.is_distance_regular()
True
sage: g.is_distance_regular(parameters = True)
([3, 2, None], [None, 1, 1])
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> g.is_distance_regular()
True
>>> g.is_distance_regular(parameters = True)
([3, 2, None], [None, 1, 1])

Cube graphs, which are not strongly regular, are a bit more interesting:

sage: graphs.CubeGraph(4).is_distance_regular()
True
sage: graphs.OddGraph(5).is_distance_regular()
True
>>> from sage.all import *
>>> graphs.CubeGraph(Integer(4)).is_distance_regular()
True
>>> graphs.OddGraph(Integer(5)).is_distance_regular()
True

Disconnected graph:

sage: (2*graphs.CubeGraph(4)).is_distance_regular()
True
>>> from sage.all import *
>>> (Integer(2)*graphs.CubeGraph(Integer(4))).is_distance_regular()
True
is_dominating(G, dom, focus=None)[source]

Check whether dom is a dominating set of G.

We say that a set \(D\) of vertices of a graph \(G\) dominates a set \(S\) if every vertex of \(S\) either belongs to \(D\) or is adjacent to a vertex of \(D\). Also, \(D\) is a dominating set of \(G\) if it dominates \(V(G)\).

INPUT:

  • dom – iterable of vertices of G; the vertices of the supposed dominating set

  • focus – iterable of vertices of G (default: None); if specified, this method checks instead if dom dominates the vertices in focus

EXAMPLES:

sage: g = graphs.CycleGraph(5)
sage: g.is_dominating([0,1], [4, 2])
True

sage: g.is_dominating([0,1])
False
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(5))
>>> g.is_dominating([Integer(0),Integer(1)], [Integer(4), Integer(2)])
True

>>> g.is_dominating([Integer(0),Integer(1)])
False
is_edge_transitive()[source]

Check if self is an edge transitive graph.

A graph is edge-transitive if its automorphism group acts transitively on its edge set.

Equivalently, if there exists for any pair of edges \(uv,u'v'\in E(G)\) an automorphism \(\phi\) of \(G\) such that \(\phi(uv)=u'v'\) (note this does not necessarily mean that \(\phi(u)=u'\) and \(\phi(v)=v'\)).

EXAMPLES:

sage: P = graphs.PetersenGraph()
sage: P.is_edge_transitive()                                                # needs sage.libs.gap
True
sage: C = graphs.CubeGraph(3)
sage: C.is_edge_transitive()                                                # needs sage.libs.gap
True
sage: G = graphs.GrayGraph()                                                # needs networkx
sage: G.is_edge_transitive()                                                # needs networkx sage.libs.gap
True
sage: P = graphs.PathGraph(4)
sage: P.is_edge_transitive()                                                # needs sage.libs.gap
False
>>> from sage.all import *
>>> P = graphs.PetersenGraph()
>>> P.is_edge_transitive()                                                # needs sage.libs.gap
True
>>> C = graphs.CubeGraph(Integer(3))
>>> C.is_edge_transitive()                                                # needs sage.libs.gap
True
>>> G = graphs.GrayGraph()                                                # needs networkx
>>> G.is_edge_transitive()                                                # needs networkx sage.libs.gap
True
>>> P = graphs.PathGraph(Integer(4))
>>> P.is_edge_transitive()                                                # needs sage.libs.gap
False
is_even_hole_free(certificate=False)[source]

Test whether self contains an induced even hole.

A Hole is a cycle of length at least 4 (included). It is said to be even (resp. odd) if its length is even (resp. odd).

Even-hole-free graphs always contain a bisimplicial vertex, which ensures that their chromatic number is at most twice their clique number [ACHRS2008].

INPUT:

  • certificate – boolean (default: False); when certificate = False, this method only returns True or False. If certificate = True, the subgraph found is returned instead of False.

EXAMPLES:

Is the Petersen Graph even-hole-free

sage: g = graphs.PetersenGraph()
sage: g.is_even_hole_free()                                                 # needs sage.modules
False
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> g.is_even_hole_free()                                                 # needs sage.modules
False

As any chordal graph is hole-free, interval graphs behave the same way:

sage: g = graphs.RandomIntervalGraph(20)
sage: g.is_even_hole_free()                                                 # needs sage.modules
True
>>> from sage.all import *
>>> g = graphs.RandomIntervalGraph(Integer(20))
>>> g.is_even_hole_free()                                                 # needs sage.modules
True

It is clear, though, that a random Bipartite Graph which is not a forest has an even hole:

sage: g = graphs.RandomBipartite(10, 10, .5)                                # needs numpy
sage: g.is_even_hole_free() and not g.is_forest()                           # needs numpy sage.modules
False
>>> from sage.all import *
>>> g = graphs.RandomBipartite(Integer(10), Integer(10), RealNumber('.5'))                                # needs numpy
>>> g.is_even_hole_free() and not g.is_forest()                           # needs numpy sage.modules
False

We can check the certificate returned is indeed an even cycle:

sage: if not g.is_forest():                                                 # needs numpy sage.modules
....:    cycle = g.is_even_hole_free(certificate=True)
....:    if cycle.order() % 2 == 1:
....:        print("Error !")
....:    if not cycle.is_isomorphic(
....:           graphs.CycleGraph(cycle.order())):
....:        print("Error !")
...
sage: print("Everything is Fine !")
Everything is Fine !
>>> from sage.all import *
>>> if not g.is_forest():                                                 # needs numpy sage.modules
...    cycle = g.is_even_hole_free(certificate=True)
...    if cycle.order() % Integer(2) == Integer(1):
...        print("Error !")
...    if not cycle.is_isomorphic(
...           graphs.CycleGraph(cycle.order())):
...        print("Error !")
...
>>> print("Everything is Fine !")
Everything is Fine !
is_factor_critical(G, matching, algorithm=None, solver='Edmonds', verbose=None, integrality_tolerance=0)[source]

Check whether the graph is factor-critical.

A graph of order \(n\) is factor-critical if every subgraph of \(n-1\) vertices have a perfect matching, hence \(n\) must be odd. See Wikipedia article Factor-critical_graph for more details.

This method implements the algorithm proposed in [LR2004] and we assume that a graph of order one is factor-critical. The time complexity of the algorithm is linear if a near perfect matching is given as input (i.e., a matching such that all vertices but one are incident to an edge of the matching). Otherwise, the time complexity is dominated by the time needed to compute a maximum matching of the graph.

INPUT:

  • matching – (default: None); a near perfect matching of the graph, that is a matching such that all vertices of the graph but one are incident to an edge of the matching. It can be given using any valid input format of Graph.

    If set to None, a matching is computed using the other parameters.

  • algorithm – string (default: 'Edmonds'); the algorithm to use to compute a maximum matching of the graph among

    • 'Edmonds' selects Edmonds’ algorithm as implemented in NetworkX

    • 'LP' uses a Linear Program formulation of the matching problem

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity: set to 0 by default, which means quiet (only useful when algorithm == "LP")

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

EXAMPLES:

Odd length cycles and odd cliques of order at least 3 are factor-critical graphs:

sage: [graphs.CycleGraph(2*i + 1).is_factor_critical() for i in range(5)]   # needs networkx
[True, True, True, True, True]
sage: [graphs.CompleteGraph(2*i + 1).is_factor_critical() for i in range(5)]            # needs networkx
[True, True, True, True, True]
>>> from sage.all import *
>>> [graphs.CycleGraph(Integer(2)*i + Integer(1)).is_factor_critical() for i in range(Integer(5))]   # needs networkx
[True, True, True, True, True]
>>> [graphs.CompleteGraph(Integer(2)*i + Integer(1)).is_factor_critical() for i in range(Integer(5))]            # needs networkx
[True, True, True, True, True]

More generally, every Hamiltonian graph with an odd number of vertices is factor-critical:

sage: G = graphs.RandomGNP(15, .2)
sage: G.add_path([0..14])
sage: G.add_edge(14, 0)
sage: G.is_hamiltonian()
True
sage: G.is_factor_critical()                                                # needs networkx
True
>>> from sage.all import *
>>> G = graphs.RandomGNP(Integer(15), RealNumber('.2'))
>>> G.add_path((ellipsis_range(Integer(0),Ellipsis,Integer(14))))
>>> G.add_edge(Integer(14), Integer(0))
>>> G.is_hamiltonian()
True
>>> G.is_factor_critical()                                                # needs networkx
True

Friendship graphs are non-Hamiltonian factor-critical graphs:

sage: [graphs.FriendshipGraph(i).is_factor_critical() for i in range(1, 5)]             # needs networkx
[True, True, True, True]
>>> from sage.all import *
>>> [graphs.FriendshipGraph(i).is_factor_critical() for i in range(Integer(1), Integer(5))]             # needs networkx
[True, True, True, True]

Bipartite graphs are not factor-critical:

sage: G = graphs.RandomBipartite(randint(1, 10), randint(1, 10), .5)        # needs numpy
sage: G.is_factor_critical()                                                # needs numpy
False
>>> from sage.all import *
>>> G = graphs.RandomBipartite(randint(Integer(1), Integer(10)), randint(Integer(1), Integer(10)), RealNumber('.5'))        # needs numpy
>>> G.is_factor_critical()                                                # needs numpy
False

Graphs with even order are not factor critical:

sage: G = graphs.RandomGNP(10, .5)
sage: G.is_factor_critical()
False
>>> from sage.all import *
>>> G = graphs.RandomGNP(Integer(10), RealNumber('.5'))
>>> G.is_factor_critical()
False

One can specify a matching:

sage: F = graphs.FriendshipGraph(4)
sage: M = F.matching()                                                      # needs networkx
sage: F.is_factor_critical(matching=M)                                      # needs networkx
True
sage: F.is_factor_critical(matching=Graph(M))                               # needs networkx
True
>>> from sage.all import *
>>> F = graphs.FriendshipGraph(Integer(4))
>>> M = F.matching()                                                      # needs networkx
>>> F.is_factor_critical(matching=M)                                      # needs networkx
True
>>> F.is_factor_critical(matching=Graph(M))                               # needs networkx
True
is_forest(certificate=False, output='vertex')[source]

Test if the graph is a forest, i.e. a disjoint union of trees.

INPUT:

  • certificate – boolean (default: False); whether to return a certificate. The method only returns boolean answers when certificate = False (default). When it is set to True, it either answers (True, None) when the graph is a forest or (False, cycle) when it contains a cycle.

  • output – either 'vertex' (default) or 'edge'; whether the certificate is given as a list of vertices (output = 'vertex') or a list of edges (output = 'edge').

EXAMPLES:

sage: seven_acre_wood = sum(graphs.trees(7), Graph())
sage: seven_acre_wood.is_forest()
True
>>> from sage.all import *
>>> seven_acre_wood = sum(graphs.trees(Integer(7)), Graph())
>>> seven_acre_wood.is_forest()
True

With certificates:

sage: g = graphs.RandomTree(30)
sage: g.is_forest(certificate=True)
(True, None)
sage: (2*g + graphs.PetersenGraph() + g).is_forest(certificate=True)
(False, [64, 69, 67, 65, 60])
>>> from sage.all import *
>>> g = graphs.RandomTree(Integer(30))
>>> g.is_forest(certificate=True)
(True, None)
>>> (Integer(2)*g + graphs.PetersenGraph() + g).is_forest(certificate=True)
(False, [64, 69, 67, 65, 60])
is_half_transitive()[source]

Check if self is a half-transitive graph.

A graph is half-transitive if it is both vertex and edge transitive but not arc-transitive.

EXAMPLES:

The Petersen Graph is not half-transitive:

sage: P = graphs.PetersenGraph()
sage: P.is_half_transitive()                                                # needs sage.libs.gap
False
>>> from sage.all import *
>>> P = graphs.PetersenGraph()
>>> P.is_half_transitive()                                                # needs sage.libs.gap
False

The smallest half-transitive graph is the Holt Graph:

sage: H = graphs.HoltGraph()
sage: H.is_half_transitive()                                                # needs sage.libs.gap
True
>>> from sage.all import *
>>> H = graphs.HoltGraph()
>>> H.is_half_transitive()                                                # needs sage.libs.gap
True
is_inscribable(solver='ppl', verbose=0)[source]

Test whether the graph is the graph of an inscribed polyhedron.

A polyhedron is inscribed if all of its vertices are on a sphere. This is dual to the notion of circumscribed polyhedron: A Polyhedron is inscribed if and only if its polar dual is circumscribed and hence a graph is inscribable if and only if its planar dual is circumscribable.

INPUT:

  • solver – (default: 'ppl') specify a Linear Program (LP) solver to be used. If set to None, the default one is used. For more information on LP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

EXAMPLES:

sage: H = graphs.HerschelGraph()
sage: H.is_inscribable()            # long time (> 1 sec)                   # needs sage.numerical.mip
False
sage: H.planar_dual().is_inscribable()      # long time (> 1 sec)           # needs sage.numerical.mip
True

sage: C = graphs.CubeGraph(3)
sage: C.is_inscribable()                                                    # needs sage.numerical.mip
True
>>> from sage.all import *
>>> H = graphs.HerschelGraph()
>>> H.is_inscribable()            # long time (> 1 sec)                   # needs sage.numerical.mip
False
>>> H.planar_dual().is_inscribable()      # long time (> 1 sec)           # needs sage.numerical.mip
True

>>> C = graphs.CubeGraph(Integer(3))
>>> C.is_inscribable()                                                    # needs sage.numerical.mip
True

Cutting off a vertex from the cube yields an uninscribable graph:

sage: C = graphs.CubeGraph(3)
sage: v = next(C.vertex_iterator())
sage: triangle = [_ + v for _ in C.neighbors(v)]
sage: C.add_edges(Combinations(triangle, 2))
sage: C.add_edges(zip(triangle, C.neighbors(v)))
sage: C.delete_vertex(v)
sage: C.is_inscribable()                                                    # needs sage.numerical.mip
False
>>> from sage.all import *
>>> C = graphs.CubeGraph(Integer(3))
>>> v = next(C.vertex_iterator())
>>> triangle = [_ + v for _ in C.neighbors(v)]
>>> C.add_edges(Combinations(triangle, Integer(2)))
>>> C.add_edges(zip(triangle, C.neighbors(v)))
>>> C.delete_vertex(v)
>>> C.is_inscribable()                                                    # needs sage.numerical.mip
False

Breaking a face of the cube yields an uninscribable graph:

sage: C = graphs.CubeGraph(3)
sage: face = choice(C.faces())
sage: C.add_edge([face[0][0], face[2][0]])
sage: C.is_inscribable()                                                    # needs sage.numerical.mip
False
>>> from sage.all import *
>>> C = graphs.CubeGraph(Integer(3))
>>> face = choice(C.faces())
>>> C.add_edge([face[Integer(0)][Integer(0)], face[Integer(2)][Integer(0)]])
>>> C.is_inscribable()                                                    # needs sage.numerical.mip
False
is_line_graph(g, certificate=False)[source]

Check whether the graph \(g\) is a line graph.

INPUT:

  • certificate – boolean; whether to return a certificate along with the boolean result. Here is what happens when certificate = True:

    • If the graph is not a line graph, the method returns a pair (b, subgraph) where b is False and subgraph is a subgraph isomorphic to one of the 9 forbidden induced subgraphs of a line graph.

    • If the graph is a line graph, the method returns a triple (b,R,isom) where b is True, R is a graph whose line graph is the graph given as input, and isom is a map associating an edge of R to each vertex of the graph.

Note

This method wastes a bit of time when the input graph is not connected. If you have performance in mind, it is probably better to only feed it with connected graphs only.

See also

EXAMPLES:

A complete graph is always the line graph of a star:

sage: graphs.CompleteGraph(5).is_line_graph()
True
>>> from sage.all import *
>>> graphs.CompleteGraph(Integer(5)).is_line_graph()
True

The Petersen Graph not being claw-free, it is not a line graph:

sage: graphs.PetersenGraph().is_line_graph()
False
>>> from sage.all import *
>>> graphs.PetersenGraph().is_line_graph()
False

This is indeed the subgraph returned:

sage: C = graphs.PetersenGraph().is_line_graph(certificate=True)[1]             # needs sage.modules
sage: C.is_isomorphic(graphs.ClawGraph())                                       # needs sage.modules
True
>>> from sage.all import *
>>> C = graphs.PetersenGraph().is_line_graph(certificate=True)[Integer(1)]             # needs sage.modules
>>> C.is_isomorphic(graphs.ClawGraph())                                       # needs sage.modules
True

The house graph is a line graph:

sage: g = graphs.HouseGraph()
sage: g.is_line_graph()
True
>>> from sage.all import *
>>> g = graphs.HouseGraph()
>>> g.is_line_graph()
True

But what is the graph whose line graph is the house ?:

sage: # needs sage.modules
sage: is_line, R, isom = g.is_line_graph(certificate=True)
sage: R.sparse6_string()
':DaHI~'
sage: R.show()                                                                  # needs sage.plot
sage: isom
{0: (0, 1), 1: (0, 2), 2: (1, 3), 3: (2, 3), 4: (3, 4)}
>>> from sage.all import *
>>> # needs sage.modules
>>> is_line, R, isom = g.is_line_graph(certificate=True)
>>> R.sparse6_string()
':DaHI~'
>>> R.show()                                                                  # needs sage.plot
>>> isom
{0: (0, 1), 1: (0, 2), 2: (1, 3), 3: (2, 3), 4: (3, 4)}
is_long_antihole_free(g, certificate=False)[source]

Test whether the given graph contains an induced subgraph that is isomorphic to the complement of a cycle of length at least 5.

INPUT:

  • certificate – boolean (default: False)

    Whether to return a certificate. When certificate = True, then the function returns

    • (False, Antihole) if g contains an induced complement of a cycle of length at least 5 returned as Antihole.

    • (True, []) if g does not contain an induced complement of a cycle of length at least 5. For this case it is not known how to provide a certificate.

    When certificate = False, the function returns just True or False accordingly.

ALGORITHM:

This algorithm tries to find a cycle in the graph of all induced \(\overline{P_4}\) of \(g\), where two copies \(\overline{P}\) and \(\overline{P'}\) of \(\overline{P_4}\) are adjacent if there exists a (not necessarily induced) copy of \(\overline{P_5}=u_1u_2u_3u_4u_5\) such that \(\overline{P}=u_1u_2u_3u_4\) and \(\overline{P'}=u_2u_3u_4u_5\).

This is done through a depth-first-search. For efficiency, the auxiliary graph is constructed on-the-fly and never stored in memory.

The run time of this algorithm is \(O(n+m^2)\) for SparseGraph and \(O(n^2\log{m} + m^2)\) for DenseGraph [NP2007] (where \(n\) is the number of vertices and \(m\) is the number of edges of the graph).

EXAMPLES:

The Petersen Graph contains an antihole:

sage: g = graphs.PetersenGraph()
sage: g.is_long_antihole_free()
False
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> g.is_long_antihole_free()
False

The complement of a cycle is an antihole:

sage: g = graphs.CycleGraph(6).complement()
sage: r,a = g.is_long_antihole_free(certificate=True)
sage: r
False
sage: a.complement().is_isomorphic(graphs.CycleGraph(6))
True
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(6)).complement()
>>> r,a = g.is_long_antihole_free(certificate=True)
>>> r
False
>>> a.complement().is_isomorphic(graphs.CycleGraph(Integer(6)))
True
is_long_hole_free(g, certificate=False)[source]

Test whether g contains an induced cycle of length at least 5.

INPUT:

  • certificate – boolean (default: False)

    Whether to return a certificate. When certificate = True, then the function returns

    • (True, []) if g does not contain such a cycle. For this case, it is not known how to provide a certificate.

    • (False, Hole) if g contains an induced cycle of length at least 5. Hole returns this cycle.

    If certificate = False, the function returns just True or False accordingly.

ALGORITHM:

This algorithm tries to find a cycle in the graph of all induced \(P_4\) of \(g\), where two copies \(P\) and \(P'\) of \(P_4\) are adjacent if there exists a (not necessarily induced) copy of \(P_5=u_1u_2u_3u_4u_5\) such that \(P=u_1u_2u_3u_4\) and \(P'=u_2u_3u_4u_5\).

This is done through a depth-first-search. For efficiency, the auxiliary graph is constructed on-the-fly and never stored in memory.

The run time of this algorithm is \(O(n+m^2)\) for SparseGraph and \(O(n^2 + m^2)\) for DenseGraph [NP2007] (where \(n\) is the number of vertices and \(m\) is the number of edges of the graph).

EXAMPLES:

The Petersen Graph contains a hole:

sage: g = graphs.PetersenGraph()
sage: g.is_long_hole_free()
False
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> g.is_long_hole_free()
False

The following graph contains a hole, which we want to display:

sage: g = graphs.FlowerSnark()
sage: r,h = g.is_long_hole_free(certificate=True)
sage: r
False
sage: Graph(h).is_isomorphic(graphs.CycleGraph(h.order()))
True
>>> from sage.all import *
>>> g = graphs.FlowerSnark()
>>> r,h = g.is_long_hole_free(certificate=True)
>>> r
False
>>> Graph(h).is_isomorphic(graphs.CycleGraph(h.order()))
True
is_matching_covered(G, matching, algorithm=None, coNP_certificate='Edmonds', solver=False, verbose=None, integrality_tolerance=0)[source]

Check if the graph is matching covered.

A connected nontrivial graph wherein each edge participates in some perfect matching is called a matching covered graph.

If a perfect matching of the graph is provided, for bipartite graph, this method implements a linear time algorithm as proposed in [LM2024] that is based on the following theorem:

Given a connected bipartite graph \(G[A, B]\) with a perfect matching \(M\). Construct a directed graph \(D\) from \(G\) such that \(V(D) := V(G)\) and for each edge in \(G\) direct the corresponding edge from \(A\) to \(B\) in \(D\), if it is in \(M\) or otherwise direct it from \(B\) to \(A\). The graph \(G\) is matching covered if and only if \(D\) is strongly connected.

For nonbipartite graph, if a perfect matching of the graph is provided, this method implements an \(\mathcal{O}(|V| \cdot |E|)\) algorithm, where \(|V|\) and \(|E|\) are the order and the size of the graph respectively. This implementation is inspired by the \(M\)-\(alternating\) \(tree\) \(search\) method explained in [LZ2001]. For nonbipartite graph, the implementation is based on the following theorem:

Given a nonbipartite graph \(G\) with a perfect matching \(M\). The graph \(G\) is matching covered if and only if for each edge \(uv\) not in \(M\), there exists an \(M\)-\(alternating\) odd length \(uv\)-path starting and ending with edges not in \(M\).

The time complexity may be dominated by the time needed to compute a maximum matching of the graph, in case a perfect matching is not provided. Also, note that for a disconnected or a trivial or a graph with a loop, a ValueError is returned.

INPUT:

  • matching – (default: None); a perfect matching of the graph, that can be given using any valid input format of Graph.

    If set to None, a matching is computed using the other parameters.

  • algorithm – string (default: 'Edmonds'); the algorithm to be used to compute a maximum matching of the graph among

    • 'Edmonds' selects Edmonds’ algorithm as implemented in NetworkX,

    • 'LP' uses a Linear Program formulation of the matching problem.

  • coNP_certificate – boolean (default: False); if set to True an edge of the graph, that does not participate in any perfect matching, is returned if \(G\) is not matching covered or otherwise None is returned.

  • solver – string (default: None); specify a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity: set to 0 by default, which means quiet (only useful when algorithm == 'LP').

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

OUTPUT:

  • A boolean indicating whether the graph is matching covered or not.

  • If coNP_certificate is set to True, an edge is returned in case the graph is not matching covered otherwise None is returned.

EXAMPLES:

The Petersen graph is matching covered:

sage: G = graphs.PetersenGraph()
sage: G.is_matching_covered()
True
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> G.is_matching_covered()
True

A graph (without a self-loop) is matching covered if and only if the underlying simple graph is matching covered:

sage: G = graphs.PetersenGraph()
sage: G.allow_multiple_edges(True)
sage: G.add_edge(0, 5)
sage: G.is_matching_covered()
True
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> G.allow_multiple_edges(True)
>>> G.add_edge(Integer(0), Integer(5))
>>> G.is_matching_covered()
True

A corollary to Tutte’s fundamental result [Tut1947], as a strengthening of Petersen’s Theorem, states that every 2-connected cubic graph is matching covered:

sage: G = Graph()
sage: G.add_edges([
....:    (0, 1), (0, 2), (0, 3),
....:    (1, 2), (1, 4), (2, 4),
....:    (3, 5), (3, 6), (4, 7),
....:    (5, 6), (5, 7), (6, 7)
....: ])
sage: G.vertex_connectivity()
2
sage: degree_sequence = G.degree_sequence()
sage: min(degree_sequence) == max(degree_sequence) == 3
True
sage: G.is_matching_covered()
True
>>> from sage.all import *
>>> G = Graph()
>>> G.add_edges([
...    (Integer(0), Integer(1)), (Integer(0), Integer(2)), (Integer(0), Integer(3)),
...    (Integer(1), Integer(2)), (Integer(1), Integer(4)), (Integer(2), Integer(4)),
...    (Integer(3), Integer(5)), (Integer(3), Integer(6)), (Integer(4), Integer(7)),
...    (Integer(5), Integer(6)), (Integer(5), Integer(7)), (Integer(6), Integer(7))
... ])
>>> G.vertex_connectivity()
2
>>> degree_sequence = G.degree_sequence()
>>> min(degree_sequence) == max(degree_sequence) == Integer(3)
True
>>> G.is_matching_covered()
True

A connected bipartite graph \(G[A, B]\), with \(|A| = |B| \geq 2\), is matching covered if and only if \(|N(X)| \geq |X| + 1\), for all \(X \subset A\) such that \(1 \leq |X| \leq |A| - 1\). For instance, the Hexahedral graph is matching covered, but not the path graphs on even number of vertices, even though they have a perfect matching:

sage: G = graphs.HexahedralGraph()
sage: G.is_bipartite()
True
sage: G.is_matching_covered()
True
sage: P = graphs.PathGraph(10)
sage: P.is_bipartite()
True
sage: M = Graph(P.matching())
sage: set(P) == set(M)
True
sage: P.is_matching_covered()
False
>>> from sage.all import *
>>> G = graphs.HexahedralGraph()
>>> G.is_bipartite()
True
>>> G.is_matching_covered()
True
>>> P = graphs.PathGraph(Integer(10))
>>> P.is_bipartite()
True
>>> M = Graph(P.matching())
>>> set(P) == set(M)
True
>>> P.is_matching_covered()
False

A connected bipartite graph \(G[A, B]\) of order six or more is matching covered if and only if \(G - a - b\) has a perfect matching for some vertex \(a\) in \(A\) and some vertex \(b\) in \(B\):

sage: G = graphs.CircularLadderGraph(8)
sage: G.is_bipartite()
True
sage: G.is_matching_covered()
True
sage: A, B = G.bipartite_sets()
sage: # needs random
sage: import random
sage: a = random.choice(list(A))
sage: b = random.choice(list(B))
sage: G.delete_vertices([a, b])
sage: M = Graph(G.matching())
sage: set(M) == set(G)
True
sage: cycle1 = graphs.CycleGraph(4)
sage: cycle2 = graphs.CycleGraph(6)
sage: cycle2.relabel(lambda v: v + 4)
sage: H = Graph()
sage: H.add_edges(cycle1.edges() + cycle2.edges())
sage: H.add_edge(3, 4)
sage: H.is_bipartite()
True
sage: H.is_matching_covered()
False
sage: H.delete_vertices([3, 4])
sage: N = Graph(H.matching())
sage: set(N) == set(H)
False
>>> from sage.all import *
>>> G = graphs.CircularLadderGraph(Integer(8))
>>> G.is_bipartite()
True
>>> G.is_matching_covered()
True
>>> A, B = G.bipartite_sets()
>>> # needs random
>>> import random
>>> a = random.choice(list(A))
>>> b = random.choice(list(B))
>>> G.delete_vertices([a, b])
>>> M = Graph(G.matching())
>>> set(M) == set(G)
True
>>> cycle1 = graphs.CycleGraph(Integer(4))
>>> cycle2 = graphs.CycleGraph(Integer(6))
>>> cycle2.relabel(lambda v: v + Integer(4))
>>> H = Graph()
>>> H.add_edges(cycle1.edges() + cycle2.edges())
>>> H.add_edge(Integer(3), Integer(4))
>>> H.is_bipartite()
True
>>> H.is_matching_covered()
False
>>> H.delete_vertices([Integer(3), Integer(4)])
>>> N = Graph(H.matching())
>>> set(N) == set(H)
False

One may specify a matching:

sage: G = graphs.WheelGraph(20)
sage: M = Graph(G.matching())
sage: G.is_matching_covered(matching=M)
True
sage: J = graphs.CycleGraph(4)
sage: J.add_edge(0, 2)
sage: N = J.matching()
sage: J.is_matching_covered(matching=N)
False
>>> from sage.all import *
>>> G = graphs.WheelGraph(Integer(20))
>>> M = Graph(G.matching())
>>> G.is_matching_covered(matching=M)
True
>>> J = graphs.CycleGraph(Integer(4))
>>> J.add_edge(Integer(0), Integer(2))
>>> N = J.matching()
>>> J.is_matching_covered(matching=N)
False

One may ask for a co-\(\mathcal{NP}\) certificate:

sage: G = graphs.CompleteGraph(14)
sage: G.is_matching_covered(coNP_certificate=True)
(True, None)
sage: H = graphs.PathGraph(20)
sage: M = H.matching()
sage: H.is_matching_covered(matching=M, coNP_certificate=True)
(False, (2, 1, None))
>>> from sage.all import *
>>> G = graphs.CompleteGraph(Integer(14))
>>> G.is_matching_covered(coNP_certificate=True)
(True, None)
>>> H = graphs.PathGraph(Integer(20))
>>> M = H.matching()
>>> H.is_matching_covered(matching=M, coNP_certificate=True)
(False, (2, 1, None))

REFERENCES:

AUTHORS:

  • Janmenjaya Panda (2024-06-23)

is_module(vertices)[source]

Check whether vertices is a module of self.

A subset \(M\) of the vertices of a graph is a module if for every vertex \(v\) outside of \(M\), either all vertices of \(M\) are neighbors of \(v\) or all vertices of \(M\) are not neighbors of \(v\).

INPUT:

  • vertices – iterable; a subset of vertices of self

EXAMPLES:

The whole graph, the empty set and singletons are trivial modules:

sage: G = graphs.PetersenGraph()
sage: G.is_module([])
True
sage: G.is_module([G.random_vertex()])
True
sage: G.is_module(G)
True
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> G.is_module([])
True
>>> G.is_module([G.random_vertex()])
True
>>> G.is_module(G)
True

Prime graphs only have trivial modules:

sage: G = graphs.PathGraph(5)
sage: G.is_prime()
True
sage: all(not G.is_module(S) for S in subsets(G)
....:                       if len(S) > 1 and len(S) < G.order())
True
>>> from sage.all import *
>>> G = graphs.PathGraph(Integer(5))
>>> G.is_prime()
True
>>> all(not G.is_module(S) for S in subsets(G)
...                       if len(S) > Integer(1) and len(S) < G.order())
True

For edgeless graphs and complete graphs, all subsets are modules:

sage: G = Graph(5)
sage: all(G.is_module(S) for S in subsets(G))
True
sage: G = graphs.CompleteGraph(5)
sage: all(G.is_module(S) for S in subsets(G))
True
>>> from sage.all import *
>>> G = Graph(Integer(5))
>>> all(G.is_module(S) for S in subsets(G))
True
>>> G = graphs.CompleteGraph(Integer(5))
>>> all(G.is_module(S) for S in subsets(G))
True

The modules of a graph and of its complements are the same:

sage: G = graphs.TuranGraph(10, 3)
sage: G.is_module([0,1,2])
True
sage: G.complement().is_module([0,1,2])
True
sage: G.is_module([3,4,5])
True
sage: G.complement().is_module([3,4,5])
True
sage: G.is_module([2,3,4])
False
sage: G.complement().is_module([2,3,4])
False
sage: G.is_module([3,4,5,6,7,8,9])
True
sage: G.complement().is_module([3,4,5,6,7,8,9])
True
>>> from sage.all import *
>>> G = graphs.TuranGraph(Integer(10), Integer(3))
>>> G.is_module([Integer(0),Integer(1),Integer(2)])
True
>>> G.complement().is_module([Integer(0),Integer(1),Integer(2)])
True
>>> G.is_module([Integer(3),Integer(4),Integer(5)])
True
>>> G.complement().is_module([Integer(3),Integer(4),Integer(5)])
True
>>> G.is_module([Integer(2),Integer(3),Integer(4)])
False
>>> G.complement().is_module([Integer(2),Integer(3),Integer(4)])
False
>>> G.is_module([Integer(3),Integer(4),Integer(5),Integer(6),Integer(7),Integer(8),Integer(9)])
True
>>> G.complement().is_module([Integer(3),Integer(4),Integer(5),Integer(6),Integer(7),Integer(8),Integer(9)])
True

Elements of vertices must be in self:

sage: G = graphs.PetersenGraph()
sage: G.is_module(['Terry'])
Traceback (most recent call last):
...
LookupError: vertex (Terry) is not a vertex of the graph
sage: G.is_module([1, 'Graham'])
Traceback (most recent call last):
...
LookupError: vertex (Graham) is not a vertex of the graph
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> G.is_module(['Terry'])
Traceback (most recent call last):
...
LookupError: vertex (Terry) is not a vertex of the graph
>>> G.is_module([Integer(1), 'Graham'])
Traceback (most recent call last):
...
LookupError: vertex (Graham) is not a vertex of the graph
is_odd_hole_free(certificate=False)[source]

Test whether self contains an induced odd hole.

A Hole is a cycle of length at least 4 (included). It is said to be even (resp. odd) if its length is even (resp. odd).

It is interesting to notice that while it is polynomial to check whether a graph has an odd hole or an odd antihole [CCLSV2005], it is not known whether testing for one of these two cases independently is polynomial too.

INPUT:

  • certificate – boolean (default: False); when certificate = False, this method only returns True or False. If certificate = True, the subgraph found is returned instead of False.

EXAMPLES:

Is the Petersen Graph odd-hole-free

sage: g = graphs.PetersenGraph()
sage: g.is_odd_hole_free()                                                  # needs sage.modules
False
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> g.is_odd_hole_free()                                                  # needs sage.modules
False

Which was to be expected, as its girth is 5

sage: g.girth()
5
>>> from sage.all import *
>>> g.girth()
5

We can check the certificate returned is indeed a 5-cycle:

sage: cycle = g.is_odd_hole_free(certificate=True)                          # needs sage.modules
sage: cycle.is_isomorphic(graphs.CycleGraph(5))                             # needs sage.modules
True
>>> from sage.all import *
>>> cycle = g.is_odd_hole_free(certificate=True)                          # needs sage.modules
>>> cycle.is_isomorphic(graphs.CycleGraph(Integer(5)))                             # needs sage.modules
True

As any chordal graph is hole-free, no interval graph has an odd hole:

sage: g = graphs.RandomIntervalGraph(20)
sage: g.is_odd_hole_free()                                                  # needs sage.modules
True
>>> from sage.all import *
>>> g = graphs.RandomIntervalGraph(Integer(20))
>>> g.is_odd_hole_free()                                                  # needs sage.modules
True
is_overfull()[source]

Test whether the current graph is overfull.

A graph \(G\) on \(n\) vertices and \(m\) edges is said to be overfull if:

  • \(n\) is odd

  • It satisfies \(2m > (n-1)\Delta(G)\), where \(\Delta(G)\) denotes the maximum degree among all vertices in \(G\).

An overfull graph must have a chromatic index of \(\Delta(G)+1\).

EXAMPLES:

A complete graph of order \(n > 1\) is overfull if and only if \(n\) is odd:

sage: graphs.CompleteGraph(6).is_overfull()
False
sage: graphs.CompleteGraph(7).is_overfull()
True
sage: graphs.CompleteGraph(1).is_overfull()
False
>>> from sage.all import *
>>> graphs.CompleteGraph(Integer(6)).is_overfull()
False
>>> graphs.CompleteGraph(Integer(7)).is_overfull()
True
>>> graphs.CompleteGraph(Integer(1)).is_overfull()
False

The claw graph is not overfull:

sage: from sage.graphs.graph_coloring import edge_coloring
sage: g = graphs.ClawGraph()
sage: g
Claw graph: Graph on 4 vertices
sage: edge_coloring(g, value_only=True)                                     # needs sage.numerical_mip
3
sage: g.is_overfull()
False
>>> from sage.all import *
>>> from sage.graphs.graph_coloring import edge_coloring
>>> g = graphs.ClawGraph()
>>> g
Claw graph: Graph on 4 vertices
>>> edge_coloring(g, value_only=True)                                     # needs sage.numerical_mip
3
>>> g.is_overfull()
False

The Holt graph is an example of a overfull graph:

sage: G = graphs.HoltGraph()
sage: G.is_overfull()
True
>>> from sage.all import *
>>> G = graphs.HoltGraph()
>>> G.is_overfull()
True

Checking that all complete graphs \(K_n\) for even \(0 \leq n \leq 100\) are not overfull:

sage: def check_overfull_Kn_even(n):
....:     i = 0
....:     while i <= n:
....:         if graphs.CompleteGraph(i).is_overfull():
....:             print("A complete graph of even order cannot be overfull.")
....:             return
....:         i += 2
....:     print("Complete graphs of even order up to %s are not overfull." % n)
...
sage: check_overfull_Kn_even(100)  # long time
Complete graphs of even order up to 100 are not overfull.
>>> from sage.all import *
>>> def check_overfull_Kn_even(n):
...     i = Integer(0)
...     while i <= n:
...         if graphs.CompleteGraph(i).is_overfull():
...             print("A complete graph of even order cannot be overfull.")
...             return
...         i += Integer(2)
...     print("Complete graphs of even order up to %s are not overfull." % n)
...
>>> check_overfull_Kn_even(Integer(100))  # long time
Complete graphs of even order up to 100 are not overfull.

The null graph, i.e. the graph with no vertices, is not overfull:

sage: Graph().is_overfull()
False
sage: graphs.CompleteGraph(0).is_overfull()
False
>>> from sage.all import *
>>> Graph().is_overfull()
False
>>> graphs.CompleteGraph(Integer(0)).is_overfull()
False

Checking that all complete graphs \(K_n\) for odd \(1 < n \leq 100\) are overfull:

sage: def check_overfull_Kn_odd(n):
....:     i = 3
....:     while i <= n:
....:         if not graphs.CompleteGraph(i).is_overfull():
....:             print("A complete graph of odd order > 1 must be overfull.")
....:             return
....:         i += 2
....:     print("Complete graphs of odd order > 1 up to %s are overfull." % n)
...
sage: check_overfull_Kn_odd(100)  # long time
Complete graphs of odd order > 1 up to 100 are overfull.
>>> from sage.all import *
>>> def check_overfull_Kn_odd(n):
...     i = Integer(3)
...     while i <= n:
...         if not graphs.CompleteGraph(i).is_overfull():
...             print("A complete graph of odd order > 1 must be overfull.")
...             return
...         i += Integer(2)
...     print("Complete graphs of odd order > 1 up to %s are overfull." % n)
...
>>> check_overfull_Kn_odd(Integer(100))  # long time
Complete graphs of odd order > 1 up to 100 are overfull.

The Petersen Graph, though, is not overfull while its chromatic index is \(\Delta+1\):

sage: g = graphs.PetersenGraph()
sage: g.is_overfull()
False
sage: from sage.graphs.graph_coloring import edge_coloring
sage: max(g.degree()) + 1 ==  edge_coloring(g, value_only=True)             # needs sage.numerical_mip
True
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> g.is_overfull()
False
>>> from sage.graphs.graph_coloring import edge_coloring
>>> max(g.degree()) + Integer(1) ==  edge_coloring(g, value_only=True)             # needs sage.numerical_mip
True
is_partial_cube(G, certificate=False)[source]

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 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
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> 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
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(10)).cartesian_product(graphs.CompleteGraph(Integer(2)))
>>> g.is_partial_cube()
True
is_path()[source]

Check whether self is a path.

A connected graph of order \(n \geq 2\) is a path if it is a tree (see is_tree()) with \(n-2\) vertices of degree 2 and two of degree 1. By convention, a graph of order 1 without loops is a path, but the empty graph is not a path.

EXAMPLES:

sage: G = graphs.PathGraph(5) sage: G.is_path() True sage: H = graphs.CycleGraph(5) sage: H.is_path() False sage: D = graphs.PathGraph(5).disjoint_union(graphs.CycleGraph(5)) sage: D.is_path() False sage: E = graphs.EmptyGraph() sage: E.is_path() False sage: O = Graph([[1], []]) sage: O.is_path() True sage: O.allow_loops(True) sage: O.add_edge(1, 1) sage: O.is_path() False

is_perfect(certificate=False)[source]

Test whether the graph is perfect.

A graph \(G\) is said to be perfect if \(\chi(H)=\omega(H)\) hold for any induced subgraph \(H\subseteq_i G\) (and so for \(G\) itself, too), where \(\chi(H)\) represents the chromatic number of \(H\), and \(\omega(H)\) its clique number. The Strong Perfect Graph Theorem [CRST2006] gives another characterization of perfect graphs:

A graph is perfect if and only if it contains no odd hole (cycle on an odd number \(k\) of vertices, \(k>3\)) nor any odd antihole (complement of a hole) as an induced subgraph.

INPUT:

  • certificate – boolean (default: False); whether to return a certificate

OUTPUT:

When certificate = False, this function returns a boolean value. When certificate = True, it returns a subgraph of self isomorphic to an odd hole or an odd antihole if any, and None otherwise.

EXAMPLES:

A Bipartite Graph is always perfect

sage: g = graphs.RandomBipartite(8,4,.5)                                    # needs numpy
sage: g.is_perfect()                                                        # needs numpy sage.modules
True
>>> from sage.all import *
>>> g = graphs.RandomBipartite(Integer(8),Integer(4),RealNumber('.5'))                                    # needs numpy
>>> g.is_perfect()                                                        # needs numpy sage.modules
True

So is the line graph of a bipartite graph:

sage: g = graphs.RandomBipartite(4,3,0.7)                                   # needs numpy
sage: g.line_graph().is_perfect()   # long time                             # needs numpy sage.modules
True
>>> from sage.all import *
>>> g = graphs.RandomBipartite(Integer(4),Integer(3),RealNumber('0.7'))                                   # needs numpy
>>> g.line_graph().is_perfect()   # long time                             # needs numpy sage.modules
True

As well as the Cartesian product of two complete graphs:

sage: g = graphs.CompleteGraph(3).cartesian_product(graphs.CompleteGraph(3))
sage: g.is_perfect()                                                        # needs sage.modules
True
>>> from sage.all import *
>>> g = graphs.CompleteGraph(Integer(3)).cartesian_product(graphs.CompleteGraph(Integer(3)))
>>> g.is_perfect()                                                        # needs sage.modules
True

Interval Graphs, which are chordal graphs, too

sage: g =  graphs.RandomIntervalGraph(7)
sage: g.is_perfect()                                                        # needs sage.modules
True
>>> from sage.all import *
>>> g =  graphs.RandomIntervalGraph(Integer(7))
>>> g.is_perfect()                                                        # needs sage.modules
True

The PetersenGraph, which is triangle-free and has chromatic number 3 is obviously not perfect:

sage: g = graphs.PetersenGraph()
sage: g.is_perfect()                                                        # needs sage.modules
False
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> g.is_perfect()                                                        # needs sage.modules
False

We can obtain an induced 5-cycle as a certificate:

sage: g.is_perfect(certificate=True)                                        # needs sage.modules
Subgraph of (Petersen graph): Graph on 5 vertices
>>> from sage.all import *
>>> g.is_perfect(certificate=True)                                        # needs sage.modules
Subgraph of (Petersen graph): Graph on 5 vertices
is_permutation(g, algorithm='greedy', certificate=False, check=True, solver=None, verbose=0)[source]

Check whether the graph is a permutation graph.

For more information on permutation graphs, refer to the documentation of the comparability module.

INPUT:

  • g – a graph

  • algorithm – string (default: 'greedy'); choose the implementation used for the subcalls to is_comparability()

    • 'greedy' – a greedy algorithm (see the documentation of the comparability module)

    • 'MILP' – a Mixed Integer Linear Program formulation of the problem. Beware, for this implementation is unable to return negative certificates ! When certificate = True, negative certificates are always equal to None. True certificates are valid, though.

  • certificate – boolean (default: False); whether to return a certificate for the answer given. For True answers the certificate is a permutation, for False answers it is a no-certificate for the test of comparability or co-comparability.

  • check – boolean (default: True); whether to check that the permutations returned indeed create the expected Permutation graph. Pretty cheap compared to the rest, hence a good investment. It is enabled by default.

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

Note

As the True certificate is a Permutation object, the segment intersection model of the permutation graph can be visualized through a call to Permutation.show.

EXAMPLES:

A permutation realizing the bull graph:

sage: from sage.graphs.comparability import is_permutation
sage: g = graphs.BullGraph()
sage: _ , certif = is_permutation(g, certificate=True)
sage: h = graphs.PermutationGraph(*certif)
sage: h.is_isomorphic(g)
True
>>> from sage.all import *
>>> from sage.graphs.comparability import is_permutation
>>> g = graphs.BullGraph()
>>> _ , certif = is_permutation(g, certificate=True)
>>> h = graphs.PermutationGraph(*certif)
>>> h.is_isomorphic(g)
True

Plotting the realization as an intersection graph of segments:

sage: true, perm = is_permutation(g, certificate=True)
sage: p1 = Permutation([nn+1 for nn in perm[0]])
sage: p2 = Permutation([nn+1 for nn in perm[1]])
sage: p = p2 * p1.inverse()
sage: p.show(representation='braid')                                            # needs sage.plot
>>> from sage.all import *
>>> true, perm = is_permutation(g, certificate=True)
>>> p1 = Permutation([nn+Integer(1) for nn in perm[Integer(0)]])
>>> p2 = Permutation([nn+Integer(1) for nn in perm[Integer(1)]])
>>> p = p2 * p1.inverse()
>>> p.show(representation='braid')                                            # needs sage.plot
is_polyhedral()[source]

Check whether the graph is the graph of the polyhedron.

By a theorem of Steinitz (Satz 43, p. 77 of [St1922]), graphs of three-dimensional polyhedra are exactly the simple 3-vertex-connected planar graphs.

EXAMPLES:

sage: C = graphs.CubeGraph(3)
sage: C.is_polyhedral()
True
sage: K33=graphs.CompleteBipartiteGraph(3, 3)
sage: K33.is_polyhedral()
False
sage: graphs.CycleGraph(17).is_polyhedral()
False
sage: [i for i in range(9) if graphs.CompleteGraph(i).is_polyhedral()]
[4]
>>> from sage.all import *
>>> C = graphs.CubeGraph(Integer(3))
>>> C.is_polyhedral()
True
>>> K33=graphs.CompleteBipartiteGraph(Integer(3), Integer(3))
>>> K33.is_polyhedral()
False
>>> graphs.CycleGraph(Integer(17)).is_polyhedral()
False
>>> [i for i in range(Integer(9)) if graphs.CompleteGraph(i).is_polyhedral()]
[4]
is_prime(algorithm=None)[source]

Test whether the current graph is prime.

A graph is prime if all its modules are trivial (i.e. empty, all of the graph or singletons) – see modular_decomposition(). This method computes the modular decomposition tree using modular_decomposition().

INPUT:

  • algorithm – string (default: None); the algorithm used to compute the modular decomposition tree; the value is forwarded directly to modular_decomposition().

EXAMPLES:

The Petersen Graph and the Bull Graph are both prime:

sage: graphs.PetersenGraph().is_prime()
True
sage: graphs.BullGraph().is_prime()
True
>>> from sage.all import *
>>> graphs.PetersenGraph().is_prime()
True
>>> graphs.BullGraph().is_prime()
True

Though quite obviously, the disjoint union of them is not:

sage: (graphs.PetersenGraph() + graphs.BullGraph()).is_prime()
False
>>> from sage.all import *
>>> (graphs.PetersenGraph() + graphs.BullGraph()).is_prime()
False
is_redundant(G, dom, focus=None)[source]

Check whether dom has redundant vertices.

For a graph \(G\) and sets \(D\) and \(S\) of vertices, we say that a vertex \(v \in D\) is redundant in \(S\) if \(v\) has no private neighbor with respect to \(D\) in \(S\). In other words, there is no vertex in \(S\) that is dominated by \(v\) but not by \(D \setminus \{v\}\).

INPUT:

  • dom – iterable of vertices of G; where we look for redundant vertices

  • focus – iterable of vertices of G (default: None); if specified, this method checks instead whether dom has a redundant vertex in focus

Warning

The assumption is made that focus (if provided) does not contain repeated vertices.

EXAMPLES:

sage: G = graphs.CubeGraph(3)
sage: G.is_redundant(['000', '101'], ['011'])
True
sage: G.is_redundant(['000', '101'])
False
>>> from sage.all import *
>>> G = graphs.CubeGraph(Integer(3))
>>> G.is_redundant(['000', '101'], ['011'])
True
>>> G.is_redundant(['000', '101'])
False
is_semi_symmetric()[source]

Check if self is semi-symmetric.

A graph is semi-symmetric if it is regular, edge-transitive but not vertex-transitive.

EXAMPLES:

The Petersen graph is not semi-symmetric:

sage: P = graphs.PetersenGraph()
sage: P.is_semi_symmetric()                                                 # needs sage.libs.gap
False
>>> from sage.all import *
>>> P = graphs.PetersenGraph()
>>> P.is_semi_symmetric()                                                 # needs sage.libs.gap
False

The Gray graph is the smallest possible cubic semi-symmetric graph:

sage: G = graphs.GrayGraph()                                                # needs networkx
sage: G.is_semi_symmetric()                                                 # needs networkx sage.libs.gap
True
>>> from sage.all import *
>>> G = graphs.GrayGraph()                                                # needs networkx
>>> G.is_semi_symmetric()                                                 # needs networkx sage.libs.gap
True

Another well known semi-symmetric graph is the Ljubljana graph:

sage: L = graphs.LjubljanaGraph()                                           # needs networkx
sage: L.is_semi_symmetric()                                                 # needs networkx sage.libs.gap
True
>>> from sage.all import *
>>> L = graphs.LjubljanaGraph()                                           # needs networkx
>>> L.is_semi_symmetric()                                                 # needs networkx sage.libs.gap
True
is_split()[source]

Return True if the graph is a Split graph, False otherwise.

A Graph \(G\) is said to be a split graph if its vertices \(V(G)\) can be partitioned into two sets \(K\) and \(I\) such that the vertices of \(K\) induce a complete graph, and those of \(I\) are an independent set.

There is a simple test to check whether a graph is a split graph (see, for instance, the book “Graph Classes, a survey” [BLS1999] page 203) :

Given the degree sequence \(d_1 \geq ... \geq d_n\) of \(G\), a graph is a split graph if and only if :

\[\sum_{i=1}^\omega d_i = \omega (\omega - 1) + \sum_{i=\omega + 1}^nd_i\]

where \(\omega = max \{i:d_i\geq i-1\}\).

EXAMPLES:

Split graphs are, in particular, chordal graphs. Hence, The Petersen graph can not be split:

sage: graphs.PetersenGraph().is_split()
False
>>> from sage.all import *
>>> graphs.PetersenGraph().is_split()
False

We can easily build some “random” split graph by creating a complete graph, and adding vertices only connected to some random vertices of the clique:

sage: g = graphs.CompleteGraph(10)
sage: sets = Subsets(Set(range(10)))
sage: for i in range(10, 25):
....:    g.add_edges([(i,k) for k in sets.random_element()])
sage: g.is_split()
True
>>> from sage.all import *
>>> g = graphs.CompleteGraph(Integer(10))
>>> sets = Subsets(Set(range(Integer(10))))
>>> for i in range(Integer(10), Integer(25)):
...    g.add_edges([(i,k) for k in sets.random_element()])
>>> g.is_split()
True

Another characterisation of split graph states that a graph is a split graph if and only if does not contain the 4-cycle, 5-cycle or \(2K_2\) as an induced subgraph. Hence for the above graph we have:

sage: forbidden_subgraphs = [graphs.CycleGraph(4),
....:                        graphs.CycleGraph(5),
....:                        2 * graphs.CompleteGraph(2)]
sage: sum(g.subgraph_search_count(H, induced=True)                          # needs sage.modules
....:     for H in forbidden_subgraphs)
0
>>> from sage.all import *
>>> forbidden_subgraphs = [graphs.CycleGraph(Integer(4)),
...                        graphs.CycleGraph(Integer(5)),
...                        Integer(2) * graphs.CompleteGraph(Integer(2))]
>>> sum(g.subgraph_search_count(H, induced=True)                          # needs sage.modules
...     for H in forbidden_subgraphs)
0
is_strongly_regular(g, parameters=False)[source]

Check whether the graph is strongly regular.

A simple graph \(G\) is said to be strongly regular with parameters \((n, k, \lambda, \mu)\) if and only if:

  • \(G\) has \(n\) vertices

  • \(G\) is \(k\)-regular

  • Any two adjacent vertices of \(G\) have \(\lambda\) common neighbors

  • Any two non-adjacent vertices of \(G\) have \(\mu\) common neighbors

By convention, the complete graphs, the graphs with no edges and the empty graph are not strongly regular.

See the Wikipedia article Strongly regular graph.

INPUT:

  • parameters – boolean (default: False); whether to return the quadruple \((n, k, \lambda, \mu)\). If parameters = False (default), this method only returns True and False answers. If parameters = True, the True answers are replaced by quadruples \((n, k, \lambda, \mu)\). See definition above.

EXAMPLES:

Petersen’s graph is strongly regular:

sage: g = graphs.PetersenGraph()
sage: g.is_strongly_regular()
True
sage: g.is_strongly_regular(parameters=True)
(10, 3, 0, 1)
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> g.is_strongly_regular()
True
>>> g.is_strongly_regular(parameters=True)
(10, 3, 0, 1)

And Clebsch’s graph is too:

sage: g = graphs.ClebschGraph()
sage: g.is_strongly_regular()
True
sage: g.is_strongly_regular(parameters=True)
(16, 5, 0, 2)
>>> from sage.all import *
>>> g = graphs.ClebschGraph()
>>> g.is_strongly_regular()
True
>>> g.is_strongly_regular(parameters=True)
(16, 5, 0, 2)

But Chvatal’s graph is not:

sage: g = graphs.ChvatalGraph()
sage: g.is_strongly_regular()
False
>>> from sage.all import *
>>> g = graphs.ChvatalGraph()
>>> g.is_strongly_regular()
False

Complete graphs are not strongly regular. (Issue #14297)

sage: g = graphs.CompleteGraph(5)
sage: g.is_strongly_regular()
False
>>> from sage.all import *
>>> g = graphs.CompleteGraph(Integer(5))
>>> g.is_strongly_regular()
False

Completements of complete graphs are not strongly regular:

sage: g = graphs.CompleteGraph(5).complement()
sage: g.is_strongly_regular()
False
>>> from sage.all import *
>>> g = graphs.CompleteGraph(Integer(5)).complement()
>>> g.is_strongly_regular()
False

The empty graph is not strongly regular:

sage: g = graphs.EmptyGraph()
sage: g.is_strongly_regular()
False
>>> from sage.all import *
>>> g = graphs.EmptyGraph()
>>> g.is_strongly_regular()
False

If the input graph has loops or multiedges an exception is raised:

sage: Graph([(1,1),(2,2)],loops=True).is_strongly_regular()
Traceback (most recent call last):
...
ValueError: This method is not known to work on graphs with
loops. Perhaps this method can be updated to handle them, but in the
meantime if you want to use it please disallow loops using
allow_loops().

sage: Graph([(1,2),(1,2)],multiedges=True).is_strongly_regular()
Traceback (most recent call last):
...
ValueError: This method is not known to work on graphs with
multiedges. Perhaps this method can be updated to handle them, but in
the meantime if you want to use it please disallow multiedges using
allow_multiple_edges().
>>> from sage.all import *
>>> Graph([(Integer(1),Integer(1)),(Integer(2),Integer(2))],loops=True).is_strongly_regular()
Traceback (most recent call last):
...
ValueError: This method is not known to work on graphs with
loops. Perhaps this method can be updated to handle them, but in the
meantime if you want to use it please disallow loops using
allow_loops().

>>> Graph([(Integer(1),Integer(2)),(Integer(1),Integer(2))],multiedges=True).is_strongly_regular()
Traceback (most recent call last):
...
ValueError: This method is not known to work on graphs with
multiedges. Perhaps this method can be updated to handle them, but in
the meantime if you want to use it please disallow multiedges using
allow_multiple_edges().
is_tree(certificate=False, output='vertex')[source]

Test if the graph is a tree.

The empty graph is defined to be not a tree.

INPUT:

  • certificate – boolean (default: False); whether to return a certificate. The method only returns boolean answers when certificate = False (default). When it is set to True, it either answers (True, None) when the graph is a tree or (False, cycle) when it contains a cycle. It returns (False, None) when the graph is empty or not connected.

  • output – either 'vertex' (default) or 'edge'; whether the certificate is given as a list of vertices (output = 'vertex') or a list of edges (output = 'edge').

When the certificate cycle is given as a list of edges, the edges are given as \((v_i, v_{i+1}, l)\) where \(v_1, v_2, \dots, v_n\) are the vertices of the cycles (in their cyclic order).

EXAMPLES:

sage: all(T.is_tree() for T in graphs.trees(15))
True
>>> from sage.all import *
>>> all(T.is_tree() for T in graphs.trees(Integer(15)))
True

With certificates:

sage: g = graphs.RandomTree(30)
sage: g.is_tree(certificate=True)
(True, None)
sage: g.add_edge(10,-1)
sage: g.add_edge(11,-1)
sage: isit, cycle = g.is_tree(certificate=True)
sage: isit
False
sage: -1 in cycle
True
>>> from sage.all import *
>>> g = graphs.RandomTree(Integer(30))
>>> g.is_tree(certificate=True)
(True, None)
>>> g.add_edge(Integer(10),-Integer(1))
>>> g.add_edge(Integer(11),-Integer(1))
>>> isit, cycle = g.is_tree(certificate=True)
>>> isit
False
>>> -Integer(1) in cycle
True

One can also ask for the certificate as a list of edges:

sage: g = graphs.CycleGraph(4)
sage: g.is_tree(certificate=True, output='edge')
(False, [(3, 2, None), (2, 1, None), (1, 0, None), (0, 3, None)])
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(4))
>>> g.is_tree(certificate=True, output='edge')
(False, [(3, 2, None), (2, 1, None), (1, 0, None), (0, 3, None)])

This is useful for graphs with multiple edges:

sage: G = Graph([(1, 2, 'a'), (1, 2, 'b')], multiedges=True)
sage: G.is_tree(certificate=True)
(False, [1, 2])
sage: G.is_tree(certificate=True, output='edge')
(False, [(1, 2, 'b'), (2, 1, 'a')])
>>> from sage.all import *
>>> G = Graph([(Integer(1), Integer(2), 'a'), (Integer(1), Integer(2), 'b')], multiedges=True)
>>> G.is_tree(certificate=True)
(False, [1, 2])
>>> G.is_tree(certificate=True, output='edge')
(False, [(1, 2, 'b'), (2, 1, 'a')])
is_triangle_free(algorithm='dense_graph', certificate=False)[source]

Check whether self is triangle-free.

INPUT:

  • algorithm – (default: 'dense_graph') specifies the algorithm to use among:

    • 'matrix' – tests if the trace of the adjacency matrix is positive

    • 'bitset' – encodes adjacencies into bitsets and uses fast bitset operations to test if the input graph contains a triangle. This method is generally faster than standard matrix multiplication.

    • 'dense_graph' – use the implementation of sage.graphs.base.static_dense_graph

  • certificate – boolean (default: False); whether to return a triangle if one is found. This parameter is ignored when algorithm is 'matrix'.

EXAMPLES:

The Petersen Graph is triangle-free:

sage: g = graphs.PetersenGraph()
sage: g.is_triangle_free()
True
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> g.is_triangle_free()
True

or a complete Bipartite Graph:

sage: G = graphs.CompleteBipartiteGraph(5,6)
sage: G.is_triangle_free(algorithm='matrix')                                # needs sage.modules
True
sage: G.is_triangle_free(algorithm='bitset')
True
sage: G.is_triangle_free(algorithm='dense_graph')
True
>>> from sage.all import *
>>> G = graphs.CompleteBipartiteGraph(Integer(5),Integer(6))
>>> G.is_triangle_free(algorithm='matrix')                                # needs sage.modules
True
>>> G.is_triangle_free(algorithm='bitset')
True
>>> G.is_triangle_free(algorithm='dense_graph')
True

a tripartite graph, though, contains many triangles:

sage: G = (3 * graphs.CompleteGraph(5)).complement()
sage: G.is_triangle_free(algorithm='matrix')                                # needs sage.modules
False
sage: G.is_triangle_free(algorithm='bitset')
False
sage: G.is_triangle_free(algorithm='dense_graph')
False
>>> from sage.all import *
>>> G = (Integer(3) * graphs.CompleteGraph(Integer(5))).complement()
>>> G.is_triangle_free(algorithm='matrix')                                # needs sage.modules
False
>>> G.is_triangle_free(algorithm='bitset')
False
>>> G.is_triangle_free(algorithm='dense_graph')
False

Asking for a certificate:

sage: K4 = graphs.CompleteGraph(4)
sage: K4.is_triangle_free(algorithm='dense_graph', certificate=True)
(False, [0, 1, 2])
sage: K4.is_triangle_free(algorithm='bitset', certificate=True)
(False, [0, 1, 2])
>>> from sage.all import *
>>> K4 = graphs.CompleteGraph(Integer(4))
>>> K4.is_triangle_free(algorithm='dense_graph', certificate=True)
(False, [0, 1, 2])
>>> K4.is_triangle_free(algorithm='bitset', certificate=True)
(False, [0, 1, 2])
is_triconnected(G)[source]

Check whether the graph is triconnected.

A triconnected graph is a connected graph on 3 or more vertices that is not broken into disconnected pieces by deleting any pair of vertices.

EXAMPLES:

The Petersen graph is triconnected:

sage: G = graphs.PetersenGraph()
sage: G.is_triconnected()
True
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> G.is_triconnected()
True

But a 2D grid is not:

sage: G = graphs.Grid2dGraph(3, 3)
sage: G.is_triconnected()
False
>>> from sage.all import *
>>> G = graphs.Grid2dGraph(Integer(3), Integer(3))
>>> G.is_triconnected()
False

By convention, a cycle of order 3 is triconnected:

sage: G = graphs.CycleGraph(3)
sage: G.is_triconnected()
True
>>> from sage.all import *
>>> G = graphs.CycleGraph(Integer(3))
>>> G.is_triconnected()
True

But cycles of order 4 and more are not:

sage: [graphs.CycleGraph(i).is_triconnected() for i in range(4, 8)]
[False, False, False, False]
>>> from sage.all import *
>>> [graphs.CycleGraph(i).is_triconnected() for i in range(Integer(4), Integer(8))]
[False, False, False, False]

Comparing different methods on random graphs that are not always triconnected:

sage: G = graphs.RandomBarabasiAlbert(50, 3)                                    # needs networkx
sage: G.is_triconnected() == G.vertex_connectivity(k=3)                         # needs networkx
True
>>> from sage.all import *
>>> G = graphs.RandomBarabasiAlbert(Integer(50), Integer(3))                                    # needs networkx
>>> G.is_triconnected() == G.vertex_connectivity(k=Integer(3))                         # needs networkx
True
is_weakly_chordal(g, certificate=False)[source]

Test whether the given graph is weakly chordal, i.e., the graph and its complement have no induced cycle of length at least 5.

INPUT:

  • certificate – boolean (default: False); whether to return a certificate. If certificate = False, return True or False according to the graph. If certificate = True, return

    • (False, forbidden_subgraph) when the graph contains a forbidden subgraph H, this graph is returned.

    • (True, []) when the graph is weakly chordal.

      For this case, it is not known how to provide a certificate.

ALGORITHM:

This algorithm checks whether the graph g or its complement contain an induced cycle of length at least 5.

Using is_long_hole_free() and is_long_antihole_free() yields a run time of \(O(n+m^2)\) for SparseGraph and \(O(n^2 + m^2)\) for DenseGraph (where \(n\) is the number of vertices and \(m\) is the number of edges of the graph).

EXAMPLES:

The Petersen Graph is not weakly chordal and contains a hole:

sage: g = graphs.PetersenGraph()
sage: r,s = g.is_weakly_chordal(certificate=True)
sage: r
False
sage: l = s.order()
sage: s.is_isomorphic(graphs.CycleGraph(l))
True
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> r,s = g.is_weakly_chordal(certificate=True)
>>> r
False
>>> l = s.order()
>>> s.is_isomorphic(graphs.CycleGraph(l))
True
join(other, labels='pairs', immutable=None)[source]

Return the join of self and other.

INPUT:

  • labels – (defaults to ‘pairs’); if set to ‘pairs’, each element \(v\) in the first graph will be named \((0, v)\) and each element \(u\) in other will be named \((1, u)\) in the result. If set to ‘integers’, the elements of the result will be relabeled with consecutive integers.

  • immutable – boolean (default: None); whether to create a mutable/immutable join. immutable=None (default) means that the graphs and their join will behave the same way.

EXAMPLES:

sage: G = graphs.CycleGraph(3)
sage: H = Graph(2)
sage: J = G.join(H); J
Cycle graph join : Graph on 5 vertices
sage: J.vertices(sort=True)
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1)]
sage: J = G.join(H, labels='integers'); J
Cycle graph join : Graph on 5 vertices
sage: J.vertices(sort=True)
[0, 1, 2, 3, 4]
sage: J.edges(sort=True)
[(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None), (1, 2, None), (1, 3, None), (1, 4, None), (2, 3, None), (2, 4, None)]
>>> from sage.all import *
>>> G = graphs.CycleGraph(Integer(3))
>>> H = Graph(Integer(2))
>>> J = G.join(H); J
Cycle graph join : Graph on 5 vertices
>>> J.vertices(sort=True)
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1)]
>>> J = G.join(H, labels='integers'); J
Cycle graph join : Graph on 5 vertices
>>> J.vertices(sort=True)
[0, 1, 2, 3, 4]
>>> J.edges(sort=True)
[(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None), (1, 2, None), (1, 3, None), (1, 4, None), (2, 3, None), (2, 4, None)]

sage: G = Graph(3)
sage: G.name("Graph on 3 vertices")
sage: H = Graph(2)
sage: H.name("Graph on 2 vertices")
sage: J = G.join(H); J
Graph on 3 vertices join Graph on 2 vertices: Graph on 5 vertices
sage: J.vertices(sort=True)
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1)]
sage: J = G.join(H, labels='integers'); J
Graph on 3 vertices join Graph on 2 vertices: Graph on 5 vertices
sage: J.edges(sort=True)
[(0, 3, None), (0, 4, None), (1, 3, None), (1, 4, None), (2, 3, None), (2, 4, None)]
>>> from sage.all import *
>>> G = Graph(Integer(3))
>>> G.name("Graph on 3 vertices")
>>> H = Graph(Integer(2))
>>> H.name("Graph on 2 vertices")
>>> J = G.join(H); J
Graph on 3 vertices join Graph on 2 vertices: Graph on 5 vertices
>>> J.vertices(sort=True)
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1)]
>>> J = G.join(H, labels='integers'); J
Graph on 3 vertices join Graph on 2 vertices: Graph on 5 vertices
>>> J.edges(sort=True)
[(0, 3, None), (0, 4, None), (1, 3, None), (1, 4, None), (2, 3, None), (2, 4, None)]
kirchhoff_symanzik_polynomial(name='t')[source]

Return the Kirchhoff-Symanzik polynomial of a graph.

This is a polynomial in variables \(t_e\) (each of them representing an edge of the graph \(G\)) defined as a sum over all spanning trees:

\[\begin{split}\Psi_G(t) = \sum_{\substack{T\subseteq V \\ \text{a spanning tree}}} \prod_{e \not\in E(T)} t_e\end{split}\]

This is also called the first Symanzik polynomial or the Kirchhoff polynomial.

INPUT:

  • name – name of the variables (default: 't')

OUTPUT: a polynomial with integer coefficients

ALGORITHM:

This is computed here using a determinant, as explained in Section 3.1 of [Mar2009a].

As an intermediate step, one computes a cycle basis \(\mathcal C\) of \(G\) and a rectangular \(|\mathcal C| \times |E(G)|\) matrix with entries in \(\{-1,0,1\}\), which describes which edge belong to which cycle of \(\mathcal C\) and their respective orientations.

More precisely, after fixing an arbitrary orientation for each edge \(e\in E(G)\) and each cycle \(C\in\mathcal C\), one gets a sign for every incident pair (edge, cycle) which is \(1\) if the orientation coincide and \(-1\) otherwise.

EXAMPLES:

For the cycle of length 5:

sage: G = graphs.CycleGraph(5)
sage: G.kirchhoff_symanzik_polynomial()                                     # needs networkx sage.modules
t0 + t1 + t2 + t3 + t4
>>> from sage.all import *
>>> G = graphs.CycleGraph(Integer(5))
>>> G.kirchhoff_symanzik_polynomial()                                     # needs networkx sage.modules
t0 + t1 + t2 + t3 + t4

One can use another letter for variables:

sage: G.kirchhoff_symanzik_polynomial(name='u')                             # needs networkx sage.modules
u0 + u1 + u2 + u3 + u4
>>> from sage.all import *
>>> G.kirchhoff_symanzik_polynomial(name='u')                             # needs networkx sage.modules
u0 + u1 + u2 + u3 + u4

For the ‘coffee bean’ graph:

sage: G = Graph([(0,1,'a'),(0,1,'b'),(0,1,'c')], multiedges=True)
sage: G.kirchhoff_symanzik_polynomial()                                     # needs networkx sage.modules
t0*t1 + t0*t2 + t1*t2
>>> from sage.all import *
>>> G = Graph([(Integer(0),Integer(1),'a'),(Integer(0),Integer(1),'b'),(Integer(0),Integer(1),'c')], multiedges=True)
>>> G.kirchhoff_symanzik_polynomial()                                     # needs networkx sage.modules
t0*t1 + t0*t2 + t1*t2

For the ‘parachute’ graph:

sage: G = Graph([(0,2,'a'),(0,2,'b'),(0,1,'c'),(1,2,'d')], multiedges=True)
sage: G.kirchhoff_symanzik_polynomial()                                     # needs networkx sage.modules
t0*t1 + t0*t2 + t1*t2 + t1*t3 + t2*t3
>>> from sage.all import *
>>> G = Graph([(Integer(0),Integer(2),'a'),(Integer(0),Integer(2),'b'),(Integer(0),Integer(1),'c'),(Integer(1),Integer(2),'d')], multiedges=True)
>>> G.kirchhoff_symanzik_polynomial()                                     # needs networkx sage.modules
t0*t1 + t0*t2 + t1*t2 + t1*t3 + t2*t3

For the complete graph with 4 vertices:

sage: G = graphs.CompleteGraph(4)
sage: G.kirchhoff_symanzik_polynomial()                                     # needs networkx sage.modules
t0*t1*t3 + t0*t2*t3 + t1*t2*t3 + t0*t1*t4 + t0*t2*t4 + t1*t2*t4
+ t1*t3*t4 + t2*t3*t4 + t0*t1*t5 + t0*t2*t5 + t1*t2*t5 + t0*t3*t5
+ t2*t3*t5 + t0*t4*t5 + t1*t4*t5 + t3*t4*t5
>>> from sage.all import *
>>> G = graphs.CompleteGraph(Integer(4))
>>> G.kirchhoff_symanzik_polynomial()                                     # needs networkx sage.modules
t0*t1*t3 + t0*t2*t3 + t1*t2*t3 + t0*t1*t4 + t0*t2*t4 + t1*t2*t4
+ t1*t3*t4 + t2*t3*t4 + t0*t1*t5 + t0*t2*t5 + t1*t2*t5 + t0*t3*t5
+ t2*t3*t5 + t0*t4*t5 + t1*t4*t5 + t3*t4*t5

REFERENCES:

[Bro2011]

least_effective_resistance(nonedgesonly=True)[source]

Return a list of pairs of nodes with the least effective resistance.

The resistance distance between vertices \(i\) and \(j\) of a simple connected graph \(G\) is defined as the effective resistance between the two vertices on an electrical network constructed from \(G\) replacing each edge of the graph by a unit (1 ohm) resistor.

INPUT:

  • nonedgesonly – boolean (default: True); if True, assign zero resistance to pairs of adjacent vertices

OUTPUT: list

EXAMPLES:

Pairs of non-adjacent nodes with least effective resistance in a straight linear 2-tree on 6 vertices:

sage: G = Graph([(0,1),(0,2),(1,2),(1,3),(3,5),(2,4),(2,3),(3,4),(4,5)])
sage: G.least_effective_resistance()                                        # needs sage.modules
[(1, 4)]
>>> from sage.all import *
>>> G = Graph([(Integer(0),Integer(1)),(Integer(0),Integer(2)),(Integer(1),Integer(2)),(Integer(1),Integer(3)),(Integer(3),Integer(5)),(Integer(2),Integer(4)),(Integer(2),Integer(3)),(Integer(3),Integer(4)),(Integer(4),Integer(5))])
>>> G.least_effective_resistance()                                        # needs sage.modules
[(1, 4)]

Pairs of (adjacent or non-adjacent) nodes with least effective resistance in a straight linear 2-tree on 6 vertices

sage: G.least_effective_resistance(nonedgesonly=False)                      # needs sage.modules
[(2, 3)]
>>> from sage.all import *
>>> G.least_effective_resistance(nonedgesonly=False)                      # needs sage.modules
[(2, 3)]

Pairs of non-adjacent nodes with least effective resistance in a fan on 6 vertices counting only non-adjacent vertex pairs

sage: H = Graph([(0,1),(0,2),(0,3),(0,4),(0,5),(0,6),(1,2),(2,3),(3,4),(4,5)])
sage: H.least_effective_resistance()                                        # needs sage.modules
[(2, 4)]
>>> from sage.all import *
>>> H = Graph([(Integer(0),Integer(1)),(Integer(0),Integer(2)),(Integer(0),Integer(3)),(Integer(0),Integer(4)),(Integer(0),Integer(5)),(Integer(0),Integer(6)),(Integer(1),Integer(2)),(Integer(2),Integer(3)),(Integer(3),Integer(4)),(Integer(4),Integer(5))])
>>> H.least_effective_resistance()                                        # needs sage.modules
[(2, 4)]

See also

lex_M(triangulation=False, labels=False, initial_vertex=None, algorithm=None)[source]

Return an ordering of the vertices according the LexM graph traversal.

LexM is a lexicographic ordering scheme that is a special type of breadth-first-search. LexM can also produce a triangulation of the given graph. This functionality is implemented in this method. For more details on the algorithms used see Sections 4 ('lex_M_slow') and 5.3 ('lex_M_fast') of [RTL76].

Note

This method works only for undirected graphs.

INPUT:

  • triangulation – boolean (default: False); whether to return a list of edges that need to be added in order to triangulate the graph

  • labels – boolean (default: False); whether to return the labels assigned to each vertex

  • initial_vertex – (default: None); the first vertex to consider

  • algorithm – string (default: None); one of the following algorithms:

    • 'lex_M_slow': slower implementation of LexM traversal

    • 'lex_M_fast': faster implementation of LexM traversal (works only when labels is set to False)

    • None: Sage chooses the best algorithm: 'lex_M_slow' if labels is set to True, 'lex_M_fast' otherwise.

OUTPUT:

Depending on the values of the parameters triangulation and labels the method will return one or more of the following (in that order):

  • an ordering of vertices of the graph according to LexM ordering scheme

  • the labels assigned to each vertex

  • a list of edges that when added to the graph will triangulate it

EXAMPLES:

LexM produces an ordering of the vertices:

sage: g = graphs.CompleteGraph(6)
sage: ord = g.lex_M(algorithm='lex_M_fast')
sage: len(ord) == g.order()
True
sage: set(ord) == set(g.vertices(sort=False))
True
sage: ord = g.lex_M(algorithm='lex_M_slow')
sage: len(ord) == g.order()
True
sage: set(ord) == set(g.vertices(sort=False))
True
>>> from sage.all import *
>>> g = graphs.CompleteGraph(Integer(6))
>>> ord = g.lex_M(algorithm='lex_M_fast')
>>> len(ord) == g.order()
True
>>> set(ord) == set(g.vertices(sort=False))
True
>>> ord = g.lex_M(algorithm='lex_M_slow')
>>> len(ord) == g.order()
True
>>> set(ord) == set(g.vertices(sort=False))
True

Both algorithms produce a valid LexM ordering \(\alpha\) (i.e the neighbors of \(\alpha(i)\) in \(G[\{\alpha(i), ..., \alpha(n)\}]\) induce a clique):

sage: from sage.graphs.traversals import is_valid_lex_M_order
sage: G = graphs.PetersenGraph()
sage: ord, F = G.lex_M(triangulation=True, algorithm='lex_M_slow')
sage: is_valid_lex_M_order(G, ord, F)
True
sage: ord, F = G.lex_M(triangulation=True, algorithm='lex_M_fast')
sage: is_valid_lex_M_order(G, ord, F)
True
>>> from sage.all import *
>>> from sage.graphs.traversals import is_valid_lex_M_order
>>> G = graphs.PetersenGraph()
>>> ord, F = G.lex_M(triangulation=True, algorithm='lex_M_slow')
>>> is_valid_lex_M_order(G, ord, F)
True
>>> ord, F = G.lex_M(triangulation=True, algorithm='lex_M_fast')
>>> is_valid_lex_M_order(G, ord, F)
True

LexM produces a triangulation of given graph:

sage: G = graphs.PetersenGraph()
sage: _, F = G.lex_M(triangulation=True)
sage: H = Graph(F, format='list_of_edges')
sage: H.is_chordal()
True
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> _, F = G.lex_M(triangulation=True)
>>> H = Graph(F, format='list_of_edges')
>>> H.is_chordal()
True

LexM ordering of the 3-sun graph:

sage: g = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)])
sage: g.lex_M()
[6, 4, 5, 3, 2, 1]
>>> from sage.all import *
>>> g = Graph([(Integer(1), Integer(2)), (Integer(1), Integer(3)), (Integer(2), Integer(3)), (Integer(2), Integer(4)), (Integer(2), Integer(5)), (Integer(3), Integer(5)), (Integer(3), Integer(6)), (Integer(4), Integer(5)), (Integer(5), Integer(6))])
>>> g.lex_M()
[6, 4, 5, 3, 2, 1]

The ordering depends on the initial vertex:

sage: G = graphs.HouseGraph()
sage: G.lex_M(algorithm='lex_M_slow', initial_vertex=0)
[4, 3, 2, 1, 0]
sage: G.lex_M(algorithm='lex_M_slow', initial_vertex=2)
[1, 4, 3, 0, 2]
sage: G.lex_M(algorithm='lex_M_fast', initial_vertex=0)
[4, 3, 2, 1, 0]
sage: G.lex_M(algorithm='lex_M_fast', initial_vertex=2)
[1, 4, 3, 0, 2]
>>> from sage.all import *
>>> G = graphs.HouseGraph()
>>> G.lex_M(algorithm='lex_M_slow', initial_vertex=Integer(0))
[4, 3, 2, 1, 0]
>>> G.lex_M(algorithm='lex_M_slow', initial_vertex=Integer(2))
[1, 4, 3, 0, 2]
>>> G.lex_M(algorithm='lex_M_fast', initial_vertex=Integer(0))
[4, 3, 2, 1, 0]
>>> G.lex_M(algorithm='lex_M_fast', initial_vertex=Integer(2))
[1, 4, 3, 0, 2]
lovasz_theta(graph)[source]

Return the value of Lovász theta-function of graph.

For a graph \(G\) this function is denoted by \(\theta(G)\), and it can be computed in polynomial time. Mathematically, its most important property is the following:

\[\alpha(G)\leq\theta(G)\leq\chi(\overline{G})\]

with \(\alpha(G)\) and \(\chi(\overline{G})\) being, respectively, the maximum size of an independent set set of \(G\) and the chromatic number of the complement \(\overline{G}\) of \(G\).

For more information, see the Wikipedia article Lovász_number.

Note

  • Implemented for undirected graphs only. Use to_undirected to convert a digraph to an undirected graph.

  • This function requires the optional package csdp, which you can install with sage -i csdp.

EXAMPLES:

sage: C = graphs.PetersenGraph()
sage: C.lovasz_theta()                             # optional - csdp
4.0
sage: graphs.CycleGraph(5).lovasz_theta()          # optional - csdp
2.236068
>>> from sage.all import *
>>> C = graphs.PetersenGraph()
>>> C.lovasz_theta()                             # optional - csdp
4.0
>>> graphs.CycleGraph(Integer(5)).lovasz_theta()          # optional - csdp
2.236068
magnitude_function()[source]

Return the magnitude function of the graph as a rational function.

This is defined as the sum of all coefficients in the inverse of the matrix \(Z\) whose coefficient \(Z_{i,j}\) indexed by a pair of vertices \((i,j)\) is \(q^d(i,j)\) where \(d\) is the distance function in the graph.

By convention, if the distance from \(i\) to \(j\) is infinite (for two vertices not path connected) then \(Z_{i,j}=0\).

The value of the magnitude function at \(q=0\) is the cardinality of the graph. The magnitude function of a disjoint union is the sum of the magnitudes functions of the connected components. The magnitude function of a Cartesian product is the product of the magnitudes functions of the factors.

EXAMPLES:

sage: g = Graph({1:[], 2:[]})
sage: g.magnitude_function()                                                # needs sage.modules
2

sage: g = graphs.CycleGraph(4)
sage: g.magnitude_function()                                                # needs sage.modules
4/(q^2 + 2*q + 1)

sage: g = graphs.CycleGraph(5)
sage: m = g.magnitude_function(); m                                         # needs sage.modules
5/(2*q^2 + 2*q + 1)
>>> from sage.all import *
>>> g = Graph({Integer(1):[], Integer(2):[]})
>>> g.magnitude_function()                                                # needs sage.modules
2

>>> g = graphs.CycleGraph(Integer(4))
>>> g.magnitude_function()                                                # needs sage.modules
4/(q^2 + 2*q + 1)

>>> g = graphs.CycleGraph(Integer(5))
>>> m = g.magnitude_function(); m                                         # needs sage.modules
5/(2*q^2 + 2*q + 1)

One can expand the magnitude as a power series in \(q\) as follows:

sage: q = QQ[['q']].gen()
sage: m(q)                                                                  # needs sage.modules
5 - 10*q + 10*q^2 - 20*q^4 + 40*q^5 - 40*q^6 + ...
>>> from sage.all import *
>>> q = QQ[['q']].gen()
>>> m(q)                                                                  # needs sage.modules
5 - 10*q + 10*q^2 - 20*q^4 + 40*q^5 - 40*q^6 + ...

One can also use the substitution \(q = exp(-t)\) to obtain the magnitude function as a function of \(t\):

sage: g = graphs.CycleGraph(6)
sage: m = g.magnitude_function()                                            # needs sage.modules
sage: t = var('t')                                                          # needs sage.modules sage.symbolic
sage: m(exp(-t))                                                            # needs sage.modules sage.symbolic
6/(2*e^(-t) + 2*e^(-2*t) + e^(-3*t) + 1)
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(6))
>>> m = g.magnitude_function()                                            # needs sage.modules
>>> t = var('t')                                                          # needs sage.modules sage.symbolic
>>> m(exp(-t))                                                            # needs sage.modules sage.symbolic
6/(2*e^(-t) + 2*e^(-2*t) + e^(-3*t) + 1)

REFERENCES:

[Lein]

Tom Leinster, The magnitude of metric spaces. Doc. Math. 18 (2013), 857-905.

matching(G, value_only, algorithm=False, use_edge_labels='Edmonds', solver=False, verbose=None, integrality_tolerance=0)[source]

Return a maximum weighted matching of the graph represented by the list of its edges.

For more information, see the Wikipedia article Matching_(graph_theory).

Given a graph \(G\) such that each edge \(e\) has a weight \(w_e\), a maximum matching is a subset \(S\) of the edges of \(G\) of maximum weight such that no two edges of \(S\) are incident with each other.

As an optimization problem, it can be expressed as:

\[\begin{split}\mbox{Maximize : }&\sum_{e\in G.edges()} w_e b_e\\ \mbox{Such that : }&\forall v \in G, \sum_{(u,v)\in G.edges()} b_{(u,v)}\leq 1\\ &\forall x\in G, b_x\mbox{ is a binary variable}\end{split}\]

INPUT:

  • value_only – boolean (default: False); when set to True, only the cardinal (or the weight) of the matching is returned

  • algorithm – string (default: 'Edmonds')

    • 'Edmonds' selects Edmonds’ algorithm as implemented in NetworkX

    • 'LP' uses a Linear Program formulation of the matching problem

  • use_edge_labels – boolean (default: False)

    • when set to True, computes a weighted matching where each edge is weighted by its label (if an edge has no label, \(1\) is assumed)

    • when set to False, each edge has weight \(1\)

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity: set to 0 by default, which means quiet (only useful when algorithm == "LP")

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

OUTPUT:

  • When value_only=False (default), this method returns an EdgesView containing the edges of a maximum matching of \(G\).

  • When value_only=True, this method returns the sum of the weights (default: 1) of the edges of a maximum matching of \(G\). The type of the output may vary according to the type of the edge labels and the algorithm used.

ALGORITHM:

The problem is solved using Edmond’s algorithm implemented in NetworkX, or using Linear Programming depending on the value of algorithm.

EXAMPLES:

Maximum matching in a Pappus Graph:

sage: g = graphs.PappusGraph()
sage: g.matching(value_only=True)                                            # needs sage.networkx
9
>>> from sage.all import *
>>> g = graphs.PappusGraph()
>>> g.matching(value_only=True)                                            # needs sage.networkx
9

Same test with the Linear Program formulation:

sage: g = graphs.PappusGraph()
sage: g.matching(algorithm='LP', value_only=True)                            # needs sage.numerical.mip
9
>>> from sage.all import *
>>> g = graphs.PappusGraph()
>>> g.matching(algorithm='LP', value_only=True)                            # needs sage.numerical.mip
9
../../_images/graph-3.svg
matching_polynomial(G, complement=True, name=None)[source]

Compute the matching polynomial of the graph \(G\).

If \(p(G, k)\) denotes the number of \(k\)-matchings (matchings with \(k\) edges) in \(G\), then the matching polynomial is defined as [God1993]:

\[\mu(x)=\sum_{k \geq 0} (-1)^k p(G,k) x^{n-2k}\]

INPUT:

  • complement – boolean (default: True); whether to use Godsil’s duality theorem to compute the matching polynomial from that of the graphs complement (see ALGORITHM)

  • name – (optional) string for the variable name in the polynomial

Note

The complement option uses matching polynomials of complete graphs, which are cached. So if you are crazy enough to try computing the matching polynomial on a graph with millions of vertices, you might not want to use this option, since it will end up caching millions of polynomials of degree in the millions.

ALGORITHM:

The algorithm used is a recursive one, based on the following observation [God1993]:

  • If \(e\) is an edge of \(G\), \(G'\) is the result of deleting the edge \(e\), and \(G''\) is the result of deleting each vertex in \(e\), then the matching polynomial of \(G\) is equal to that of \(G'\) minus that of \(G''\).

    (the algorithm actually computes the signless matching polynomial, for which the recursion is the same when one replaces the subtraction by an addition. It is then converted into the matching polynomial and returned)

Depending on the value of complement, Godsil’s duality theorem [God1993] can also be used to compute \(\mu(x)\) :

\[\mu(\overline{G}, x) = \sum_{k \geq 0} p(G,k) \mu( K_{n-2k}, x)\]

Where \(\overline{G}\) is the complement of \(G\), and \(K_n\) the complete graph on \(n\) vertices.

EXAMPLES:

sage: g = graphs.PetersenGraph()
sage: g.matching_polynomial()
x^10 - 15*x^8 + 75*x^6 - 145*x^4 + 90*x^2 - 6
sage: g.matching_polynomial(complement=False)
x^10 - 15*x^8 + 75*x^6 - 145*x^4 + 90*x^2 - 6
sage: g.matching_polynomial(name='tom')
tom^10 - 15*tom^8 + 75*tom^6 - 145*tom^4 + 90*tom^2 - 6
sage: g = Graph()
sage: L = [graphs.RandomGNP(8, .3) for i in range(1, 6)]
sage: prod([h.matching_polynomial() for h in L]) == sum(L, g).matching_polynomial()  # long time (up to 10s on sage.math, 2011)
True
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> g.matching_polynomial()
x^10 - 15*x^8 + 75*x^6 - 145*x^4 + 90*x^2 - 6
>>> g.matching_polynomial(complement=False)
x^10 - 15*x^8 + 75*x^6 - 145*x^4 + 90*x^2 - 6
>>> g.matching_polynomial(name='tom')
tom^10 - 15*tom^8 + 75*tom^6 - 145*tom^4 + 90*tom^2 - 6
>>> g = Graph()
>>> L = [graphs.RandomGNP(Integer(8), RealNumber('.3')) for i in range(Integer(1), Integer(6))]
>>> prod([h.matching_polynomial() for h in L]) == sum(L, g).matching_polynomial()  # long time (up to 10s on sage.math, 2011)
True

sage: for i in range(1, 12):  # long time (10s on sage.math, 2011)
....:     for t in graphs.trees(i):
....:         if t.matching_polynomial() != t.characteristic_polynomial():
....:             raise RuntimeError('bug for a tree A of size {0}'.format(i))
....:         c = t.complement()
....:         if c.matching_polynomial(complement=False) != c.matching_polynomial():
....:             raise RuntimeError('bug for a tree B of size {0}'.format(i))
>>> from sage.all import *
>>> for i in range(Integer(1), Integer(12)):  # long time (10s on sage.math, 2011)
...     for t in graphs.trees(i):
...         if t.matching_polynomial() != t.characteristic_polynomial():
...             raise RuntimeError('bug for a tree A of size {0}'.format(i))
...         c = t.complement()
...         if c.matching_polynomial(complement=False) != c.matching_polynomial():
...             raise RuntimeError('bug for a tree B of size {0}'.format(i))

sage: from sage.graphs.matchpoly import matching_polynomial
sage: matching_polynomial(graphs.CompleteGraph(0))
1
sage: matching_polynomial(graphs.CompleteGraph(1))
x
sage: matching_polynomial(graphs.CompleteGraph(2))
x^2 - 1
sage: matching_polynomial(graphs.CompleteGraph(3))
x^3 - 3*x
sage: matching_polynomial(graphs.CompleteGraph(4))
x^4 - 6*x^2 + 3
sage: matching_polynomial(graphs.CompleteGraph(5))
x^5 - 10*x^3 + 15*x
sage: matching_polynomial(graphs.CompleteGraph(6))
x^6 - 15*x^4 + 45*x^2 - 15
sage: matching_polynomial(graphs.CompleteGraph(7))
x^7 - 21*x^5 + 105*x^3 - 105*x
sage: matching_polynomial(graphs.CompleteGraph(8))
x^8 - 28*x^6 + 210*x^4 - 420*x^2 + 105
sage: matching_polynomial(graphs.CompleteGraph(9))
x^9 - 36*x^7 + 378*x^5 - 1260*x^3 + 945*x
sage: matching_polynomial(graphs.CompleteGraph(10))
x^10 - 45*x^8 + 630*x^6 - 3150*x^4 + 4725*x^2 - 945
sage: matching_polynomial(graphs.CompleteGraph(11))
x^11 - 55*x^9 + 990*x^7 - 6930*x^5 + 17325*x^3 - 10395*x
sage: matching_polynomial(graphs.CompleteGraph(12))
x^12 - 66*x^10 + 1485*x^8 - 13860*x^6 + 51975*x^4 - 62370*x^2 + 10395
sage: matching_polynomial(graphs.CompleteGraph(13))
x^13 - 78*x^11 + 2145*x^9 - 25740*x^7 + 135135*x^5 - 270270*x^3 + 135135*x
>>> from sage.all import *
>>> from sage.graphs.matchpoly import matching_polynomial
>>> matching_polynomial(graphs.CompleteGraph(Integer(0)))
1
>>> matching_polynomial(graphs.CompleteGraph(Integer(1)))
x
>>> matching_polynomial(graphs.CompleteGraph(Integer(2)))
x^2 - 1
>>> matching_polynomial(graphs.CompleteGraph(Integer(3)))
x^3 - 3*x
>>> matching_polynomial(graphs.CompleteGraph(Integer(4)))
x^4 - 6*x^2 + 3
>>> matching_polynomial(graphs.CompleteGraph(Integer(5)))
x^5 - 10*x^3 + 15*x
>>> matching_polynomial(graphs.CompleteGraph(Integer(6)))
x^6 - 15*x^4 + 45*x^2 - 15
>>> matching_polynomial(graphs.CompleteGraph(Integer(7)))
x^7 - 21*x^5 + 105*x^3 - 105*x
>>> matching_polynomial(graphs.CompleteGraph(Integer(8)))
x^8 - 28*x^6 + 210*x^4 - 420*x^2 + 105
>>> matching_polynomial(graphs.CompleteGraph(Integer(9)))
x^9 - 36*x^7 + 378*x^5 - 1260*x^3 + 945*x
>>> matching_polynomial(graphs.CompleteGraph(Integer(10)))
x^10 - 45*x^8 + 630*x^6 - 3150*x^4 + 4725*x^2 - 945
>>> matching_polynomial(graphs.CompleteGraph(Integer(11)))
x^11 - 55*x^9 + 990*x^7 - 6930*x^5 + 17325*x^3 - 10395*x
>>> matching_polynomial(graphs.CompleteGraph(Integer(12)))
x^12 - 66*x^10 + 1485*x^8 - 13860*x^6 + 51975*x^4 - 62370*x^2 + 10395
>>> matching_polynomial(graphs.CompleteGraph(Integer(13)))
x^13 - 78*x^11 + 2145*x^9 - 25740*x^7 + 135135*x^5 - 270270*x^3 + 135135*x

sage: G = Graph({0:[1,2], 1:[2]})
sage: matching_polynomial(G)
x^3 - 3*x
sage: G = Graph({0:[1,2]})
sage: matching_polynomial(G)
x^3 - 2*x
sage: G = Graph({0:[1], 2:[]})
sage: matching_polynomial(G)
x^3 - x
sage: G = Graph({0:[], 1:[], 2:[]})
sage: matching_polynomial(G)
x^3
>>> from sage.all import *
>>> G = Graph({Integer(0):[Integer(1),Integer(2)], Integer(1):[Integer(2)]})
>>> matching_polynomial(G)
x^3 - 3*x
>>> G = Graph({Integer(0):[Integer(1),Integer(2)]})
>>> matching_polynomial(G)
x^3 - 2*x
>>> G = Graph({Integer(0):[Integer(1)], Integer(2):[]})
>>> matching_polynomial(G)
x^3 - x
>>> G = Graph({Integer(0):[], Integer(1):[], Integer(2):[]})
>>> matching_polynomial(G)
x^3

sage: matching_polynomial(graphs.CompleteGraph(0), complement=False)
1
sage: matching_polynomial(graphs.CompleteGraph(1), complement=False)
x
sage: matching_polynomial(graphs.CompleteGraph(2), complement=False)
x^2 - 1
sage: matching_polynomial(graphs.CompleteGraph(3), complement=False)
x^3 - 3*x
sage: matching_polynomial(graphs.CompleteGraph(4), complement=False)
x^4 - 6*x^2 + 3
sage: matching_polynomial(graphs.CompleteGraph(5), complement=False)
x^5 - 10*x^3 + 15*x
sage: matching_polynomial(graphs.CompleteGraph(6), complement=False)
x^6 - 15*x^4 + 45*x^2 - 15
sage: matching_polynomial(graphs.CompleteGraph(7), complement=False)
x^7 - 21*x^5 + 105*x^3 - 105*x
sage: matching_polynomial(graphs.CompleteGraph(8), complement=False)
x^8 - 28*x^6 + 210*x^4 - 420*x^2 + 105
sage: matching_polynomial(graphs.CompleteGraph(9), complement=False)
x^9 - 36*x^7 + 378*x^5 - 1260*x^3 + 945*x
sage: matching_polynomial(graphs.CompleteGraph(10), complement=False)
x^10 - 45*x^8 + 630*x^6 - 3150*x^4 + 4725*x^2 - 945
sage: matching_polynomial(graphs.CompleteGraph(11), complement=False)
x^11 - 55*x^9 + 990*x^7 - 6930*x^5 + 17325*x^3 - 10395*x
sage: matching_polynomial(graphs.CompleteGraph(12), complement=False)
x^12 - 66*x^10 + 1485*x^8 - 13860*x^6 + 51975*x^4 - 62370*x^2 + 10395
sage: matching_polynomial(graphs.CompleteGraph(13), complement=False)
x^13 - 78*x^11 + 2145*x^9 - 25740*x^7 + 135135*x^5 - 270270*x^3 + 135135*x
>>> from sage.all import *
>>> matching_polynomial(graphs.CompleteGraph(Integer(0)), complement=False)
1
>>> matching_polynomial(graphs.CompleteGraph(Integer(1)), complement=False)
x
>>> matching_polynomial(graphs.CompleteGraph(Integer(2)), complement=False)
x^2 - 1
>>> matching_polynomial(graphs.CompleteGraph(Integer(3)), complement=False)
x^3 - 3*x
>>> matching_polynomial(graphs.CompleteGraph(Integer(4)), complement=False)
x^4 - 6*x^2 + 3
>>> matching_polynomial(graphs.CompleteGraph(Integer(5)), complement=False)
x^5 - 10*x^3 + 15*x
>>> matching_polynomial(graphs.CompleteGraph(Integer(6)), complement=False)
x^6 - 15*x^4 + 45*x^2 - 15
>>> matching_polynomial(graphs.CompleteGraph(Integer(7)), complement=False)
x^7 - 21*x^5 + 105*x^3 - 105*x
>>> matching_polynomial(graphs.CompleteGraph(Integer(8)), complement=False)
x^8 - 28*x^6 + 210*x^4 - 420*x^2 + 105
>>> matching_polynomial(graphs.CompleteGraph(Integer(9)), complement=False)
x^9 - 36*x^7 + 378*x^5 - 1260*x^3 + 945*x
>>> matching_polynomial(graphs.CompleteGraph(Integer(10)), complement=False)
x^10 - 45*x^8 + 630*x^6 - 3150*x^4 + 4725*x^2 - 945
>>> matching_polynomial(graphs.CompleteGraph(Integer(11)), complement=False)
x^11 - 55*x^9 + 990*x^7 - 6930*x^5 + 17325*x^3 - 10395*x
>>> matching_polynomial(graphs.CompleteGraph(Integer(12)), complement=False)
x^12 - 66*x^10 + 1485*x^8 - 13860*x^6 + 51975*x^4 - 62370*x^2 + 10395
>>> matching_polynomial(graphs.CompleteGraph(Integer(13)), complement=False)
x^13 - 78*x^11 + 2145*x^9 - 25740*x^7 + 135135*x^5 - 270270*x^3 + 135135*x
maximum_average_degree(value_only=True, solver=None, verbose=0)[source]

Return the Maximum Average Degree (MAD) of the current graph.

The Maximum Average Degree (MAD) of a graph is defined as the average degree of its densest subgraph. More formally, Mad(G) = \max_{H\subseteq G} Ad(H), where \(Ad(G)\) denotes the average degree of \(G\).

This can be computed in polynomial time.

INPUT:

  • value_only – boolean (default: True)

    • If value_only=True, only the numerical value of the \(MAD\) is returned.

    • Else, the subgraph of \(G\) realizing the \(MAD\) is returned.

  • solver – (default: None) specify a Linear Program (LP) solver to be used. If set to None, the default one is used. For more information on LP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

EXAMPLES:

In any graph, the \(Mad\) is always larger than the average degree:

sage: g = graphs.RandomGNP(20,.3)
sage: mad_g = g.maximum_average_degree()                                    # needs sage.numerical.mip
sage: g.average_degree() <= mad_g                                           # needs sage.numerical.mip
True
>>> from sage.all import *
>>> g = graphs.RandomGNP(Integer(20),RealNumber('.3'))
>>> mad_g = g.maximum_average_degree()                                    # needs sage.numerical.mip
>>> g.average_degree() <= mad_g                                           # needs sage.numerical.mip
True

Unlike the average degree, the \(Mad\) of the disjoint union of two graphs is the maximum of the \(Mad\) of each graphs:

sage: h = graphs.RandomGNP(20,.3)
sage: mad_h = h.maximum_average_degree()                                    # needs sage.numerical.mip
sage: (g+h).maximum_average_degree() == max(mad_g, mad_h)                   # needs sage.numerical.mip
True
>>> from sage.all import *
>>> h = graphs.RandomGNP(Integer(20),RealNumber('.3'))
>>> mad_h = h.maximum_average_degree()                                    # needs sage.numerical.mip
>>> (g+h).maximum_average_degree() == max(mad_g, mad_h)                   # needs sage.numerical.mip
True

The subgraph of a regular graph realizing the maximum average degree is always the whole graph

sage: g = graphs.CompleteGraph(5)
sage: mad_g = g.maximum_average_degree(value_only=False)                    # needs sage.numerical.mip
sage: g.is_isomorphic(mad_g)                                                # needs sage.numerical.mip
True
>>> from sage.all import *
>>> g = graphs.CompleteGraph(Integer(5))
>>> mad_g = g.maximum_average_degree(value_only=False)                    # needs sage.numerical.mip
>>> g.is_isomorphic(mad_g)                                                # needs sage.numerical.mip
True

This also works for complete bipartite graphs

sage: g = graphs.CompleteBipartiteGraph(3,4)
sage: mad_g = g.maximum_average_degree(value_only=False)                    # needs sage.numerical.mip
sage: g.is_isomorphic(mad_g)                                                # needs sage.numerical.mip
True
>>> from sage.all import *
>>> g = graphs.CompleteBipartiteGraph(Integer(3),Integer(4))
>>> mad_g = g.maximum_average_degree(value_only=False)                    # needs sage.numerical.mip
>>> g.is_isomorphic(mad_g)                                                # needs sage.numerical.mip
True

Return an ordering of the vertices according a maximum cardinality search.

Maximum cardinality search (MCS) is a graph traversal introduced in [TY1984]. It starts by assigning an arbitrary vertex (or the specified initial_vertex) of \(G\) the last position in the ordering \(\alpha\). Every vertex keeps a weight equal to the number of its already processed neighbors (i.e., already added to \(\alpha\)), and a vertex of largest such number is chosen at each step \(i\) to be placed in position \(n - i\) in \(\alpha\). This ordering can be computed in time \(O(n + m)\).

Time complexity is \(O(n+m)\) for SparseGraph and \(O(n^2)\) for DenseGraph where \(n\) is the number of vertices and \(m\) is the number of edges.

When the graph is chordal, the ordering returned by MCS is a perfect elimination ordering, like lex_BFS(). So this ordering can be used to recognize chordal graphs. See [He2006] for more details.

Note

The current implementation is for connected graphs only.

INPUT:

  • G – a Sage graph

  • reverse – boolean (default: False); whether to return the vertices in discovery order, or the reverse

  • tree – boolean (default: False); whether to also return the discovery directed tree (each vertex being linked to the one that saw it for the first time)

  • initial_vertex – (default: None) the first vertex to consider

OUTPUT:

By default, return the ordering \(\alpha\) as a list. When tree is True, the method returns a tuple \((\alpha, T)\), where \(T\) is a directed tree with the same set of vertices as \(G\) and a directed edge from \(u\) to \(v\) if \(u\) was the first vertex to see \(v\).

EXAMPLES:

When specified, the initial_vertex is placed at the end of the ordering, unless parameter reverse is True, in which case it is placed at the beginning:

sage: G = graphs.PathGraph(4)
sage: G.maximum_cardinality_search(initial_vertex=0)
[3, 2, 1, 0]
sage: G.maximum_cardinality_search(initial_vertex=1)
[3, 2, 0, 1]
sage: G.maximum_cardinality_search(initial_vertex=2)
[0, 3, 1, 2]
sage: G.maximum_cardinality_search(initial_vertex=3)
[0, 1, 2, 3]
sage: G.maximum_cardinality_search(initial_vertex=3, reverse=True)
[3, 2, 1, 0]
>>> from sage.all import *
>>> G = graphs.PathGraph(Integer(4))
>>> G.maximum_cardinality_search(initial_vertex=Integer(0))
[3, 2, 1, 0]
>>> G.maximum_cardinality_search(initial_vertex=Integer(1))
[3, 2, 0, 1]
>>> G.maximum_cardinality_search(initial_vertex=Integer(2))
[0, 3, 1, 2]
>>> G.maximum_cardinality_search(initial_vertex=Integer(3))
[0, 1, 2, 3]
>>> G.maximum_cardinality_search(initial_vertex=Integer(3), reverse=True)
[3, 2, 1, 0]

Returning the discovery tree:

sage: G = graphs.PathGraph(4)
sage: _, T = G.maximum_cardinality_search(tree=True, initial_vertex=0)
sage: T.order(), T.size()
(4, 3)
sage: T.edges(labels=False, sort=True)
[(1, 0), (2, 1), (3, 2)]
sage: _, T = G.maximum_cardinality_search(tree=True, initial_vertex=3)
sage: T.edges(labels=False, sort=True)
[(0, 1), (1, 2), (2, 3)]
>>> from sage.all import *
>>> G = graphs.PathGraph(Integer(4))
>>> _, T = G.maximum_cardinality_search(tree=True, initial_vertex=Integer(0))
>>> T.order(), T.size()
(4, 3)
>>> T.edges(labels=False, sort=True)
[(1, 0), (2, 1), (3, 2)]
>>> _, T = G.maximum_cardinality_search(tree=True, initial_vertex=Integer(3))
>>> T.edges(labels=False, sort=True)
[(0, 1), (1, 2), (2, 3)]
maximum_cardinality_search_M(G, initial_vertex=None)[source]

Return the ordering and the edges of the triangulation produced by MCS-M.

Maximum cardinality search M (MCS-M) is an extension of MCS (maximum_cardinality_search()) in the same way that Lex-M (lex_M()) is an extension of Lex-BFS (lex_BFS()). That is, in MCS-M when \(u\) receives number \(i\) at step \(n - i + 1\), it increments the weight of all unnumbered vertices \(v\) for which there exists a path between \(u\) and \(v\) consisting only of unnumbered vertices with weight strictly less than \(w^-(u)\) and \(w^-(v)\), where \(w^-\) is the number of times a vertex has been reached during previous iterations. See [BBHP2004] for the details of this \(O(nm)\) time algorithm.

If \(G\) is not connected, the orderings of each of its connected components are added consecutively. Furthermore, if \(G\) has \(k\) connected components \(C_i\) for \(0 \leq i < k\), \(X\) contains at least one vertex of \(C_i\) for each \(i \geq 1\). Hence, \(|X| \geq k - 1\). In particular, some isolated vertices (i.e., of degree 0) can appear in \(X\) as for such a vertex \(x\), we have that \(G \setminus N(x) = G\) is not connected.

INPUT:

  • G – a Sage graph

  • initial_vertex – (default: None) the first vertex to consider

OUTPUT: a tuple \((\alpha, F, X)\), where

  • \(\alpha\) is the resulting ordering of the vertices. If an initial vertex is specified, it gets the last position in the ordering \(\alpha\).

  • \(F\) is the list of edges of a minimal triangulation of \(G\) according \(\alpha\)

  • \(X\) is a list of vertices such that for each \(x \in X\), the neighborhood of \(x\) in \(G\) is a separator (i.e., \(G \setminus N(x)\) is not connected). Note that we may have \(N(x) = \emptyset\) if \(G\) is not connected and \(x\) has degree 0.

EXAMPLES:

Chordal graphs have a perfect elimination ordering, and so the set \(F\) of edges of the triangulation is empty:

sage: G = graphs.RandomChordalGraph(20)
sage: alpha, F, X = G.maximum_cardinality_search_M(); F
[]
>>> from sage.all import *
>>> G = graphs.RandomChordalGraph(Integer(20))
>>> alpha, F, X = G.maximum_cardinality_search_M(); F
[]

The cycle of order 4 is not chordal and so the triangulation has one edge:

sage: G = graphs.CycleGraph(4)
sage: alpha, F, X = G.maximum_cardinality_search_M(); len(F)
1
>>> from sage.all import *
>>> G = graphs.CycleGraph(Integer(4))
>>> alpha, F, X = G.maximum_cardinality_search_M(); len(F)
1

The number of edges needed to triangulate of a cycle graph or order \(n\) is \(n - 3\), independently of the initial vertex:

sage: n = randint(3, 20)
sage: C = graphs.CycleGraph(n)
sage: _, F, X = C.maximum_cardinality_search_M()
sage: len(F) == n - 3
True
sage: _, F, X = C.maximum_cardinality_search_M(initial_vertex=C.random_vertex())
sage: len(F) == n - 3
True
>>> from sage.all import *
>>> n = randint(Integer(3), Integer(20))
>>> C = graphs.CycleGraph(n)
>>> _, F, X = C.maximum_cardinality_search_M()
>>> len(F) == n - Integer(3)
True
>>> _, F, X = C.maximum_cardinality_search_M(initial_vertex=C.random_vertex())
>>> len(F) == n - Integer(3)
True

When an initial vertex is specified, it gets the last position in the ordering:

sage: G = graphs.PathGraph(4)
sage: G.maximum_cardinality_search_M(initial_vertex=0)
([3, 2, 1, 0], [], [2, 3])
sage: G.maximum_cardinality_search_M(initial_vertex=1)
([3, 2, 0, 1], [], [2, 3])
sage: G.maximum_cardinality_search_M(initial_vertex=2)
([0, 1, 3, 2], [], [0, 1])
sage: G.maximum_cardinality_search_M(initial_vertex=3)
([0, 1, 2, 3], [], [0, 1])
>>> from sage.all import *
>>> G = graphs.PathGraph(Integer(4))
>>> G.maximum_cardinality_search_M(initial_vertex=Integer(0))
([3, 2, 1, 0], [], [2, 3])
>>> G.maximum_cardinality_search_M(initial_vertex=Integer(1))
([3, 2, 0, 1], [], [2, 3])
>>> G.maximum_cardinality_search_M(initial_vertex=Integer(2))
([0, 1, 3, 2], [], [0, 1])
>>> G.maximum_cardinality_search_M(initial_vertex=Integer(3))
([0, 1, 2, 3], [], [0, 1])

When \(G\) is not connected, the orderings of each of its connected components are added consecutively, the vertices of the component containing the initial vertex occupying the last positions:

sage: G = graphs.CycleGraph(4) * 2
sage: G.maximum_cardinality_search_M()[0]
[5, 4, 6, 7, 2, 3, 1, 0]
sage: G.maximum_cardinality_search_M(initial_vertex=7)[0]
[2, 1, 3, 0, 5, 6, 4, 7]
>>> from sage.all import *
>>> G = graphs.CycleGraph(Integer(4)) * Integer(2)
>>> G.maximum_cardinality_search_M()[Integer(0)]
[5, 4, 6, 7, 2, 3, 1, 0]
>>> G.maximum_cardinality_search_M(initial_vertex=Integer(7))[Integer(0)]
[2, 1, 3, 0, 5, 6, 4, 7]

Furthermore, if \(G\) has \(k\) connected components, \(X\) contains at least one vertex per connected component, except for the first one, and so at least \(k - 1\) vertices:

sage: for k in range(1, 5):
....:     _, _, X = Graph(k).maximum_cardinality_search_M()
....:     if len(X) < k - 1:
....:         raise ValueError("something goes wrong")
sage: G = graphs.RandomGNP(10, .2)
sage: cc = G.connected_components(sort=False)
sage: _, _, X = G.maximum_cardinality_search_M()
sage: len(X) >= len(cc) - 1
True
>>> from sage.all import *
>>> for k in range(Integer(1), Integer(5)):
...     _, _, X = Graph(k).maximum_cardinality_search_M()
...     if len(X) < k - Integer(1):
...         raise ValueError("something goes wrong")
>>> G = graphs.RandomGNP(Integer(10), RealNumber('.2'))
>>> cc = G.connected_components(sort=False)
>>> _, _, X = G.maximum_cardinality_search_M()
>>> len(X) >= len(cc) - Integer(1)
True

In the example of [BPS2010], the triangulation has 3 edges:

sage: G = Graph({'a': ['b', 'k'], 'b': ['c'], 'c': ['d', 'j', 'k'],
....:            'd': ['e', 'f', 'j', 'k'], 'e': ['g'],
....:            'f': ['g', 'j', 'k'], 'g': ['j', 'k'], 'h': ['i', 'j'],
....:            'i': ['k'], 'j': ['k']})
sage: _, F, _ = G.maximum_cardinality_search_M(initial_vertex='a')
sage: len(F)
3
>>> from sage.all import *
>>> G = Graph({'a': ['b', 'k'], 'b': ['c'], 'c': ['d', 'j', 'k'],
...            'd': ['e', 'f', 'j', 'k'], 'e': ['g'],
...            'f': ['g', 'j', 'k'], 'g': ['j', 'k'], 'h': ['i', 'j'],
...            'i': ['k'], 'j': ['k']})
>>> _, F, _ = G.maximum_cardinality_search_M(initial_vertex='a')
>>> len(F)
3
minimal_dominating_sets(G, to_dominate=None, work_on_copy=True, k=1)[source]

Return an iterator over the minimal dominating sets of a graph.

INPUT:

  • G – a graph

  • to_dominate – vertex iterable or None (default: None); the set of vertices to be dominated

  • work_on_copy – boolean (default: True); whether or not to work on a copy of the input graph; if set to False, the input graph will be modified (relabeled)

  • k – nonnegative integer (default: \(1\)); the domination distance

OUTPUT:

An iterator over the inclusion-minimal sets of vertices of G. If to_dominate is provided, return an iterator over the inclusion-minimal sets of vertices that dominate the vertices of to_dominate.

ALGORITHM: The algorithm described in [BDHPR2019].

AUTHOR: Jean-Florent Raymond (2019-03-04) – initial version.

EXAMPLES:

sage: G = graphs.ButterflyGraph()
sage: ll = list(G.minimal_dominating_sets())
sage: pp = [{0, 1}, {1, 3}, {0, 2}, {2, 3}, {4}]
sage: len(ll) == len(pp) and all(x in pp for x in ll) and all(x in ll for x in pp)
True

sage: ll = list(G.minimal_dominating_sets([0,3]))
sage: pp = [{0}, {3}, {4}]
sage: len(ll) == len(pp) and all(x in pp for x in ll) and all(x in ll for x in pp)
True

sage: ll = list(G.minimal_dominating_sets([4]))
sage: pp = [{4}, {0}, {1}, {2}, {3}]
sage: len(ll) == len(pp) and all(x in pp for x in ll) and all(x in ll for x in pp)
True
>>> from sage.all import *
>>> G = graphs.ButterflyGraph()
>>> ll = list(G.minimal_dominating_sets())
>>> pp = [{Integer(0), Integer(1)}, {Integer(1), Integer(3)}, {Integer(0), Integer(2)}, {Integer(2), Integer(3)}, {Integer(4)}]
>>> len(ll) == len(pp) and all(x in pp for x in ll) and all(x in ll for x in pp)
True

>>> ll = list(G.minimal_dominating_sets([Integer(0),Integer(3)]))
>>> pp = [{Integer(0)}, {Integer(3)}, {Integer(4)}]
>>> len(ll) == len(pp) and all(x in pp for x in ll) and all(x in ll for x in pp)
True

>>> ll = list(G.minimal_dominating_sets([Integer(4)]))
>>> pp = [{Integer(4)}, {Integer(0)}, {Integer(1)}, {Integer(2)}, {Integer(3)}]
>>> len(ll) == len(pp) and all(x in pp for x in ll) and all(x in ll for x in pp)
True

sage: ll = list(graphs.PetersenGraph().minimal_dominating_sets())
sage: pp = [{0, 2, 6},
....:       {0, 9, 3},
....:       {0, 8, 7},
....:       {1, 3, 7},
....:       {1, 4, 5},
....:       {8, 1, 9},
....:       {8, 2, 4},
....:       {9, 2, 5},
....:       {3, 5, 6},
....:       {4, 6, 7},
....:       {0, 8, 2, 9},
....:       {0, 3, 6, 7},
....:       {1, 3, 5, 9},
....:       {8, 1, 4, 7},
....:       {2, 4, 5, 6},
....:       {0, 1, 2, 3, 4},
....:       {0, 1, 2, 5, 7},
....:       {0, 1, 4, 6, 9},
....:       {0, 1, 5, 6, 8},
....:       {0, 8, 3, 4, 5},
....:       {0, 9, 4, 5, 7},
....:       {8, 1, 2, 3, 6},
....:       {1, 2, 9, 6, 7},
....:       {9, 2, 3, 4, 7},
....:       {8, 2, 3, 5, 7},
....:       {8, 9, 3, 4, 6},
....:       {8, 9, 5, 6, 7}]
sage: len(ll) == len(pp) and all(x in pp for x in ll) and all(x in ll for x in pp)
True
>>> from sage.all import *
>>> ll = list(graphs.PetersenGraph().minimal_dominating_sets())
>>> pp = [{Integer(0), Integer(2), Integer(6)},
...       {Integer(0), Integer(9), Integer(3)},
...       {Integer(0), Integer(8), Integer(7)},
...       {Integer(1), Integer(3), Integer(7)},
...       {Integer(1), Integer(4), Integer(5)},
...       {Integer(8), Integer(1), Integer(9)},
...       {Integer(8), Integer(2), Integer(4)},
...       {Integer(9), Integer(2), Integer(5)},
...       {Integer(3), Integer(5), Integer(6)},
...       {Integer(4), Integer(6), Integer(7)},
...       {Integer(0), Integer(8), Integer(2), Integer(9)},
...       {Integer(0), Integer(3), Integer(6), Integer(7)},
...       {Integer(1), Integer(3), Integer(5), Integer(9)},
...       {Integer(8), Integer(1), Integer(4), Integer(7)},
...       {Integer(2), Integer(4), Integer(5), Integer(6)},
...       {Integer(0), Integer(1), Integer(2), Integer(3), Integer(4)},
...       {Integer(0), Integer(1), Integer(2), Integer(5), Integer(7)},
...       {Integer(0), Integer(1), Integer(4), Integer(6), Integer(9)},
...       {Integer(0), Integer(1), Integer(5), Integer(6), Integer(8)},
...       {Integer(0), Integer(8), Integer(3), Integer(4), Integer(5)},
...       {Integer(0), Integer(9), Integer(4), Integer(5), Integer(7)},
...       {Integer(8), Integer(1), Integer(2), Integer(3), Integer(6)},
...       {Integer(1), Integer(2), Integer(9), Integer(6), Integer(7)},
...       {Integer(9), Integer(2), Integer(3), Integer(4), Integer(7)},
...       {Integer(8), Integer(2), Integer(3), Integer(5), Integer(7)},
...       {Integer(8), Integer(9), Integer(3), Integer(4), Integer(6)},
...       {Integer(8), Integer(9), Integer(5), Integer(6), Integer(7)}]
>>> len(ll) == len(pp) and all(x in pp for x in ll) and all(x in ll for x in pp)
True

Listing minimal distance-\(k\) dominating sets:

sage: G = graphs.Grid2dGraph(2, 3)
sage: list(G.minimal_dominating_sets(k=0))
[{(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)}]
sage: list(G.minimal_dominating_sets(k=1))
[{(0, 0), (0, 2), (1, 1)},
 {(0, 1), (1, 1)},
 {(0, 0), (0, 1), (0, 2)},
 {(0, 2), (1, 0)},
 {(0, 0), (1, 2)},
 {(0, 1), (1, 0), (1, 2)},
 {(1, 0), (1, 1), (1, 2)}]
sage: list(G.minimal_dominating_sets(k=2))
[{(0, 0), (1, 2)},
 {(0, 2), (1, 2)},
 {(1, 0), (1, 2)},
 {(0, 1)},
 {(0, 0), (0, 2)},
 {(0, 2), (1, 0)},
 {(0, 0), (1, 0)},
 {(1, 1)}]
sage: list(G.minimal_dominating_sets(k=3))
[{(0, 0)}, {(0, 1)}, {(0, 2)}, {(1, 0)}, {(1, 1)}, {(1, 2)}]
>>> from sage.all import *
>>> G = graphs.Grid2dGraph(Integer(2), Integer(3))
>>> list(G.minimal_dominating_sets(k=Integer(0)))
[{(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)}]
>>> list(G.minimal_dominating_sets(k=Integer(1)))
[{(0, 0), (0, 2), (1, 1)},
 {(0, 1), (1, 1)},
 {(0, 0), (0, 1), (0, 2)},
 {(0, 2), (1, 0)},
 {(0, 0), (1, 2)},
 {(0, 1), (1, 0), (1, 2)},
 {(1, 0), (1, 1), (1, 2)}]
>>> list(G.minimal_dominating_sets(k=Integer(2)))
[{(0, 0), (1, 2)},
 {(0, 2), (1, 2)},
 {(1, 0), (1, 2)},
 {(0, 1)},
 {(0, 0), (0, 2)},
 {(0, 2), (1, 0)},
 {(0, 0), (1, 0)},
 {(1, 1)}]
>>> list(G.minimal_dominating_sets(k=Integer(3)))
[{(0, 0)}, {(0, 1)}, {(0, 2)}, {(1, 0)}, {(1, 1)}, {(1, 2)}]

When parameter work_on_copy is False, the input graph is modified (relabeled):

sage: G = Graph([('A', 'B')])
sage: _ = list(G.minimal_dominating_sets(work_on_copy=True))
sage: set(G) == {'A', 'B'}
True
sage: _ = list(G.minimal_dominating_sets(work_on_copy=False))
sage: set(G) == {'A', 'B'}
False
sage: set(G) == {0, 1}
True
>>> from sage.all import *
>>> G = Graph([('A', 'B')])
>>> _ = list(G.minimal_dominating_sets(work_on_copy=True))
>>> set(G) == {'A', 'B'}
True
>>> _ = list(G.minimal_dominating_sets(work_on_copy=False))
>>> set(G) == {'A', 'B'}
False
>>> set(G) == {Integer(0), Integer(1)}
True
minimal_separators(G, forbidden_vertices=None)[source]

Return an iterator over the minimal separators of G.

A separator in a graph is a set of vertices whose removal increases the number of connected components. In other words, a separator is a vertex cut. This method implements the algorithm proposed in [BBC2000]. It computes the set \(S\) of minimal separators of a graph in \(O(n^3)\) time per separator, and so overall in \(O(n^3 |S|)\) time.

Warning

Note that all separators are recorded during the execution of the algorithm and so the memory consumption of this method might be huge.

INPUT:

  • G – an undirected graph

  • forbidden_vertices – list (default: None); set of vertices to avoid during the search

EXAMPLES:

sage: P = graphs.PathGraph(5)
sage: sorted(sorted(sep) for sep in P.minimal_separators())
[[1], [2], [3]]
sage: C = graphs.CycleGraph(6)
sage: sorted(sorted(sep) for sep in C.minimal_separators())
[[0, 2], [0, 3], [0, 4], [1, 3], [1, 4], [1, 5], [2, 4], [2, 5], [3, 5]]
sage: sorted(sorted(sep) for sep in C.minimal_separators(forbidden_vertices=[0]))
[[2], [3], [4]]
sage: sorted(sorted(sep) for sep in (P + C).minimal_separators())
[[1], [2], [3], [5, 7], [5, 8], [5, 9], [6, 8],
 [6, 9], [6, 10], [7, 9], [7, 10], [8, 10]]
sage: sorted(sorted(sep) for sep in (P + C).minimal_separators(forbidden_vertices=[10]))
[[1], [2], [3], [6], [7], [8]]

sage: G = graphs.RandomGNP(10, .3)
sage: all(G.is_vertex_cut(sep) for sep in G.minimal_separators())
True
>>> from sage.all import *
>>> P = graphs.PathGraph(Integer(5))
>>> sorted(sorted(sep) for sep in P.minimal_separators())
[[1], [2], [3]]
>>> C = graphs.CycleGraph(Integer(6))
>>> sorted(sorted(sep) for sep in C.minimal_separators())
[[0, 2], [0, 3], [0, 4], [1, 3], [1, 4], [1, 5], [2, 4], [2, 5], [3, 5]]
>>> sorted(sorted(sep) for sep in C.minimal_separators(forbidden_vertices=[Integer(0)]))
[[2], [3], [4]]
>>> sorted(sorted(sep) for sep in (P + C).minimal_separators())
[[1], [2], [3], [5, 7], [5, 8], [5, 9], [6, 8],
 [6, 9], [6, 10], [7, 9], [7, 10], [8, 10]]
>>> sorted(sorted(sep) for sep in (P + C).minimal_separators(forbidden_vertices=[Integer(10)]))
[[1], [2], [3], [6], [7], [8]]

>>> G = graphs.RandomGNP(Integer(10), RealNumber('.3'))
>>> all(G.is_vertex_cut(sep) for sep in G.minimal_separators())
True
minimum_outdegree_orientation(G, use_edge_labels, solver=False, verbose=None, integrality_tolerance=0)[source]

Return an orientation of \(G\) with the smallest possible maximum outdegree.

Given a Graph \(G\), it is polynomial to compute an orientation \(D\) of the edges of \(G\) such that the maximum out-degree in \(D\) is minimized. This problem, though, is NP-complete in the weighted case [AMOZ2006].

INPUT:

  • G – an undirected graph

  • use_edge_labels – boolean (default: False)

    • When set to True, uses edge labels as weights to compute the orientation and assumes a weight of \(1\) when there is no value available for a given edge.

    • When set to False (default), gives a weight of 1 to all the edges.

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

EXAMPLES:

Given a complete bipartite graph \(K_{n,m}\), the maximum out-degree of an optimal orientation is \(\left\lceil \frac {nm} {n+m}\right\rceil\):

sage: g = graphs.CompleteBipartiteGraph(3,4)
sage: o = g.minimum_outdegree_orientation()                                     # needs sage.numerical.mip
sage: max(o.out_degree()) == integer_ceil((4*3)/(3+4))                          # needs sage.numerical.mip
True
>>> from sage.all import *
>>> g = graphs.CompleteBipartiteGraph(Integer(3),Integer(4))
>>> o = g.minimum_outdegree_orientation()                                     # needs sage.numerical.mip
>>> max(o.out_degree()) == integer_ceil((Integer(4)*Integer(3))/(Integer(3)+Integer(4)))                          # needs sage.numerical.mip
True

Show the influence of edge labels on the solution:

sage: # needs sage.numerical.mip
sage: g = graphs.PetersenGraph()
sage: o = g.minimum_outdegree_orientation(use_edge_labels=False)
sage: max(o.out_degree())
2
sage: _ = [g.set_edge_label(u, v, 1) for u, v in g.edge_iterator(labels=False)]
sage: o = g.minimum_outdegree_orientation(use_edge_labels=True)
sage: max(o.out_degree())
2
sage: g.set_edge_label(0, 1, 100)
sage: o = g.minimum_outdegree_orientation(use_edge_labels=True)
sage: max(o.out_degree())
3
>>> from sage.all import *
>>> # needs sage.numerical.mip
>>> g = graphs.PetersenGraph()
>>> o = g.minimum_outdegree_orientation(use_edge_labels=False)
>>> max(o.out_degree())
2
>>> _ = [g.set_edge_label(u, v, Integer(1)) for u, v in g.edge_iterator(labels=False)]
>>> o = g.minimum_outdegree_orientation(use_edge_labels=True)
>>> max(o.out_degree())
2
>>> g.set_edge_label(Integer(0), Integer(1), Integer(100))
>>> o = g.minimum_outdegree_orientation(use_edge_labels=True)
>>> max(o.out_degree())
3
minor(H, solver, verbose=None, induced=0, integrality_tolerance=False)[source]

Return the vertices of a minor isomorphic to \(H\) in the current graph.

We say that a graph \(G\) has a \(H\)-minor (or that it has a graph isomorphic to \(H\) as a minor), if for all \(h\in H\), there exist disjoint sets \(S_h \subseteq V(G)\) such that once the vertices of each \(S_h\) have been merged to create a new graph \(G'\), this new graph contains \(H\) as a subgraph.

When parameter induced is True, this method returns an induced minor isomorphic to \(H\), if it exists.

We say that a graph \(G\) has an induced \(H\)-minor (or that it has a graph isomorphic to \(H\) as an induced minor), if \(H\) can be obtained from an induced subgraph of \(G\) by contracting edges. Otherwise, \(G\) is said to be \(H\)-induced minor-free.

For more information, see the Wikipedia article Minor_(graph_theory).

INPUT:

  • H – the minor to find for in the current graph

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

  • induced – boolean (default: False); if True, returns an induced minor isomorphic to \(H\) if it exists, and raises a ValueError otherwise.

OUTPUT:

A dictionary associating to each vertex of \(H\) the set of vertices in the current graph representing it.

ALGORITHM:

Mixed Integer Linear Programming

COMPLEXITY:

Theoretically, when \(H\) is fixed, testing for the existence of a \(H\)-minor is polynomial. The known algorithms are highly exponential in \(H\), though.

Note

This function can be expected to be very slow, especially where the minor does not exist.

EXAMPLES:

Trying to find a minor isomorphic to \(K_4\) in the \(4\times 4\) grid:

sage: # needs sage.numerical.mip
sage: g = graphs.GridGraph([4,4])
sage: h = graphs.CompleteGraph(4)
sage: L = g.minor(h)
sage: gg = g.subgraph(flatten(L.values(), max_level = 1))
sage: _ = [gg.merge_vertices(l) for l in L.values() if len(l)>1]
sage: gg.is_isomorphic(h)
True
>>> from sage.all import *
>>> # needs sage.numerical.mip
>>> g = graphs.GridGraph([Integer(4),Integer(4)])
>>> h = graphs.CompleteGraph(Integer(4))
>>> L = g.minor(h)
>>> gg = g.subgraph(flatten(L.values(), max_level = Integer(1)))
>>> _ = [gg.merge_vertices(l) for l in L.values() if len(l)>Integer(1)]
>>> gg.is_isomorphic(h)
True

We can also try to prove this way that the Petersen graph is not planar, as it has a \(K_5\) minor:

sage: g = graphs.PetersenGraph()
sage: K5_minor = g.minor(graphs.CompleteGraph(5))   # long time             # needs sage.numerical.mip
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> K5_minor = g.minor(graphs.CompleteGraph(Integer(5)))   # long time             # needs sage.numerical.mip

And even a \(K_{3,3}\) minor:

sage: K33_minor = g.minor(graphs.CompleteBipartiteGraph(3,3))       # long time, needs sage.numerical.mip
>>> from sage.all import *
>>> K33_minor = g.minor(graphs.CompleteBipartiteGraph(Integer(3),Integer(3)))       # long time, needs sage.numerical.mip

(It is much faster to use the linear-time test of planarity in this situation, though.)

As there is no cycle in a tree, looking for a \(K_3\) minor is useless. This function will raise an exception in this case:

sage: g = graphs.RandomGNP(20, .5)
sage: g = g.subgraph(edges=g.min_spanning_tree())
sage: g.is_tree()
True
sage: L = g.minor(graphs.CompleteGraph(3))                                  # needs sage.numerical.mip
Traceback (most recent call last):
...
ValueError: This graph has no minor isomorphic to H !
>>> from sage.all import *
>>> g = graphs.RandomGNP(Integer(20), RealNumber('.5'))
>>> g = g.subgraph(edges=g.min_spanning_tree())
>>> g.is_tree()
True
>>> L = g.minor(graphs.CompleteGraph(Integer(3)))                                  # needs sage.numerical.mip
Traceback (most recent call last):
...
ValueError: This graph has no minor isomorphic to H !

Trying to find an induced minor isomorphic to \(C_5\) in a graph containing an induced \(C_6\):

sage: g = graphs.CycleGraph(6)
sage: for i in range(randint(10, 30)):
....:     g.add_edge(randint(0, 5), g.add_vertex())
sage: h = graphs.CycleGraph(5)
sage: L = g.minor(h, induced=True)
sage: gg = g.subgraph(flatten(L.values(), max_level=1))
sage: _ = [gg.merge_vertices(l) for l in L.values() if len(l) > 1]
sage: gg.is_isomorphic(h)
True
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(6))
>>> for i in range(randint(Integer(10), Integer(30))):
...     g.add_edge(randint(Integer(0), Integer(5)), g.add_vertex())
>>> h = graphs.CycleGraph(Integer(5))
>>> L = g.minor(h, induced=True)
>>> gg = g.subgraph(flatten(L.values(), max_level=Integer(1)))
>>> _ = [gg.merge_vertices(l) for l in L.values() if len(l) > Integer(1)]
>>> gg.is_isomorphic(h)
True
modular_decomposition(algorithm=None, style='tuple')[source]

Return the modular decomposition of the current graph.

A module of an undirected graph is a subset of vertices such that every vertex outside the module is either connected to all members of the module or to none of them. Every graph that has a nontrivial module can be partitioned into modules, and the increasingly fine partitions into modules form a tree. The modular_decomposition method returns that tree.

INPUT:

  • algorithm – string (default: None); the algorithm to use among:

    • None or 'corneil_habib_paul_tedder' – will use the Corneil-Habib-Paul-Tedder algorithm from [TCHP2008], its complexity is linear in the number of vertices and edges.

    • 'habib_maurer' – will use the Habib-Maurer algorithm from [HM1979], its complexity is cubic in the number of vertices.

  • style – string (default: 'tuple'); specifies the output format:

OUTPUT:

The modular decomposition tree, either as nested tuples (if style='tuple') or as an object of LabelledRootedTree (if style='tree')

Crash course on modular decomposition:

A module \(M\) of a graph \(G\) is a proper subset of its vertices such that for all \(u \in V(G)-M, v,w\in M\) the relation \(u \sim v \Leftrightarrow u \sim w\) holds, where \(\sim\) denotes the adjacency relation in \(G\). Equivalently, \(M \subset V(G)\) is a module if all its vertices have the same adjacency relations with each vertex outside of the module (vertex by vertex).

Hence, for a set like a module, it is very easy to encode the information of the adjacencies between the vertices inside and outside the module – we can actually add a new vertex \(v_M\) to our graph representing our module \(M\), and let \(v_M\) be adjacent to \(u\in V(G)-M\) if and only if some \(v\in M\) (and hence all the vertices contained in the module) is adjacent to \(u\). We can now independently (and recursively) study the structure of our module \(M\) and the new graph \(G-M+\{v_M\}\), without any loss of information.

Here are two very simple modules :

  • A connected component \(C\) (or the union of some –but not all– of them) of a disconnected graph \(G\), for instance, is a module, as no vertex of \(C\) has a neighbor outside of it.

  • An anticomponent \(C\) (or the union of some –but not all– of them) of a non-anticonnected graph \(G\), for the same reason (it is just the complement of the previous graph !).

These modules being of special interest, the disjoint union of graphs is called a Parallel composition, and the complement of a disjoint union is called a Series composition. A graph whose only modules are singletons is called Prime.

For more information on modular decomposition, in particular for an explanation of the terms “Parallel,” “Prime” and “Series,” see the Wikipedia article Modular_decomposition.

You may also be interested in the survey from Michel Habib and Christophe Paul entitled “A survey on Algorithmic aspects of modular decomposition” [HP2010].

EXAMPLES:

The Bull Graph is prime:

sage: graphs.BullGraph().modular_decomposition()
(PRIME, [1, 2, 0, 3, 4])
>>> from sage.all import *
>>> graphs.BullGraph().modular_decomposition()
(PRIME, [1, 2, 0, 3, 4])

The Petersen Graph too:

sage: graphs.PetersenGraph().modular_decomposition()
(PRIME, [1, 4, 5, 0, 6, 2, 3, 9, 7, 8])
>>> from sage.all import *
>>> graphs.PetersenGraph().modular_decomposition()
(PRIME, [1, 4, 5, 0, 6, 2, 3, 9, 7, 8])

Graph from the Wikipedia article Modular_decomposition:

sage: G = Graph('Jv\\zoKF@wN?', format='graph6')
sage: G.relabel([1..11])
sage: G.modular_decomposition()
(PRIME,
 [(SERIES, [4, (PARALLEL, [2, 3])]),
  1,
  5,
  (PARALLEL, [6, 7]),
  (SERIES, [(PARALLEL, [10, 11]), 9, 8])])
>>> from sage.all import *
>>> G = Graph('Jv\\zoKF@wN?', format='graph6')
>>> G.relabel((ellipsis_range(Integer(1),Ellipsis,Integer(11))))
>>> G.modular_decomposition()
(PRIME,
 [(SERIES, [4, (PARALLEL, [2, 3])]),
  1,
  5,
  (PARALLEL, [6, 7]),
  (SERIES, [(PARALLEL, [10, 11]), 9, 8])])

This a clique on 5 vertices with 2 pendant edges, though, has a more interesting decomposition:

sage: g = graphs.CompleteGraph(5)
sage: g.add_edge(0,5)
sage: g.add_edge(0,6)
sage: g.modular_decomposition()
(SERIES, [(PARALLEL, [(SERIES, [3, 4, 2, 1]), 5, 6]), 0])
>>> from sage.all import *
>>> g = graphs.CompleteGraph(Integer(5))
>>> g.add_edge(Integer(0),Integer(5))
>>> g.add_edge(Integer(0),Integer(6))
>>> g.modular_decomposition()
(SERIES, [(PARALLEL, [(SERIES, [3, 4, 2, 1]), 5, 6]), 0])

Turán graphs are co-graphs:

sage: graphs.TuranGraph(11, 3).modular_decomposition()
(SERIES,
 [(PARALLEL, [7, 8, 9, 10]), (PARALLEL, [3, 4, 5, 6]), (PARALLEL, [0, 1, 2])])
>>> from sage.all import *
>>> graphs.TuranGraph(Integer(11), Integer(3)).modular_decomposition()
(SERIES,
 [(PARALLEL, [7, 8, 9, 10]), (PARALLEL, [3, 4, 5, 6]), (PARALLEL, [0, 1, 2])])

We can choose output to be a LabelledRootedTree:

sage: g.modular_decomposition(style='tree')
SERIES[0[], PARALLEL[5[], 6[], SERIES[1[], 2[], 3[], 4[]]]]
sage: ascii_art(g.modular_decomposition(algorithm="habib_maurer",style='tree'))
  __SERIES
 /      /
0   ___PARALLEL
   / /     /
  5 6   __SERIES
       / / / /
      1 2 3 4
>>> from sage.all import *
>>> g.modular_decomposition(style='tree')
SERIES[0[], PARALLEL[5[], 6[], SERIES[1[], 2[], 3[], 4[]]]]
>>> ascii_art(g.modular_decomposition(algorithm="habib_maurer",style='tree'))
  __SERIES
 /      /
0   ___PARALLEL
   / /     /
  5 6   __SERIES
       / / / /
      1 2 3 4

ALGORITHM:

This function can use either the algorithm of D. Corneil, M. Habib, C. Paul and M. Tedder [TCHP2008] or the algorithm of M. Habib and M. Maurer [HM1979].

Note

A buggy implementation of the linear time algorithm from [TCHP2008] was removed in Sage 9.7, see Issue #25872. A new implementation was reintroduced in Sage 10.6 after some corrections to the original algorithm, see Issue #39038.

most_common_neighbors(nonedgesonly=True)[source]

Return vertex pairs with maximal number of common neighbors.

This method is only valid for simple (no loops, no multiple edges) graphs with order \(\geq 2\)

INPUT:

  • nonedgesonly – boolean (default: True); if True, assigns \(0\) value to adjacent vertices

OUTPUT: list of tuples of edge pairs

EXAMPLES:

The maximum common neighbor (non-adjacent) pairs for a straight linear 2-tree

sage: G1 = Graph([(0,1),(0,2),(1,2),(1,3),(3,5),(2,4),(2,3),(3,4),(4,5)])
sage: G1.most_common_neighbors()                                            # needs sage.modules
[(0, 3), (1, 4), (2, 5)]
>>> from sage.all import *
>>> G1 = Graph([(Integer(0),Integer(1)),(Integer(0),Integer(2)),(Integer(1),Integer(2)),(Integer(1),Integer(3)),(Integer(3),Integer(5)),(Integer(2),Integer(4)),(Integer(2),Integer(3)),(Integer(3),Integer(4)),(Integer(4),Integer(5))])
>>> G1.most_common_neighbors()                                            # needs sage.modules
[(0, 3), (1, 4), (2, 5)]

If we include non-adjacent pairs

sage: G1.most_common_neighbors(nonedgesonly=False)                          # needs sage.modules
[(0, 3), (1, 2), (1, 4), (2, 3), (2, 5), (3, 4)]
>>> from sage.all import *
>>> G1.most_common_neighbors(nonedgesonly=False)                          # needs sage.modules
[(0, 3), (1, 2), (1, 4), (2, 3), (2, 5), (3, 4)]

The common neighbors matrix for a fan on 6 vertices counting only non-adjacent vertex pairs

sage: H = Graph([(0,1),(0,2),(0,3),(0,4),(0,5),(0,6),(1,2),(2,3),(3,4),(4,5)])
sage: H.most_common_neighbors()                                             # needs sage.modules
[(1, 3), (2, 4), (3, 5)]
>>> from sage.all import *
>>> H = Graph([(Integer(0),Integer(1)),(Integer(0),Integer(2)),(Integer(0),Integer(3)),(Integer(0),Integer(4)),(Integer(0),Integer(5)),(Integer(0),Integer(6)),(Integer(1),Integer(2)),(Integer(2),Integer(3)),(Integer(3),Integer(4)),(Integer(4),Integer(5))])
>>> H.most_common_neighbors()                                             # needs sage.modules
[(1, 3), (2, 4), (3, 5)]

See also

orient(G, f, weighted=None, data_structure=None, sparse=None, immutable=None, hash_labels=None)[source]

Return an oriented version of \(G\) according the input function \(f\).

INPUT:

  • G – an undirected graph

  • f – a function that inputs an edge and outputs an orientation of this edge

  • weighted – boolean (default: None); weightedness for the oriented digraph. By default (None), the graph and its orientation will behave the same.

  • sparse – boolean (default: None); sparse=True is an alias for data_structure="sparse", and sparse=False is an alias for data_structure="dense". Only used when data_structure=None.

  • data_structure – string (default: None); one of 'sparse', 'static_sparse', or 'dense'. See the documentation of DiGraph.

  • immutable – boolean (default: None); whether to create a mutable/immutable digraph. Only used when data_structure=None.

    • immutable=None (default) means that the graph and its orientation will behave the same way.

    • immutable=True is a shortcut for data_structure='static_sparse'

    • immutable=False means that the created digraph is mutable. When used to orient an immutable graph, the data structure used is 'sparse' unless anything else is specified.

  • hash_labels – boolean (default: None); whether to include edge labels during hashing of the oriented digraph. This parameter defaults to True if the graph is weighted. This parameter is ignored when parameter immutable is not True. Beware that trying to hash unhashable labels will raise an error.

OUTPUT: a DiGraph object

Note

This method behaves similarly to method copy(). That is, the returned digraph uses the same data structure by default, unless the user asks to use another data structure, and the attributes of the input graph are copied.

EXAMPLES:

sage: G = graphs.CycleGraph(4); G
Cycle graph: Graph on 4 vertices
sage: D = G.orient(lambda e:e if e[0] < e[1] else (e[1], e[0], e[2])); D
Orientation of Cycle graph: Digraph on 4 vertices
sage: sorted(D.edges(labels=False))
[(0, 1), (0, 3), (1, 2), (2, 3)]
>>> from sage.all import *
>>> G = graphs.CycleGraph(Integer(4)); G
Cycle graph: Graph on 4 vertices
>>> D = G.orient(lambda e:e if e[Integer(0)] < e[Integer(1)] else (e[Integer(1)], e[Integer(0)], e[Integer(2)])); D
Orientation of Cycle graph: Digraph on 4 vertices
>>> sorted(D.edges(labels=False))
[(0, 1), (0, 3), (1, 2), (2, 3)]
orientations(G, data_structure=None, sparse=None)[source]

Return an iterator over orientations of \(G\).

An orientation of an undirected graph is a directed graph such that every edge is assigned a direction. Hence there are \(2^s\) oriented digraphs for a simple graph with \(s\) edges.

INPUT:

  • G – an undirected graph

  • data_structure – one of 'sparse', 'static_sparse', or 'dense'; see the documentation of Graph or DiGraph; default is the data structure of \(G\)

  • sparse – boolean (default: None); sparse=True is an alias for data_structure="sparse", and sparse=False is an alias for data_structure="dense". By default (None), guess the most suitable data structure.

Warning

This always considers multiple edges of graphs as distinguishable, and hence, may have repeated digraphs.

EXAMPLES:

sage: G = Graph([[1,2,3], [(1, 2, 'a'), (1, 3, 'b')]], format='vertices_and_edges')
sage: it = G.orientations()
sage: D = next(it)
sage: D.edges(sort=True)
[(1, 2, 'a'), (1, 3, 'b')]
sage: D = next(it)
sage: D.edges(sort=True)
[(1, 2, 'a'), (3, 1, 'b')]
>>> from sage.all import *
>>> G = Graph([[Integer(1),Integer(2),Integer(3)], [(Integer(1), Integer(2), 'a'), (Integer(1), Integer(3), 'b')]], format='vertices_and_edges')
>>> it = G.orientations()
>>> D = next(it)
>>> D.edges(sort=True)
[(1, 2, 'a'), (1, 3, 'b')]
>>> D = next(it)
>>> D.edges(sort=True)
[(1, 2, 'a'), (3, 1, 'b')]
pathwidth(k=None, certificate=False, algorithm='BAB', verbose=False, max_prefix_length=20, max_prefix_number=1000000, solver=None)[source]

Compute the pathwidth of self (and provides a decomposition).

INPUT:

  • k – integer (default: None); the width to be considered. When k is an integer, the method checks that the graph has pathwidth \(\leq k\). If k is None (default), the method computes the optimal pathwidth.

  • certificate – boolean (default: False); whether to return the path-decomposition itself

  • algorithm – string (default: 'BAB'); algorithm to use among:

    • 'BAB' – use a branch-and-bound algorithm. This algorithm has no size restriction but could take a very long time on large graphs. It can also be used to test is the input graph has pathwidth \(\leq k\), in which cas it will return the first found solution with width \(\leq k\) is certificate==True.

    • exponential – use an exponential time and space algorithm. This algorithm only works of graphs on less than 32 vertices

    • MILP – use a mixed integer linear programming formulation. This algorithm has no size restriction but could take a very long time

  • verbose – boolean (default: False); whether to display information on the computations

  • max_prefix_length – integer (default: 20); limits the length of the stored prefixes to prevent storing too many prefixes. This parameter is used only when algorithm=="BAB".

  • max_prefix_number – integer (default: 10**6); upper bound on the number of stored prefixes used to prevent using too much memory. This parameter is used only when algorithm=="BAB".

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

OUTPUT:

Return the pathwidth of self. When k is specified, it returns False when no path-decomposition of width \(\leq k\) exists or True otherwise. When certificate=True, the path-decomposition is also returned.

See also

EXAMPLES:

The pathwidth of a cycle is equal to 2:

sage: g = graphs.CycleGraph(6)
sage: g.pathwidth()
2
sage: pw, decomp = g.pathwidth(certificate=True)
sage: sorted(decomp, key=str)
[{0, 1, 5}, {1, 2, 5}, {2, 3, 4}, {2, 4, 5}]
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(6))
>>> g.pathwidth()
2
>>> pw, decomp = g.pathwidth(certificate=True)
>>> sorted(decomp, key=str)
[{0, 1, 5}, {1, 2, 5}, {2, 3, 4}, {2, 4, 5}]

The pathwidth of a Petersen graph is 5:

sage: g = graphs.PetersenGraph()
sage: g.pathwidth()
5
sage: g.pathwidth(k=2)
False
sage: g.pathwidth(k=6)
True
sage: g.pathwidth(k=6, certificate=True)
(True, Graph on 5 vertices)
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> g.pathwidth()
5
>>> g.pathwidth(k=Integer(2))
False
>>> g.pathwidth(k=Integer(6))
True
>>> g.pathwidth(k=Integer(6), certificate=True)
(True, Graph on 5 vertices)
perfect_matchings(G, labels=False)[source]

Return an iterator over all perfect matchings of the graph.

ALGORITHM:

Choose a vertex \(v\), then recurse through all edges incident to \(v\), removing one edge at a time whenever an edge is added to a matching.

INPUT:

  • labels – boolean (default: False); when True, the edges in each perfect matching are triples (containing the label as the third element), otherwise the edges are pairs.

See also

matching()

EXAMPLES:

sage: G=graphs.GridGraph([2,3])
sage: for m in G.perfect_matchings():
....:     print(sorted(m))
[((0, 0), (0, 1)), ((0, 2), (1, 2)), ((1, 0), (1, 1))]
[((0, 0), (1, 0)), ((0, 1), (0, 2)), ((1, 1), (1, 2))]
[((0, 0), (1, 0)), ((0, 1), (1, 1)), ((0, 2), (1, 2))]

sage: G = graphs.CompleteGraph(4)
sage: for m in G.perfect_matchings(labels=True):
....:     print(sorted(m))
[(0, 1, None), (2, 3, None)]
[(0, 2, None), (1, 3, None)]
[(0, 3, None), (1, 2, None)]

sage: G = Graph([[1,-1,'a'], [2,-2, 'b'], [1,-2,'x'], [2,-1,'y']])
sage: sorted(sorted(m) for m in G.perfect_matchings(labels=True))
[[(-2, 1, 'x'), (-1, 2, 'y')], [(-2, 2, 'b'), (-1, 1, 'a')]]

sage: G = graphs.CompleteGraph(8)
sage: mpc = G.matching_polynomial().coefficients(sparse=False)[0]           # needs sage.libs.flint
sage: len(list(G.perfect_matchings())) == mpc                               # needs sage.libs.flint
True

sage: G = graphs.PetersenGraph().copy(immutable=True)
sage: [sorted(m) for m in G.perfect_matchings()]
[[(0, 1), (2, 3), (4, 9), (5, 7), (6, 8)],
    [(0, 1), (2, 7), (3, 4), (5, 8), (6, 9)],
    [(0, 4), (1, 2), (3, 8), (5, 7), (6, 9)],
    [(0, 4), (1, 6), (2, 3), (5, 8), (7, 9)],
    [(0, 5), (1, 2), (3, 4), (6, 8), (7, 9)],
    [(0, 5), (1, 6), (2, 7), (3, 8), (4, 9)]]

sage: list(Graph().perfect_matchings())
[[]]

sage: G = graphs.CompleteGraph(5)
sage: list(G.perfect_matchings())
[]
>>> from sage.all import *
>>> G=graphs.GridGraph([Integer(2),Integer(3)])
>>> for m in G.perfect_matchings():
...     print(sorted(m))
[((0, 0), (0, 1)), ((0, 2), (1, 2)), ((1, 0), (1, 1))]
[((0, 0), (1, 0)), ((0, 1), (0, 2)), ((1, 1), (1, 2))]
[((0, 0), (1, 0)), ((0, 1), (1, 1)), ((0, 2), (1, 2))]

>>> G = graphs.CompleteGraph(Integer(4))
>>> for m in G.perfect_matchings(labels=True):
...     print(sorted(m))
[(0, 1, None), (2, 3, None)]
[(0, 2, None), (1, 3, None)]
[(0, 3, None), (1, 2, None)]

>>> G = Graph([[Integer(1),-Integer(1),'a'], [Integer(2),-Integer(2), 'b'], [Integer(1),-Integer(2),'x'], [Integer(2),-Integer(1),'y']])
>>> sorted(sorted(m) for m in G.perfect_matchings(labels=True))
[[(-2, 1, 'x'), (-1, 2, 'y')], [(-2, 2, 'b'), (-1, 1, 'a')]]

>>> G = graphs.CompleteGraph(Integer(8))
>>> mpc = G.matching_polynomial().coefficients(sparse=False)[Integer(0)]           # needs sage.libs.flint
>>> len(list(G.perfect_matchings())) == mpc                               # needs sage.libs.flint
True

>>> G = graphs.PetersenGraph().copy(immutable=True)
>>> [sorted(m) for m in G.perfect_matchings()]
[[(0, 1), (2, 3), (4, 9), (5, 7), (6, 8)],
    [(0, 1), (2, 7), (3, 4), (5, 8), (6, 9)],
    [(0, 4), (1, 2), (3, 8), (5, 7), (6, 9)],
    [(0, 4), (1, 6), (2, 3), (5, 8), (7, 9)],
    [(0, 5), (1, 2), (3, 4), (6, 8), (7, 9)],
    [(0, 5), (1, 6), (2, 7), (3, 8), (4, 9)]]

>>> list(Graph().perfect_matchings())
[[]]

>>> G = graphs.CompleteGraph(Integer(5))
>>> list(G.perfect_matchings())
[]
periphery(by_weight=False, algorithm=None, weight_function=None, check_weight=True)[source]

Return the set of vertices in the periphery of the graph.

The periphery is the set of vertices whose eccentricity is equal to the diameter of the graph, i.e., achieving the maximum eccentricity.

For more information and examples on how to use input variables, see shortest_paths() and eccentricity()

INPUT:

  • by_weight – boolean (default: False); if True, edge weights are taken into account; if False, all edges have weight 1

  • algorithm – string (default: None); see method eccentricity() for the list of available algorithms

  • weight_function – function (default: None); a function that takes as input an edge (u, v, l) and outputs its weight. If not None, by_weight is automatically set to True. If None and by_weight is True, we use the edge label l as a weight, if l is not None, else 1 as a weight.

  • check_weight – boolean (default: True); if True, we check that the weight_function outputs a number for each edge

EXAMPLES:

sage: G = graphs.DiamondGraph()
sage: G.periphery()
[0, 3]
sage: P = graphs.PetersenGraph()
sage: P.subgraph(P.periphery()) == P
True
sage: S = graphs.StarGraph(19)
sage: S.periphery()
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
sage: G = Graph()
sage: G.periphery()
[]
sage: G.add_vertex()
0
sage: G.periphery()
[0]
>>> from sage.all import *
>>> G = graphs.DiamondGraph()
>>> G.periphery()
[0, 3]
>>> P = graphs.PetersenGraph()
>>> P.subgraph(P.periphery()) == P
True
>>> S = graphs.StarGraph(Integer(19))
>>> S.periphery()
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> G = Graph()
>>> G.periphery()
[]
>>> G.add_vertex()
0
>>> G.periphery()
[0]
private_neighbors(G, vertex, dom)[source]

Return the private neighbors of a vertex with respect to other vertices.

A private neighbor of a vertex \(v\) with respect to a vertex subset \(D\) is a closed neighbor of \(v\) that is not dominated by a vertex of \(D \setminus \{v\}\).

INPUT:

  • vertex – a vertex of G

  • dom – iterable of vertices of G; the vertices possibly stealing private neighbors from vertex

OUTPUT:

Return the closed neighbors of vertex that are not closed neighbors of any other vertex of dom.

EXAMPLES:

sage: g = graphs.PathGraph(5)
sage: list(g.private_neighbors(1, [1, 3, 4]))
[1, 0]

sage: list(g.private_neighbors(1, [3, 4]))
[1, 0]

sage: list(g.private_neighbors(1, [3, 4, 0]))
[]
>>> from sage.all import *
>>> g = graphs.PathGraph(Integer(5))
>>> list(g.private_neighbors(Integer(1), [Integer(1), Integer(3), Integer(4)]))
[1, 0]

>>> list(g.private_neighbors(Integer(1), [Integer(3), Integer(4)]))
[1, 0]

>>> list(g.private_neighbors(Integer(1), [Integer(3), Integer(4), Integer(0)]))
[]
radius(by_weight=False, algorithm='DHV', weight_function=None, check_weight=True)[source]

Return the radius of the graph.

The radius is defined to be the minimum eccentricity of any vertex, where the eccentricity is the maximum distance to any other vertex. For more information and examples on how to use input variables, see shortest_paths() and eccentricity()

INPUT:

  • by_weight – boolean (default: False); if True, edge weights are taken into account; if False, all edges have weight 1

  • algorithm – string (default: 'DHV')

  • weight_function – function (default: None); a function that takes as input an edge (u, v, l) and outputs its weight. If not None, by_weight is automatically set to True. If None and by_weight is True, we use the edge label l as a weight, if l is not None, else 1 as a weight.

  • check_weight – boolean (default: True); if True, we check that the weight_function outputs a number for each edge

EXAMPLES:

The more symmetric a graph is, the smaller (diameter - radius) is:

sage: G = graphs.BarbellGraph(9, 3)
sage: G.radius()
3
sage: G.diameter()
6
>>> from sage.all import *
>>> G = graphs.BarbellGraph(Integer(9), Integer(3))
>>> G.radius()
3
>>> G.diameter()
6

sage: G = graphs.OctahedralGraph()
sage: G.radius()
2
sage: G.diameter()
2
>>> from sage.all import *
>>> G = graphs.OctahedralGraph()
>>> G.radius()
2
>>> G.diameter()
2
random_orientation(G)[source]

Return a random orientation of a graph \(G\).

An orientation of an undirected graph is a directed graph such that every edge is assigned a direction. Hence there are \(2^m\) oriented digraphs for a simple graph with \(m\) edges.

INPUT:

  • G – a Graph

EXAMPLES:

sage: from sage.graphs.orientations import random_orientation
sage: G = graphs.PetersenGraph()
sage: D = random_orientation(G)
sage: D.order() == G.order(), D.size() == G.size()
(True, True)
>>> from sage.all import *
>>> from sage.graphs.orientations import random_orientation
>>> G = graphs.PetersenGraph()
>>> D = random_orientation(G)
>>> D.order() == G.order(), D.size() == G.size()
(True, True)
random_spanning_tree(G, output_as_graph=False, by_weight=False, weight_function=None, check_weight=True)[source]

Return a random spanning tree of the graph.

This uses the Aldous-Broder algorithm ([Bro1989], [Ald1990]) to generate a random spanning tree with the uniform distribution, as follows.

Start from any vertex. Perform a random walk by choosing at every step one neighbor uniformly at random. Every time a new vertex \(j\) is met, add the edge \((i, j)\) to the spanning tree, where \(i\) is the previous vertex in the random walk.

When by_weight is True or a weight function is given, the selection of the neighbor is done proportionaly to the edge weights.

INPUT:

  • G – an undirected graph

  • output_as_graph – boolean (default: False); whether to return a list of edges or a graph

  • by_weight – boolean (default: False); if True, the edges in the graph are weighted, otherwise all edges have weight 1

  • weight_function – function (default: None); a function that takes as input an edge (u, v, l) and outputs its weight. If not None, by_weight is automatically set to True. If None and by_weight is True, we use the edge label l , if l is not None, else 1 as a weight. The weight_function can be used to transform the label into a weight (note that, if the weight returned is not convertible to a float, an error is raised)

  • check_weight – boolean (default: True); whether to check that the weight_function outputs a number for each edge

EXAMPLES:

sage: G = graphs.TietzeGraph()
sage: G.random_spanning_tree(output_as_graph=True)
Graph on 12 vertices
sage: rg = G.random_spanning_tree(); rg # random
[(0, 9),
(9, 11),
(0, 8),
(8, 7),
(7, 6),
(7, 2),
(2, 1),
(1, 5),
(9, 10),
(5, 4),
(2, 3)]
sage: Graph(rg).is_tree()
True
>>> from sage.all import *
>>> G = graphs.TietzeGraph()
>>> G.random_spanning_tree(output_as_graph=True)
Graph on 12 vertices
>>> rg = G.random_spanning_tree(); rg # random
[(0, 9),
(9, 11),
(0, 8),
(8, 7),
(7, 6),
(7, 2),
(2, 1),
(1, 5),
(9, 10),
(5, 4),
(2, 3)]
>>> Graph(rg).is_tree()
True

A visual example for the grid graph:

sage: G = graphs.Grid2dGraph(6, 6)
sage: pos = G.get_pos()
sage: T = G.random_spanning_tree(True)
sage: T.set_pos(pos)
sage: T.show(vertex_labels=False)                                               # needs sage.plot
>>> from sage.all import *
>>> G = graphs.Grid2dGraph(Integer(6), Integer(6))
>>> pos = G.get_pos()
>>> T = G.random_spanning_tree(True)
>>> T.set_pos(pos)
>>> T.show(vertex_labels=False)                                               # needs sage.plot

We can also use edge weights to change the probability of returning a spanning tree:

sage: def foo(G, k):
....:     S = set()
....:     for _ in range(k):
....:         E = G.random_spanning_tree(by_weight=True)
....:         S.add(Graph(E).graph6_string())
....:     return S
sage: K3 = graphs.CompleteGraph(3)
sage: for u, v in K3.edges(sort=True, labels=False):
....:     K3.set_edge_label(u, v, randint(1, 2))
sage: foo(K3, 100) == {'BW', 'Bg', 'Bo'}  # random
True
sage: K4 = graphs.CompleteGraph(4)
sage: for u, v in K4.edges(sort=True, labels=False):
....:     K4.set_edge_label(u, v, randint(1, 2))
sage: print(len(foo(K4, 100)))  # random
16
>>> from sage.all import *
>>> def foo(G, k):
...     S = set()
...     for _ in range(k):
...         E = G.random_spanning_tree(by_weight=True)
...         S.add(Graph(E).graph6_string())
...     return S
>>> K3 = graphs.CompleteGraph(Integer(3))
>>> for u, v in K3.edges(sort=True, labels=False):
...     K3.set_edge_label(u, v, randint(Integer(1), Integer(2)))
>>> foo(K3, Integer(100)) == {'BW', 'Bg', 'Bo'}  # random
True
>>> K4 = graphs.CompleteGraph(Integer(4))
>>> for u, v in K4.edges(sort=True, labels=False):
...     K4.set_edge_label(u, v, randint(Integer(1), Integer(2)))
>>> print(len(foo(K4, Integer(100))))  # random
16

Check that the spanning tree returned when using weights is a tree:

sage: # needs networkx
sage: G = graphs.RandomBarabasiAlbert(50, 2)
sage: for u, v in G.edge_iterator(labels=False):
....:     G.set_edge_label(u, v, randint(1, 10))
sage: T = G.random_spanning_tree(by_weight=True, output_as_graph=True)
sage: T.is_tree()
True
>>> from sage.all import *
>>> # needs networkx
>>> G = graphs.RandomBarabasiAlbert(Integer(50), Integer(2))
>>> for u, v in G.edge_iterator(labels=False):
...     G.set_edge_label(u, v, randint(Integer(1), Integer(10)))
>>> T = G.random_spanning_tree(by_weight=True, output_as_graph=True)
>>> T.is_tree()
True
rank_decomposition(G, verbose=False)[source]

Compute an optimal rank-decomposition of the given graph.

This function is available as a method of the Graph class. See rank_decomposition.

INPUT:

  • verbose – boolean (default: False); whether to display progress information while computing the decomposition

OUTPUT:

A pair (rankwidth, decomposition_tree), where rankwidth is a numerical value and decomposition_tree is a ternary tree describing the decomposition (cf. the module’s documentation).

EXAMPLES:

sage: from sage.graphs.graph_decompositions.rankwidth import rank_decomposition
sage: g = graphs.PetersenGraph()
sage: rank_decomposition(g)
(3, Graph on 19 vertices)
>>> from sage.all import *
>>> from sage.graphs.graph_decompositions.rankwidth import rank_decomposition
>>> g = graphs.PetersenGraph()
>>> rank_decomposition(g)
(3, Graph on 19 vertices)

On more than 32 vertices:

sage: g = graphs.RandomGNP(40, .5)
sage: rank_decomposition(g)
Traceback (most recent call last):
...
RuntimeError: the rank decomposition cannot be computed on graphs of >= 32 vertices
>>> from sage.all import *
>>> g = graphs.RandomGNP(Integer(40), RealNumber('.5'))
>>> rank_decomposition(g)
Traceback (most recent call last):
...
RuntimeError: the rank decomposition cannot be computed on graphs of >= 32 vertices

The empty graph:

sage: g = Graph()
sage: rank_decomposition(g)
(0, Graph on 0 vertices)
>>> from sage.all import *
>>> g = Graph()
>>> rank_decomposition(g)
(0, Graph on 0 vertices)
seidel_adjacency_matrix(vertices, base_ring=None, **kwds)[source]

Return the Seidel adjacency matrix of self.

Returns \(J-I-2A\), for \(A\) the (ordinary) adjacency matrix of self, \(I\) the identity matrix, and \(J\) the all-1 matrix. It is closely related to twograph().

By default, the matrix returned is over the integers.

INPUT:

  • vertices – list of vertices (default: None); the ordering of the vertices defining how they should appear in the matrix. By default, the ordering given by vertices() is used.

  • base_ring – a ring (default: None); the base ring of the matrix space to use

  • **kwds – other keywords to pass to matrix()

EXAMPLES:

sage: G = graphs.CycleGraph(5)
sage: G = G.disjoint_union(graphs.CompleteGraph(1))
sage: G.seidel_adjacency_matrix().minpoly()                                 # needs sage.libs.pari sage.modules
x^2 - 5
>>> from sage.all import *
>>> G = graphs.CycleGraph(Integer(5))
>>> G = G.disjoint_union(graphs.CompleteGraph(Integer(1)))
>>> G.seidel_adjacency_matrix().minpoly()                                 # needs sage.libs.pari sage.modules
x^2 - 5

Selecting the base ring:

sage: G.seidel_adjacency_matrix()[0, 0].parent()                            # needs sage.modules
Integer Ring
sage: G.seidel_adjacency_matrix(base_ring=RDF)[0, 0].parent()               # needs sage.modules
Real Double Field
>>> from sage.all import *
>>> G.seidel_adjacency_matrix()[Integer(0), Integer(0)].parent()                            # needs sage.modules
Integer Ring
>>> G.seidel_adjacency_matrix(base_ring=RDF)[Integer(0), Integer(0)].parent()               # needs sage.modules
Real Double Field
seidel_switching(s, inplace=True)[source]

Return the Seidel switching of self w.r.t. subset of vertices s.

Returns the graph obtained by Seidel switching of self with respect to the subset of vertices s. This is the graph given by Seidel adjacency matrix \(DSD\), for \(S\) the Seidel adjacency matrix of self, and \(D\) the diagonal matrix with -1s at positions corresponding to s, and 1s elsewhere.

INPUT:

  • s – list of vertices of self

  • inplace – boolean (default: True); whether to do the modification inplace, or to return a copy of the graph after switching

EXAMPLES:

sage: G = graphs.CycleGraph(5)
sage: G = G.disjoint_union(graphs.CompleteGraph(1))
sage: G.seidel_switching([(0,1),(1,0),(0,0)])
sage: G.seidel_adjacency_matrix().minpoly()                                 # needs sage.libs.pari sage.modules
x^2 - 5
sage: G.is_connected()
True
>>> from sage.all import *
>>> G = graphs.CycleGraph(Integer(5))
>>> G = G.disjoint_union(graphs.CompleteGraph(Integer(1)))
>>> G.seidel_switching([(Integer(0),Integer(1)),(Integer(1),Integer(0)),(Integer(0),Integer(0))])
>>> G.seidel_adjacency_matrix().minpoly()                                 # needs sage.libs.pari sage.modules
x^2 - 5
>>> G.is_connected()
True
slice_decomposition(G, initial_vertex=None)[source]

Compute a slice decomposition of the simple undirected graph

INPUT:

  • G – a Sage graph.

  • initial_vertex – (default: None); the first vertex to consider.

OUTPUT:

An object of type SliceDecomposition that represents a slice decomposition of G

Note

Loops and multiple edges are ignored during the computation of the slice decomposition.

ALGORITHM:

The method use the algorithm based on “partition refinement” described in [HMPV2000] and [TCHP2008]. The time complexity of this algorithm is in \(O(n + m)\), and our implementation follows that complexity for SparseGraph. For DenseGraph, the complexity is \(O(n^2)\).

EXAMPLES:

Slice decomposition of the Petersen Graph:

sage: G = graphs.PetersenGraph()
sage: SD = G.slice_decomposition(); SD
[0[1[4[5]]] [2[6]] [3] [9] [7] [8]]
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> SD = G.slice_decomposition(); SD
[0[1[4[5]]] [2[6]] [3] [9] [7] [8]]

The graph can have loops or multiple edges but they are ignored:

sage: H = Graph(G,loops=True,multiedges=True)
sage: H.add_edges([(4, 4), (2, 2), (1, 6)])
sage: SD2 = H.slice_decomposition()
sage: SD2 == SD
True
sage: SD2.underlying_graph() == G.to_simple(immutable=True)
True
>>> from sage.all import *
>>> H = Graph(G,loops=True,multiedges=True)
>>> H.add_edges([(Integer(4), Integer(4)), (Integer(2), Integer(2)), (Integer(1), Integer(6))])
>>> SD2 = H.slice_decomposition()
>>> SD2 == SD
True
>>> SD2.underlying_graph() == G.to_simple(immutable=True)
True

The tree corresponding to the slice decomposition can be displayed using view:

sage: from sage.graphs.graph_latex import check_tkz_graph
sage: check_tkz_graph()  # random - depends on Tex installation
sage: view(G)  # not tested
sage: latex(G)  # to obtain the corresponding LaTeX code
\begin{tikzpicture}
...
\end{tikzpicture}
>>> from sage.all import *
>>> from sage.graphs.graph_latex import check_tkz_graph
>>> check_tkz_graph()  # random - depends on Tex installation
>>> view(G)  # not tested
>>> latex(G)  # to obtain the corresponding LaTeX code
\begin{tikzpicture}
...
\end{tikzpicture}

Slice decompositions are only defined for undirected graphs:

sage: from sage.graphs.graph_decompositions.slice_decomposition import slice_decomposition
sage: slice_decomposition(DiGraph())
Traceback (most recent call last):
...
ValueError: parameter G must be an undirected graph
>>> from sage.all import *
>>> from sage.graphs.graph_decompositions.slice_decomposition import slice_decomposition
>>> slice_decomposition(DiGraph())
Traceback (most recent call last):
...
ValueError: parameter G must be an undirected graph
spanning_trees(g, labels=False)[source]

Return an iterator over all spanning trees of the graph \(g\).

A disconnected graph has no spanning tree.

Uses the Read-Tarjan backtracking algorithm [RT1975a].

INPUT:

  • labels – boolean (default: False); whether to return edges labels in the spanning trees or not

EXAMPLES:

sage: G = Graph([(1,2),(1,2),(1,3),(1,3),(2,3),(1,4)], multiedges=True)
sage: len(list(G.spanning_trees()))
8
sage: G.spanning_trees_count()                                                  # needs sage.modules
8
sage: G = Graph([(1,2),(2,3),(3,1),(3,4),(4,5),(4,5),(4,6)], multiedges=True)
sage: len(list(G.spanning_trees()))
6
sage: G.spanning_trees_count()                                                  # needs sage.modules
6
>>> from sage.all import *
>>> G = Graph([(Integer(1),Integer(2)),(Integer(1),Integer(2)),(Integer(1),Integer(3)),(Integer(1),Integer(3)),(Integer(2),Integer(3)),(Integer(1),Integer(4))], multiedges=True)
>>> len(list(G.spanning_trees()))
8
>>> G.spanning_trees_count()                                                  # needs sage.modules
8
>>> G = Graph([(Integer(1),Integer(2)),(Integer(2),Integer(3)),(Integer(3),Integer(1)),(Integer(3),Integer(4)),(Integer(4),Integer(5)),(Integer(4),Integer(5)),(Integer(4),Integer(6))], multiedges=True)
>>> len(list(G.spanning_trees()))
6
>>> G.spanning_trees_count()                                                  # needs sage.modules
6

See also

sparse6_string()[source]

Return the sparse6 representation of the graph as an ASCII string.

Only valid for undirected graphs on 0 to 262143 vertices, but loops and multiple edges are permitted.

Note

As the sparse6 format only handles graphs whose vertex set is \(\{0,...,n-1\}\), a relabelled copy of your graph will be encoded if necessary.

EXAMPLES:

sage: G = graphs.BullGraph()
sage: G.sparse6_string()
':Da@en'
>>> from sage.all import *
>>> G = graphs.BullGraph()
>>> G.sparse6_string()
':Da@en'

sage: G = Graph(loops=True, multiedges=True, data_structure='sparse')
sage: Graph(':?', data_structure='sparse') == G
True
>>> from sage.all import *
>>> G = Graph(loops=True, multiedges=True, data_structure='sparse')
>>> Graph(':?', data_structure='sparse') == G
True
spqr_tree(G, algorithm='Hopcroft_Tarjan', solver=None, verbose=0, integrality_tolerance=0.001)[source]

Return an SPQR-tree representing the triconnected components of the graph.

An SPQR-tree is a tree data structure used to represent the triconnected components of a biconnected (multi)graph and the 2-vertex cuts separating them. A node of a SPQR-tree, and the graph associated with it, can be one of the following four types:

  • 'S' – the associated graph is a cycle with at least three vertices 'S' stands for series

  • 'P' – the associated graph is a dipole graph, a multigraph with two vertices and three or more edges. 'P' stands for parallel

  • 'Q' – the associated graph has a single real edge. This trivial case is necessary to handle the graph that has only one edge

  • 'R' – the associated graph is a 3-connected graph that is not a cycle or dipole. 'R' stands for rigid

This method decomposes a biconnected graph into cycles, cocycles, and 3-connected blocks summed over cocycles, and arranges them as a SPQR-tree. More precisely, it splits the graph at each of its 2-vertex cuts, giving a unique decomposition into 3-connected blocks, cycles and cocycles. The cocycles are dipole graphs with one edge per real edge between the included vertices and one additional (virtual) edge per connected component resulting from deletion of the vertices in the cut. See the Wikipedia article SPQR_tree.

INPUT:

  • G – the input graph

  • algorithm – string (default: 'Hopcroft_Tarjan'); the algorithm to use among:

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

OUTPUT: SPQR-tree a tree whose vertices are labeled with the block’s type and the subgraph of three-blocks in the decomposition.

EXAMPLES:

sage: from sage.graphs.connectivity import spqr_tree
sage: G = Graph(2)
sage: for i in range(3):
....:     G.add_clique([0, 1, G.add_vertex(), G.add_vertex()])
sage: Tree = spqr_tree(G)
sage: Tree.order()
4
sage: K4 = graphs.CompleteGraph(4)
sage: all(u[1].is_isomorphic(K4) for u in Tree if u[0] == 'R')
True
sage: from sage.graphs.connectivity import spqr_tree_to_graph
sage: G.is_isomorphic(spqr_tree_to_graph(Tree))
True

sage: G = Graph(2)
sage: for i in range(3):
....:     G.add_path([0, G.add_vertex(), G.add_vertex(), 1])
sage: Tree = spqr_tree(G)
sage: Tree.order()
4
sage: C4 = graphs.CycleGraph(4)
sage: all(u[1].is_isomorphic(C4) for u in Tree if u[0] == 'S')
True
sage: G.is_isomorphic(spqr_tree_to_graph(Tree))
True

sage: G.allow_multiple_edges(True)
sage: G.add_edges(G.edge_iterator())
sage: Tree = spqr_tree(G)
sage: Tree.order()
13
sage: all(u[1].is_isomorphic(C4) for u in Tree if u[0] == 'S')
True
sage: G.is_isomorphic(spqr_tree_to_graph(Tree))
True

sage: G = graphs.CycleGraph(6)
sage: Tree = spqr_tree(G)
sage: Tree.order()
1
sage: G.is_isomorphic(spqr_tree_to_graph(Tree))
True
sage: G.add_edge(0, 3)
sage: Tree = spqr_tree(G)
sage: Tree.order()
3
sage: G.is_isomorphic(spqr_tree_to_graph(Tree))
True

sage: G = Graph('LlCG{O@?GBoMw?')
sage: T = spqr_tree(G, algorithm='Hopcroft_Tarjan')
sage: G.is_isomorphic(spqr_tree_to_graph(T))
True
sage: T2 = spqr_tree(G, algorithm='cleave')                                     # needs sage.numerical.mip
sage: G.is_isomorphic(spqr_tree_to_graph(T2))                                   # needs sage.numerical.mip
True

sage: G = Graph([(0, 1)], multiedges=True)
sage: T = spqr_tree(G, algorithm='cleave')                                      # needs sage.numerical.mip
sage: T.vertices(sort=True)                                                     # needs sage.numerical.mip
[('Q', Multi-graph on 2 vertices)]
sage: G.is_isomorphic(spqr_tree_to_graph(T))                                    # needs sage.numerical.mip
True
sage: T = spqr_tree(G, algorithm='Hopcroft_Tarjan')
sage: T.vertices(sort=True)
[('Q', Multi-graph on 2 vertices)]
sage: G.add_edge(0, 1)
sage: spqr_tree(G, algorithm='cleave').vertices(sort=True)                      # needs sage.numerical.mip
[('P', Multi-graph on 2 vertices)]

sage: from collections import Counter
sage: G = graphs.PetersenGraph()
sage: T = G.spqr_tree(algorithm='Hopcroft_Tarjan')
sage: Counter(u[0] for u in T)
Counter({'R': 1})
sage: T = G.spqr_tree(algorithm='cleave')                                       # needs sage.numerical.mip
sage: Counter(u[0] for u in T)                                                  # needs sage.numerical.mip
Counter({'R': 1})
sage: for u,v in list(G.edges(labels=False, sort=False)):
....:     G.add_path([u, G.add_vertex(), G.add_vertex(), v])
sage: T = G.spqr_tree(algorithm='Hopcroft_Tarjan')
sage: sorted(Counter(u[0] for u in T).items())
[('P', 15), ('R', 1), ('S', 15)]
sage: T = G.spqr_tree(algorithm='cleave')                                       # needs sage.numerical.mip
sage: sorted(Counter(u[0] for u in T).items())                                  # needs sage.numerical.mip
[('P', 15), ('R', 1), ('S', 15)]
sage: for u,v in list(G.edges(labels=False, sort=False)):
....:     G.add_path([u, G.add_vertex(), G.add_vertex(), v])
sage: T = G.spqr_tree(algorithm='Hopcroft_Tarjan')
sage: sorted(Counter(u[0] for u in T).items())
[('P', 60), ('R', 1), ('S', 75)]
sage: T = G.spqr_tree(algorithm='cleave')       # long time                     # needs sage.numerical.mip
sage: sorted(Counter(u[0] for u in T).items())  # long time                     # needs sage.numerical.mip
[('P', 60), ('R', 1), ('S', 75)]
>>> from sage.all import *
>>> from sage.graphs.connectivity import spqr_tree
>>> G = Graph(Integer(2))
>>> for i in range(Integer(3)):
...     G.add_clique([Integer(0), Integer(1), G.add_vertex(), G.add_vertex()])
>>> Tree = spqr_tree(G)
>>> Tree.order()
4
>>> K4 = graphs.CompleteGraph(Integer(4))
>>> all(u[Integer(1)].is_isomorphic(K4) for u in Tree if u[Integer(0)] == 'R')
True
>>> from sage.graphs.connectivity import spqr_tree_to_graph
>>> G.is_isomorphic(spqr_tree_to_graph(Tree))
True

>>> G = Graph(Integer(2))
>>> for i in range(Integer(3)):
...     G.add_path([Integer(0), G.add_vertex(), G.add_vertex(), Integer(1)])
>>> Tree = spqr_tree(G)
>>> Tree.order()
4
>>> C4 = graphs.CycleGraph(Integer(4))
>>> all(u[Integer(1)].is_isomorphic(C4) for u in Tree if u[Integer(0)] == 'S')
True
>>> G.is_isomorphic(spqr_tree_to_graph(Tree))
True

>>> G.allow_multiple_edges(True)
>>> G.add_edges(G.edge_iterator())
>>> Tree = spqr_tree(G)
>>> Tree.order()
13
>>> all(u[Integer(1)].is_isomorphic(C4) for u in Tree if u[Integer(0)] == 'S')
True
>>> G.is_isomorphic(spqr_tree_to_graph(Tree))
True

>>> G = graphs.CycleGraph(Integer(6))
>>> Tree = spqr_tree(G)
>>> Tree.order()
1
>>> G.is_isomorphic(spqr_tree_to_graph(Tree))
True
>>> G.add_edge(Integer(0), Integer(3))
>>> Tree = spqr_tree(G)
>>> Tree.order()
3
>>> G.is_isomorphic(spqr_tree_to_graph(Tree))
True

>>> G = Graph('LlCG{O@?GBoMw?')
>>> T = spqr_tree(G, algorithm='Hopcroft_Tarjan')
>>> G.is_isomorphic(spqr_tree_to_graph(T))
True
>>> T2 = spqr_tree(G, algorithm='cleave')                                     # needs sage.numerical.mip
>>> G.is_isomorphic(spqr_tree_to_graph(T2))                                   # needs sage.numerical.mip
True

>>> G = Graph([(Integer(0), Integer(1))], multiedges=True)
>>> T = spqr_tree(G, algorithm='cleave')                                      # needs sage.numerical.mip
>>> T.vertices(sort=True)                                                     # needs sage.numerical.mip
[('Q', Multi-graph on 2 vertices)]
>>> G.is_isomorphic(spqr_tree_to_graph(T))                                    # needs sage.numerical.mip
True
>>> T = spqr_tree(G, algorithm='Hopcroft_Tarjan')
>>> T.vertices(sort=True)
[('Q', Multi-graph on 2 vertices)]
>>> G.add_edge(Integer(0), Integer(1))
>>> spqr_tree(G, algorithm='cleave').vertices(sort=True)                      # needs sage.numerical.mip
[('P', Multi-graph on 2 vertices)]

>>> from collections import Counter
>>> G = graphs.PetersenGraph()
>>> T = G.spqr_tree(algorithm='Hopcroft_Tarjan')
>>> Counter(u[Integer(0)] for u in T)
Counter({'R': 1})
>>> T = G.spqr_tree(algorithm='cleave')                                       # needs sage.numerical.mip
>>> Counter(u[Integer(0)] for u in T)                                                  # needs sage.numerical.mip
Counter({'R': 1})
>>> for u,v in list(G.edges(labels=False, sort=False)):
...     G.add_path([u, G.add_vertex(), G.add_vertex(), v])
>>> T = G.spqr_tree(algorithm='Hopcroft_Tarjan')
>>> sorted(Counter(u[Integer(0)] for u in T).items())
[('P', 15), ('R', 1), ('S', 15)]
>>> T = G.spqr_tree(algorithm='cleave')                                       # needs sage.numerical.mip
>>> sorted(Counter(u[Integer(0)] for u in T).items())                                  # needs sage.numerical.mip
[('P', 15), ('R', 1), ('S', 15)]
>>> for u,v in list(G.edges(labels=False, sort=False)):
...     G.add_path([u, G.add_vertex(), G.add_vertex(), v])
>>> T = G.spqr_tree(algorithm='Hopcroft_Tarjan')
>>> sorted(Counter(u[Integer(0)] for u in T).items())
[('P', 60), ('R', 1), ('S', 75)]
>>> T = G.spqr_tree(algorithm='cleave')       # long time                     # needs sage.numerical.mip
>>> sorted(Counter(u[Integer(0)] for u in T).items())  # long time                     # needs sage.numerical.mip
[('P', 60), ('R', 1), ('S', 75)]
strong_orientation(G)[source]

Return a strongly connected orientation of the graph \(G\).

An orientation of an undirected graph is a digraph obtained by giving an unique direction to each of its edges. An orientation is said to be strong if there is a directed path between each pair of vertices. See also the Wikipedia article Strongly_connected_component.

If the graph is 2-edge-connected, a strongly connected orientation can be found in linear time. If the given graph is not 2-connected, the orientation returned will ensure that each 2-connected component has a strongly connected orientation.

INPUT:

  • G – an undirected graph

OUTPUT: a digraph representing an orientation of the current graph

Note

  • This method assumes that the input the graph is connected.

  • The time complexity is \(O(n+m)\) for SparseGraph and \(O(n^2)\) for DenseGraph .

EXAMPLES:

For a 2-regular graph, a strong orientation gives to each vertex an out-degree equal to 1:

sage: g = graphs.CycleGraph(5)
sage: g.strong_orientation().out_degree()
[1, 1, 1, 1, 1]
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(5))
>>> g.strong_orientation().out_degree()
[1, 1, 1, 1, 1]

The Petersen Graph is 2-edge connected. It then has a strongly connected orientation:

sage: g = graphs.PetersenGraph()
sage: o = g.strong_orientation()
sage: len(o.strongly_connected_components())
1
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> o = g.strong_orientation()
>>> len(o.strongly_connected_components())
1

The same goes for the CubeGraph in any dimension:

sage: all(len(graphs.CubeGraph(i).strong_orientation().strongly_connected_components()) == 1
....:     for i in range(2,6))
True
>>> from sage.all import *
>>> all(len(graphs.CubeGraph(i).strong_orientation().strongly_connected_components()) == Integer(1)
...     for i in range(Integer(2),Integer(6)))
True

A multigraph also has a strong orientation:

sage: g = Graph([(0, 1), (0, 2), (1, 2)] * 2, multiedges=True)
sage: g.strong_orientation()
Multi-digraph on 3 vertices
>>> from sage.all import *
>>> g = Graph([(Integer(0), Integer(1)), (Integer(0), Integer(2)), (Integer(1), Integer(2))] * Integer(2), multiedges=True)
>>> g.strong_orientation()
Multi-digraph on 3 vertices
strong_orientations_iterator(G)[source]

Return an iterator over all strong orientations of a graph \(G\).

A strong orientation of a graph is an orientation of its edges such that the obtained digraph is strongly connected (i.e. there exist a directed path between each pair of vertices). According to Robbins’ theorem (see the Wikipedia article Robbins_theorem), the graphs that have strong orientations are exactly the 2-edge-connected graphs (i.e., the bridgeless graphs).

ALGORITHM:

It is an adaptation of the algorithm published in [CGMRV16]. It runs in \(O(mn)\) amortized time, where \(m\) is the number of edges and \(n\) is the number of vertices. The amortized time can be improved to \(O(m)\) with a more involved method. In this function, first the graph is preprocessed and a spanning tree is generated. Then every orientation of the non-tree edges of the graph can be extended to at least one new strong orientation by orienting properly the edges of the spanning tree (this property is proved in [CGMRV16]). Therefore, this function generates all partial orientations of the non-tree edges and then launches a helper function corresponding to the generation algorithm described in [CGMRV16]. In order to avoid trivial symmetries, the orientation of an arbitrary edge is fixed before the start of the enumeration process.

INPUT:

  • G – an undirected graph

OUTPUT: an iterator which will produce all strong orientations of this graph

Note

Works only for simple graphs (no multiple edges). To avoid symmetries an orientation of an arbitrary edge is fixed.

EXAMPLES:

A cycle has one possible (non-symmetric) strong orientation:

sage: g = graphs.CycleGraph(4)
sage: it = g.strong_orientations_iterator()
sage: len(list(it))
1
>>> from sage.all import *
>>> g = graphs.CycleGraph(Integer(4))
>>> it = g.strong_orientations_iterator()
>>> len(list(it))
1

A tree cannot be strongly oriented:

sage: g = graphs.RandomTree(10)
sage: len(list(g.strong_orientations_iterator()))
0
>>> from sage.all import *
>>> g = graphs.RandomTree(Integer(10))
>>> len(list(g.strong_orientations_iterator()))
0

Neither can be a disconnected graph:

sage: g = graphs.CompleteGraph(6)
sage: g.add_vertex(7)
sage: len(list(g.strong_orientations_iterator()))
0
>>> from sage.all import *
>>> g = graphs.CompleteGraph(Integer(6))
>>> g.add_vertex(Integer(7))
>>> len(list(g.strong_orientations_iterator()))
0
to_directed(data_structure=None, sparse=None)[source]

Return a directed version of the graph.

A single edge becomes two edges, one in each direction.

INPUT:

  • data_structure – one of 'sparse', 'static_sparse', or 'dense'. See the documentation of Graph or DiGraph.

  • sparse – boolean (default: None); sparse=True is an alias for data_structure="sparse", and sparse=False is an alias for data_structure="dense".

EXAMPLES:

sage: graphs.PetersenGraph().to_directed()
Petersen graph: Digraph on 10 vertices
>>> from sage.all import *
>>> graphs.PetersenGraph().to_directed()
Petersen graph: Digraph on 10 vertices
to_undirected()[source]

Since the graph is already undirected, simply returns a copy of itself.

EXAMPLES:

sage: graphs.PetersenGraph().to_undirected()
Petersen graph: Graph on 10 vertices
>>> from sage.all import *
>>> graphs.PetersenGraph().to_undirected()
Petersen graph: Graph on 10 vertices
topological_minor(H, vertices, paths=False, solver=False, verbose=None, integrality_tolerance=0)[source]

Return a topological \(H\)-minor from self if one exists.

We say that a graph \(G\) has a topological \(H\)-minor (or that it has a graph isomorphic to \(H\) as a topological minor), if \(G\) contains a subdivision of a graph isomorphic to \(H\) (i.e. obtained from \(H\) through arbitrary subdivision of its edges) as a subgraph.

For more information, see the Wikipedia article Minor_(graph_theory).

INPUT:

  • H – the topological minor to find in the current graph

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

OUTPUT:

The topological \(H\)-minor found is returned as a subgraph \(M\) of self, such that the vertex \(v\) of \(M\) that represents a vertex \(h\in H\) has h as a label (see get_vertex and set_vertex), and such that every edge of \(M\) has as a label the edge of \(H\) it (partially) represents.

If no topological minor is found, this method returns False.

ALGORITHM:

Mixed Integer Linear Programming.

COMPLEXITY:

Theoretically, when \(H\) is fixed, testing for the existence of a topological \(H\)-minor is polynomial. The known algorithms are highly exponential in \(H\), though.

Note

This function can be expected to be very slow, especially where the topological minor does not exist.

(CPLEX seems to be much more efficient than GLPK on this kind of problem)

EXAMPLES:

Petersen’s graph has a topological \(K_4\)-minor:

sage: g = graphs.PetersenGraph()
sage: g.topological_minor(graphs.CompleteGraph(4))                          # needs sage.numerical.mip
Subgraph of (Petersen graph): Graph on ...
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> g.topological_minor(graphs.CompleteGraph(Integer(4)))                          # needs sage.numerical.mip
Subgraph of (Petersen graph): Graph on ...

And a topological \(K_{3,3}\)-minor:

sage: g.topological_minor(graphs.CompleteBipartiteGraph(3,3))               # needs sage.numerical.mip
Subgraph of (Petersen graph): Graph on ...
>>> from sage.all import *
>>> g.topological_minor(graphs.CompleteBipartiteGraph(Integer(3),Integer(3)))               # needs sage.numerical.mip
Subgraph of (Petersen graph): Graph on ...

And of course, a tree has no topological \(C_3\)-minor:

sage: g = graphs.RandomGNP(15,.3)
sage: g = g.subgraph(edges=g.min_spanning_tree())
sage: g.topological_minor(graphs.CycleGraph(3))                             # needs sage.numerical.mip
False
>>> from sage.all import *
>>> g = graphs.RandomGNP(Integer(15),RealNumber('.3'))
>>> g = g.subgraph(edges=g.min_spanning_tree())
>>> g.topological_minor(graphs.CycleGraph(Integer(3)))                             # needs sage.numerical.mip
False
treelength(G, k=None, certificate=False)[source]

Compute the treelength of \(G\) (and provide a decomposition).

The length of a tree decomposition, as proposed in [DG2006], is the maximum diameter in \(G\) of its bags, where the diameter of a bag \(X_i\) is the largest distance in \(G\) between the vertices in \(X_i\) (i.e., \(\max_{u, v \in X_i} \dist_G(u, v)\)). The treelength \(tl(G)\) of a graph \(G\) is the minimum length among all possible tree decompositions of \(G\). See the documentation of the tree_decomposition module for more details.

INPUT:

  • G – a sage Graph

  • k – integer (default: None); indicates the length to be considered. When \(k\) is an integer, the method checks that the graph has treelength \(\leq k\). If \(k\) is None (default), the method computes the optimal treelength.

  • certificate – boolean (default: False); whether to also return the tree-decomposition itself

OUTPUT:

G.treelength() returns the treelength of \(G\). When \(k\) is specified, it returns False when no tree-decomposition of length \(\leq k\) exists or True otherwise. When certificate=True, the tree-decomposition is also returned.

ALGORITHM:

This method virtually explores the graph of all pairs (vertex_cut, connected_component), where vertex_cut is a vertex cut of the graph of length \(\leq k\), and connected_component is a connected component of the graph induced by G - vertex_cut.

We deduce that the pair (vertex_cut, connected_component) is feasible with treelength \(k\) if connected_component is empty, or if a vertex v from vertex_cut can be replaced with a vertex from connected_component, such that the pair (vertex_cut + v, connected_component - v) is feasible.

In practice, this method decomposes the graph by its clique minimal separators into atoms, computes the treelength of each of atom and returns the maximum value over all the atoms. Indeed, we have that \(tl(G) = \max_{X \in A} tl(G[X])\) where \(A\) is the set of atoms of the decomposition by clique separators of \(G\). When certificate == True, the tree-decompositions of the atoms are connected to each others by adding edges with respect to the clique separators.

See also

EXAMPLES:

The PetersenGraph has treelength 2:

sage: G = graphs.PetersenGraph()
sage: G.treelength()
2
>>> from sage.all import *
>>> G = graphs.PetersenGraph()
>>> G.treelength()
2

Disconnected graphs have infinite treelength:

sage: G = Graph(2)
sage: G.treelength()
+Infinity
sage: G.treelength(k=+Infinity)
True
sage: G.treelength(k=2)
False
sage: G.treelength(certificate=True)
Traceback (most recent call last):
...
ValueError: the tree decomposition of a disconnected graph is not defined
>>> from sage.all import *
>>> G = Graph(Integer(2))
>>> G.treelength()
+Infinity
>>> G.treelength(k=+Infinity)
True
>>> G.treelength(k=Integer(2))
False
>>> G.treelength(certificate=True)
Traceback (most recent call last):
...
ValueError: the tree decomposition of a disconnected graph is not defined

Chordal graphs have treelength 1:

sage: G = graphs.RandomChordalGraph(30)
sage: while not G.is_connected():
....:     G = graphs.RandomChordalGraph(30)
sage: G.treelength()
1
>>> from sage.all import *
>>> G = graphs.RandomChordalGraph(Integer(30))
>>> while not G.is_connected():
...     G = graphs.RandomChordalGraph(Integer(30))
>>> G.treelength()
1

Cycles have treelength \(\lceil n/3 \rceil\):

sage: [graphs.CycleGraph(n).treelength() for n in range(3, 11)]
[1, 2, 2, 2, 3, 3, 3, 4]
>>> from sage.all import *
>>> [graphs.CycleGraph(n).treelength() for n in range(Integer(3), Integer(11))]
[1, 2, 2, 2, 3, 3, 3, 4]
treewidth(g, k=None, kmin=None, certificate=False, algorithm=None, nice=False)[source]

Compute the treewidth of \(g\) (and provide a decomposition).

INPUT:

  • g – a Sage Graph

  • k – integer (default: None); indicates the width to be considered. When k is an integer, the method checks that the graph has treewidth \(\leq k\). If k is None (default), the method computes the optimal treewidth.

  • kmin – integer (default: None); when specified, search for a tree-decomposition of width at least kmin. This parameter is useful when the graph can be decomposed into atoms. This parameter is ignored when k is not None or when algorithm == 'tdlib'.

  • certificate – boolean (default: False); whether to return the tree-decomposition itself

  • algorithm – whether to use 'sage' or 'tdlib' (requires the installation of the sagemath_tdlib: Tree decompositions with tdlib package). The default behaviour is to use ‘tdlib’ if it is available, and Sage’s own algorithm when it is not.

  • nice – boolean (default: False); whether or not to return the nice tree decomposition, provided certificate is True

OUTPUT:

g.treewidth() returns treewidth of the graph g.

When k is specified, it returns False if there is no tree decomposition of width \(\leq k\), and True otherwise.

When certificate=True, the tree decomposition is returned.

When nice=True, the nice tree decomposition is returned.

ALGORITHM:

This function virtually explores the graph of all pairs (vertex_cut, cc), where vertex_cut is a vertex cut of the graph of cardinality \(\leq k+1\), and connected_component is a connected component of the graph induced by G-vertex_cut.

We deduce that the pair (vertex_cut, cc) is feasible with treewidth \(k\) if cc is empty, or if a vertex v from vertex_cut can be replaced with a vertex from cc, such that the pair (vertex_cut+v,cc-v) is feasible.

Note

The implementation would be much faster if cc, the argument of the recursive function, was a bitset. It would also be very nice to not copy the graph in order to compute connected components, for this is really a waste of time.

See also

path_decomposition() computes the pathwidth of a graph. See also the vertex_separation module.

EXAMPLES:

The PetersenGraph has treewidth 4:

sage: petersen = graphs.PetersenGraph()
sage: petersen.treewidth(algorithm='sage')
4
sage: petersen.treewidth(algorithm='sage', certificate=True)
Tree decomposition: Graph on 6 vertices
>>> from sage.all import *
>>> petersen = graphs.PetersenGraph()
>>> petersen.treewidth(algorithm='sage')
4
>>> petersen.treewidth(algorithm='sage', certificate=True)
Tree decomposition: Graph on 6 vertices

The PetersenGraph has treewidth 4 (with tdlib):

sage: petersen = graphs.PetersenGraph()
sage: petersen.treewidth(algorithm='tdlib')                    # optional - tdlib
4
sage: petersen.treewidth(algorithm='tdlib', certificate=True)  # optional - tdlib
Tree decomposition: Graph on 6 vertices
>>> from sage.all import *
>>> petersen = graphs.PetersenGraph()
>>> petersen.treewidth(algorithm='tdlib')                    # optional - tdlib
4
>>> petersen.treewidth(algorithm='tdlib', certificate=True)  # optional - tdlib
Tree decomposition: Graph on 6 vertices

Nice tree decomposition of the PetersenGraph has 28 nodes:

sage: petersen = graphs.PetersenGraph()
sage: petersen.treewidth(algorithm='sage', certificate=True, nice=True)
Nice tree decomposition of Tree decomposition: Graph on 28 vertices
sage: petersen.treewidth(algorithm='tdlib', certificate=True, nice=True)  # optional - tdlib
Nice tree decomposition of Tree decomposition: Graph on 28 vertices
>>> from sage.all import *
>>> petersen = graphs.PetersenGraph()
>>> petersen.treewidth(algorithm='sage', certificate=True, nice=True)
Nice tree decomposition of Tree decomposition: Graph on 28 vertices
>>> petersen.treewidth(algorithm='tdlib', certificate=True, nice=True)  # optional - tdlib
Nice tree decomposition of Tree decomposition: Graph on 28 vertices

The treewidth of a 2-dimensional grid is its smallest side:

sage: graphs.Grid2dGraph(2,5).treewidth()
2
sage: graphs.Grid2dGraph(3,5).treewidth()
3
>>> from sage.all import *
>>> graphs.Grid2dGraph(Integer(2),Integer(5)).treewidth()
2
>>> graphs.Grid2dGraph(Integer(3),Integer(5)).treewidth()
3

When parameter kmin is specified, the method searches for a tree-decomposition of width at least kmin:

sage: g = graphs.PetersenGraph()
sage: g.treewidth()
4
sage: g.treewidth(kmin=2, algorithm='sage')
4
sage: g.treewidth(kmin=g.order(), certificate=True, algorithm='sage')
Tree decomposition: Graph on 1 vertex
>>> from sage.all import *
>>> g = graphs.PetersenGraph()
>>> g.treewidth()
4
>>> g.treewidth(kmin=Integer(2), algorithm='sage')
4
>>> g.treewidth(kmin=g.order(), certificate=True, algorithm='sage')
Tree decomposition: Graph on 1 vertex
tutte_polynomial(G, edge_selector=None, cache=None)[source]

Return the Tutte polynomial of the graph \(G\).

INPUT:

  • edge_selector – method (optional); this argument allows the user to specify his own heuristic for selecting edges used in the deletion contraction recurrence

  • cache – (optional) dictionary to cache the Tutte polynomials generated in the recursive process. One will be created automatically if not provided.

EXAMPLES:

The Tutte polynomial of any tree of order \(n\) is \(x^{n-1}\):

sage: all(T.tutte_polynomial() == x**9 for T in graphs.trees(10))               # needs sage.symbolic
True
>>> from sage.all import *
>>> all(T.tutte_polynomial() == x**Integer(9) for T in graphs.trees(Integer(10)))               # needs sage.symbolic
True

The Tutte polynomial of the Petersen graph is:

sage: P = graphs.PetersenGraph()
sage: P.tutte_polynomial()
x^9 + 6*x^8 + 21*x^7 + 56*x^6 + 12*x^5*y + y^6 + 114*x^5 + 70*x^4*y
+ 30*x^3*y^2 + 15*x^2*y^3 + 10*x*y^4 + 9*y^5 + 170*x^4 + 170*x^3*y
+ 105*x^2*y^2 + 65*x*y^3 + 35*y^4 + 180*x^3 + 240*x^2*y + 171*x*y^2
+ 75*y^3 + 120*x^2 + 168*x*y + 84*y^2 + 36*x + 36*y
>>> from sage.all import *
>>> P = graphs.PetersenGraph()
>>> P.tutte_polynomial()
x^9 + 6*x^8 + 21*x^7 + 56*x^6 + 12*x^5*y + y^6 + 114*x^5 + 70*x^4*y
+ 30*x^3*y^2 + 15*x^2*y^3 + 10*x*y^4 + 9*y^5 + 170*x^4 + 170*x^3*y
+ 105*x^2*y^2 + 65*x*y^3 + 35*y^4 + 180*x^3 + 240*x^2*y + 171*x*y^2
+ 75*y^3 + 120*x^2 + 168*x*y + 84*y^2 + 36*x + 36*y

The Tutte polynomial of a connected graph \(G\) evaluated at (1,1) is the number of spanning trees of \(G\):

sage: G = graphs.RandomGNP(10,0.6)
sage: while not G.is_connected():
....:     G = graphs.RandomGNP(10,0.6)
sage: G.tutte_polynomial()(1,1) == G.spanning_trees_count()                     # needs sage.modules
True
>>> from sage.all import *
>>> G = graphs.RandomGNP(Integer(10),RealNumber('0.6'))
>>> while not G.is_connected():
...     G = graphs.RandomGNP(Integer(10),RealNumber('0.6'))
>>> G.tutte_polynomial()(Integer(1),Integer(1)) == G.spanning_trees_count()                     # needs sage.modules
True

Given that \(T(x,y)\) is the Tutte polynomial of a graph \(G\) with \(n\) vertices and \(c\) connected components, then \((-1)^{n-c} x^k T(1-x,0)\) is the chromatic polynomial of \(G\).

sage: G = graphs.OctahedralGraph()
sage: T = G.tutte_polynomial()
sage: R = PolynomialRing(ZZ, 'x')
sage: R((-1)^5*x*T(1-x,0)).factor()                                             # needs sage.symbolic
(x - 2) * (x - 1) * x * (x^3 - 9*x^2 + 29*x - 32)
sage: G.chromatic_polynomial().factor()                                         # needs sage.libs.flint
(x - 2) * (x - 1) * x * (x^3 - 9*x^2 + 29*x - 32)
>>> from sage.all import *
>>> G = graphs.OctahedralGraph()
>>> T = G.tutte_polynomial()
>>> R = PolynomialRing(ZZ, 'x')
>>> R((-Integer(1))**Integer(5)*x*T(Integer(1)-x,Integer(0))).factor()                                             # needs sage.symbolic
(x - 2) * (x - 1) * x * (x^3 - 9*x^2 + 29*x - 32)
>>> G.chromatic_polynomial().factor()                                         # needs sage.libs.flint
(x - 2) * (x - 1) * x * (x^3 - 9*x^2 + 29*x - 32)
tutte_symmetric_function(R=None, t=None)[source]

Return the Tutte symmetric function of self.

Let \(G\) be a graph. The Tutte symmetric function \(XB_G\) of the graph \(G\) was introduced in [Sta1998]. We present the equivalent definition given in [CS2022].

\[XB_G = \sum_{\pi \vdash V} (1+t)^{e(\pi)} \tilde{m}_{\lambda(\pi)},\]

where the sum ranges over all set-partitions \(\pi\) of the vertex set \(V\), \(\lambda(\pi)\) is the partition determined by the sizes of the blocks of \(\pi\), and \(e(\pi)\) is the number of edges whose endpoints lie in the same block of \(\pi\). In particular, the coefficients of \(XB_G\) when expanded in terms of augmented monomial symmetric functions are polynomials in \(t\) with non-negative integer coefficients.

For an integer partition \(\lambda = 1^{r_1}2^{r_2}\cdots\) expressed in the exponential notation, the augmented monomial symmetric function is defined as

\[\tilde{m}_{\lambda} = \left(\prod_{i} r_i! \right) m_{\lambda}.\]

INPUT:

  • R – (default: the parent of t) the base ring for the symmetric functions

  • t – (default: \(t\) in \(\ZZ[t]\)) the parameter \(t\)

EXAMPLES:

sage: p = SymmetricFunctions(ZZ).p()                                        # needs sage.combinat sage.modules
sage: G = Graph([[1,2],[2,3],[3,4],[4,1],[1,3]])
sage: XB_G = G.tutte_symmetric_function(); XB_G                             # needs sage.combinat sage.modules
24*m[1, 1, 1, 1] + (10*t+12)*m[2, 1, 1] + (4*t^2+10*t+6)*m[2, 2]
 + (2*t^3+8*t^2+10*t+4)*m[3, 1]
 + (t^5+5*t^4+10*t^3+10*t^2+5*t+1)*m[4]
sage: p(XB_G)                                                               # needs sage.combinat sage.modules
p[1, 1, 1, 1] + 5*t*p[2, 1, 1] + 2*t^2*p[2, 2]
 + (2*t^3+8*t^2)*p[3, 1] + (t^5+5*t^4+8*t^3)*p[4]
>>> from sage.all import *
>>> p = SymmetricFunctions(ZZ).p()                                        # needs sage.combinat sage.modules
>>> G = Graph([[Integer(1),Integer(2)],[Integer(2),Integer(3)],[Integer(3),Integer(4)],[Integer(4),Integer(1)],[Integer(1),Integer(3)]])
>>> XB_G = G.tutte_symmetric_function(); XB_G                             # needs sage.combinat sage.modules
24*m[1, 1, 1, 1] + (10*t+12)*m[2, 1, 1] + (4*t^2+10*t+6)*m[2, 2]
 + (2*t^3+8*t^2+10*t+4)*m[3, 1]
 + (t^5+5*t^4+10*t^3+10*t^2+5*t+1)*m[4]
>>> p(XB_G)                                                               # needs sage.combinat sage.modules
p[1, 1, 1, 1] + 5*t*p[2, 1, 1] + 2*t^2*p[2, 2]
 + (2*t^3+8*t^2)*p[3, 1] + (t^5+5*t^4+8*t^3)*p[4]

Graphs are allowed to have multiedges and loops:

sage: G = Graph([[1,2],[2,3],[2,3]], multiedges = True)
sage: XB_G = G.tutte_symmetric_function(); XB_G                             # needs sage.combinat sage.modules
6*m[1, 1, 1] + (t^2+3*t+3)*m[2, 1] + (t^3+3*t^2+3*t+1)*m[3]
>>> from sage.all import *
>>> G = Graph([[Integer(1),Integer(2)],[Integer(2),Integer(3)],[Integer(2),Integer(3)]], multiedges = True)
>>> XB_G = G.tutte_symmetric_function(); XB_G                             # needs sage.combinat sage.modules
6*m[1, 1, 1] + (t^2+3*t+3)*m[2, 1] + (t^3+3*t^2+3*t+1)*m[3]

We check that at \(t = -1\), we recover the usual chromatic symmetric function:

sage: G = Graph([[1,2],[1,2],[2,3],[3,4],[4,5]], multiedges=True)
sage: XB_G = G.tutte_symmetric_function(t=-1); XB_G                         # needs sage.combinat sage.modules
120*m[1, 1, 1, 1, 1] + 36*m[2, 1, 1, 1] + 12*m[2, 2, 1]
 + 2*m[3, 1, 1] + m[3, 2]
sage: X_G = G.chromatic_symmetric_function(); X_G                           # needs sage.combinat sage.modules
p[1, 1, 1, 1, 1] - 4*p[2, 1, 1, 1] + 3*p[2, 2, 1] + 3*p[3, 1, 1]
 - 2*p[3, 2] - 2*p[4, 1] + p[5]
sage: XB_G == X_G                                                           # needs sage.combinat sage.modules
True
>>> from sage.all import *
>>> G = Graph([[Integer(1),Integer(2)],[Integer(1),Integer(2)],[Integer(2),Integer(3)],[Integer(3),Integer(4)],[Integer(4),Integer(5)]], multiedges=True)
>>> XB_G = G.tutte_symmetric_function(t=-Integer(1)); XB_G                         # needs sage.combinat sage.modules
120*m[1, 1, 1, 1, 1] + 36*m[2, 1, 1, 1] + 12*m[2, 2, 1]
 + 2*m[3, 1, 1] + m[3, 2]
>>> X_G = G.chromatic_symmetric_function(); X_G                           # needs sage.combinat sage.modules
p[1, 1, 1, 1, 1] - 4*p[2, 1, 1, 1] + 3*p[2, 2, 1] + 3*p[3, 1, 1]
 - 2*p[3, 2] - 2*p[4, 1] + p[5]
>>> XB_G == X_G                                                           # needs sage.combinat sage.modules
True
two_factor_petersen(solver, verbose=None, integrality_tolerance=0)[source]

Return a decomposition of the graph into 2-factors.

Petersen’s 2-factor decomposition theorem asserts that any \(2r\)-regular graph \(G\) can be decomposed into 2-factors. Equivalently, it means that the edges of any \(2r\)-regular graphs can be partitioned in \(r\) sets \(C_1,\dots,C_r\) such that for all \(i\), the set \(C_i\) is a disjoint union of cycles (a 2-regular graph).

As any graph of maximal degree \(\Delta\) can be completed into a regular graph of degree \(2\lceil\frac\Delta 2\rceil\), this result also means that the edges of any graph of degree \(\Delta\) can be partitioned in \(r=2\lceil\frac\Delta 2\rceil\) sets \(C_1,\dots,C_r\) such that for all \(i\), the set \(C_i\) is a graph of maximal degree \(2\) (a disjoint union of paths and cycles).

INPUT:

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

EXAMPLES:

The Complete Graph on \(7\) vertices is a \(6\)-regular graph, so it can be edge-partitionned into \(2\)-regular graphs:

sage: g = graphs.CompleteGraph(7)
sage: classes = g.two_factor_petersen()                                     # needs sage.numerical.mip
sage: for c in classes:                                                     # needs sage.numerical.mip
....:     gg = Graph()
....:     gg.add_edges(c)
....:     print(max(gg.degree())<=2)
True
True
True
sage: Set(set(classes[0])                                                   # needs sage.numerical.mip
....:     | set(classes[1])
....:     | set(classes[2])).cardinality() == g.size()
True
>>> from sage.all import *
>>> g = graphs.CompleteGraph(Integer(7))
>>> classes = g.two_factor_petersen()                                     # needs sage.numerical.mip
>>> for c in classes:                                                     # needs sage.numerical.mip
...     gg = Graph()
...     gg.add_edges(c)
...     print(max(gg.degree())<=Integer(2))
True
True
True
>>> Set(set(classes[Integer(0)])                                                   # needs sage.numerical.mip
...     | set(classes[Integer(1)])
...     | set(classes[Integer(2)])).cardinality() == g.size()
True

sage: g = graphs.CirculantGraph(24, [7, 11])
sage: cl = g.two_factor_petersen()                                          # needs sage.numerical.mip
sage: g.plot(edge_colors={'black':cl[0], 'red':cl[1]})                      # needs sage.numerical.mip sage.plot
Graphics object consisting of 73 graphics primitives
>>> from sage.all import *
>>> g = graphs.CirculantGraph(Integer(24), [Integer(7), Integer(11)])
>>> cl = g.two_factor_petersen()                                          # needs sage.numerical.mip
>>> g.plot(edge_colors={'black':cl[Integer(0)], 'red':cl[Integer(1)]})                      # needs sage.numerical.mip sage.plot
Graphics object consisting of 73 graphics primitives
twograph()[source]

Return the two-graph of self.

Returns the two-graph with the triples \(T=\{t \in \binom {V}{3} : \left| \binom {t}{2} \cap E \right| \text{odd} \}\) where \(V\) and \(E\) are vertices and edges of self, respectively.

EXAMPLES:

sage: p = graphs.PetersenGraph()
sage: p.twograph()                                                          # needs sage.modules
Incidence structure with 10 points and 60 blocks
sage: p = graphs.chang_graphs()
sage: T8 = graphs.CompleteGraph(8).line_graph()
sage: C = T8.seidel_switching([(0,1,None), (2,3,None), (4,5,None), (6,7,None)],
....:                         inplace=False)
sage: T8.twograph() == C.twograph()                                         # needs sage.modules
True
sage: T8.is_isomorphic(C)
False
>>> from sage.all import *
>>> p = graphs.PetersenGraph()
>>> p.twograph()                                                          # needs sage.modules
Incidence structure with 10 points and 60 blocks
>>> p = graphs.chang_graphs()
>>> T8 = graphs.CompleteGraph(Integer(8)).line_graph()
>>> C = T8.seidel_switching([(Integer(0),Integer(1),None), (Integer(2),Integer(3),None), (Integer(4),Integer(5),None), (Integer(6),Integer(7),None)],
...                         inplace=False)
>>> T8.twograph() == C.twograph()                                         # needs sage.modules
True
>>> T8.is_isomorphic(C)
False

See also

vertex_cover(algorithm, value_only='Cliquer', reduction_rules=False, solver=True, verbose=None, integrality_tolerance=0)[source]

Return a minimum vertex cover of self represented by a set of vertices.

A minimum vertex cover of a graph is a set \(S\) of vertices such that each edge is incident to at least one element of \(S\), and such that \(S\) is of minimum cardinality. For more information, see the Wikipedia article Vertex_cover.

Equivalently, a vertex cover is defined as the complement of an independent set.

As an optimization problem, it can be expressed as follows:

\[\begin{split}\mbox{Minimize : }&\sum_{v\in G} b_v\\ \mbox{Such that : }&\forall (u,v) \in G.edges(), b_u+b_v\geq 1\\ &\forall x\in G, b_x\mbox{ is a binary variable}\end{split}\]

INPUT:

  • algorithm – string (default: 'Cliquer'); indicating which algorithm to use. It can be one of those values.

    • 'Cliquer' will compute a minimum vertex cover using the Cliquer package

    • 'MILP' will compute a minimum vertex cover through a mixed integer linear program

    • 'mcqd' will use the MCQD solver (http://www.sicmm.org/~konc/maxclique/). Note that the MCQD package must be installed.

  • value_only – boolean (default: False); if set to True, only the size of a minimum vertex cover is returned. Otherwise, a minimum vertex cover is returned as a list of vertices.

  • reduction_rules – (default: True) specify if the reductions rules from kernelization must be applied as pre-processing or not. See [ACFLSS04] for more details. Note that depending on the instance, it might be faster to disable reduction rules.

  • solver – string (default: None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

EXAMPLES:

On the Pappus graph:

sage: g = graphs.PappusGraph()
sage: g.vertex_cover(value_only=True)
9
>>> from sage.all import *
>>> g = graphs.PappusGraph()
>>> g.vertex_cover(value_only=True)
9
../../_images/graph-4.svg
vertex_isoperimetric_number(g)[source]

Return the vertex-isoperimetric number of the graph.

The vertex-isoperimetric number of a graph \(G = (V,E)\) is also sometimes called the magnifying constant. It is defined as the minimum of \(|N(S)| / |S|\) where \(|N(S)|\) is the vertex boundary of \(S\) and the minimum is taken over the subsets \(S\) of vertices of size at most half of the vertices.

See also

Alternative but similar quantities can be obtained via the methods cheeger_constant() and edge_isoperimetric_number().

EXAMPLES:

The vertex-isoperimetric number of a complete graph on \(n\) vertices is \(\lceil n/2 \rceil/\lfloor n/2 \rfloor\):

sage: [graphs.CompleteGraph(k).vertex_isoperimetric_number() for k in range(2,15)]
[1, 2, 1, 3/2, 1, 4/3, 1, 5/4, 1, 6/5, 1, 7/6, 1]
>>> from sage.all import *
>>> [graphs.CompleteGraph(k).vertex_isoperimetric_number() for k in range(Integer(2),Integer(15))]
[1, 2, 1, 3/2, 1, 4/3, 1, 5/4, 1, 6/5, 1, 7/6, 1]

The vertex-isoperimetric number of a cycle on \(n\) vertices is \(2/\lfloor n/2 \rfloor\):

sage: [graphs.CycleGraph(k).vertex_isoperimetric_number() for k in range(2,15)]
[1, 2, 1, 1, 2/3, 2/3, 1/2, 1/2, 2/5, 2/5, 1/3, 1/3, 2/7]
>>> from sage.all import *
>>> [graphs.CycleGraph(k).vertex_isoperimetric_number() for k in range(Integer(2),Integer(15))]
[1, 2, 1, 1, 2/3, 2/3, 1/2, 1/2, 2/5, 2/5, 1/3, 1/3, 2/7]

And the vertex-isoperimetric number of a disconnected graph is \(0\):

sage: Graph([[1,2,3],[(1,2)]]).vertex_isoperimetric_number()
0
>>> from sage.all import *
>>> Graph([[Integer(1),Integer(2),Integer(3)],[(Integer(1),Integer(2))]]).vertex_isoperimetric_number()
0

The vertex-isoperimetric number is independent of edge multiplicity:

sage: G = graphs.CycleGraph(6)
sage: G.vertex_isoperimetric_number()
2/3
sage: G.allow_multiple_edges(True)
sage: G.add_edges(G.edges(sort=False))
sage: G.vertex_isoperimetric_number()
2/3
>>> from sage.all import *
>>> G = graphs.CycleGraph(Integer(6))
>>> G.vertex_isoperimetric_number()
2/3
>>> G.allow_multiple_edges(True)
>>> G.add_edges(G.edges(sort=False))
>>> G.vertex_isoperimetric_number()
2/3
write_to_eps(filename, **options)[source]

Write a plot of the graph to filename in eps format.

INPUT:

  • filename – string

  • **options – same layout options as layout()

EXAMPLES:

sage: P = graphs.PetersenGraph()
sage: P.write_to_eps(tmp_filename(ext='.eps'))
>>> from sage.all import *
>>> P = graphs.PetersenGraph()
>>> P.write_to_eps(tmp_filename(ext='.eps'))

It is relatively simple to include this file in a LaTeX document. \usepackage{graphics} must appear in the preamble, and \includegraphics{filename} will include the file. To compile the document to pdf with pdflatex or xelatex the file needs first to be converted to pdf, for example with ps2pdf filename.eps filename.pdf.