Ticket #13961: trac_13961-new_module.patch

File trac_13961-new_module.patch, 16.0 KB (added by ncohen, 8 years ago)
  • doc/en/reference/graphs/index.rst

    # HG changeset patch
    # User Nathann Cohen <nathann.cohen@gmail.com>
    # Date 1358333251 -3600
    # Node ID 2427a5ec0acc060acd7ba5843ceacb9e1f1550b3
    # Parent  1ddd56f1ce4b9722752064741003a9a540d74abc
    Compute the root graph of a graph -- sage.graphs.line_graph module
    
    diff --git a/doc/en/reference/graphs/index.rst b/doc/en/reference/graphs/index.rst
    a b  
    4848   sage/graphs/graph_coloring
    4949   sage/graphs/cliquer
    5050   sage/graphs/comparability
     51   sage/graphs/line_graph
    5152   sage/graphs/spanning_tree
    5253   sage/graphs/pq_trees
    5354   sage/graphs/trees
  • sage/graphs/graph.py

    diff --git a/sage/graphs/graph.py b/sage/graphs/graph.py
    a b  
    18521852          forbidden induced subgraphs of a line graph (instead of the
    18531853          usual ``False``)
    18541854
    1855          
    1856         TODO:
    1857 
    1858         This methods sequentially tests each of the forbidden
    1859         subgraphs, which is a very slow method. There exist much
    1860         better algorithms, including those which are actually able to
    1861         return a graph whose line graph is isomorphic to the given
    1862         graph.
     1855        .. TODO::
     1856
     1857            This methods sequentially tests each of the forbidden subgraphs,
     1858            which is a very slow method. There exist much better algorithms,
     1859            including those which are actually able to return a graph whose line
     1860            graph is isomorphic to the given graph.
    18631861
    18641862        EXAMPLES:
    18651863
     
    18701868
    18711869        The Petersen Graph not being claw-free, it is not a line
    18721870        graph:
    1873        
     1871
    18741872            sage: graphs.PetersenGraph().is_line_graph()
    18751873            False
    18761874
  • new file sage/graphs/line_graph.py

    diff --git a/sage/graphs/line_graph.py b/sage/graphs/line_graph.py
    new file mode 100644
    - +  
     1r"""
     2Line graphs
     3
     4This modules gather everything which is related to line graphs. Right now, this
     5amounts to the following functions :
     6
     7.. csv-table::
     8    :class: contentstable
     9    :widths: 30, 70
     10    :delim: |
     11
     12    :meth:`line_graph` | Computes the line graph of a given graph
     13    :meth:`is_line_graph` | Check whether a graph is a line graph
     14    :meth:`root_graph` | Computes the root graph corresponding to the given graph
     15
     16Author:
     17
     18- Nathann Cohen (01-2013), while listening to Nina Simone *"I wish I
     19  knew how it would feel to be free"*. Crazy good song. And *"Prendre
     20  ta douleur"*, too.
     21
     22Definition
     23-----------
     24
     25Given a graph `G`, the *line graph* `L(G)` of `G` is the graph such that
     26
     27.. MATH::
     28
     29    V(L(G)) =& E(G)\\
     30    E(L(G)) =& \{(e,e'):\text{ and }e,e'\text{ have a common endpoint in }G\}\\
     31
     32The definition is extended to directed graphs. In this situation, there is an
     33arc `(e,e')` in `L(G)` if the destination of `e` is the origin of `e'`.
     34
     35For more information, see the :wikipedia:`Wikipedia page on line graphs
     36<Line_graph>`.
     37
     38Root graph
     39----------
     40
     41A graph whose line graph is `LG` is called the *root graph* of `LG`. The root
     42graph of a (connected) graph is unique ([Whitney32]_, [Harary69]_), except when
     43`LG=K_3`, as both `L(K_3)` and `L(K_{1,3})` are equal to `K_3`.
     44
     45Here is how we can *"see"* `G` by staring (very intently) at `LG` :
     46
     47  A graph `LG` is the line graph of `G` if there exists a collection
     48  `(S_v)_{v\in G}` of subsets of `V(LG)` such that :
     49
     50  * Every `S_v` is a complete subgraph of `LG`.
     51
     52  * Every `v\in LG` belongs to exactly two sets of the family `(S_v)_{v\in G}`.
     53
     54  * Any two sets of `(S_v)_{v\in G}` have at most one common elements
     55
     56  * For any edge `(u,v)\in LG` there exists a set of `(S_v)_{v\in G}` containing
     57    both `u` and `v`.
     58
     59  In this family, each set `S_v` represent a vertex of `G`, and contains "the
     60  set of edges incident to `v` in `G`". Two elements `S_v,S_{v'}` have a
     61  nonempty intersection whenever `vv'` is an edge of `G`.
     62
     63  Hence, finding the root graph of `LG` is the job of finding this collection of
     64  sets.
     65
     66In particular, what we know for sure is that a maximal clique `S` of size `2` or
     67`\geq 4` in `LG` corresponds to a vertex of degree `|S|` in `G`, whose incident
     68edges are the elements of `S` itself.
     69
     70The main problem lies with maximal cliques of size 3, i.e. triangles. Those we
     71have to split into two categories, *even* and *odd* triangles :
     72
     73  A triangle `\{e_1,e_2,e_3\}\subseteq V(LG)` is said to be an *odd* triangle if
     74  there exists a vertex `e\in V(G)` incident to exactly *one* or *all* of
     75  `\{e_1,e_2,e_3\}`, and it is said to be *even* otherwise.
     76
     77The very good point of this definition is that an inclusionwise maximal clique
     78which is an odd triangle will always correspond to a vertex of degree 3 in `G`,
     79while an even triangle could result from either a vertex of degree 3 in `G` or a
     80triangle in `G`. And in order to build the root graph we obviously have to
     81decide *which*.
     82
     83Beineke proves in [Beineke70]_ that the collection of sets we are looking for
     84can be easily found. Indeed it turns out that it is the union of :
     85
     86#. The family of all maximal cliques of `LG` of size 2 or `\geq 4`, as well as
     87   all odd triangles.
     88
     89#. The family of all pairs of adjacent vertices which appear in exactly *one*
     90   maximal clique which is an even triangle.
     91
     92There are actually four special cases to which the decomposition above does not
     93apply, i.e. graphs containing an edge which belongs to exactly two even
     94triangles. We deal with those independently.
     95
     96* The :meth:`Complete graph
     97  <sage.graphs.graph_generators.GraphGenerators.CompleteGraph>` `K_3`.
     98
     99* The :meth:`Diamond graph
     100  <sage.graphs.graph_generators.GraphGenerators.DiamondGraph>` -- the line graph
     101  of `K_{1,3}` plus an edge.
     102
     103* The :meth:`Wheel graph
     104  <sage.graphs.graph_generators.GraphGenerators.WheelGraph>` on `4+1` vertices
     105  -- the line graph of the :meth:`Diamond graph
     106  <sage.graphs.graph_generators.GraphGenerators.DiamondGraph>`
     107
     108* The :meth:`Octahedron
     109  <sage.graphs.graph_generators.GraphGenerators.OctahedralGraph>` -- the line
     110  graph of `K_4`.
     111
     112This decomposition turns out to be very easy to implement :-)
     113
     114.. WARNING::
     115
     116    Even though the root graph is *NOT UNIQUE* for the triangle, this method
     117    returns `K_{1,3}` (and not `K_3`) in this case. Pay *very close* attention
     118    to that, for this answer is not theoretically correct : there is no unique
     119    answer in this case, and we deal with this case by returning one of the two
     120    possible answers.
     121
     122.. [Whitney32] Congruent graphs and the connectivity of graphs,
     123  Whitney,
     124  American Journal of Mathematics,
     125  pages 150--168, 1932,
     126  `available on JSTOR <http://www.jstor.org/stable/2371086>`_
     127
     128.. [Harary69] Graph Theory,
     129  Harary,
     130  Addison-Wesley, 1969
     131
     132.. [Beineke70] Lowell Beineke,
     133  Characterizations of derived graphs,
     134  Journal of Combinatorial Theory,
     135  Vol. 9(2), pages 129-135, 1970
     136  http://dx.doi.org/10.1016/S0021-9800(70)80019-9
     137
     138Functions
     139---------
     140"""
     141
     142def root_graph(g, verbose = False):
     143    r"""
     144    Computes the root graph corresponding to the given graph
     145
     146    See the documentation of :mod:`sage.graphs.line_graph` to know how it works.
     147
     148    INPUT:
     149
     150    - ``g`` -- a graph
     151
     152    - ``verbose`` (boolean) -- display some information about what is happening
     153      inside of the algorithm.
     154
     155    .. NOTE::
     156
     157        It is best to use this code through
     158        :meth:`~sage.graphs.graph.Graph.is_line_graph`, which first checks that
     159        the graph is indeed a line graph, and deals with the disconnected
     160        case. But if you are sure of yourself, dig in !
     161
     162    .. WARNING::
     163
     164        * This code assumes that the graph is connected.
     165
     166        * If the graph is *not* a line graph, this implementation will take a
     167          loooooong time to run. Its first step is to enumerate all maximal
     168          cliques, and that can take a while for general graphs. As soon as
     169          there is a way to iterate over maximal cliques without first building
     170          the (long) list of them this implementation can be updated, and will
     171          deal reasonably with non-line grph too !
     172
     173    TESTS:
     174
     175    All connected graphs on 6 vertices::
     176
     177        sage: from sage.graphs.line_graph import root_graph
     178        sage: def test(g):
     179        ...      gl = g.line_graph(labels = False)
     180        ...      d=root_graph(gl)
     181        sage: for i,g in enumerate(graphs(6)): # long time
     182        ...     if not g.is_connected():       # long time
     183        ...       continue                     # long time
     184        ...     test(g)                        # long time
     185
     186    Non line-graphs::
     187
     188        sage: root_graph(graphs.PetersenGraph())
     189        Traceback (most recent call last):
     190        ...
     191        ValueError: This graph is not a line graph !
     192
     193    Small corner-cases::
     194
     195        sage: from sage.graphs.line_graph import root_graph
     196        sage: root_graph(graphs.CompleteGraph(3))
     197        (Complete bipartite graph: Graph on 4 vertices, {0: (0, 1), 1: (0, 2), 2: (0, 3)})
     198        sage: root_graph(graphs.OctahedralGraph())
     199        (Complete graph: Graph on 4 vertices, {0: (0, 1), 1: (0, 2), 2: (0, 3), 3: (1, 2), 4: (1, 3), 5: (2, 3)})
     200        sage: root_graph(graphs.DiamondGraph())
     201        (Graph on 4 vertices, {0: (0, 3), 1: (0, 1), 2: (0, 2), 3: (1, 2)})
     202        sage: root_graph(graphs.WheelGraph(5))
     203        (Diamond Graph: Graph on 4 vertices, {0: (1, 2), 1: (0, 1), 2: (0, 2), 3: (2, 3), 4: (1, 3)})
     204    """
     205    from sage.graphs.digraph import DiGraph
     206
     207    if isinstance(g, DiGraph):
     208        raise ValueError("g cannot be a DiGraph !")
     209    if g.has_multiple_edges():
     210        raise ValueError("g cannot have multiple edges !")
     211    if not g.is_connected():
     212        raise ValueError("g is not connected !")
     213
     214    # Complete Graph ?
     215    if g.is_clique():
     216        from sage.graphs.generators.basic import CompleteBipartiteGraph
     217        return (CompleteBipartiteGraph(1,g.order()),
     218                {v : (0,1+i) for i,v in enumerate(g)})
     219
     220    # Diamond Graph ?
     221    elif g.order() == 4 and g.size() == 5:
     222        from sage.graphs.graph import Graph
     223        root = Graph([(0,1),(1,2),(2,0),(0,3)])
     224        return (root,
     225                g.is_isomorphic(root.line_graph(labels = False), certify = True)[1])
     226
     227    # Wheel on 5 vertices ?
     228    elif g.order() == 5 and g.size() == 8 and min(g.degree()) == 3:
     229        from sage.graphs.generators.basic import DiamondGraph
     230        root = DiamondGraph()
     231        return (root,
     232                g.is_isomorphic(root.line_graph(labels = False), certify = True)[1])
     233
     234    # Octahedron ?
     235    elif g.order() == 6 and g.size() == 12 and g.is_regular(k=4):
     236        from sage.graphs.generators.platonic_solids import OctahedralGraph
     237        if g.is_isomorphic(OctahedralGraph()):
     238            from sage.graphs.generators.basic import CompleteGraph
     239            root = CompleteGraph(4)
     240            return (root,
     241                    g.is_isomorphic(root.line_graph(labels = False), certify = True)[1])
     242
     243    # From now on we can assume (thanks to Beineke) that no edge belongs to two
     244    # even triangles at once.
     245
     246    error_message = ("It looks like there is a problem somewhere. You"
     247                     "found a bug here ! Please report it on sage-devel,"
     248                     "our google group !")
     249
     250    # Better to work on integers... Everything takes more time
     251    # otherwise.
     252    G = g.relabel(inplace = False)
     253
     254    # Dictionary of (pairs of) cliques, i.e. the two cliques
     255    # associated with each vertex.
     256    v_cliques = {v:[] for v in G}
     257
     258    # All the even triangles we meet
     259    even_triangles = []
     260
     261
     262    # Here is THE "problem" of this implementation. Listing all maximal cliques
     263    # takes an exponential time on general graphs (while it is obviously
     264    # polynomial on line graphs). The problem is that this implementation cannot
     265    # be used to *recognise* line graphs for as long as cliques_maximal returns
     266    # a list and does not ITERATE on the maximal cliques : if there are too many
     267    # cliques in the graph, this implementation will notice it and answer that
     268    # the graph is not a line graph. If, on the other hand, the first thing it
     269    # does is enumerate ALL maximal cliques, then there is no way to say early
     270    # that the graph is not a line graph.
     271    #
     272    # If this cliques_maximal thing is replaced by an iterator that does not
     273    # build the list of all cliques before returning them, then this method is a
     274    # good recognition algorithm.
     275
     276    for S in G.cliques_maximal():
     277
     278        # Triangles... even or odd ?
     279        if len(S) == 3:
     280
     281            # If a vertex of G has an odd number of neighbors among the vertices
     282            # of S, then the triangle is odd. We compute the list of such
     283            # vertices by taking the symmetric difference of the neighborhood of
     284            # our three vertices.
     285            #
     286            # Note that the elements of S do not appear in this set as they are
     287            # all seen exactly twice.
     288
     289            odd_neighbors = set(G.neighbors(S[0]))
     290            odd_neighbors.symmetric_difference_update(G.neighbors(S[1]))
     291            odd_neighbors.symmetric_difference_update(G.neighbors(S[2]))
     292
     293            # Even triangles
     294            if not odd_neighbors:
     295                even_triangles.append(tuple(S))
     296                continue
     297
     298            # We manage odd triangles the same way we manage other cliques ...
     299
     300        # We now associate the clique to all the vertices it contains.
     301        for v in S:
     302            if len(v_cliques[v]) == 2:
     303                raise ValueError("This graph is not a line graph !")
     304            v_cliques[v].append(tuple(S))
     305
     306        if verbose:
     307            print "Added clique", S
     308
     309    # Deal with even triangles
     310    for u,v,w in even_triangles:
     311
     312        # According to Beineke, we must go through all even triangles, and for
     313        # each triangle uvw consider its three pairs of adjacent verties uv, vw,
     314        # wu. For all pairs xy among those such that xy do not appear together
     315        # in any clique we have found so far, we add xy to the list of cliques
     316        # describing our covering.
     317
     318        for x,y in [(u,v), (v,w), (w,u)]:
     319
     320            # If edge xy does not appear in any of the cliques associated with y
     321            if all([not x in C for C in v_cliques[y]]):
     322                if len(v_cliques[y]) >= 2 or len(v_cliques[x]) >= 2:
     323                    raise ValueError("This graph is not a line graph !")
     324
     325                v_cliques[x].append((x,y))
     326                v_cliques[y].append((x,y))
     327
     328                if verbose:
     329                    print "Adding pair",(x,y),"appearing in the even triangle", (u,v,w)
     330
     331    # Deal with vertices contained in only one clique. All edges must be defined
     332    # by TWO endpoints, so we add a fake clique.
     333    for x, clique_list in v_cliques.iteritems():
     334        if len(clique_list) == 1:
     335            clique_list.append((x,))
     336
     337    # We now have all our cliques. Let's build the root graph to check that it
     338    # all fits !
     339    from sage.graphs.graph import Graph
     340    R = Graph()
     341
     342    # Associates an integer to each clique
     343    relabel = {}
     344
     345    # Associates to each vertex of G its pair of coordinates in R
     346    vertex_to_map = {}
     347
     348    for v,L in v_cliques.iteritems():
     349
     350        # Add cliques to relabel dictionary
     351        for S in L:
     352            if not S in relabel:
     353                relabel[S] = len(relabel)
     354
     355        # The coordinates of edge v
     356        vertex_to_map[v] = relabel[L[0]], relabel[L[1]]
     357
     358    if verbose:
     359        print "Final associations :"
     360        for v,L in v_cliques.iteritems():
     361            print v,L
     362
     363    # We now build R
     364    R.add_edges(vertex_to_map.values())
     365
     366    # Even if whatever is written above is complete nonsense, here we
     367    # make sure that we do not return gibberish. Is the line graph of
     368    # R isomorphic to the input ? If so, we return R, and the
     369    # isomorphism. Else, we panic and scream.
     370    #
     371    # It's actually "just to make sure twice". This can be removed later if it
     372    # turns out to be too costly.
     373    is_isom, isom = g.is_isomorphic(R.line_graph(labels = False), certify = True)
     374
     375    if not is_isom:
     376        raise Exception(error_message)
     377
     378    return R, isom
     379
     380
     381