Skip to content

Commit 0b86b7f

Browse files
author
Release Manager
committed
gh-38742: Introduced the class `MatchingCoveredGraph` <!-- ^ 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 #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 #12345". --> Fixes #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). <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> cc: @dcoudert. URL: #38742 Reported by: Janmenjaya Panda Reviewer(s): David Coudert, Janmenjaya Panda
2 parents 8f2f636 + c63a2bb commit 0b86b7f

File tree

6 files changed

+2133
-39
lines changed

6 files changed

+2133
-39
lines changed

src/doc/en/reference/graphs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Graph objects and methods
1414
sage/graphs/graph
1515
sage/graphs/digraph
1616
sage/graphs/bipartite_graph
17+
sage/graphs/matching_covered_graph
1718
sage/graphs/views
1819

1920
Constructors and databases

src/sage/graphs/all.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from sage.graphs.graph import Graph
1010
from sage.graphs.digraph import DiGraph
1111
from sage.graphs.bipartite_graph import BipartiteGraph
12+
from sage.graphs.matching_covered_graph import MatchingCoveredGraph
1213
import sage.graphs.weakly_chordal
1314
import sage.graphs.lovasz_theta
1415
import sage.graphs.partial_cube

src/sage/graphs/generic_graph.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14285,7 +14285,7 @@ def _subgraph_by_adding(self, vertices=None, edges=None, edge_property=None, imm
1428514285
or (v, u) in edges_to_keep_unlabeled)):
1428614286
edges_to_keep.append((u, v, l))
1428714287
else:
14288-
s_vertices = set(vertices)
14288+
s_vertices = set(G.vertices()) if vertices is None else set(vertices)
1428914289
edges_to_keep = [e for e in self.edges(vertices=vertices, sort=False, sort_vertices=False)
1429014290
if e[0] in s_vertices and e[1] in s_vertices]
1429114291

