Skip to content

Commit

Permalink
sagemathgh-38742: Introduced the class MatchingCoveredGraph
Browse files Browse the repository at this point in the history
    
<!-- ^ Please provide a concise and informative title. -->
The objective of this issue is to introduce a new class
`MatchingCoveredGraph` through a new file
`src/sage/graphs/matching_covered_graph.py` in order to address the
decompositions, generation methods and related concepts in the theory of
Matching Covered Graphs.

<!-- ^ Don't put issue numbers in the title, do this in the PR
description below. -->
<!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method
to calculate 1 + 2". -->
<!-- v Describe your changes below in detail. -->
This PR introduces a new class pertaining to matching, namely
`MatchingCoveredGraph` and aims to list out all functions fundamentally
related to matching covered graph in the file
`src/sage/graphs/matching_covered_graph.py`. The initialization and some
basic class methods in this context, that shall be addressed through
this PR, are described below:

- [x] `__init__()`: Create a matching covered graph, that is a connected
nontrivial graph wherein each edge participates in some perfect
matching.
- [x] `__repr__()`: Return a short string representation of the matching
covered graph.
- [x] `_subgraph_by_adding()`: Return the matching covered subgraph
containing the given vertices and edges.
- [x] `_upgrade_from_graph()`: Upgrade the given graph to a matching
covered graph if eligible.
- [x] `add_edge()`: Add an edge from vertex ``u`` to vertex ``v``.
- [x] `add_edges()`: Add edges from an iterable container.
- [x] `add_vertex()`: Add a vertex to the (matching covered) graph.
- [x] `add_vertices()`: Add vertices to the (matching covered) graph
from an iterable container of vertices.
- [x] `allow_loops()`: Change whether loops are allowed in (matching
covered) graphs.
- [x] `allows_loops()`: Return whether loops are permitted in (matching
covered) graph.
- [x] `delete_vertex()`: Delete a vertex, removing all incident edges.0
- [x] `delete_vertices()`: Delete specified vertices form ``self``.
- [x] `get_matching()`: Return a :class:`~EdgesView` of
``self._matching`` (a perfect matching of the (matching covered) graph
computed at the initialization).
- [x] `has_perfect_matching()`: Return whether the graph has a perfect
matching.
- [x] `update_matching()`: Update the perfect matching captured in
``self._matching``.

<!-- v Why is this change required? What problem does it solve? -->
This PR shall establish a foundation to address the methods related to
matching covered graphs.
<!-- v If this PR resolves an open issue, please link to it here. For
example, "Fixes sagemath#12345". -->
Fixes sagemath#38216.
Note that this issue fixes a small part of the above-mentioned issue.


### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [x] I have updated the documentation and checked the documentation
preview.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on. For example,
-->
Nothing as of now (up to my knowledge).
<!-- - sagemath#12345: short description why this is a dependency -->
<!-- - sagemath#34567: ... -->

cc: @dcoudert.
    
URL: sagemath#38742
Reported by: Janmenjaya Panda
Reviewer(s): David Coudert, Janmenjaya Panda
  • Loading branch information
Release Manager committed Nov 14, 2024
2 parents 8f2f636 + c63a2bb commit 0b86b7f
Show file tree
Hide file tree
Showing 6 changed files with 2,133 additions and 39 deletions.
1 change: 1 addition & 0 deletions src/doc/en/reference/graphs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Graph objects and methods
sage/graphs/graph
sage/graphs/digraph
sage/graphs/bipartite_graph
sage/graphs/matching_covered_graph
sage/graphs/views

Constructors and databases
Expand Down
1 change: 1 addition & 0 deletions src/sage/graphs/all.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from sage.graphs.graph import Graph
from sage.graphs.digraph import DiGraph
from sage.graphs.bipartite_graph import BipartiteGraph
from sage.graphs.matching_covered_graph import MatchingCoveredGraph
import sage.graphs.weakly_chordal
import sage.graphs.lovasz_theta
import sage.graphs.partial_cube
Expand Down
2 changes: 1 addition & 1 deletion src/sage/graphs/generic_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -14285,7 +14285,7 @@ def _subgraph_by_adding(self, vertices=None, edges=None, edge_property=None, imm
or (v, u) in edges_to_keep_unlabeled)):
edges_to_keep.append((u, v, l))
else:
s_vertices = set(vertices)
s_vertices = set(G.vertices()) if vertices is None else set(vertices)
edges_to_keep = [e for e in self.edges(vertices=vertices, sort=False, sort_vertices=False)
if e[0] in s_vertices and e[1] in s_vertices]

