Morphisms

This module gathers methods related to homeomorphims, homomorphisms, isomorphisms, etc. in (di)graphs.

This module contains the following methods

is_homeomorphic()

Check whether G and H are homeomorphic.

reduced_homeomorphic_graph()

Return the smallest graph homeomorphic to G.

has_homomorphism_to()

Check whether there is a homomorphism between two graphs.

Todo

  • Move methods related to graph automorphisms to this module

  • Move methods related to graph isomorphisms to this module

Methods

sage.graphs.morphisms.has_homomorphism_to(G, 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 of order \(k\).

For more information, see the Wikipedia article Graph_homomorphism.

INPUT:

  • G – the graph to map

  • H – the graph to which G should be sent

  • core – boolean (default: False); whether to minimize the size of the mapping’s image (see examples 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().

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
[Python]
>>> 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

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

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

The chromatic number of a graph is the order of the smallest clique to which it has an homomorphism:

sage: # needs sage.numerical.mip
sage: g = graphs.CycleGraph(9)
sage: g.chromatic_number()
3
sage: g.has_homomorphism_to(graphs.CompleteGraph(3)) is not False
True
sage: g.has_homomorphism_to(graphs.CompleteGraph(2)) is not False
False
sage: K6 = graphs.CompleteGraph(6)
sage: g.has_homomorphism_to(K6) is not False
True
sage: mapping = g.has_homomorphism_to(K6, core=True)
sage: print(f"The size of the core is {len(set(mapping.values()))}")
The size of the core is 3
[Python]
>>> from sage.all import *
>>> # needs sage.numerical.mip
>>> g = graphs.CycleGraph(Integer(9))
>>> g.chromatic_number()
3
>>> g.has_homomorphism_to(graphs.CompleteGraph(Integer(3))) is not False
True
>>> g.has_homomorphism_to(graphs.CompleteGraph(Integer(2))) is not False
False
>>> K6 = graphs.CompleteGraph(Integer(6))
>>> g.has_homomorphism_to(K6) is not False
True
>>> mapping = g.has_homomorphism_to(K6, core=True)
>>> print(f"The size of the core is {len(set(mapping.values()))}")
The size of the core is 3

A circuit of order \(n\) admits a homomorphism to smaller circuit of order \(p \leq n\) if \(p\) is a divisor of \(n\):

sage: g = digraphs.Circuit(12)
sage: [i for i in range(2, g.order() + 1)                                       # needs sage.numerical.mip
....:  if g.has_homomorphism_to(digraphs.Circuit(i)) is not False]
[2, 3, 4, 6, 12]
>>> from sage.all import *
>>> g = digraphs.Circuit(Integer(12))
>>> [i for i in range(Integer(2), g.order() + Integer(1))                                       # needs sage.numerical.mip
...  if g.has_homomorphism_to(digraphs.Circuit(i)) is not False]
[2, 3, 4, 6, 12]
sage.graphs.morphisms.is_homeomorphic(G, H)[source]

Check whether G and H are homeomorphic.

Two graphs \(G\) and \(H\) are homeomorphic if there is an isomorphism from some subdivision of \(G\) to some subdivision of \(H\). To check whether \(G\) and \(H\) are homeomorphic, it suffices to check whether their reduced homeomorphic (di)graphs are isomorphic. For more details, see the Wikipedia article Homeomorphism_(graph_theory).

INPUT:

  • G, H – two (di)graphs

EXAMPLES:

sage: G = graphs.RandomGNP(10, .2)
sage: H = G.copy()
sage: for e in list(G.edges()):
....:     G.subdivide_edge(e, randint(0, 5))
....:     H.subdivide_edge(e, randint(0, 5))
sage: G.is_homeomorphic(H)
True
sage: G = graphs.RandomGNP(10, .2)
sage: G.allow_multiple_edges(True)
sage: G.add_edges(G.edges())
sage: H = G.copy()
sage: for e in list(G.edges()):
....:     G.subdivide_edge(e, randint(0, 5))
....:     H.subdivide_edge(e, randint(0, 5))
sage: G.is_homeomorphic(H)
True

sage: G = digraphs.RandomDirectedGNP(10, .2)
sage: H = G.copy()
sage: for e in list(G.edges()):
....:     G.subdivide_edge(e, randint(0, 5))
....:     H.subdivide_edge(e, randint(0, 5))
sage: G.is_homeomorphic(H)
True
sage: G = digraphs.RandomDirectedGNP(10, .2)
sage: G.allow_multiple_edges(True)
sage: G.add_edges(G.edges())
sage: H = G.copy()
sage: for e in list(G.edges()):
....:     G.subdivide_edge(e, randint(0, 5))
....:     H.subdivide_edge(e, randint(0, 5))
sage: G.is_homeomorphic(H)
True

sage: G = digraphs.RandomDirectedGNP(10, .2)
sage: G.allow_loops(True)
sage: G.add_edges((u, u) for u in G if randint(0, 1))
sage: G.allow_multiple_edges(True)
sage: G.add_edges(G.edges())
sage: H = G.copy()
sage: for e in list(G.edges()):
....:     G.subdivide_edge(e, randint(0, 5))
....:     H.subdivide_edge(e, randint(0, 5))
sage: G.is_homeomorphic(H)
True
>>> from sage.all import *
>>> G = graphs.RandomGNP(Integer(10), RealNumber('.2'))
>>> H = G.copy()
>>> for e in list(G.edges()):
...     G.subdivide_edge(e, randint(Integer(0), Integer(5)))
...     H.subdivide_edge(e, randint(Integer(0), Integer(5)))
>>> G.is_homeomorphic(H)
True
>>> G = graphs.RandomGNP(Integer(10), RealNumber('.2'))
>>> G.allow_multiple_edges(True)
>>> G.add_edges(G.edges())
>>> H = G.copy()
>>> for e in list(G.edges()):
...     G.subdivide_edge(e, randint(Integer(0), Integer(5)))
...     H.subdivide_edge(e, randint(Integer(0), Integer(5)))
>>> G.is_homeomorphic(H)
True

>>> G = digraphs.RandomDirectedGNP(Integer(10), RealNumber('.2'))
>>> H = G.copy()
>>> for e in list(G.edges()):
...     G.subdivide_edge(e, randint(Integer(0), Integer(5)))
...     H.subdivide_edge(e, randint(Integer(0), Integer(5)))
>>> G.is_homeomorphic(H)
True
>>> G = digraphs.RandomDirectedGNP(Integer(10), RealNumber('.2'))
>>> G.allow_multiple_edges(True)
>>> G.add_edges(G.edges())
>>> H = G.copy()
>>> for e in list(G.edges()):
...     G.subdivide_edge(e, randint(Integer(0), Integer(5)))
...     H.subdivide_edge(e, randint(Integer(0), Integer(5)))
>>> G.is_homeomorphic(H)
True

>>> G = digraphs.RandomDirectedGNP(Integer(10), RealNumber('.2'))
>>> G.allow_loops(True)
>>> G.add_edges((u, u) for u in G if randint(Integer(0), Integer(1)))
>>> G.allow_multiple_edges(True)
>>> G.add_edges(G.edges())
>>> H = G.copy()
>>> for e in list(G.edges()):
...     G.subdivide_edge(e, randint(Integer(0), Integer(5)))
...     H.subdivide_edge(e, randint(Integer(0), Integer(5)))
>>> G.is_homeomorphic(H)
True
sage.graphs.morphisms.reduced_homeomorphic_graph(G, allow_multiple_edges=False, allow_loops=False, return_steps=False, immutable=None)[source]

Return the smallest graph homeomorphic to \(G\).

Two graphs \(G\) and \(H\) are homeomorphic if there is an isomorphism from some subdivision of \(G\) to some subdivision of \(H\). For more details, see the Wikipedia article Homeomorphism_(graph_theory).

By default (i.e., when allow_multiple_edges == False and allow_loops == False), given a graph \(G\), a vertex \(u\) of degree two and its neighbors \(x\) and \(y\), with \(x \neq y\), this methods replaces the path \((x, u, y)\) with the edge \((x, y)\) unless the graph already has edge \((x, y)\). This process is repeated for each vertex of degree two. The resulting graph \(H\) is the smallest graph that is homeomorphic to \(G\).

When allow_multiple_edges == True and allow_loops == False, this method always replaces the path \((x, u, y)\) with a new edge \((x, y)\). Hence, the resulting graph may have several edges between \(x\) and \(y\). This operation is performed only if \(x \neq y\).

When allow_loops == True, this method also assumes that allow_multiple_edges == True. If a vertex \(u\) of degree two is connected by two edges to a vertex \(x\), this method replaces the two edges by a loop edge on \(x\).

For digraphs, the method considers the vertices with in and out degree one.

INPUT:

  • G – a graph or a digraph

  • allow_multiple_edges – boolean (default: False); whether to allow the creation of new multiple edges. This parameter is considered True when allow_loops is True.

  • allow_loops – boolean (default: False); whether to allow the creation of new loops

  • return_steps – boolean (default: False); whether to return the steps of the reduction as a list of triples \((x, u, y)\) indicating that path \((x, u, y)\) has been replaced by edge \((x, y)\). The original graph can be reconstructed by using this list in reverse order.

  • immutable – boolean (default: None); whether to create a mutable/immutable (di)graph. immutable=None (default) means that the (di)graph and its reduced (di)graph will behave the same way.

OUTPUT: When return_steps is False, this method returns the reduced graph. When return_steps is True, this method returns both the reduced graph and the ordered list of reduction operations. Each reduction operation is a triple \((x, u, y)\) indicating that the path \((x, u, y)\), with \(u\) of degree two (or with in and out degree one for digraphs), has been replaced by edge \((x, y)\).

EXAMPLES:

Reduction of a Cycle Graph:

sage: G = graphs.CycleGraph(4)
sage: G.reduced_homeomorphic_graph()
Graph on 3 vertices
sage: G.reduced_homeomorphic_graph(allow_multiple_edges=True)
Multi-graph on 2 vertices
sage: G.reduced_homeomorphic_graph(allow_loops=True)
Looped multi-graph on 1 vertex
>>> from sage.all import *
>>> G = graphs.CycleGraph(Integer(4))
>>> G.reduced_homeomorphic_graph()
Graph on 3 vertices
>>> G.reduced_homeomorphic_graph(allow_multiple_edges=True)
Multi-graph on 2 vertices
>>> G.reduced_homeomorphic_graph(allow_loops=True)
Looped multi-graph on 1 vertex

Reduction of a Circuit:

sage: G = digraphs.Circuit(4)
sage: G.reduced_homeomorphic_graph()
Digraph on 2 vertices
sage: G.reduced_homeomorphic_graph(allow_multiple_edges=True)
Multi-digraph on 2 vertices
sage: G.reduced_homeomorphic_graph(allow_loops=True)
Looped multi-digraph on 1 vertex
[Python]
>>> from sage.all import *
>>> G = digraphs.Circuit(Integer(4))
>>> G.reduced_homeomorphic_graph()
Digraph on 2 vertices
>>> G.reduced_homeomorphic_graph(allow_multiple_edges=True)
Multi-digraph on 2 vertices
>>> G.reduced_homeomorphic_graph(allow_loops=True)
Looped multi-digraph on 1 vertex

Check that the construction is reversible:

sage: def revert_steps(g, steps):
....:     h = g.copy(immutable=False)
....:     for P in reversed(steps):
....:         h.add_path(P)
....:         h.delete_edge(P[0], P[2])
....:     return h

sage: G = graphs.WindmillGraph(3, 5)
sage: G.order(), G.size()
(11, 15)
sage: H, steps = G.reduced_homeomorphic_graph(return_steps=True)
sage: H.order(), H.size()
(11, 15)
sage: G.is_isomorphic(revert_steps(H, steps))
True
sage: H, steps = G.reduced_homeomorphic_graph(allow_multiple_edges=True, return_steps=True)
sage: H.order(), H.size()
(6, 10)
sage: G.is_isomorphic(revert_steps(H, steps))
True
sage: H, steps = G.reduced_homeomorphic_graph(allow_loops=True, return_steps=True)
sage: H.order(), H.size()
(1, 5)
sage: len(H.loop_edges())
5
sage: G.is_isomorphic(revert_steps(H, steps))
True
>>> from sage.all import *
>>> def revert_steps(g, steps):
...     h = g.copy(immutable=False)
...     for P in reversed(steps):
...         h.add_path(P)
...         h.delete_edge(P[Integer(0)], P[Integer(2)])
...     return h

>>> G = graphs.WindmillGraph(Integer(3), Integer(5))
>>> G.order(), G.size()
(11, 15)
>>> H, steps = G.reduced_homeomorphic_graph(return_steps=True)
>>> H.order(), H.size()
(11, 15)
>>> G.is_isomorphic(revert_steps(H, steps))
True
>>> H, steps = G.reduced_homeomorphic_graph(allow_multiple_edges=True, return_steps=True)
>>> H.order(), H.size()
(6, 10)
>>> G.is_isomorphic(revert_steps(H, steps))
True
>>> H, steps = G.reduced_homeomorphic_graph(allow_loops=True, return_steps=True)
>>> H.order(), H.size()
(1, 5)
>>> len(H.loop_edges())
5
>>> G.is_isomorphic(revert_steps(H, steps))
True

Random digraph:

sage: G = digraphs.RandomDirectedGNP(20, 0.05, loops=True)
sage: H, steps = G.reduced_homeomorphic_graph(return_steps=True)
sage: G.is_isomorphic(revert_steps(H, steps))
True
sage: H, steps = G.reduced_homeomorphic_graph(allow_multiple_edges=True, return_steps=True)
sage: G.is_isomorphic(revert_steps(H, steps))
True
sage: H, steps = G.reduced_homeomorphic_graph(allow_loops=True, return_steps=True)
sage: G.is_isomorphic(revert_steps(H, steps))
True
[Python]
>>> from sage.all import *
>>> G = digraphs.RandomDirectedGNP(Integer(20), RealNumber('0.05'), loops=True)
>>> H, steps = G.reduced_homeomorphic_graph(return_steps=True)
>>> G.is_isomorphic(revert_steps(H, steps))
True
>>> H, steps = G.reduced_homeomorphic_graph(allow_multiple_edges=True, return_steps=True)
>>> G.is_isomorphic(revert_steps(H, steps))
True
>>> H, steps = G.reduced_homeomorphic_graph(allow_loops=True, return_steps=True)
>>> G.is_isomorphic(revert_steps(H, steps))
True