test_distance_measures.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. from random import Random
  2. import pytest
  3. import networkx as nx
  4. from networkx import convert_node_labels_to_integers as cnlti
  5. from networkx.algorithms.distance_measures import _extrema_bounding
  6. def test__extrema_bounding_invalid_compute_kwarg():
  7. G = nx.path_graph(3)
  8. with pytest.raises(ValueError, match="compute must be one of"):
  9. _extrema_bounding(G, compute="spam")
  10. class TestDistance:
  11. def setup_method(self):
  12. G = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted")
  13. self.G = G
  14. def test_eccentricity(self):
  15. assert nx.eccentricity(self.G, 1) == 6
  16. e = nx.eccentricity(self.G)
  17. assert e[1] == 6
  18. sp = dict(nx.shortest_path_length(self.G))
  19. e = nx.eccentricity(self.G, sp=sp)
  20. assert e[1] == 6
  21. e = nx.eccentricity(self.G, v=1)
  22. assert e == 6
  23. # This behavior changed in version 1.8 (ticket #739)
  24. e = nx.eccentricity(self.G, v=[1, 1])
  25. assert e[1] == 6
  26. e = nx.eccentricity(self.G, v=[1, 2])
  27. assert e[1] == 6
  28. # test against graph with one node
  29. G = nx.path_graph(1)
  30. e = nx.eccentricity(G)
  31. assert e[0] == 0
  32. e = nx.eccentricity(G, v=0)
  33. assert e == 0
  34. pytest.raises(nx.NetworkXError, nx.eccentricity, G, 1)
  35. # test against empty graph
  36. G = nx.empty_graph()
  37. e = nx.eccentricity(G)
  38. assert e == {}
  39. def test_diameter(self):
  40. assert nx.diameter(self.G) == 6
  41. def test_radius(self):
  42. assert nx.radius(self.G) == 4
  43. def test_periphery(self):
  44. assert set(nx.periphery(self.G)) == {1, 4, 13, 16}
  45. def test_center(self):
  46. assert set(nx.center(self.G)) == {6, 7, 10, 11}
  47. def test_bound_diameter(self):
  48. assert nx.diameter(self.G, usebounds=True) == 6
  49. def test_bound_radius(self):
  50. assert nx.radius(self.G, usebounds=True) == 4
  51. def test_bound_periphery(self):
  52. result = {1, 4, 13, 16}
  53. assert set(nx.periphery(self.G, usebounds=True)) == result
  54. def test_bound_center(self):
  55. result = {6, 7, 10, 11}
  56. assert set(nx.center(self.G, usebounds=True)) == result
  57. def test_radius_exception(self):
  58. G = nx.Graph()
  59. G.add_edge(1, 2)
  60. G.add_edge(3, 4)
  61. pytest.raises(nx.NetworkXError, nx.diameter, G)
  62. def test_eccentricity_infinite(self):
  63. with pytest.raises(nx.NetworkXError):
  64. G = nx.Graph([(1, 2), (3, 4)])
  65. e = nx.eccentricity(G)
  66. def test_eccentricity_undirected_not_connected(self):
  67. with pytest.raises(nx.NetworkXError):
  68. G = nx.Graph([(1, 2), (3, 4)])
  69. e = nx.eccentricity(G, sp=1)
  70. def test_eccentricity_directed_weakly_connected(self):
  71. with pytest.raises(nx.NetworkXError):
  72. DG = nx.DiGraph([(1, 2), (1, 3)])
  73. nx.eccentricity(DG)
  74. class TestWeightedDistance:
  75. def setup_method(self):
  76. G = nx.Graph()
  77. G.add_edge(0, 1, weight=0.6, cost=0.6, high_cost=6)
  78. G.add_edge(0, 2, weight=0.2, cost=0.2, high_cost=2)
  79. G.add_edge(2, 3, weight=0.1, cost=0.1, high_cost=1)
  80. G.add_edge(2, 4, weight=0.7, cost=0.7, high_cost=7)
  81. G.add_edge(2, 5, weight=0.9, cost=0.9, high_cost=9)
  82. G.add_edge(1, 5, weight=0.3, cost=0.3, high_cost=3)
  83. self.G = G
  84. self.weight_fn = lambda v, u, e: 2
  85. def test_eccentricity_weight_None(self):
  86. assert nx.eccentricity(self.G, 1, weight=None) == 3
  87. e = nx.eccentricity(self.G, weight=None)
  88. assert e[1] == 3
  89. e = nx.eccentricity(self.G, v=1, weight=None)
  90. assert e == 3
  91. # This behavior changed in version 1.8 (ticket #739)
  92. e = nx.eccentricity(self.G, v=[1, 1], weight=None)
  93. assert e[1] == 3
  94. e = nx.eccentricity(self.G, v=[1, 2], weight=None)
  95. assert e[1] == 3
  96. def test_eccentricity_weight_attr(self):
  97. assert nx.eccentricity(self.G, 1, weight="weight") == 1.5
  98. e = nx.eccentricity(self.G, weight="weight")
  99. assert (
  100. e
  101. == nx.eccentricity(self.G, weight="cost")
  102. != nx.eccentricity(self.G, weight="high_cost")
  103. )
  104. assert e[1] == 1.5
  105. e = nx.eccentricity(self.G, v=1, weight="weight")
  106. assert e == 1.5
  107. # This behavior changed in version 1.8 (ticket #739)
  108. e = nx.eccentricity(self.G, v=[1, 1], weight="weight")
  109. assert e[1] == 1.5
  110. e = nx.eccentricity(self.G, v=[1, 2], weight="weight")
  111. assert e[1] == 1.5
  112. def test_eccentricity_weight_fn(self):
  113. assert nx.eccentricity(self.G, 1, weight=self.weight_fn) == 6
  114. e = nx.eccentricity(self.G, weight=self.weight_fn)
  115. assert e[1] == 6
  116. e = nx.eccentricity(self.G, v=1, weight=self.weight_fn)
  117. assert e == 6
  118. # This behavior changed in version 1.8 (ticket #739)
  119. e = nx.eccentricity(self.G, v=[1, 1], weight=self.weight_fn)
  120. assert e[1] == 6
  121. e = nx.eccentricity(self.G, v=[1, 2], weight=self.weight_fn)
  122. assert e[1] == 6
  123. def test_diameter_weight_None(self):
  124. assert nx.diameter(self.G, weight=None) == 3
  125. def test_diameter_weight_attr(self):
  126. assert (
  127. nx.diameter(self.G, weight="weight")
  128. == nx.diameter(self.G, weight="cost")
  129. == 1.6
  130. != nx.diameter(self.G, weight="high_cost")
  131. )
  132. def test_diameter_weight_fn(self):
  133. assert nx.diameter(self.G, weight=self.weight_fn) == 6
  134. def test_radius_weight_None(self):
  135. assert pytest.approx(nx.radius(self.G, weight=None)) == 2
  136. def test_radius_weight_attr(self):
  137. assert (
  138. pytest.approx(nx.radius(self.G, weight="weight"))
  139. == pytest.approx(nx.radius(self.G, weight="cost"))
  140. == 0.9
  141. != nx.radius(self.G, weight="high_cost")
  142. )
  143. def test_radius_weight_fn(self):
  144. assert nx.radius(self.G, weight=self.weight_fn) == 4
  145. def test_periphery_weight_None(self):
  146. for v in set(nx.periphery(self.G, weight=None)):
  147. assert nx.eccentricity(self.G, v, weight=None) == nx.diameter(
  148. self.G, weight=None
  149. )
  150. def test_periphery_weight_attr(self):
  151. periphery = set(nx.periphery(self.G, weight="weight"))
  152. assert (
  153. periphery
  154. == set(nx.periphery(self.G, weight="cost"))
  155. == set(nx.periphery(self.G, weight="high_cost"))
  156. )
  157. for v in periphery:
  158. assert (
  159. nx.eccentricity(self.G, v, weight="high_cost")
  160. != nx.eccentricity(self.G, v, weight="weight")
  161. == nx.eccentricity(self.G, v, weight="cost")
  162. == nx.diameter(self.G, weight="weight")
  163. == nx.diameter(self.G, weight="cost")
  164. != nx.diameter(self.G, weight="high_cost")
  165. )
  166. assert nx.eccentricity(self.G, v, weight="high_cost") == nx.diameter(
  167. self.G, weight="high_cost"
  168. )
  169. def test_periphery_weight_fn(self):
  170. for v in set(nx.periphery(self.G, weight=self.weight_fn)):
  171. assert nx.eccentricity(self.G, v, weight=self.weight_fn) == nx.diameter(
  172. self.G, weight=self.weight_fn
  173. )
  174. def test_center_weight_None(self):
  175. for v in set(nx.center(self.G, weight=None)):
  176. assert pytest.approx(nx.eccentricity(self.G, v, weight=None)) == nx.radius(
  177. self.G, weight=None
  178. )
  179. def test_center_weight_attr(self):
  180. center = set(nx.center(self.G, weight="weight"))
  181. assert (
  182. center
  183. == set(nx.center(self.G, weight="cost"))
  184. != set(nx.center(self.G, weight="high_cost"))
  185. )
  186. for v in center:
  187. assert (
  188. nx.eccentricity(self.G, v, weight="high_cost")
  189. != pytest.approx(nx.eccentricity(self.G, v, weight="weight"))
  190. == pytest.approx(nx.eccentricity(self.G, v, weight="cost"))
  191. == nx.radius(self.G, weight="weight")
  192. == nx.radius(self.G, weight="cost")
  193. != nx.radius(self.G, weight="high_cost")
  194. )
  195. assert nx.eccentricity(self.G, v, weight="high_cost") == nx.radius(
  196. self.G, weight="high_cost"
  197. )
  198. def test_center_weight_fn(self):
  199. for v in set(nx.center(self.G, weight=self.weight_fn)):
  200. assert nx.eccentricity(self.G, v, weight=self.weight_fn) == nx.radius(
  201. self.G, weight=self.weight_fn
  202. )
  203. def test_bound_diameter_weight_None(self):
  204. assert nx.diameter(self.G, usebounds=True, weight=None) == 3
  205. def test_bound_diameter_weight_attr(self):
  206. assert (
  207. nx.diameter(self.G, usebounds=True, weight="high_cost")
  208. != nx.diameter(self.G, usebounds=True, weight="weight")
  209. == nx.diameter(self.G, usebounds=True, weight="cost")
  210. == 1.6
  211. != nx.diameter(self.G, usebounds=True, weight="high_cost")
  212. )
  213. assert nx.diameter(self.G, usebounds=True, weight="high_cost") == nx.diameter(
  214. self.G, usebounds=True, weight="high_cost"
  215. )
  216. def test_bound_diameter_weight_fn(self):
  217. assert nx.diameter(self.G, usebounds=True, weight=self.weight_fn) == 6
  218. def test_bound_radius_weight_None(self):
  219. assert pytest.approx(nx.radius(self.G, usebounds=True, weight=None)) == 2
  220. def test_bound_radius_weight_attr(self):
  221. assert (
  222. nx.radius(self.G, usebounds=True, weight="high_cost")
  223. != pytest.approx(nx.radius(self.G, usebounds=True, weight="weight"))
  224. == pytest.approx(nx.radius(self.G, usebounds=True, weight="cost"))
  225. == 0.9
  226. != nx.radius(self.G, usebounds=True, weight="high_cost")
  227. )
  228. assert nx.radius(self.G, usebounds=True, weight="high_cost") == nx.radius(
  229. self.G, usebounds=True, weight="high_cost"
  230. )
  231. def test_bound_radius_weight_fn(self):
  232. assert nx.radius(self.G, usebounds=True, weight=self.weight_fn) == 4
  233. def test_bound_periphery_weight_None(self):
  234. result = {1, 3, 4}
  235. assert set(nx.periphery(self.G, usebounds=True, weight=None)) == result
  236. def test_bound_periphery_weight_attr(self):
  237. result = {4, 5}
  238. assert (
  239. set(nx.periphery(self.G, usebounds=True, weight="weight"))
  240. == set(nx.periphery(self.G, usebounds=True, weight="cost"))
  241. == result
  242. )
  243. def test_bound_periphery_weight_fn(self):
  244. result = {1, 3, 4}
  245. assert (
  246. set(nx.periphery(self.G, usebounds=True, weight=self.weight_fn)) == result
  247. )
  248. def test_bound_center_weight_None(self):
  249. result = {0, 2, 5}
  250. assert set(nx.center(self.G, usebounds=True, weight=None)) == result
  251. def test_bound_center_weight_attr(self):
  252. result = {0}
  253. assert (
  254. set(nx.center(self.G, usebounds=True, weight="weight"))
  255. == set(nx.center(self.G, usebounds=True, weight="cost"))
  256. == result
  257. )
  258. def test_bound_center_weight_fn(self):
  259. result = {0, 2, 5}
  260. assert set(nx.center(self.G, usebounds=True, weight=self.weight_fn)) == result
  261. class TestResistanceDistance:
  262. @classmethod
  263. def setup_class(cls):
  264. global np
  265. global sp
  266. np = pytest.importorskip("numpy")
  267. sp = pytest.importorskip("scipy")
  268. def setup_method(self):
  269. G = nx.Graph()
  270. G.add_edge(1, 2, weight=2)
  271. G.add_edge(2, 3, weight=4)
  272. G.add_edge(3, 4, weight=1)
  273. G.add_edge(1, 4, weight=3)
  274. self.G = G
  275. def test_resistance_distance(self):
  276. rd = nx.resistance_distance(self.G, 1, 3, "weight", True)
  277. test_data = 1 / (1 / (2 + 4) + 1 / (1 + 3))
  278. assert round(rd, 5) == round(test_data, 5)
  279. def test_resistance_distance_noinv(self):
  280. rd = nx.resistance_distance(self.G, 1, 3, "weight", False)
  281. test_data = 1 / (1 / (1 / 2 + 1 / 4) + 1 / (1 / 1 + 1 / 3))
  282. assert round(rd, 5) == round(test_data, 5)
  283. def test_resistance_distance_no_weight(self):
  284. rd = nx.resistance_distance(self.G, 1, 3)
  285. assert round(rd, 5) == 1
  286. def test_resistance_distance_neg_weight(self):
  287. self.G[2][3]["weight"] = -4
  288. rd = nx.resistance_distance(self.G, 1, 3, "weight", True)
  289. test_data = 1 / (1 / (2 + -4) + 1 / (1 + 3))
  290. assert round(rd, 5) == round(test_data, 5)
  291. def test_multigraph(self):
  292. G = nx.MultiGraph()
  293. G.add_edge(1, 2, weight=2)
  294. G.add_edge(2, 3, weight=4)
  295. G.add_edge(3, 4, weight=1)
  296. G.add_edge(1, 4, weight=3)
  297. rd = nx.resistance_distance(G, 1, 3, "weight", True)
  298. assert np.isclose(rd, 1 / (1 / (2 + 4) + 1 / (1 + 3)))
  299. def test_resistance_distance_div0(self):
  300. with pytest.raises(ZeroDivisionError):
  301. self.G[1][2]["weight"] = 0
  302. nx.resistance_distance(self.G, 1, 3, "weight")
  303. def test_resistance_distance_not_connected(self):
  304. with pytest.raises(nx.NetworkXError):
  305. self.G.add_node(5)
  306. nx.resistance_distance(self.G, 1, 5)
  307. def test_resistance_distance_same_node(self):
  308. with pytest.raises(nx.NetworkXError):
  309. nx.resistance_distance(self.G, 1, 1)
  310. def test_resistance_distance_nodeA_not_in_graph(self):
  311. with pytest.raises(nx.NetworkXError):
  312. nx.resistance_distance(self.G, 9, 1)
  313. def test_resistance_distance_nodeB_not_in_graph(self):
  314. with pytest.raises(nx.NetworkXError):
  315. nx.resistance_distance(self.G, 1, 9)
  316. class TestBarycenter:
  317. """Test :func:`networkx.algorithms.distance_measures.barycenter`."""
  318. def barycenter_as_subgraph(self, g, **kwargs):
  319. """Return the subgraph induced on the barycenter of g"""
  320. b = nx.barycenter(g, **kwargs)
  321. assert isinstance(b, list)
  322. assert set(b) <= set(g)
  323. return g.subgraph(b)
  324. def test_must_be_connected(self):
  325. pytest.raises(nx.NetworkXNoPath, nx.barycenter, nx.empty_graph(5))
  326. def test_sp_kwarg(self):
  327. # Complete graph K_5. Normally it works...
  328. K_5 = nx.complete_graph(5)
  329. sp = dict(nx.shortest_path_length(K_5))
  330. assert nx.barycenter(K_5, sp=sp) == list(K_5)
  331. # ...but not with the weight argument
  332. for u, v, data in K_5.edges.data():
  333. data["weight"] = 1
  334. pytest.raises(ValueError, nx.barycenter, K_5, sp=sp, weight="weight")
  335. # ...and a corrupted sp can make it seem like K_5 is disconnected
  336. del sp[0][1]
  337. pytest.raises(nx.NetworkXNoPath, nx.barycenter, K_5, sp=sp)
  338. def test_trees(self):
  339. """The barycenter of a tree is a single vertex or an edge.
  340. See [West01]_, p. 78.
  341. """
  342. prng = Random(0xDEADBEEF)
  343. for i in range(50):
  344. RT = nx.random_tree(prng.randint(1, 75), prng)
  345. b = self.barycenter_as_subgraph(RT)
  346. if len(b) == 2:
  347. assert b.size() == 1
  348. else:
  349. assert len(b) == 1
  350. assert b.size() == 0
  351. def test_this_one_specific_tree(self):
  352. """Test the tree pictured at the bottom of [West01]_, p. 78."""
  353. g = nx.Graph(
  354. {
  355. "a": ["b"],
  356. "b": ["a", "x"],
  357. "x": ["b", "y"],
  358. "y": ["x", "z"],
  359. "z": ["y", 0, 1, 2, 3, 4],
  360. 0: ["z"],
  361. 1: ["z"],
  362. 2: ["z"],
  363. 3: ["z"],
  364. 4: ["z"],
  365. }
  366. )
  367. b = self.barycenter_as_subgraph(g, attr="barycentricity")
  368. assert list(b) == ["z"]
  369. assert not b.edges
  370. expected_barycentricity = {
  371. 0: 23,
  372. 1: 23,
  373. 2: 23,
  374. 3: 23,
  375. 4: 23,
  376. "a": 35,
  377. "b": 27,
  378. "x": 21,
  379. "y": 17,
  380. "z": 15,
  381. }
  382. for node, barycentricity in expected_barycentricity.items():
  383. assert g.nodes[node]["barycentricity"] == barycentricity
  384. # Doubling weights should do nothing but double the barycentricities
  385. for edge in g.edges:
  386. g.edges[edge]["weight"] = 2
  387. b = self.barycenter_as_subgraph(g, weight="weight", attr="barycentricity2")
  388. assert list(b) == ["z"]
  389. assert not b.edges
  390. for node, barycentricity in expected_barycentricity.items():
  391. assert g.nodes[node]["barycentricity2"] == barycentricity * 2