Morphisms¶
This module gathers methods related to homeomorphims, homomorphisms, isomorphisms, etc. in (di)graphs.
This module contains the following methods
Check whether |
|
Return the smallest graph homeomorphic 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 mapH– the graph to whichGshould be sentcore– boolean (default:False); whether to minimize the size of the mapping’s image (see examples below). This is set toFalseby default.solver– string (default:None); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set toNone, the default one is used. For more information on MILP solvers and which default solver is used, see the methodsolveof the classMixedIntegerLinearProgram.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; seeMixedIntegerLinearProgram.get_values().
OUTPUT:
This method returns
Falsewhen 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
GandHare 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 == Falseandallow_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 == Trueandallow_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 thatallow_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 digraphallow_multiple_edges– boolean (default:False); whether to allow the creation of new multiple edges. This parameter is consideredTruewhenallow_loopsisTrue.allow_loops– boolean (default:False); whether to allow the creation of new loopsreturn_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_stepsisFalse, this method returns the reduced graph. Whenreturn_stepsisTrue, 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