Expand Down
74 changes: 36 additions & 38 deletions src/sage/graphs/matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
def has_perfect_matching(G, algorithm='Edmonds', solver=None, verbose=0,
*, integrality_tolerance=1e-3):
r"""
Return whether the graph has a perfect matching
Return whether the graph has a perfect matching.
INPUT:
Expand Down Expand Up @@ -162,7 +162,7 @@ def has_perfect_matching(G, algorithm='Edmonds', solver=None, verbose=0,
def is_bicritical(G, matching=None, algorithm='Edmonds', coNP_certificate=False,
solver=None, verbose=0, *, integrality_tolerance=0.001):
r"""
Check if the graph is bicritical
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
Expand Down Expand Up @@ -270,12 +270,9 @@ def is_bicritical(G, matching=None, algorithm='Edmonds', coNP_certificate=False,
A graph (of order more than two) with more that one component is not bicritical::
sage: cycle1 = graphs.CycleGraph(4)
sage: cycle2 = graphs.CycleGraph(6)
sage: cycle2.relabel(lambda v: v + 4)
sage: G = Graph()
sage: G.add_edges(cycle1.edges() + cycle2.edges())
sage: len(G.connected_components(sort=False))
sage: G = graphs.CycleGraph(4)
sage: G += graphs.CycleGraph(6)
sage: G.connected_components_number()
2
sage: G.is_bicritical()
False
Expand Down Expand Up @@ -449,40 +446,38 @@ def is_bicritical(G, matching=None, algorithm='Edmonds', coNP_certificate=False,
return (False, set(list(A)[:2]))
return (False, set(list(B)[:2]))

# A graph (without a self-loop) is bicritical if and only if the underlying
# simple graph is bicritical
G_simple = G.to_simple()

from sage.graphs.graph import Graph
if matching:
# The input matching must be a valid perfect matching of the graph
M = Graph(matching)
if any(d != 1 for d in M.degree()):
raise ValueError("the input is not a matching")
if any(not G_simple.has_edge(edge) for edge in M.edge_iterator()):

if any(not G.has_edge(edge) for edge in M.edge_iterator()):
raise ValueError("the input is not a matching of the graph")
if (G_simple.order() != M.order()) or (G_simple.order() != 2*M.size()):

if (G.order() != M.order()) or (G.order() != 2*M.size()):
raise ValueError("the input is not a perfect matching of the graph")
else:
# A maximum matching of the graph is computed
M = Graph(G_simple.matching(algorithm=algorithm, solver=solver, verbose=verbose,
M = Graph(G.matching(algorithm=algorithm, solver=solver, verbose=verbose,
integrality_tolerance=integrality_tolerance))

# It must be a perfect matching
if G_simple.order() != M.order():
if G.order() != M.order():
u, v = next(M.edge_iterator(labels=False))
return (False, set([u, v])) if coNP_certificate else False

# G is bicritical if and only if for each vertex u with its M-matched neighbor being v,
# every vertex of the graph distinct from v must be reachable from u through an even length
# M-alternating uv-path starting with an edge not in M and ending with an edge in M

for u in G_simple:
for u in G:
v = next(M.neighbor_iterator(u))

even = M_alternating_even_mark(G_simple, u, M)
even = M_alternating_even_mark(G, u, M)

for w in G_simple:
for w in G:
if w != v and w not in even:
return (False, set([v, w])) if coNP_certificate else False

Expand Down Expand Up @@ -980,27 +975,26 @@ def is_matching_covered(G, matching=None, algorithm='Edmonds', coNP_certificate=
if G.order() == 2:
return (True, None) if coNP_certificate else True

# A graph (without a self-loop) is matching covered if and only if the
# underlying simple graph is matching covered
G_simple = G.to_simple()

from sage.graphs.graph import Graph
if matching:
# The input matching must be a valid perfect matching of the graph
M = Graph(matching)

if any(d != 1 for d in M.degree()):
raise ValueError("the input is not a matching")
if any(not G_simple.has_edge(edge) for edge in M.edge_iterator()):

if any(not G.has_edge(edge) for edge in M.edge_iterator()):
raise ValueError("the input is not a matching of the graph")
if (G_simple.order() != M.order()) or (G_simple.order() != 2*M.size()):

if (G.order() != M.order()) or (G.order() != 2*M.size()):
raise ValueError("the input is not a perfect matching of the graph")
else:
# A maximum matching of the graph is computed
M = Graph(G_simple.matching(algorithm=algorithm, solver=solver, verbose=verbose,
M = Graph(G.matching(algorithm=algorithm, solver=solver, verbose=verbose,
integrality_tolerance=integrality_tolerance))

# It must be a perfect matching
if G_simple.order() != M.order():
if G.order() != M.order():
return (False, next(M.edge_iterator())) if coNP_certificate else False

# Biparite graph:
Expand All @@ -1011,17 +1005,17 @@ def is_matching_covered(G, matching=None, algorithm='Edmonds', coNP_certificate=
# 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.

if G_simple.is_bipartite():
A, _ = G_simple.bipartite_sets()
if G.is_bipartite():
A, _ = G.bipartite_sets()
color = dict()

for u in G_simple:
for u in G:
color[u] = 0 if u in A else 1

from sage.graphs.digraph import DiGraph
H = DiGraph()

for u, v in G_simple.edge_iterator(labels=False):
for u, v in G.edge_iterator(labels=False):
if color[u]:
u, v = v, u

Expand Down Expand Up @@ -1075,12 +1069,12 @@ def dfs(J, v, visited, orientation):
# an M-alternating odd length uv-path starting and ending with edges not
# in M.

for u in G_simple:
for u in G:
v = next(M.neighbor_iterator(u))

even = M_alternating_even_mark(G_simple, u, M)
even = M_alternating_even_mark(G, u, M)

for w in G_simple.neighbor_iterator(v):
for w in G.neighbor_iterator(v):
if w != u and w not in even:
return (False, (v, w)) if coNP_certificate else False

Expand All @@ -1092,7 +1086,7 @@ def matching(G, value_only=False, algorithm='Edmonds',
*, integrality_tolerance=1e-3):
r"""
Return a maximum weighted matching of the graph represented by the list
of its edges
of its edges.
For more information, see the :wikipedia:`Matching_(graph_theory)`.
Expand Down Expand Up @@ -1291,7 +1285,7 @@ def weight(x):

def perfect_matchings(G, labels=False):
r"""
Return an iterator over all perfect matchings of the graph
Return an iterator over all perfect matchings of the graph.
ALGORITHM:
Expand Down Expand Up @@ -1404,7 +1398,7 @@ def rec(G):
def M_alternating_even_mark(G, vertex, matching):
r"""
Return the vertices reachable from ``vertex`` via an even alternating path
starting with a non-matching edge
starting with a non-matching edge.
This method implements the algorithm proposed in [LR2004]_. Note that
the complexity of the algorithm is linear in number of edges.
Expand Down Expand Up @@ -1570,7 +1564,8 @@ def M_alternating_even_mark(G, vertex, matching):
M = Graph(matching)
if any(d != 1 for d in M.degree()):
raise ValueError("the input is not a matching")
if any(not G_simple.has_edge(edge) for edge in M.edge_iterator()):

if any(not G.has_edge(edge) for edge in M.edge_iterator()):
raise ValueError("the input is not a matching of the graph")

# Build an M-alternating tree T rooted at vertex
Expand Down Expand Up @@ -1605,8 +1600,10 @@ def M_alternating_even_mark(G, vertex, matching):
while ancestor_x[-1] != ancestor_y[-1]:
if rank[ancestor_x[-1]] > rank[ancestor_y[-1]]:
ancestor_x.append(predecessor[ancestor_x[-1]])

elif rank[ancestor_x[-1]] < rank[ancestor_y[-1]]:
ancestor_y.append(predecessor[ancestor_y[-1]])

else:
ancestor_x.append(predecessor[ancestor_x[-1]])
ancestor_y.append(predecessor[ancestor_y[-1]])
Expand All @@ -1616,6 +1613,7 @@ def M_alternating_even_mark(G, vertex, matching):
# Set t as pred of all vertices of the chains and add
# vertices marked odd to the queue
next_rank_to_lcs_rank = rank[lcs] + 1

for a in itertools.chain(ancestor_x, ancestor_y):
predecessor[a] = lcs
rank[a] = next_rank_to_lcs_rank
Expand Down
Loading

0 comments on commit 0b86b7f

Please sign in to comment.