|
33 | 33 | #***************************************************************************** |
34 | 34 | from __future__ import print_function |
35 | 35 | from __future__ import absolute_import |
| 36 | +from six import iteritems |
36 | 37 | from six.moves import range |
37 | 38 |
|
38 | 39 | from .graph import Graph |
| 40 | +from sage.rings.integer import Integer |
39 | 41 |
|
40 | 42 | class BipartiteGraph(Graph): |
41 | 43 | r""" |
@@ -1281,3 +1283,135 @@ def reduced_adjacency_matrix(self, sparse=True): |
1281 | 1283 | # now construct and return the matrix from the dictionary we created |
1282 | 1284 | from sage.matrix.constructor import matrix |
1283 | 1285 | return matrix(len(self.right), len(self.left), D, sparse=sparse) |
| 1286 | + |
| 1287 | + def matching(self, value_only=False, algorithm=None, |
| 1288 | + use_edge_labels=False, solver=None, verbose=0): |
| 1289 | + r""" |
| 1290 | + Return a maximum matching of the graph represented by the list of its |
| 1291 | + edges. |
| 1292 | +
|
| 1293 | + Given a graph `G` such that each edge `e` has a weight `w_e`, a maximum |
| 1294 | + matching is a subset `S` of the edges of `G` of maximum weight such that |
| 1295 | + no two edges of `S` are incident with each other. |
| 1296 | +
|
| 1297 | + INPUT: |
| 1298 | +
|
| 1299 | + - ``value_only`` -- boolean (default: ``False``); when set to ``True``, |
| 1300 | + only the cardinal (or the weight) of the matching is returned |
| 1301 | +
|
| 1302 | + - ``algorithm`` -- string (default: ``"Hopcroft-Karp"`` if |
| 1303 | + ``use_edge_labels==False``, otherwise ``"Edmonds"``) |
| 1304 | +
|
| 1305 | + - ``"Hopcroft-Karp"`` selects the default bipartite graph algorithm as |
| 1306 | + implemented in NetworkX |
| 1307 | +
|
| 1308 | + - ``"Eppstein"`` selects Eppstein's algorithm as implemented in |
| 1309 | + NetworkX |
| 1310 | +
|
| 1311 | + - ``"Edmonds"`` selects Edmonds' algorithm as implemented in NetworkX |
| 1312 | +
|
| 1313 | + - ``"LP"`` uses a Linear Program formulation of the matching problem |
| 1314 | +
|
| 1315 | + - ``use_edge_labels`` -- boolean (default: ``False``) |
| 1316 | +
|
| 1317 | + - when set to ``True``, computes a weighted matching where each edge |
| 1318 | + is weighted by its label (if an edge has no label, `1` is assumed); |
| 1319 | + only if ``algorithm`` is ``"Edmonds"``, ``"LP"`` |
| 1320 | +
|
| 1321 | + - when set to ``False``, each edge has weight `1` |
| 1322 | +
|
| 1323 | + - ``solver`` -- (default: ``None``) a specific Linear Program (LP) |
| 1324 | + solver to be used |
| 1325 | +
|
| 1326 | + - ``verbose`` -- integer (default: ``0``); sets the level of verbosity: |
| 1327 | + set to 0 by default, which means quiet |
| 1328 | +
|
| 1329 | + .. SEEALSO:: |
| 1330 | +
|
| 1331 | + - :wikipedia:`Matching_(graph_theory)` |
| 1332 | + - :meth:`~Graph.matching` |
| 1333 | +
|
| 1334 | + EXAMPLES: |
| 1335 | +
|
| 1336 | + Maximum matching in a cycle graph:: |
| 1337 | +
|
| 1338 | + sage: G = BipartiteGraph(graphs.CycleGraph(10)) |
| 1339 | + sage: G.matching() |
| 1340 | + [(0, 1, None), (2, 3, None), (4, 5, None), (6, 7, None), (8, 9, None)] |
| 1341 | +
|
| 1342 | + The size of a maximum matching in a complete bipartite graph using |
| 1343 | + Eppstein:: |
| 1344 | +
|
| 1345 | + sage: G = BipartiteGraph(graphs.CompleteBipartiteGraph(4,5)) |
| 1346 | + sage: G.matching(algorithm="Eppstein", value_only=True) |
| 1347 | + 4 |
| 1348 | +
|
| 1349 | + TESTS: |
| 1350 | +
|
| 1351 | + If ``algorithm`` is not set to one of the supported algorithms, an |
| 1352 | + exception is raised:: |
| 1353 | +
|
| 1354 | + sage: G = BipartiteGraph(graphs.CompleteBipartiteGraph(4,5)) |
| 1355 | + sage: G.matching(algorithm="somethingdifferent") |
| 1356 | + Traceback (most recent call last): |
| 1357 | + ... |
| 1358 | + ValueError: algorithm must be "Hopcroft-Karp", "Eppstein", "Edmonds" or "LP" |
| 1359 | +
|
| 1360 | + Maximum matching in a weighted bipartite graph:: |
| 1361 | +
|
| 1362 | + sage: G = graphs.CycleGraph(4) |
| 1363 | + sage: B = BipartiteGraph([(u,v,2) for u,v in G.edges(labels=0)]) |
| 1364 | + sage: B.matching(use_edge_labels=True) |
| 1365 | + [(0, 3, 2), (1, 2, 2)] |
| 1366 | + sage: B.matching(use_edge_labels=True, value_only=True) |
| 1367 | + 4 |
| 1368 | + sage: B.matching(use_edge_labels=True, value_only=True, algorithm='Edmonds') |
| 1369 | + 4 |
| 1370 | + sage: B.matching(use_edge_labels=True, value_only=True, algorithm='LP') |
| 1371 | + 4.0 |
| 1372 | + sage: B.matching(use_edge_labels=True, value_only=True, algorithm='Eppstein') |
| 1373 | + Traceback (most recent call last): |
| 1374 | + ... |
| 1375 | + ValueError: use_edge_labels can not be used with "Hopcroft-Karp" or "Eppstein" |
| 1376 | + sage: B.matching(use_edge_labels=True, value_only=True, algorithm='Hopcroft-Karp') |
| 1377 | + Traceback (most recent call last): |
| 1378 | + ... |
| 1379 | + ValueError: use_edge_labels can not be used with "Hopcroft-Karp" or "Eppstein" |
| 1380 | + sage: B.matching(use_edge_labels=False, value_only=True, algorithm='Hopcroft-Karp') |
| 1381 | + 2 |
| 1382 | + sage: B.matching(use_edge_labels=False, value_only=True, algorithm='Eppstein') |
| 1383 | + 2 |
| 1384 | + sage: B.matching(use_edge_labels=False, value_only=True, algorithm='Edmonds') |
| 1385 | + 2 |
| 1386 | + sage: B.matching(use_edge_labels=False, value_only=True, algorithm='LP') |
| 1387 | + 2 |
| 1388 | + """ |
| 1389 | + self._scream_if_not_simple() |
| 1390 | + |
| 1391 | + if algorithm is None: |
| 1392 | + algorithm = "Edmonds" if use_edge_labels else "Hopcroft-Karp" |
| 1393 | + |
| 1394 | + if algorithm == "Hopcroft-Karp" or algorithm == "Eppstein": |
| 1395 | + if use_edge_labels: |
| 1396 | + raise ValueError('use_edge_labels can not be used with ' + |
| 1397 | + '"Hopcroft-Karp" or "Eppstein"') |
| 1398 | + import networkx |
| 1399 | + #this is necessary to call the methods for bipartite matchings |
| 1400 | + g = self.networkx_graph() |
| 1401 | + if algorithm == "Hopcroft-Karp": |
| 1402 | + d = networkx.bipartite.hopcroft_karp_matching(g) |
| 1403 | + else: |
| 1404 | + d = networkx.bipartite.eppstein_matching(g) |
| 1405 | + if value_only: |
| 1406 | + return Integer(len(d) // 2) |
| 1407 | + else: |
| 1408 | + return [(u, v, self.edge_label(u, v)) |
| 1409 | + for u, v in iteritems(d) if u < v] |
| 1410 | + elif algorithm == "Edmonds" or algorithm == "LP": |
| 1411 | + return Graph.matching(self, value_only=value_only, |
| 1412 | + algorithm=algorithm, |
| 1413 | + use_edge_labels=use_edge_labels, |
| 1414 | + solver=solver, verbose=verbose) |
| 1415 | + else: |
| 1416 | + raise ValueError('algorithm must be "Hopcroft-Karp", ' + |
| 1417 | + '"Eppstein", "Edmonds" or "LP"') |
0 commit comments