src/sage/graphs/matching.py

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
def has_perfect_matching(G, algorithm='Edmonds', solver=None, verbose=0,
5656
*, integrality_tolerance=1e-3):
5757
r"""
58-
Return whether the graph has a perfect matching
58+
Return whether the graph has a perfect matching.
5959
6060
INPUT:
6161
@@ -162,7 +162,7 @@ def has_perfect_matching(G, algorithm='Edmonds', solver=None, verbose=0,
162162
def is_bicritical(G, matching=None, algorithm='Edmonds', coNP_certificate=False,
163163
solver=None, verbose=0, *, integrality_tolerance=0.001):
164164
r"""
165-
Check if the graph is bicritical
165+
Check if the graph is bicritical.
166166
167167
A nontrivial graph `G` is *bicritical* if `G - u - v` has a perfect
168168
matching for any two distinct vertices `u` and `v` of `G`. Bicritical
@@ -270,12 +270,9 @@ def is_bicritical(G, matching=None, algorithm='Edmonds', coNP_certificate=False,
270270
271271
A graph (of order more than two) with more that one component is not bicritical::
272272
273-
sage: cycle1 = graphs.CycleGraph(4)
274-
sage: cycle2 = graphs.CycleGraph(6)
275-
sage: cycle2.relabel(lambda v: v + 4)
276-
sage: G = Graph()
277-
sage: G.add_edges(cycle1.edges() + cycle2.edges())
278-
sage: len(G.connected_components(sort=False))
273+
sage: G = graphs.CycleGraph(4)
274+
sage: G += graphs.CycleGraph(6)
275+
sage: G.connected_components_number()
279276
2
280277
sage: G.is_bicritical()
281278
False
@@ -449,40 +446,38 @@ def is_bicritical(G, matching=None, algorithm='Edmonds', coNP_certificate=False,
449446
return (False, set(list(A)[:2]))
450447
return (False, set(list(B)[:2]))
451448

452-
# A graph (without a self-loop) is bicritical if and only if the underlying
453-
# simple graph is bicritical
454-
G_simple = G.to_simple()
455-
456449
from sage.graphs.graph import Graph
457450
if matching:
458451
# The input matching must be a valid perfect matching of the graph
459452
M = Graph(matching)
460453
if any(d != 1 for d in M.degree()):
461454
raise ValueError("the input is not a matching")
462-
if any(not G_simple.has_edge(edge) for edge in M.edge_iterator()):
455+
456+
if any(not G.has_edge(edge) for edge in M.edge_iterator()):
463457
raise ValueError("the input is not a matching of the graph")
464-
if (G_simple.order() != M.order()) or (G_simple.order() != 2*M.size()):
458+
459+
if (G.order() != M.order()) or (G.order() != 2*M.size()):
465460
raise ValueError("the input is not a perfect matching of the graph")
466461
else:
467462
# A maximum matching of the graph is computed
468-
M = Graph(G_simple.matching(algorithm=algorithm, solver=solver, verbose=verbose,
463+
M = Graph(G.matching(algorithm=algorithm, solver=solver, verbose=verbose,
469464
integrality_tolerance=integrality_tolerance))
470465

471466
# It must be a perfect matching
472-
if G_simple.order() != M.order():
467+
if G.order() != M.order():
473468
u, v = next(M.edge_iterator(labels=False))
474469
return (False, set([u, v])) if coNP_certificate else False
475470

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

480-
for u in G_simple:
475+
for u in G:
481476
v = next(M.neighbor_iterator(u))
482477

483-
even = M_alternating_even_mark(G_simple, u, M)
478+
even = M_alternating_even_mark(G, u, M)
484479

485-
for w in G_simple:
480+
for w in G:
486481
if w != v and w not in even:
487482
return (False, set([v, w])) if coNP_certificate else False
488483

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

983-
# A graph (without a self-loop) is matching covered if and only if the
984-
# underlying simple graph is matching covered
985-
G_simple = G.to_simple()
986-
987978
from sage.graphs.graph import Graph
988979
if matching:
989980
# The input matching must be a valid perfect matching of the graph
990981
M = Graph(matching)
982+
991983
if any(d != 1 for d in M.degree()):
992984
raise ValueError("the input is not a matching")
993-
if any(not G_simple.has_edge(edge) for edge in M.edge_iterator()):
985+
986+
if any(not G.has_edge(edge) for edge in M.edge_iterator()):
994987
raise ValueError("the input is not a matching of the graph")
995-
if (G_simple.order() != M.order()) or (G_simple.order() != 2*M.size()):
988+
989+
if (G.order() != M.order()) or (G.order() != 2*M.size()):
996990
raise ValueError("the input is not a perfect matching of the graph")
997991
else:
998992
# A maximum matching of the graph is computed
999-
M = Graph(G_simple.matching(algorithm=algorithm, solver=solver, verbose=verbose,
993+
M = Graph(G.matching(algorithm=algorithm, solver=solver, verbose=verbose,
1000994
integrality_tolerance=integrality_tolerance))
1001995

1002996
# It must be a perfect matching
1003-
if G_simple.order() != M.order():
997+
if G.order() != M.order():
1004998
return (False, next(M.edge_iterator())) if coNP_certificate else False
1005999

10061000
# Biparite graph:
@@ -1011,17 +1005,17 @@ def is_matching_covered(G, matching=None, algorithm='Edmonds', coNP_certificate=
10111005
# if it is in M or otherwise direct it from B to A. The graph G is
10121006
# matching covered if and only if D is strongly connected.
10131007

1014-
if G_simple.is_bipartite():
1015-
A, _ = G_simple.bipartite_sets()
1008+
if G.is_bipartite():
1009+
A, _ = G.bipartite_sets()
10161010
color = dict()
10171011

1018-
for u in G_simple:
1012+
for u in G:
10191013
color[u] = 0 if u in A else 1
10201014

10211015
from sage.graphs.digraph import DiGraph
10221016
H = DiGraph()
10231017

1024-
for u, v in G_simple.edge_iterator(labels=False):
1018+
for u, v in G.edge_iterator(labels=False):
10251019
if color[u]:
10261020
u, v = v, u
10271021

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

1078-
for u in G_simple:
1072+
for u in G:
10791073
v = next(M.neighbor_iterator(u))
10801074

1081-
even = M_alternating_even_mark(G_simple, u, M)
1075+
even = M_alternating_even_mark(G, u, M)
10821076

1083-
for w in G_simple.neighbor_iterator(v):
1077+
for w in G.neighbor_iterator(v):
10841078
if w != u and w not in even:
10851079
return (False, (v, w)) if coNP_certificate else False
10861080

@@ -1092,7 +1086,7 @@ def matching(G, value_only=False, algorithm='Edmonds',
10921086
*, integrality_tolerance=1e-3):
10931087
r"""
10941088
Return a maximum weighted matching of the graph represented by the list
1095-
of its edges
1089+
of its edges.
10961090
10971091
For more information, see the :wikipedia:`Matching_(graph_theory)`.
10981092
@@ -1291,7 +1285,7 @@ def weight(x):
12911285

12921286
def perfect_matchings(G, labels=False):
12931287
r"""
1294-
Return an iterator over all perfect matchings of the graph
1288+
Return an iterator over all perfect matchings of the graph.
12951289
12961290
ALGORITHM:
12971291
@@ -1404,7 +1398,7 @@ def rec(G):
14041398
def M_alternating_even_mark(G, vertex, matching):
14051399
r"""
14061400
Return the vertices reachable from ``vertex`` via an even alternating path
1407-
starting with a non-matching edge
1401+
starting with a non-matching edge.
14081402
14091403
This method implements the algorithm proposed in [LR2004]_. Note that
14101404
the complexity of the algorithm is linear in number of edges.
@@ -1570,7 +1564,8 @@ def M_alternating_even_mark(G, vertex, matching):
15701564
M = Graph(matching)
15711565
if any(d != 1 for d in M.degree()):
15721566
raise ValueError("the input is not a matching")
1573-
if any(not G_simple.has_edge(edge) for edge in M.edge_iterator()):
1567+
1568+
if any(not G.has_edge(edge) for edge in M.edge_iterator()):
15741569
raise ValueError("the input is not a matching of the graph")
15751570

15761571
# Build an M-alternating tree T rooted at vertex
@@ -1605,8 +1600,10 @@ def M_alternating_even_mark(G, vertex, matching):
16051600
while ancestor_x[-1] != ancestor_y[-1]:
16061601
if rank[ancestor_x[-1]] > rank[ancestor_y[-1]]:
16071602
ancestor_x.append(predecessor[ancestor_x[-1]])
1603+
16081604
elif rank[ancestor_x[-1]] < rank[ancestor_y[-1]]:
16091605
ancestor_y.append(predecessor[ancestor_y[-1]])
1606+
16101607
else:
16111608
ancestor_x.append(predecessor[ancestor_x[-1]])
16121609
ancestor_y.append(predecessor[ancestor_y[-1]])
@@ -1616,6 +1613,7 @@ def M_alternating_even_mark(G, vertex, matching):
16161613
# Set t as pred of all vertices of the chains and add
16171614
# vertices marked odd to the queue
16181615
next_rank_to_lcs_rank = rank[lcs] + 1
1616+
16191617
for a in itertools.chain(ancestor_x, ancestor_y):
16201618
predecessor[a] = lcs
16211619
rank[a] = next_rank_to_lcs_rank

0 commit comments

Comments
 (0)