test_matching.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. import math
  2. from itertools import permutations
  3. from pytest import raises
  4. import networkx as nx
  5. from networkx.algorithms.matching import matching_dict_to_set
  6. from networkx.utils import edges_equal
  7. class TestMaxWeightMatching:
  8. """Unit tests for the
  9. :func:`~networkx.algorithms.matching.max_weight_matching` function.
  10. """
  11. def test_trivial1(self):
  12. """Empty graph"""
  13. G = nx.Graph()
  14. assert nx.max_weight_matching(G) == set()
  15. assert nx.min_weight_matching(G) == set()
  16. def test_selfloop(self):
  17. G = nx.Graph()
  18. G.add_edge(0, 0, weight=100)
  19. assert nx.max_weight_matching(G) == set()
  20. assert nx.min_weight_matching(G) == set()
  21. def test_single_edge(self):
  22. G = nx.Graph()
  23. G.add_edge(0, 1)
  24. assert edges_equal(
  25. nx.max_weight_matching(G), matching_dict_to_set({0: 1, 1: 0})
  26. )
  27. assert edges_equal(
  28. nx.min_weight_matching(G), matching_dict_to_set({0: 1, 1: 0})
  29. )
  30. def test_two_path(self):
  31. G = nx.Graph()
  32. G.add_edge("one", "two", weight=10)
  33. G.add_edge("two", "three", weight=11)
  34. assert edges_equal(
  35. nx.max_weight_matching(G),
  36. matching_dict_to_set({"three": "two", "two": "three"}),
  37. )
  38. assert edges_equal(
  39. nx.min_weight_matching(G),
  40. matching_dict_to_set({"one": "two", "two": "one"}),
  41. )
  42. def test_path(self):
  43. G = nx.Graph()
  44. G.add_edge(1, 2, weight=5)
  45. G.add_edge(2, 3, weight=11)
  46. G.add_edge(3, 4, weight=5)
  47. assert edges_equal(
  48. nx.max_weight_matching(G), matching_dict_to_set({2: 3, 3: 2})
  49. )
  50. assert edges_equal(
  51. nx.max_weight_matching(G, 1), matching_dict_to_set({1: 2, 2: 1, 3: 4, 4: 3})
  52. )
  53. assert edges_equal(
  54. nx.min_weight_matching(G), matching_dict_to_set({1: 2, 3: 4})
  55. )
  56. assert edges_equal(
  57. nx.min_weight_matching(G, 1), matching_dict_to_set({1: 2, 3: 4})
  58. )
  59. def test_square(self):
  60. G = nx.Graph()
  61. G.add_edge(1, 4, weight=2)
  62. G.add_edge(2, 3, weight=2)
  63. G.add_edge(1, 2, weight=1)
  64. G.add_edge(3, 4, weight=4)
  65. assert edges_equal(
  66. nx.max_weight_matching(G), matching_dict_to_set({1: 2, 3: 4})
  67. )
  68. assert edges_equal(
  69. nx.min_weight_matching(G), matching_dict_to_set({1: 4, 2: 3})
  70. )
  71. def test_edge_attribute_name(self):
  72. G = nx.Graph()
  73. G.add_edge("one", "two", weight=10, abcd=11)
  74. G.add_edge("two", "three", weight=11, abcd=10)
  75. assert edges_equal(
  76. nx.max_weight_matching(G, weight="abcd"),
  77. matching_dict_to_set({"one": "two", "two": "one"}),
  78. )
  79. assert edges_equal(
  80. nx.min_weight_matching(G, weight="abcd"),
  81. matching_dict_to_set({"three": "two"}),
  82. )
  83. def test_floating_point_weights(self):
  84. G = nx.Graph()
  85. G.add_edge(1, 2, weight=math.pi)
  86. G.add_edge(2, 3, weight=math.exp(1))
  87. G.add_edge(1, 3, weight=3.0)
  88. G.add_edge(1, 4, weight=math.sqrt(2.0))
  89. assert edges_equal(
  90. nx.max_weight_matching(G), matching_dict_to_set({1: 4, 2: 3, 3: 2, 4: 1})
  91. )
  92. assert edges_equal(
  93. nx.min_weight_matching(G), matching_dict_to_set({1: 4, 2: 3, 3: 2, 4: 1})
  94. )
  95. def test_negative_weights(self):
  96. G = nx.Graph()
  97. G.add_edge(1, 2, weight=2)
  98. G.add_edge(1, 3, weight=-2)
  99. G.add_edge(2, 3, weight=1)
  100. G.add_edge(2, 4, weight=-1)
  101. G.add_edge(3, 4, weight=-6)
  102. assert edges_equal(
  103. nx.max_weight_matching(G), matching_dict_to_set({1: 2, 2: 1})
  104. )
  105. assert edges_equal(
  106. nx.max_weight_matching(G, maxcardinality=True),
  107. matching_dict_to_set({1: 3, 2: 4, 3: 1, 4: 2}),
  108. )
  109. assert edges_equal(
  110. nx.min_weight_matching(G), matching_dict_to_set({1: 2, 3: 4})
  111. )
  112. def test_s_blossom(self):
  113. """Create S-blossom and use it for augmentation:"""
  114. G = nx.Graph()
  115. G.add_weighted_edges_from([(1, 2, 8), (1, 3, 9), (2, 3, 10), (3, 4, 7)])
  116. answer = matching_dict_to_set({1: 2, 2: 1, 3: 4, 4: 3})
  117. assert edges_equal(nx.max_weight_matching(G), answer)
  118. assert edges_equal(nx.min_weight_matching(G), answer)
  119. G.add_weighted_edges_from([(1, 6, 5), (4, 5, 6)])
  120. answer = matching_dict_to_set({1: 6, 2: 3, 3: 2, 4: 5, 5: 4, 6: 1})
  121. assert edges_equal(nx.max_weight_matching(G), answer)
  122. assert edges_equal(nx.min_weight_matching(G), answer)
  123. def test_s_t_blossom(self):
  124. """Create S-blossom, relabel as T-blossom, use for augmentation:"""
  125. G = nx.Graph()
  126. G.add_weighted_edges_from(
  127. [(1, 2, 9), (1, 3, 8), (2, 3, 10), (1, 4, 5), (4, 5, 4), (1, 6, 3)]
  128. )
  129. answer = matching_dict_to_set({1: 6, 2: 3, 3: 2, 4: 5, 5: 4, 6: 1})
  130. assert edges_equal(nx.max_weight_matching(G), answer)
  131. assert edges_equal(nx.min_weight_matching(G), answer)
  132. G.add_edge(4, 5, weight=3)
  133. G.add_edge(1, 6, weight=4)
  134. assert edges_equal(nx.max_weight_matching(G), answer)
  135. assert edges_equal(nx.min_weight_matching(G), answer)
  136. G.remove_edge(1, 6)
  137. G.add_edge(3, 6, weight=4)
  138. answer = matching_dict_to_set({1: 2, 2: 1, 3: 6, 4: 5, 5: 4, 6: 3})
  139. assert edges_equal(nx.max_weight_matching(G), answer)
  140. assert edges_equal(nx.min_weight_matching(G), answer)
  141. def test_nested_s_blossom(self):
  142. """Create nested S-blossom, use for augmentation:"""
  143. G = nx.Graph()
  144. G.add_weighted_edges_from(
  145. [
  146. (1, 2, 9),
  147. (1, 3, 9),
  148. (2, 3, 10),
  149. (2, 4, 8),
  150. (3, 5, 8),
  151. (4, 5, 10),
  152. (5, 6, 6),
  153. ]
  154. )
  155. dict_format = {1: 3, 2: 4, 3: 1, 4: 2, 5: 6, 6: 5}
  156. expected = {frozenset(e) for e in matching_dict_to_set(dict_format)}
  157. answer = {frozenset(e) for e in nx.max_weight_matching(G)}
  158. assert answer == expected
  159. answer = {frozenset(e) for e in nx.min_weight_matching(G)}
  160. assert answer == expected
  161. def test_nested_s_blossom_relabel(self):
  162. """Create S-blossom, relabel as S, include in nested S-blossom:"""
  163. G = nx.Graph()
  164. G.add_weighted_edges_from(
  165. [
  166. (1, 2, 10),
  167. (1, 7, 10),
  168. (2, 3, 12),
  169. (3, 4, 20),
  170. (3, 5, 20),
  171. (4, 5, 25),
  172. (5, 6, 10),
  173. (6, 7, 10),
  174. (7, 8, 8),
  175. ]
  176. )
  177. answer = matching_dict_to_set({1: 2, 2: 1, 3: 4, 4: 3, 5: 6, 6: 5, 7: 8, 8: 7})
  178. assert edges_equal(nx.max_weight_matching(G), answer)
  179. assert edges_equal(nx.min_weight_matching(G), answer)
  180. def test_nested_s_blossom_expand(self):
  181. """Create nested S-blossom, augment, expand recursively:"""
  182. G = nx.Graph()
  183. G.add_weighted_edges_from(
  184. [
  185. (1, 2, 8),
  186. (1, 3, 8),
  187. (2, 3, 10),
  188. (2, 4, 12),
  189. (3, 5, 12),
  190. (4, 5, 14),
  191. (4, 6, 12),
  192. (5, 7, 12),
  193. (6, 7, 14),
  194. (7, 8, 12),
  195. ]
  196. )
  197. answer = matching_dict_to_set({1: 2, 2: 1, 3: 5, 4: 6, 5: 3, 6: 4, 7: 8, 8: 7})
  198. assert edges_equal(nx.max_weight_matching(G), answer)
  199. assert edges_equal(nx.min_weight_matching(G), answer)
  200. def test_s_blossom_relabel_expand(self):
  201. """Create S-blossom, relabel as T, expand:"""
  202. G = nx.Graph()
  203. G.add_weighted_edges_from(
  204. [
  205. (1, 2, 23),
  206. (1, 5, 22),
  207. (1, 6, 15),
  208. (2, 3, 25),
  209. (3, 4, 22),
  210. (4, 5, 25),
  211. (4, 8, 14),
  212. (5, 7, 13),
  213. ]
  214. )
  215. answer = matching_dict_to_set({1: 6, 2: 3, 3: 2, 4: 8, 5: 7, 6: 1, 7: 5, 8: 4})
  216. assert edges_equal(nx.max_weight_matching(G), answer)
  217. assert edges_equal(nx.min_weight_matching(G), answer)
  218. def test_nested_s_blossom_relabel_expand(self):
  219. """Create nested S-blossom, relabel as T, expand:"""
  220. G = nx.Graph()
  221. G.add_weighted_edges_from(
  222. [
  223. (1, 2, 19),
  224. (1, 3, 20),
  225. (1, 8, 8),
  226. (2, 3, 25),
  227. (2, 4, 18),
  228. (3, 5, 18),
  229. (4, 5, 13),
  230. (4, 7, 7),
  231. (5, 6, 7),
  232. ]
  233. )
  234. answer = matching_dict_to_set({1: 8, 2: 3, 3: 2, 4: 7, 5: 6, 6: 5, 7: 4, 8: 1})
  235. assert edges_equal(nx.max_weight_matching(G), answer)
  236. assert edges_equal(nx.min_weight_matching(G), answer)
  237. def test_nasty_blossom1(self):
  238. """Create blossom, relabel as T in more than one way, expand,
  239. augment:
  240. """
  241. G = nx.Graph()
  242. G.add_weighted_edges_from(
  243. [
  244. (1, 2, 45),
  245. (1, 5, 45),
  246. (2, 3, 50),
  247. (3, 4, 45),
  248. (4, 5, 50),
  249. (1, 6, 30),
  250. (3, 9, 35),
  251. (4, 8, 35),
  252. (5, 7, 26),
  253. (9, 10, 5),
  254. ]
  255. )
  256. ansdict = {1: 6, 2: 3, 3: 2, 4: 8, 5: 7, 6: 1, 7: 5, 8: 4, 9: 10, 10: 9}
  257. answer = matching_dict_to_set(ansdict)
  258. assert edges_equal(nx.max_weight_matching(G), answer)
  259. assert edges_equal(nx.min_weight_matching(G), answer)
  260. def test_nasty_blossom2(self):
  261. """Again but slightly different:"""
  262. G = nx.Graph()
  263. G.add_weighted_edges_from(
  264. [
  265. (1, 2, 45),
  266. (1, 5, 45),
  267. (2, 3, 50),
  268. (3, 4, 45),
  269. (4, 5, 50),
  270. (1, 6, 30),
  271. (3, 9, 35),
  272. (4, 8, 26),
  273. (5, 7, 40),
  274. (9, 10, 5),
  275. ]
  276. )
  277. ans = {1: 6, 2: 3, 3: 2, 4: 8, 5: 7, 6: 1, 7: 5, 8: 4, 9: 10, 10: 9}
  278. answer = matching_dict_to_set(ans)
  279. assert edges_equal(nx.max_weight_matching(G), answer)
  280. assert edges_equal(nx.min_weight_matching(G), answer)
  281. def test_nasty_blossom_least_slack(self):
  282. """Create blossom, relabel as T, expand such that a new
  283. least-slack S-to-free dge is produced, augment:
  284. """
  285. G = nx.Graph()
  286. G.add_weighted_edges_from(
  287. [
  288. (1, 2, 45),
  289. (1, 5, 45),
  290. (2, 3, 50),
  291. (3, 4, 45),
  292. (4, 5, 50),
  293. (1, 6, 30),
  294. (3, 9, 35),
  295. (4, 8, 28),
  296. (5, 7, 26),
  297. (9, 10, 5),
  298. ]
  299. )
  300. ans = {1: 6, 2: 3, 3: 2, 4: 8, 5: 7, 6: 1, 7: 5, 8: 4, 9: 10, 10: 9}
  301. answer = matching_dict_to_set(ans)
  302. assert edges_equal(nx.max_weight_matching(G), answer)
  303. assert edges_equal(nx.min_weight_matching(G), answer)
  304. def test_nasty_blossom_augmenting(self):
  305. """Create nested blossom, relabel as T in more than one way"""
  306. # expand outer blossom such that inner blossom ends up on an
  307. # augmenting path:
  308. G = nx.Graph()
  309. G.add_weighted_edges_from(
  310. [
  311. (1, 2, 45),
  312. (1, 7, 45),
  313. (2, 3, 50),
  314. (3, 4, 45),
  315. (4, 5, 95),
  316. (4, 6, 94),
  317. (5, 6, 94),
  318. (6, 7, 50),
  319. (1, 8, 30),
  320. (3, 11, 35),
  321. (5, 9, 36),
  322. (7, 10, 26),
  323. (11, 12, 5),
  324. ]
  325. )
  326. ans = {
  327. 1: 8,
  328. 2: 3,
  329. 3: 2,
  330. 4: 6,
  331. 5: 9,
  332. 6: 4,
  333. 7: 10,
  334. 8: 1,
  335. 9: 5,
  336. 10: 7,
  337. 11: 12,
  338. 12: 11,
  339. }
  340. answer = matching_dict_to_set(ans)
  341. assert edges_equal(nx.max_weight_matching(G), answer)
  342. assert edges_equal(nx.min_weight_matching(G), answer)
  343. def test_nasty_blossom_expand_recursively(self):
  344. """Create nested S-blossom, relabel as S, expand recursively:"""
  345. G = nx.Graph()
  346. G.add_weighted_edges_from(
  347. [
  348. (1, 2, 40),
  349. (1, 3, 40),
  350. (2, 3, 60),
  351. (2, 4, 55),
  352. (3, 5, 55),
  353. (4, 5, 50),
  354. (1, 8, 15),
  355. (5, 7, 30),
  356. (7, 6, 10),
  357. (8, 10, 10),
  358. (4, 9, 30),
  359. ]
  360. )
  361. ans = {1: 2, 2: 1, 3: 5, 4: 9, 5: 3, 6: 7, 7: 6, 8: 10, 9: 4, 10: 8}
  362. answer = matching_dict_to_set(ans)
  363. assert edges_equal(nx.max_weight_matching(G), answer)
  364. assert edges_equal(nx.min_weight_matching(G), answer)
  365. def test_wrong_graph_type(self):
  366. error = nx.NetworkXNotImplemented
  367. raises(error, nx.max_weight_matching, nx.MultiGraph())
  368. raises(error, nx.max_weight_matching, nx.MultiDiGraph())
  369. raises(error, nx.max_weight_matching, nx.DiGraph())
  370. raises(error, nx.min_weight_matching, nx.DiGraph())
  371. class TestIsMatching:
  372. """Unit tests for the
  373. :func:`~networkx.algorithms.matching.is_matching` function.
  374. """
  375. def test_dict(self):
  376. G = nx.path_graph(4)
  377. assert nx.is_matching(G, {0: 1, 1: 0, 2: 3, 3: 2})
  378. def test_empty_matching(self):
  379. G = nx.path_graph(4)
  380. assert nx.is_matching(G, set())
  381. def test_single_edge(self):
  382. G = nx.path_graph(4)
  383. assert nx.is_matching(G, {(1, 2)})
  384. def test_edge_order(self):
  385. G = nx.path_graph(4)
  386. assert nx.is_matching(G, {(0, 1), (2, 3)})
  387. assert nx.is_matching(G, {(1, 0), (2, 3)})
  388. assert nx.is_matching(G, {(0, 1), (3, 2)})
  389. assert nx.is_matching(G, {(1, 0), (3, 2)})
  390. def test_valid_matching(self):
  391. G = nx.path_graph(4)
  392. assert nx.is_matching(G, {(0, 1), (2, 3)})
  393. def test_invalid_input(self):
  394. error = nx.NetworkXError
  395. G = nx.path_graph(4)
  396. # edge to node not in G
  397. raises(error, nx.is_matching, G, {(0, 5), (2, 3)})
  398. # edge not a 2-tuple
  399. raises(error, nx.is_matching, G, {(0, 1, 2), (2, 3)})
  400. raises(error, nx.is_matching, G, {(0,), (2, 3)})
  401. def test_selfloops(self):
  402. error = nx.NetworkXError
  403. G = nx.path_graph(4)
  404. # selfloop for node not in G
  405. raises(error, nx.is_matching, G, {(5, 5), (2, 3)})
  406. # selfloop edge not in G
  407. assert not nx.is_matching(G, {(0, 0), (1, 2), (2, 3)})
  408. # selfloop edge in G
  409. G.add_edge(0, 0)
  410. assert not nx.is_matching(G, {(0, 0), (1, 2)})
  411. def test_invalid_matching(self):
  412. G = nx.path_graph(4)
  413. assert not nx.is_matching(G, {(0, 1), (1, 2), (2, 3)})
  414. def test_invalid_edge(self):
  415. G = nx.path_graph(4)
  416. assert not nx.is_matching(G, {(0, 3), (1, 2)})
  417. raises(nx.NetworkXError, nx.is_matching, G, {(0, 55)})
  418. G = nx.DiGraph(G.edges)
  419. assert nx.is_matching(G, {(0, 1)})
  420. assert not nx.is_matching(G, {(1, 0)})
  421. class TestIsMaximalMatching:
  422. """Unit tests for the
  423. :func:`~networkx.algorithms.matching.is_maximal_matching` function.
  424. """
  425. def test_dict(self):
  426. G = nx.path_graph(4)
  427. assert nx.is_maximal_matching(G, {0: 1, 1: 0, 2: 3, 3: 2})
  428. def test_invalid_input(self):
  429. error = nx.NetworkXError
  430. G = nx.path_graph(4)
  431. # edge to node not in G
  432. raises(error, nx.is_maximal_matching, G, {(0, 5)})
  433. raises(error, nx.is_maximal_matching, G, {(5, 0)})
  434. # edge not a 2-tuple
  435. raises(error, nx.is_maximal_matching, G, {(0, 1, 2), (2, 3)})
  436. raises(error, nx.is_maximal_matching, G, {(0,), (2, 3)})
  437. def test_valid(self):
  438. G = nx.path_graph(4)
  439. assert nx.is_maximal_matching(G, {(0, 1), (2, 3)})
  440. def test_not_matching(self):
  441. G = nx.path_graph(4)
  442. assert not nx.is_maximal_matching(G, {(0, 1), (1, 2), (2, 3)})
  443. assert not nx.is_maximal_matching(G, {(0, 3)})
  444. G.add_edge(0, 0)
  445. assert not nx.is_maximal_matching(G, {(0, 0)})
  446. def test_not_maximal(self):
  447. G = nx.path_graph(4)
  448. assert not nx.is_maximal_matching(G, {(0, 1)})
  449. class TestIsPerfectMatching:
  450. """Unit tests for the
  451. :func:`~networkx.algorithms.matching.is_perfect_matching` function.
  452. """
  453. def test_dict(self):
  454. G = nx.path_graph(4)
  455. assert nx.is_perfect_matching(G, {0: 1, 1: 0, 2: 3, 3: 2})
  456. def test_valid(self):
  457. G = nx.path_graph(4)
  458. assert nx.is_perfect_matching(G, {(0, 1), (2, 3)})
  459. def test_valid_not_path(self):
  460. G = nx.cycle_graph(4)
  461. G.add_edge(0, 4)
  462. G.add_edge(1, 4)
  463. G.add_edge(5, 2)
  464. assert nx.is_perfect_matching(G, {(1, 4), (0, 3), (5, 2)})
  465. def test_invalid_input(self):
  466. error = nx.NetworkXError
  467. G = nx.path_graph(4)
  468. # edge to node not in G
  469. raises(error, nx.is_perfect_matching, G, {(0, 5)})
  470. raises(error, nx.is_perfect_matching, G, {(5, 0)})
  471. # edge not a 2-tuple
  472. raises(error, nx.is_perfect_matching, G, {(0, 1, 2), (2, 3)})
  473. raises(error, nx.is_perfect_matching, G, {(0,), (2, 3)})
  474. def test_selfloops(self):
  475. error = nx.NetworkXError
  476. G = nx.path_graph(4)
  477. # selfloop for node not in G
  478. raises(error, nx.is_perfect_matching, G, {(5, 5), (2, 3)})
  479. # selfloop edge not in G
  480. assert not nx.is_perfect_matching(G, {(0, 0), (1, 2), (2, 3)})
  481. # selfloop edge in G
  482. G.add_edge(0, 0)
  483. assert not nx.is_perfect_matching(G, {(0, 0), (1, 2)})
  484. def test_not_matching(self):
  485. G = nx.path_graph(4)
  486. assert not nx.is_perfect_matching(G, {(0, 3)})
  487. assert not nx.is_perfect_matching(G, {(0, 1), (1, 2), (2, 3)})
  488. def test_maximal_but_not_perfect(self):
  489. G = nx.cycle_graph(4)
  490. G.add_edge(0, 4)
  491. G.add_edge(1, 4)
  492. assert not nx.is_perfect_matching(G, {(1, 4), (0, 3)})
  493. class TestMaximalMatching:
  494. """Unit tests for the
  495. :func:`~networkx.algorithms.matching.maximal_matching`.
  496. """
  497. def test_valid_matching(self):
  498. edges = [(1, 2), (1, 5), (2, 3), (2, 5), (3, 4), (3, 6), (5, 6)]
  499. G = nx.Graph(edges)
  500. matching = nx.maximal_matching(G)
  501. assert nx.is_maximal_matching(G, matching)
  502. def test_single_edge_matching(self):
  503. # In the star graph, any maximal matching has just one edge.
  504. G = nx.star_graph(5)
  505. matching = nx.maximal_matching(G)
  506. assert 1 == len(matching)
  507. assert nx.is_maximal_matching(G, matching)
  508. def test_self_loops(self):
  509. # Create the path graph with two self-loops.
  510. G = nx.path_graph(3)
  511. G.add_edges_from([(0, 0), (1, 1)])
  512. matching = nx.maximal_matching(G)
  513. assert len(matching) == 1
  514. # The matching should never include self-loops.
  515. assert not any(u == v for u, v in matching)
  516. assert nx.is_maximal_matching(G, matching)
  517. def test_ordering(self):
  518. """Tests that a maximal matching is computed correctly
  519. regardless of the order in which nodes are added to the graph.
  520. """
  521. for nodes in permutations(range(3)):
  522. G = nx.Graph()
  523. G.add_nodes_from(nodes)
  524. G.add_edges_from([(0, 1), (0, 2)])
  525. matching = nx.maximal_matching(G)
  526. assert len(matching) == 1
  527. assert nx.is_maximal_matching(G, matching)
  528. def test_wrong_graph_type(self):
  529. error = nx.NetworkXNotImplemented
  530. raises(error, nx.maximal_matching, nx.MultiGraph())
  531. raises(error, nx.maximal_matching, nx.MultiDiGraph())
  532. raises(error, nx.maximal_matching, nx.DiGraph())