test_text.py 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742
  1. import random
  2. from itertools import product
  3. from textwrap import dedent
  4. import pytest
  5. import networkx as nx
  6. def test_generate_network_text_forest_directed():
  7. # Create a directed forest with labels
  8. graph = nx.balanced_tree(r=2, h=2, create_using=nx.DiGraph)
  9. for node in graph.nodes:
  10. graph.nodes[node]["label"] = "node_" + chr(ord("a") + node)
  11. node_target = dedent(
  12. """
  13. ╙── 0
  14. ├─╼ 1
  15. │ ├─╼ 3
  16. │ └─╼ 4
  17. └─╼ 2
  18. ├─╼ 5
  19. └─╼ 6
  20. """
  21. ).strip()
  22. label_target = dedent(
  23. """
  24. ╙── node_a
  25. ├─╼ node_b
  26. │ ├─╼ node_d
  27. │ └─╼ node_e
  28. └─╼ node_c
  29. ├─╼ node_f
  30. └─╼ node_g
  31. """
  32. ).strip()
  33. # Basic node case
  34. ret = nx.generate_network_text(graph, with_labels=False)
  35. assert "\n".join(ret) == node_target
  36. # Basic label case
  37. ret = nx.generate_network_text(graph, with_labels=True)
  38. assert "\n".join(ret) == label_target
  39. def test_write_network_text_empty_graph():
  40. def _graph_str(g, **kw):
  41. printbuf = []
  42. nx.write_network_text(g, printbuf.append, end="", **kw)
  43. return "\n".join(printbuf)
  44. assert _graph_str(nx.DiGraph()) == "╙"
  45. assert _graph_str(nx.Graph()) == "╙"
  46. assert _graph_str(nx.DiGraph(), ascii_only=True) == "+"
  47. assert _graph_str(nx.Graph(), ascii_only=True) == "+"
  48. def test_write_network_text_within_forest_glyph():
  49. g = nx.DiGraph()
  50. g.add_nodes_from([1, 2, 3, 4])
  51. g.add_edge(2, 4)
  52. lines = []
  53. write = lines.append
  54. nx.write_network_text(g, path=write, end="")
  55. nx.write_network_text(g, path=write, ascii_only=True, end="")
  56. text = "\n".join(lines)
  57. target = dedent(
  58. """
  59. ╟── 1
  60. ╟── 2
  61. ╎ └─╼ 4
  62. ╙── 3
  63. +-- 1
  64. +-- 2
  65. : L-> 4
  66. +-- 3
  67. """
  68. ).strip()
  69. assert text == target
  70. def test_generate_network_text_directed_multi_tree():
  71. tree1 = nx.balanced_tree(r=2, h=2, create_using=nx.DiGraph)
  72. tree2 = nx.balanced_tree(r=2, h=2, create_using=nx.DiGraph)
  73. forest = nx.disjoint_union_all([tree1, tree2])
  74. ret = "\n".join(nx.generate_network_text(forest))
  75. target = dedent(
  76. """
  77. ╟── 0
  78. ╎ ├─╼ 1
  79. ╎ │ ├─╼ 3
  80. ╎ │ └─╼ 4
  81. ╎ └─╼ 2
  82. ╎ ├─╼ 5
  83. ╎ └─╼ 6
  84. ╙── 7
  85. ├─╼ 8
  86. │ ├─╼ 10
  87. │ └─╼ 11
  88. └─╼ 9
  89. ├─╼ 12
  90. └─╼ 13
  91. """
  92. ).strip()
  93. assert ret == target
  94. tree3 = nx.balanced_tree(r=2, h=2, create_using=nx.DiGraph)
  95. forest = nx.disjoint_union_all([tree1, tree2, tree3])
  96. ret = "\n".join(nx.generate_network_text(forest, sources=[0, 14, 7]))
  97. target = dedent(
  98. """
  99. ╟── 0
  100. ╎ ├─╼ 1
  101. ╎ │ ├─╼ 3
  102. ╎ │ └─╼ 4
  103. ╎ └─╼ 2
  104. ╎ ├─╼ 5
  105. ╎ └─╼ 6
  106. ╟── 14
  107. ╎ ├─╼ 15
  108. ╎ │ ├─╼ 17
  109. ╎ │ └─╼ 18
  110. ╎ └─╼ 16
  111. ╎ ├─╼ 19
  112. ╎ └─╼ 20
  113. ╙── 7
  114. ├─╼ 8
  115. │ ├─╼ 10
  116. │ └─╼ 11
  117. └─╼ 9
  118. ├─╼ 12
  119. └─╼ 13
  120. """
  121. ).strip()
  122. assert ret == target
  123. ret = "\n".join(
  124. nx.generate_network_text(forest, sources=[0, 14, 7], ascii_only=True)
  125. )
  126. target = dedent(
  127. """
  128. +-- 0
  129. : |-> 1
  130. : | |-> 3
  131. : | L-> 4
  132. : L-> 2
  133. : |-> 5
  134. : L-> 6
  135. +-- 14
  136. : |-> 15
  137. : | |-> 17
  138. : | L-> 18
  139. : L-> 16
  140. : |-> 19
  141. : L-> 20
  142. +-- 7
  143. |-> 8
  144. | |-> 10
  145. | L-> 11
  146. L-> 9
  147. |-> 12
  148. L-> 13
  149. """
  150. ).strip()
  151. assert ret == target
  152. def test_generate_network_text_undirected_multi_tree():
  153. tree1 = nx.balanced_tree(r=2, h=2, create_using=nx.Graph)
  154. tree2 = nx.balanced_tree(r=2, h=2, create_using=nx.Graph)
  155. tree2 = nx.relabel_nodes(tree2, {n: n + len(tree1) for n in tree2.nodes})
  156. forest = nx.union(tree1, tree2)
  157. ret = "\n".join(nx.generate_network_text(forest, sources=[0, 7]))
  158. target = dedent(
  159. """
  160. ╟── 0
  161. ╎ ├── 1
  162. ╎ │ ├── 3
  163. ╎ │ └── 4
  164. ╎ └── 2
  165. ╎ ├── 5
  166. ╎ └── 6
  167. ╙── 7
  168. ├── 8
  169. │ ├── 10
  170. │ └── 11
  171. └── 9
  172. ├── 12
  173. └── 13
  174. """
  175. ).strip()
  176. assert ret == target
  177. ret = "\n".join(nx.generate_network_text(forest, sources=[0, 7], ascii_only=True))
  178. target = dedent(
  179. """
  180. +-- 0
  181. : |-- 1
  182. : | |-- 3
  183. : | L-- 4
  184. : L-- 2
  185. : |-- 5
  186. : L-- 6
  187. +-- 7
  188. |-- 8
  189. | |-- 10
  190. | L-- 11
  191. L-- 9
  192. |-- 12
  193. L-- 13
  194. """
  195. ).strip()
  196. assert ret == target
  197. def test_generate_network_text_forest_undirected():
  198. # Create a directed forest
  199. graph = nx.balanced_tree(r=2, h=2, create_using=nx.Graph)
  200. node_target0 = dedent(
  201. """
  202. ╙── 0
  203. ├── 1
  204. │ ├── 3
  205. │ └── 4
  206. └── 2
  207. ├── 5
  208. └── 6
  209. """
  210. ).strip()
  211. # defined starting point
  212. ret = "\n".join(nx.generate_network_text(graph, sources=[0]))
  213. assert ret == node_target0
  214. # defined starting point
  215. node_target2 = dedent(
  216. """
  217. ╙── 2
  218. ├── 0
  219. │ └── 1
  220. │ ├── 3
  221. │ └── 4
  222. ├── 5
  223. └── 6
  224. """
  225. ).strip()
  226. ret = "\n".join(nx.generate_network_text(graph, sources=[2]))
  227. assert ret == node_target2
  228. def test_generate_network_text_overspecified_sources():
  229. """
  230. When sources are directly specified, we won't be able to determine when we
  231. are in the last component, so there will always be a trailing, leftmost
  232. pipe.
  233. """
  234. graph = nx.disjoint_union_all(
  235. [
  236. nx.balanced_tree(r=2, h=1, create_using=nx.DiGraph),
  237. nx.balanced_tree(r=1, h=2, create_using=nx.DiGraph),
  238. nx.balanced_tree(r=2, h=1, create_using=nx.DiGraph),
  239. ]
  240. )
  241. # defined starting point
  242. target1 = dedent(
  243. """
  244. ╟── 0
  245. ╎ ├─╼ 1
  246. ╎ └─╼ 2
  247. ╟── 3
  248. ╎ └─╼ 4
  249. ╎ └─╼ 5
  250. ╟── 6
  251. ╎ ├─╼ 7
  252. ╎ └─╼ 8
  253. """
  254. ).strip()
  255. target2 = dedent(
  256. """
  257. ╟── 0
  258. ╎ ├─╼ 1
  259. ╎ └─╼ 2
  260. ╟── 3
  261. ╎ └─╼ 4
  262. ╎ └─╼ 5
  263. ╙── 6
  264. ├─╼ 7
  265. └─╼ 8
  266. """
  267. ).strip()
  268. got1 = "\n".join(nx.generate_network_text(graph, sources=graph.nodes))
  269. got2 = "\n".join(nx.generate_network_text(graph))
  270. assert got1 == target1
  271. assert got2 == target2
  272. def test_write_network_text_iterative_add_directed_edges():
  273. """
  274. Walk through the cases going from a disconnected to fully connected graph
  275. """
  276. graph = nx.DiGraph()
  277. graph.add_nodes_from([1, 2, 3, 4])
  278. lines = []
  279. write = lines.append
  280. write("--- initial state ---")
  281. nx.write_network_text(graph, path=write, end="")
  282. for i, j in product(graph.nodes, graph.nodes):
  283. write(f"--- add_edge({i}, {j}) ---")
  284. graph.add_edge(i, j)
  285. nx.write_network_text(graph, path=write, end="")
  286. text = "\n".join(lines)
  287. # defined starting point
  288. target = dedent(
  289. """
  290. --- initial state ---
  291. ╟── 1
  292. ╟── 2
  293. ╟── 3
  294. ╙── 4
  295. --- add_edge(1, 1) ---
  296. ╟── 1 ╾ 1
  297. ╎ └─╼ ...
  298. ╟── 2
  299. ╟── 3
  300. ╙── 4
  301. --- add_edge(1, 2) ---
  302. ╟── 1 ╾ 1
  303. ╎ ├─╼ 2
  304. ╎ └─╼ ...
  305. ╟── 3
  306. ╙── 4
  307. --- add_edge(1, 3) ---
  308. ╟── 1 ╾ 1
  309. ╎ ├─╼ 2
  310. ╎ ├─╼ 3
  311. ╎ └─╼ ...
  312. ╙── 4
  313. --- add_edge(1, 4) ---
  314. ╙── 1 ╾ 1
  315. ├─╼ 2
  316. ├─╼ 3
  317. ├─╼ 4
  318. └─╼ ...
  319. --- add_edge(2, 1) ---
  320. ╙── 2 ╾ 1
  321. └─╼ 1 ╾ 1
  322. ├─╼ 3
  323. ├─╼ 4
  324. └─╼ ...
  325. --- add_edge(2, 2) ---
  326. ╙── 1 ╾ 1, 2
  327. ├─╼ 2 ╾ 2
  328. │ └─╼ ...
  329. ├─╼ 3
  330. ├─╼ 4
  331. └─╼ ...
  332. --- add_edge(2, 3) ---
  333. ╙── 1 ╾ 1, 2
  334. ├─╼ 2 ╾ 2
  335. │ ├─╼ 3 ╾ 1
  336. │ └─╼ ...
  337. ├─╼ 4
  338. └─╼ ...
  339. --- add_edge(2, 4) ---
  340. ╙── 1 ╾ 1, 2
  341. ├─╼ 2 ╾ 2
  342. │ ├─╼ 3 ╾ 1
  343. │ ├─╼ 4 ╾ 1
  344. │ └─╼ ...
  345. └─╼ ...
  346. --- add_edge(3, 1) ---
  347. ╙── 2 ╾ 1, 2
  348. ├─╼ 1 ╾ 1, 3
  349. │ ├─╼ 3 ╾ 2
  350. │ │ └─╼ ...
  351. │ ├─╼ 4 ╾ 2
  352. │ └─╼ ...
  353. └─╼ ...
  354. --- add_edge(3, 2) ---
  355. ╙── 3 ╾ 1, 2
  356. ├─╼ 1 ╾ 1, 2
  357. │ ├─╼ 2 ╾ 2, 3
  358. │ │ ├─╼ 4 ╾ 1
  359. │ │ └─╼ ...
  360. │ └─╼ ...
  361. └─╼ ...
  362. --- add_edge(3, 3) ---
  363. ╙── 1 ╾ 1, 2, 3
  364. ├─╼ 2 ╾ 2, 3
  365. │ ├─╼ 3 ╾ 1, 3
  366. │ │ └─╼ ...
  367. │ ├─╼ 4 ╾ 1
  368. │ └─╼ ...
  369. └─╼ ...
  370. --- add_edge(3, 4) ---
  371. ╙── 1 ╾ 1, 2, 3
  372. ├─╼ 2 ╾ 2, 3
  373. │ ├─╼ 3 ╾ 1, 3
  374. │ │ ├─╼ 4 ╾ 1, 2
  375. │ │ └─╼ ...
  376. │ └─╼ ...
  377. └─╼ ...
  378. --- add_edge(4, 1) ---
  379. ╙── 2 ╾ 1, 2, 3
  380. ├─╼ 1 ╾ 1, 3, 4
  381. │ ├─╼ 3 ╾ 2, 3
  382. │ │ ├─╼ 4 ╾ 1, 2
  383. │ │ │ └─╼ ...
  384. │ │ └─╼ ...
  385. │ └─╼ ...
  386. └─╼ ...
  387. --- add_edge(4, 2) ---
  388. ╙── 3 ╾ 1, 2, 3
  389. ├─╼ 1 ╾ 1, 2, 4
  390. │ ├─╼ 2 ╾ 2, 3, 4
  391. │ │ ├─╼ 4 ╾ 1, 3
  392. │ │ │ └─╼ ...
  393. │ │ └─╼ ...
  394. │ └─╼ ...
  395. └─╼ ...
  396. --- add_edge(4, 3) ---
  397. ╙── 4 ╾ 1, 2, 3
  398. ├─╼ 1 ╾ 1, 2, 3
  399. │ ├─╼ 2 ╾ 2, 3, 4
  400. │ │ ├─╼ 3 ╾ 1, 3, 4
  401. │ │ │ └─╼ ...
  402. │ │ └─╼ ...
  403. │ └─╼ ...
  404. └─╼ ...
  405. --- add_edge(4, 4) ---
  406. ╙── 1 ╾ 1, 2, 3, 4
  407. ├─╼ 2 ╾ 2, 3, 4
  408. │ ├─╼ 3 ╾ 1, 3, 4
  409. │ │ ├─╼ 4 ╾ 1, 2, 4
  410. │ │ │ └─╼ ...
  411. │ │ └─╼ ...
  412. │ └─╼ ...
  413. └─╼ ...
  414. """
  415. ).strip()
  416. assert target == text
  417. def test_write_network_text_iterative_add_undirected_edges():
  418. """
  419. Walk through the cases going from a disconnected to fully connected graph
  420. """
  421. graph = nx.Graph()
  422. graph.add_nodes_from([1, 2, 3, 4])
  423. lines = []
  424. write = lines.append
  425. write("--- initial state ---")
  426. nx.write_network_text(graph, path=write, end="")
  427. for i, j in product(graph.nodes, graph.nodes):
  428. if i == j:
  429. continue
  430. write(f"--- add_edge({i}, {j}) ---")
  431. graph.add_edge(i, j)
  432. nx.write_network_text(graph, path=write, end="")
  433. text = "\n".join(lines)
  434. target = dedent(
  435. """
  436. --- initial state ---
  437. ╟── 1
  438. ╟── 2
  439. ╟── 3
  440. ╙── 4
  441. --- add_edge(1, 2) ---
  442. ╟── 3
  443. ╟── 4
  444. ╙── 1
  445. └── 2
  446. --- add_edge(1, 3) ---
  447. ╟── 4
  448. ╙── 2
  449. └── 1
  450. └── 3
  451. --- add_edge(1, 4) ---
  452. ╙── 2
  453. └── 1
  454. ├── 3
  455. └── 4
  456. --- add_edge(2, 1) ---
  457. ╙── 2
  458. └── 1
  459. ├── 3
  460. └── 4
  461. --- add_edge(2, 3) ---
  462. ╙── 4
  463. └── 1
  464. ├── 2
  465. │ └── 3 ─ 1
  466. └── ...
  467. --- add_edge(2, 4) ---
  468. ╙── 3
  469. ├── 1
  470. │ ├── 2 ─ 3
  471. │ │ └── 4 ─ 1
  472. │ └── ...
  473. └── ...
  474. --- add_edge(3, 1) ---
  475. ╙── 3
  476. ├── 1
  477. │ ├── 2 ─ 3
  478. │ │ └── 4 ─ 1
  479. │ └── ...
  480. └── ...
  481. --- add_edge(3, 2) ---
  482. ╙── 3
  483. ├── 1
  484. │ ├── 2 ─ 3
  485. │ │ └── 4 ─ 1
  486. │ └── ...
  487. └── ...
  488. --- add_edge(3, 4) ---
  489. ╙── 1
  490. ├── 2
  491. │ ├── 3 ─ 1
  492. │ │ └── 4 ─ 1, 2
  493. │ └── ...
  494. └── ...
  495. --- add_edge(4, 1) ---
  496. ╙── 1
  497. ├── 2
  498. │ ├── 3 ─ 1
  499. │ │ └── 4 ─ 1, 2
  500. │ └── ...
  501. └── ...
  502. --- add_edge(4, 2) ---
  503. ╙── 1
  504. ├── 2
  505. │ ├── 3 ─ 1
  506. │ │ └── 4 ─ 1, 2
  507. │ └── ...
  508. └── ...
  509. --- add_edge(4, 3) ---
  510. ╙── 1
  511. ├── 2
  512. │ ├── 3 ─ 1
  513. │ │ └── 4 ─ 1, 2
  514. │ └── ...
  515. └── ...
  516. """
  517. ).strip()
  518. assert target == text
  519. def test_write_network_text_iterative_add_random_directed_edges():
  520. """
  521. Walk through the cases going from a disconnected to fully connected graph
  522. """
  523. rng = random.Random(724466096)
  524. graph = nx.DiGraph()
  525. graph.add_nodes_from([1, 2, 3, 4, 5])
  526. possible_edges = list(product(graph.nodes, graph.nodes))
  527. rng.shuffle(possible_edges)
  528. graph.add_edges_from(possible_edges[0:8])
  529. lines = []
  530. write = lines.append
  531. write("--- initial state ---")
  532. nx.write_network_text(graph, path=write, end="")
  533. for i, j in possible_edges[8:12]:
  534. write(f"--- add_edge({i}, {j}) ---")
  535. graph.add_edge(i, j)
  536. nx.write_network_text(graph, path=write, end="")
  537. text = "\n".join(lines)
  538. target = dedent(
  539. """
  540. --- initial state ---
  541. ╙── 3 ╾ 5
  542. └─╼ 2 ╾ 2
  543. ├─╼ 4 ╾ 4
  544. │ ├─╼ 5
  545. │ │ ├─╼ 1 ╾ 1
  546. │ │ │ └─╼ ...
  547. │ │ └─╼ ...
  548. │ └─╼ ...
  549. └─╼ ...
  550. --- add_edge(4, 1) ---
  551. ╙── 3 ╾ 5
  552. └─╼ 2 ╾ 2
  553. ├─╼ 4 ╾ 4
  554. │ ├─╼ 5
  555. │ │ ├─╼ 1 ╾ 1, 4
  556. │ │ │ └─╼ ...
  557. │ │ └─╼ ...
  558. │ └─╼ ...
  559. └─╼ ...
  560. --- add_edge(2, 1) ---
  561. ╙── 3 ╾ 5
  562. └─╼ 2 ╾ 2
  563. ├─╼ 4 ╾ 4
  564. │ ├─╼ 5
  565. │ │ ├─╼ 1 ╾ 1, 4, 2
  566. │ │ │ └─╼ ...
  567. │ │ └─╼ ...
  568. │ └─╼ ...
  569. └─╼ ...
  570. --- add_edge(5, 2) ---
  571. ╙── 3 ╾ 5
  572. └─╼ 2 ╾ 2, 5
  573. ├─╼ 4 ╾ 4
  574. │ ├─╼ 5
  575. │ │ ├─╼ 1 ╾ 1, 4, 2
  576. │ │ │ └─╼ ...
  577. │ │ └─╼ ...
  578. │ └─╼ ...
  579. └─╼ ...
  580. --- add_edge(1, 5) ---
  581. ╙── 3 ╾ 5
  582. └─╼ 2 ╾ 2, 5
  583. ├─╼ 4 ╾ 4
  584. │ ├─╼ 5 ╾ 1
  585. │ │ ├─╼ 1 ╾ 1, 4, 2
  586. │ │ │ └─╼ ...
  587. │ │ └─╼ ...
  588. │ └─╼ ...
  589. └─╼ ...
  590. """
  591. ).strip()
  592. assert target == text
  593. def test_write_network_text_nearly_forest():
  594. g = nx.DiGraph()
  595. g.add_edge(1, 2)
  596. g.add_edge(1, 5)
  597. g.add_edge(2, 3)
  598. g.add_edge(3, 4)
  599. g.add_edge(5, 6)
  600. g.add_edge(6, 7)
  601. g.add_edge(6, 8)
  602. orig = g.copy()
  603. g.add_edge(1, 8) # forward edge
  604. g.add_edge(4, 2) # back edge
  605. g.add_edge(6, 3) # cross edge
  606. lines = []
  607. write = lines.append
  608. write("--- directed case ---")
  609. nx.write_network_text(orig, path=write, end="")
  610. write("--- add (1, 8), (4, 2), (6, 3) ---")
  611. nx.write_network_text(g, path=write, end="")
  612. write("--- undirected case ---")
  613. nx.write_network_text(orig.to_undirected(), path=write, sources=[1], end="")
  614. write("--- add (1, 8), (4, 2), (6, 3) ---")
  615. nx.write_network_text(g.to_undirected(), path=write, sources=[1], end="")
  616. text = "\n".join(lines)
  617. target = dedent(
  618. """
  619. --- directed case ---
  620. ╙── 1
  621. ├─╼ 2
  622. │ └─╼ 3
  623. │ └─╼ 4
  624. └─╼ 5
  625. └─╼ 6
  626. ├─╼ 7
  627. └─╼ 8
  628. --- add (1, 8), (4, 2), (6, 3) ---
  629. ╙── 1
  630. ├─╼ 2 ╾ 4
  631. │ └─╼ 3 ╾ 6
  632. │ └─╼ 4
  633. │ └─╼ ...
  634. ├─╼ 5
  635. │ └─╼ 6
  636. │ ├─╼ 7
  637. │ ├─╼ 8 ╾ 1
  638. │ └─╼ ...
  639. └─╼ ...
  640. --- undirected case ---
  641. ╙── 1
  642. ├── 2
  643. │ └── 3
  644. │ └── 4
  645. └── 5
  646. └── 6
  647. ├── 7
  648. └── 8
  649. --- add (1, 8), (4, 2), (6, 3) ---
  650. ╙── 1
  651. ├── 2
  652. │ ├── 3
  653. │ │ ├── 4 ─ 2
  654. │ │ └── 6
  655. │ │ ├── 5 ─ 1
  656. │ │ ├── 7
  657. │ │ └── 8 ─ 1
  658. │ └── ...
  659. └── ...
  660. """
  661. ).strip()
  662. assert target == text
  663. def test_write_network_text_complete_graph_ascii_only():
  664. graph = nx.generators.complete_graph(5, create_using=nx.DiGraph)
  665. lines = []
  666. write = lines.append
  667. write("--- directed case ---")
  668. nx.write_network_text(graph, path=write, ascii_only=True, end="")
  669. write("--- undirected case ---")
  670. nx.write_network_text(graph.to_undirected(), path=write, ascii_only=True, end="")
  671. text = "\n".join(lines)
  672. target = dedent(
  673. """
  674. --- directed case ---
  675. +-- 0 <- 1, 2, 3, 4
  676. |-> 1 <- 2, 3, 4
  677. | |-> 2 <- 0, 3, 4
  678. | | |-> 3 <- 0, 1, 4
  679. | | | |-> 4 <- 0, 1, 2
  680. | | | | L-> ...
  681. | | | L-> ...
  682. | | L-> ...
  683. | L-> ...
  684. L-> ...
  685. --- undirected case ---
  686. +-- 0
  687. |-- 1
  688. | |-- 2 - 0
  689. | | |-- 3 - 0, 1
  690. | | | L-- 4 - 0, 1, 2
  691. | | L-- ...
  692. | L-- ...
  693. L-- ...
  694. """
  695. ).strip()
  696. assert target == text
  697. def test_write_network_text_with_labels():
  698. graph = nx.generators.complete_graph(5, create_using=nx.DiGraph)
  699. for n in graph.nodes:
  700. graph.nodes[n]["label"] = f"Node(n={n})"
  701. lines = []
  702. write = lines.append
  703. nx.write_network_text(graph, path=write, with_labels=True, ascii_only=False, end="")
  704. text = "\n".join(lines)
  705. # Non trees with labels can get somewhat out of hand with network text
  706. # because we need to immediately show every non-tree edge to the right
  707. target = dedent(
  708. """
  709. ╙── Node(n=0) ╾ Node(n=1), Node(n=2), Node(n=3), Node(n=4)
  710. ├─╼ Node(n=1) ╾ Node(n=2), Node(n=3), Node(n=4)
  711. │ ├─╼ Node(n=2) ╾ Node(n=0), Node(n=3), Node(n=4)
  712. │ │ ├─╼ Node(n=3) ╾ Node(n=0), Node(n=1), Node(n=4)
  713. │ │ │ ├─╼ Node(n=4) ╾ Node(n=0), Node(n=1), Node(n=2)
  714. │ │ │ │ └─╼ ...
  715. │ │ │ └─╼ ...
  716. │ │ └─╼ ...
  717. │ └─╼ ...
  718. └─╼ ...
  719. """
  720. ).strip()
  721. assert target == text
  722. def test_write_network_text_complete_graphs():
  723. lines = []
  724. write = lines.append
  725. for k in [0, 1, 2, 3, 4, 5]:
  726. g = nx.generators.complete_graph(k)
  727. write(f"--- undirected k={k} ---")
  728. nx.write_network_text(g, path=write, end="")
  729. for k in [0, 1, 2, 3, 4, 5]:
  730. g = nx.generators.complete_graph(k, nx.DiGraph)
  731. write(f"--- directed k={k} ---")
  732. nx.write_network_text(g, path=write, end="")
  733. text = "\n".join(lines)
  734. target = dedent(
  735. """
  736. --- undirected k=0 ---
  737. --- undirected k=1 ---
  738. ╙── 0
  739. --- undirected k=2 ---
  740. ╙── 0
  741. └── 1
  742. --- undirected k=3 ---
  743. ╙── 0
  744. ├── 1
  745. │ └── 2 ─ 0
  746. └── ...
  747. --- undirected k=4 ---
  748. ╙── 0
  749. ├── 1
  750. │ ├── 2 ─ 0
  751. │ │ └── 3 ─ 0, 1
  752. │ └── ...
  753. └── ...
  754. --- undirected k=5 ---
  755. ╙── 0
  756. ├── 1
  757. │ ├── 2 ─ 0
  758. │ │ ├── 3 ─ 0, 1
  759. │ │ │ └── 4 ─ 0, 1, 2
  760. │ │ └── ...
  761. │ └── ...
  762. └── ...
  763. --- directed k=0 ---
  764. --- directed k=1 ---
  765. ╙── 0
  766. --- directed k=2 ---
  767. ╙── 0 ╾ 1
  768. └─╼ 1
  769. └─╼ ...
  770. --- directed k=3 ---
  771. ╙── 0 ╾ 1, 2
  772. ├─╼ 1 ╾ 2
  773. │ ├─╼ 2 ╾ 0
  774. │ │ └─╼ ...
  775. │ └─╼ ...
  776. └─╼ ...
  777. --- directed k=4 ---
  778. ╙── 0 ╾ 1, 2, 3
  779. ├─╼ 1 ╾ 2, 3
  780. │ ├─╼ 2 ╾ 0, 3
  781. │ │ ├─╼ 3 ╾ 0, 1
  782. │ │ │ └─╼ ...
  783. │ │ └─╼ ...
  784. │ └─╼ ...
  785. └─╼ ...
  786. --- directed k=5 ---
  787. ╙── 0 ╾ 1, 2, 3, 4
  788. ├─╼ 1 ╾ 2, 3, 4
  789. │ ├─╼ 2 ╾ 0, 3, 4
  790. │ │ ├─╼ 3 ╾ 0, 1, 4
  791. │ │ │ ├─╼ 4 ╾ 0, 1, 2
  792. │ │ │ │ └─╼ ...
  793. │ │ │ └─╼ ...
  794. │ │ └─╼ ...
  795. │ └─╼ ...
  796. └─╼ ...
  797. """
  798. ).strip()
  799. assert target == text
  800. def test_write_network_text_multiple_sources():
  801. g = nx.DiGraph()
  802. g.add_edge(1, 2)
  803. g.add_edge(1, 3)
  804. g.add_edge(2, 4)
  805. g.add_edge(3, 5)
  806. g.add_edge(3, 6)
  807. g.add_edge(5, 4)
  808. g.add_edge(4, 1)
  809. g.add_edge(1, 5)
  810. lines = []
  811. write = lines.append
  812. # Use each node as the starting point to demonstrate how the representation
  813. # changes.
  814. nodes = sorted(g.nodes())
  815. for n in nodes:
  816. write(f"--- source node: {n} ---")
  817. nx.write_network_text(g, path=write, sources=[n], end="")
  818. text = "\n".join(lines)
  819. target = dedent(
  820. """
  821. --- source node: 1 ---
  822. ╙── 1 ╾ 4
  823. ├─╼ 2
  824. │ └─╼ 4 ╾ 5
  825. │ └─╼ ...
  826. ├─╼ 3
  827. │ ├─╼ 5 ╾ 1
  828. │ │ └─╼ ...
  829. │ └─╼ 6
  830. └─╼ ...
  831. --- source node: 2 ---
  832. ╙── 2 ╾ 1
  833. └─╼ 4 ╾ 5
  834. └─╼ 1
  835. ├─╼ 3
  836. │ ├─╼ 5 ╾ 1
  837. │ │ └─╼ ...
  838. │ └─╼ 6
  839. └─╼ ...
  840. --- source node: 3 ---
  841. ╙── 3 ╾ 1
  842. ├─╼ 5 ╾ 1
  843. │ └─╼ 4 ╾ 2
  844. │ └─╼ 1
  845. │ ├─╼ 2
  846. │ │ └─╼ ...
  847. │ └─╼ ...
  848. └─╼ 6
  849. --- source node: 4 ---
  850. ╙── 4 ╾ 2, 5
  851. └─╼ 1
  852. ├─╼ 2
  853. │ └─╼ ...
  854. ├─╼ 3
  855. │ ├─╼ 5 ╾ 1
  856. │ │ └─╼ ...
  857. │ └─╼ 6
  858. └─╼ ...
  859. --- source node: 5 ---
  860. ╙── 5 ╾ 3, 1
  861. └─╼ 4 ╾ 2
  862. └─╼ 1
  863. ├─╼ 2
  864. │ └─╼ ...
  865. ├─╼ 3
  866. │ ├─╼ 6
  867. │ └─╼ ...
  868. └─╼ ...
  869. --- source node: 6 ---
  870. ╙── 6 ╾ 3
  871. """
  872. ).strip()
  873. assert target == text
  874. def test_write_network_text_star_graph():
  875. graph = nx.star_graph(5, create_using=nx.Graph)
  876. lines = []
  877. write = lines.append
  878. nx.write_network_text(graph, path=write, end="")
  879. text = "\n".join(lines)
  880. target = dedent(
  881. """
  882. ╙── 1
  883. └── 0
  884. ├── 2
  885. ├── 3
  886. ├── 4
  887. └── 5
  888. """
  889. ).strip()
  890. assert target == text
  891. def test_write_network_text_path_graph():
  892. graph = nx.path_graph(3, create_using=nx.Graph)
  893. lines = []
  894. write = lines.append
  895. nx.write_network_text(graph, path=write, end="")
  896. text = "\n".join(lines)
  897. target = dedent(
  898. """
  899. ╙── 0
  900. └── 1
  901. └── 2
  902. """
  903. ).strip()
  904. assert target == text
  905. def test_write_network_text_lollipop_graph():
  906. graph = nx.lollipop_graph(4, 2, create_using=nx.Graph)
  907. lines = []
  908. write = lines.append
  909. nx.write_network_text(graph, path=write, end="")
  910. text = "\n".join(lines)
  911. target = dedent(
  912. """
  913. ╙── 5
  914. └── 4
  915. └── 3
  916. ├── 0
  917. │ ├── 1 ─ 3
  918. │ │ └── 2 ─ 0, 3
  919. │ └── ...
  920. └── ...
  921. """
  922. ).strip()
  923. assert target == text
  924. def test_write_network_text_wheel_graph():
  925. graph = nx.wheel_graph(7, create_using=nx.Graph)
  926. lines = []
  927. write = lines.append
  928. nx.write_network_text(graph, path=write, end="")
  929. text = "\n".join(lines)
  930. target = dedent(
  931. """
  932. ╙── 1
  933. ├── 0
  934. │ ├── 2 ─ 1
  935. │ │ └── 3 ─ 0
  936. │ │ └── 4 ─ 0
  937. │ │ └── 5 ─ 0
  938. │ │ └── 6 ─ 0, 1
  939. │ └── ...
  940. └── ...
  941. """
  942. ).strip()
  943. assert target == text
  944. def test_write_network_text_circular_ladder_graph():
  945. graph = nx.circular_ladder_graph(4, create_using=nx.Graph)
  946. lines = []
  947. write = lines.append
  948. nx.write_network_text(graph, path=write, end="")
  949. text = "\n".join(lines)
  950. target = dedent(
  951. """
  952. ╙── 0
  953. ├── 1
  954. │ ├── 2
  955. │ │ ├── 3 ─ 0
  956. │ │ │ └── 7
  957. │ │ │ ├── 6 ─ 2
  958. │ │ │ │ └── 5 ─ 1
  959. │ │ │ │ └── 4 ─ 0, 7
  960. │ │ │ └── ...
  961. │ │ └── ...
  962. │ └── ...
  963. └── ...
  964. """
  965. ).strip()
  966. assert target == text
  967. def test_write_network_text_dorogovtsev_goltsev_mendes_graph():
  968. graph = nx.dorogovtsev_goltsev_mendes_graph(4, create_using=nx.Graph)
  969. lines = []
  970. write = lines.append
  971. nx.write_network_text(graph, path=write, end="")
  972. text = "\n".join(lines)
  973. target = dedent(
  974. """
  975. ╙── 15
  976. ├── 0
  977. │ ├── 1 ─ 15
  978. │ │ ├── 2 ─ 0
  979. │ │ │ ├── 4 ─ 0
  980. │ │ │ │ ├── 9 ─ 0
  981. │ │ │ │ │ ├── 22 ─ 0
  982. │ │ │ │ │ └── 38 ─ 4
  983. │ │ │ │ ├── 13 ─ 2
  984. │ │ │ │ │ ├── 34 ─ 2
  985. │ │ │ │ │ └── 39 ─ 4
  986. │ │ │ │ ├── 18 ─ 0
  987. │ │ │ │ ├── 30 ─ 2
  988. │ │ │ │ └── ...
  989. │ │ │ ├── 5 ─ 1
  990. │ │ │ │ ├── 12 ─ 1
  991. │ │ │ │ │ ├── 29 ─ 1
  992. │ │ │ │ │ └── 40 ─ 5
  993. │ │ │ │ ├── 14 ─ 2
  994. │ │ │ │ │ ├── 35 ─ 2
  995. │ │ │ │ │ └── 41 ─ 5
  996. │ │ │ │ ├── 25 ─ 1
  997. │ │ │ │ ├── 31 ─ 2
  998. │ │ │ │ └── ...
  999. │ │ │ ├── 7 ─ 0
  1000. │ │ │ │ ├── 20 ─ 0
  1001. │ │ │ │ └── 32 ─ 2
  1002. │ │ │ ├── 10 ─ 1
  1003. │ │ │ │ ├── 27 ─ 1
  1004. │ │ │ │ └── 33 ─ 2
  1005. │ │ │ ├── 16 ─ 0
  1006. │ │ │ ├── 23 ─ 1
  1007. │ │ │ └── ...
  1008. │ │ ├── 3 ─ 0
  1009. │ │ │ ├── 8 ─ 0
  1010. │ │ │ │ ├── 21 ─ 0
  1011. │ │ │ │ └── 36 ─ 3
  1012. │ │ │ ├── 11 ─ 1
  1013. │ │ │ │ ├── 28 ─ 1
  1014. │ │ │ │ └── 37 ─ 3
  1015. │ │ │ ├── 17 ─ 0
  1016. │ │ │ ├── 24 ─ 1
  1017. │ │ │ └── ...
  1018. │ │ ├── 6 ─ 0
  1019. │ │ │ ├── 19 ─ 0
  1020. │ │ │ └── 26 ─ 1
  1021. │ │ └── ...
  1022. │ └── ...
  1023. └── ...
  1024. """
  1025. ).strip()
  1026. assert target == text
  1027. def test_write_network_text_tree_max_depth():
  1028. orig = nx.balanced_tree(r=1, h=3, create_using=nx.DiGraph)
  1029. lines = []
  1030. write = lines.append
  1031. write("--- directed case, max_depth=0 ---")
  1032. nx.write_network_text(orig, path=write, end="", max_depth=0)
  1033. write("--- directed case, max_depth=1 ---")
  1034. nx.write_network_text(orig, path=write, end="", max_depth=1)
  1035. write("--- directed case, max_depth=2 ---")
  1036. nx.write_network_text(orig, path=write, end="", max_depth=2)
  1037. write("--- directed case, max_depth=3 ---")
  1038. nx.write_network_text(orig, path=write, end="", max_depth=3)
  1039. write("--- directed case, max_depth=4 ---")
  1040. nx.write_network_text(orig, path=write, end="", max_depth=4)
  1041. write("--- undirected case, max_depth=0 ---")
  1042. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=0)
  1043. write("--- undirected case, max_depth=1 ---")
  1044. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=1)
  1045. write("--- undirected case, max_depth=2 ---")
  1046. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=2)
  1047. write("--- undirected case, max_depth=3 ---")
  1048. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=3)
  1049. write("--- undirected case, max_depth=4 ---")
  1050. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=4)
  1051. text = "\n".join(lines)
  1052. target = dedent(
  1053. """
  1054. --- directed case, max_depth=0 ---
  1055. ╙ ...
  1056. --- directed case, max_depth=1 ---
  1057. ╙── 0
  1058. └─╼ ...
  1059. --- directed case, max_depth=2 ---
  1060. ╙── 0
  1061. └─╼ 1
  1062. └─╼ ...
  1063. --- directed case, max_depth=3 ---
  1064. ╙── 0
  1065. └─╼ 1
  1066. └─╼ 2
  1067. └─╼ ...
  1068. --- directed case, max_depth=4 ---
  1069. ╙── 0
  1070. └─╼ 1
  1071. └─╼ 2
  1072. └─╼ 3
  1073. --- undirected case, max_depth=0 ---
  1074. ╙ ...
  1075. --- undirected case, max_depth=1 ---
  1076. ╙── 0 ─ 1
  1077. └── ...
  1078. --- undirected case, max_depth=2 ---
  1079. ╙── 0
  1080. └── 1 ─ 2
  1081. └── ...
  1082. --- undirected case, max_depth=3 ---
  1083. ╙── 0
  1084. └── 1
  1085. └── 2 ─ 3
  1086. └── ...
  1087. --- undirected case, max_depth=4 ---
  1088. ╙── 0
  1089. └── 1
  1090. └── 2
  1091. └── 3
  1092. """
  1093. ).strip()
  1094. assert target == text
  1095. def test_write_network_text_graph_max_depth():
  1096. orig = nx.erdos_renyi_graph(10, 0.15, directed=True, seed=40392)
  1097. lines = []
  1098. write = lines.append
  1099. write("--- directed case, max_depth=None ---")
  1100. nx.write_network_text(orig, path=write, end="", max_depth=None)
  1101. write("--- directed case, max_depth=0 ---")
  1102. nx.write_network_text(orig, path=write, end="", max_depth=0)
  1103. write("--- directed case, max_depth=1 ---")
  1104. nx.write_network_text(orig, path=write, end="", max_depth=1)
  1105. write("--- directed case, max_depth=2 ---")
  1106. nx.write_network_text(orig, path=write, end="", max_depth=2)
  1107. write("--- directed case, max_depth=3 ---")
  1108. nx.write_network_text(orig, path=write, end="", max_depth=3)
  1109. write("--- undirected case, max_depth=None ---")
  1110. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=None)
  1111. write("--- undirected case, max_depth=0 ---")
  1112. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=0)
  1113. write("--- undirected case, max_depth=1 ---")
  1114. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=1)
  1115. write("--- undirected case, max_depth=2 ---")
  1116. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=2)
  1117. write("--- undirected case, max_depth=3 ---")
  1118. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=3)
  1119. text = "\n".join(lines)
  1120. target = dedent(
  1121. """
  1122. --- directed case, max_depth=None ---
  1123. ╟── 4
  1124. ╎ ├─╼ 0 ╾ 3
  1125. ╎ ├─╼ 5 ╾ 7
  1126. ╎ │ └─╼ 3
  1127. ╎ │ ├─╼ 1 ╾ 9
  1128. ╎ │ │ └─╼ 9 ╾ 6
  1129. ╎ │ │ ├─╼ 6
  1130. ╎ │ │ │ └─╼ ...
  1131. ╎ │ │ ├─╼ 7 ╾ 4
  1132. ╎ │ │ │ ├─╼ 2
  1133. ╎ │ │ │ └─╼ ...
  1134. ╎ │ │ └─╼ ...
  1135. ╎ │ └─╼ ...
  1136. ╎ └─╼ ...
  1137. ╙── 8
  1138. --- directed case, max_depth=0 ---
  1139. ╙ ...
  1140. --- directed case, max_depth=1 ---
  1141. ╟── 4
  1142. ╎ └─╼ ...
  1143. ╙── 8
  1144. --- directed case, max_depth=2 ---
  1145. ╟── 4
  1146. ╎ ├─╼ 0 ╾ 3
  1147. ╎ ├─╼ 5 ╾ 7
  1148. ╎ │ └─╼ ...
  1149. ╎ └─╼ 7 ╾ 9
  1150. ╎ └─╼ ...
  1151. ╙── 8
  1152. --- directed case, max_depth=3 ---
  1153. ╟── 4
  1154. ╎ ├─╼ 0 ╾ 3
  1155. ╎ ├─╼ 5 ╾ 7
  1156. ╎ │ └─╼ 3
  1157. ╎ │ └─╼ ...
  1158. ╎ └─╼ 7 ╾ 9
  1159. ╎ ├─╼ 2
  1160. ╎ └─╼ ...
  1161. ╙── 8
  1162. --- undirected case, max_depth=None ---
  1163. ╟── 8
  1164. ╙── 2
  1165. └── 7
  1166. ├── 4
  1167. │ ├── 0
  1168. │ │ └── 3
  1169. │ │ ├── 1
  1170. │ │ │ └── 9 ─ 7
  1171. │ │ │ └── 6
  1172. │ │ └── 5 ─ 4, 7
  1173. │ └── ...
  1174. └── ...
  1175. --- undirected case, max_depth=0 ---
  1176. ╙ ...
  1177. --- undirected case, max_depth=1 ---
  1178. ╟── 8
  1179. ╙── 2 ─ 7
  1180. └── ...
  1181. --- undirected case, max_depth=2 ---
  1182. ╟── 8
  1183. ╙── 2
  1184. └── 7 ─ 4, 5, 9
  1185. └── ...
  1186. --- undirected case, max_depth=3 ---
  1187. ╟── 8
  1188. ╙── 2
  1189. └── 7
  1190. ├── 4 ─ 0, 5
  1191. │ └── ...
  1192. ├── 5 ─ 4, 3
  1193. │ └── ...
  1194. └── 9 ─ 1, 6
  1195. └── ...
  1196. """
  1197. ).strip()
  1198. assert target == text
  1199. def test_write_network_text_clique_max_depth():
  1200. orig = nx.complete_graph(5, nx.DiGraph)
  1201. lines = []
  1202. write = lines.append
  1203. write("--- directed case, max_depth=None ---")
  1204. nx.write_network_text(orig, path=write, end="", max_depth=None)
  1205. write("--- directed case, max_depth=0 ---")
  1206. nx.write_network_text(orig, path=write, end="", max_depth=0)
  1207. write("--- directed case, max_depth=1 ---")
  1208. nx.write_network_text(orig, path=write, end="", max_depth=1)
  1209. write("--- directed case, max_depth=2 ---")
  1210. nx.write_network_text(orig, path=write, end="", max_depth=2)
  1211. write("--- directed case, max_depth=3 ---")
  1212. nx.write_network_text(orig, path=write, end="", max_depth=3)
  1213. write("--- undirected case, max_depth=None ---")
  1214. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=None)
  1215. write("--- undirected case, max_depth=0 ---")
  1216. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=0)
  1217. write("--- undirected case, max_depth=1 ---")
  1218. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=1)
  1219. write("--- undirected case, max_depth=2 ---")
  1220. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=2)
  1221. write("--- undirected case, max_depth=3 ---")
  1222. nx.write_network_text(orig.to_undirected(), path=write, end="", max_depth=3)
  1223. text = "\n".join(lines)
  1224. target = dedent(
  1225. """
  1226. --- directed case, max_depth=None ---
  1227. ╙── 0 ╾ 1, 2, 3, 4
  1228. ├─╼ 1 ╾ 2, 3, 4
  1229. │ ├─╼ 2 ╾ 0, 3, 4
  1230. │ │ ├─╼ 3 ╾ 0, 1, 4
  1231. │ │ │ ├─╼ 4 ╾ 0, 1, 2
  1232. │ │ │ │ └─╼ ...
  1233. │ │ │ └─╼ ...
  1234. │ │ └─╼ ...
  1235. │ └─╼ ...
  1236. └─╼ ...
  1237. --- directed case, max_depth=0 ---
  1238. ╙ ...
  1239. --- directed case, max_depth=1 ---
  1240. ╙── 0 ╾ 1, 2, 3, 4
  1241. └─╼ ...
  1242. --- directed case, max_depth=2 ---
  1243. ╙── 0 ╾ 1, 2, 3, 4
  1244. ├─╼ 1 ╾ 2, 3, 4
  1245. │ └─╼ ...
  1246. ├─╼ 2 ╾ 1, 3, 4
  1247. │ └─╼ ...
  1248. ├─╼ 3 ╾ 1, 2, 4
  1249. │ └─╼ ...
  1250. └─╼ 4 ╾ 1, 2, 3
  1251. └─╼ ...
  1252. --- directed case, max_depth=3 ---
  1253. ╙── 0 ╾ 1, 2, 3, 4
  1254. ├─╼ 1 ╾ 2, 3, 4
  1255. │ ├─╼ 2 ╾ 0, 3, 4
  1256. │ │ └─╼ ...
  1257. │ ├─╼ 3 ╾ 0, 2, 4
  1258. │ │ └─╼ ...
  1259. │ ├─╼ 4 ╾ 0, 2, 3
  1260. │ │ └─╼ ...
  1261. │ └─╼ ...
  1262. └─╼ ...
  1263. --- undirected case, max_depth=None ---
  1264. ╙── 0
  1265. ├── 1
  1266. │ ├── 2 ─ 0
  1267. │ │ ├── 3 ─ 0, 1
  1268. │ │ │ └── 4 ─ 0, 1, 2
  1269. │ │ └── ...
  1270. │ └── ...
  1271. └── ...
  1272. --- undirected case, max_depth=0 ---
  1273. ╙ ...
  1274. --- undirected case, max_depth=1 ---
  1275. ╙── 0 ─ 1, 2, 3, 4
  1276. └── ...
  1277. --- undirected case, max_depth=2 ---
  1278. ╙── 0
  1279. ├── 1 ─ 2, 3, 4
  1280. │ └── ...
  1281. ├── 2 ─ 1, 3, 4
  1282. │ └── ...
  1283. ├── 3 ─ 1, 2, 4
  1284. │ └── ...
  1285. └── 4 ─ 1, 2, 3
  1286. --- undirected case, max_depth=3 ---
  1287. ╙── 0
  1288. ├── 1
  1289. │ ├── 2 ─ 0, 3, 4
  1290. │ │ └── ...
  1291. │ ├── 3 ─ 0, 2, 4
  1292. │ │ └── ...
  1293. │ └── 4 ─ 0, 2, 3
  1294. └── ...
  1295. """
  1296. ).strip()
  1297. assert target == text
  1298. def test_write_network_text_custom_label():
  1299. # Create a directed forest with labels
  1300. graph = nx.erdos_renyi_graph(5, 0.4, directed=True, seed=359222358)
  1301. for node in graph.nodes:
  1302. graph.nodes[node]["label"] = f"Node({node})"
  1303. graph.nodes[node]["chr"] = chr(node + ord("a") - 1)
  1304. if node % 2 == 0:
  1305. graph.nodes[node]["part"] = chr(node + ord("a"))
  1306. lines = []
  1307. write = lines.append
  1308. write("--- when with_labels=True, uses the 'label' attr ---")
  1309. nx.write_network_text(graph, path=write, with_labels=True, end="", max_depth=None)
  1310. write("--- when with_labels=False, uses str(node) value ---")
  1311. nx.write_network_text(graph, path=write, with_labels=False, end="", max_depth=None)
  1312. write("--- when with_labels is a string, use that attr ---")
  1313. nx.write_network_text(graph, path=write, with_labels="chr", end="", max_depth=None)
  1314. write("--- fallback to str(node) when the attr does not exist ---")
  1315. nx.write_network_text(graph, path=write, with_labels="part", end="", max_depth=None)
  1316. text = "\n".join(lines)
  1317. target = dedent(
  1318. """
  1319. --- when with_labels=True, uses the 'label' attr ---
  1320. ╙── Node(1)
  1321. └─╼ Node(3) ╾ Node(2)
  1322. ├─╼ Node(0)
  1323. │ ├─╼ Node(2) ╾ Node(3), Node(4)
  1324. │ │ └─╼ ...
  1325. │ └─╼ Node(4)
  1326. │ └─╼ ...
  1327. └─╼ ...
  1328. --- when with_labels=False, uses str(node) value ---
  1329. ╙── 1
  1330. └─╼ 3 ╾ 2
  1331. ├─╼ 0
  1332. │ ├─╼ 2 ╾ 3, 4
  1333. │ │ └─╼ ...
  1334. │ └─╼ 4
  1335. │ └─╼ ...
  1336. └─╼ ...
  1337. --- when with_labels is a string, use that attr ---
  1338. ╙── a
  1339. └─╼ c ╾ b
  1340. ├─╼ `
  1341. │ ├─╼ b ╾ c, d
  1342. │ │ └─╼ ...
  1343. │ └─╼ d
  1344. │ └─╼ ...
  1345. └─╼ ...
  1346. --- fallback to str(node) when the attr does not exist ---
  1347. ╙── 1
  1348. └─╼ 3 ╾ c
  1349. ├─╼ a
  1350. │ ├─╼ c ╾ 3, e
  1351. │ │ └─╼ ...
  1352. │ └─╼ e
  1353. │ └─╼ ...
  1354. └─╼ ...
  1355. """
  1356. ).strip()
  1357. assert target == text
  1358. def test_write_network_text_vertical_chains():
  1359. graph1 = nx.lollipop_graph(4, 2, create_using=nx.Graph)
  1360. graph1.add_edge(0, -1)
  1361. graph1.add_edge(-1, -2)
  1362. graph1.add_edge(-2, -3)
  1363. graph2 = graph1.to_directed()
  1364. graph2.remove_edges_from([(u, v) for u, v in graph2.edges if v > u])
  1365. lines = []
  1366. write = lines.append
  1367. write("--- Undirected UTF ---")
  1368. nx.write_network_text(graph1, path=write, end="", vertical_chains=True)
  1369. write("--- Undirected ASCI ---")
  1370. nx.write_network_text(
  1371. graph1, path=write, end="", vertical_chains=True, ascii_only=True
  1372. )
  1373. write("--- Directed UTF ---")
  1374. nx.write_network_text(graph2, path=write, end="", vertical_chains=True)
  1375. write("--- Directed ASCI ---")
  1376. nx.write_network_text(
  1377. graph2, path=write, end="", vertical_chains=True, ascii_only=True
  1378. )
  1379. text = "\n".join(lines)
  1380. target = dedent(
  1381. """
  1382. --- Undirected UTF ---
  1383. ╙── 5
  1384. 4
  1385. 3
  1386. ├── 0
  1387. │ ├── 1 ─ 3
  1388. │ │ │
  1389. │ │ 2 ─ 0, 3
  1390. │ ├── -1
  1391. │ │ │
  1392. │ │ -2
  1393. │ │ │
  1394. │ │ -3
  1395. │ └── ...
  1396. └── ...
  1397. --- Undirected ASCI ---
  1398. +-- 5
  1399. |
  1400. 4
  1401. |
  1402. 3
  1403. |-- 0
  1404. | |-- 1 - 3
  1405. | | |
  1406. | | 2 - 0, 3
  1407. | |-- -1
  1408. | | |
  1409. | | -2
  1410. | | |
  1411. | | -3
  1412. | L-- ...
  1413. L-- ...
  1414. --- Directed UTF ---
  1415. ╙── 5
  1416. 4
  1417. 3
  1418. ├─╼ 0 ╾ 1, 2
  1419. │ ╽
  1420. │ -1
  1421. │ ╽
  1422. │ -2
  1423. │ ╽
  1424. │ -3
  1425. ├─╼ 1 ╾ 2
  1426. │ └─╼ ...
  1427. └─╼ 2
  1428. └─╼ ...
  1429. --- Directed ASCI ---
  1430. +-- 5
  1431. !
  1432. 4
  1433. !
  1434. 3
  1435. |-> 0 <- 1, 2
  1436. | !
  1437. | -1
  1438. | !
  1439. | -2
  1440. | !
  1441. | -3
  1442. |-> 1 <- 2
  1443. | L-> ...
  1444. L-> 2
  1445. L-> ...
  1446. """
  1447. ).strip()
  1448. assert target == text
  1449. def test_collapse_directed():
  1450. graph = nx.balanced_tree(r=2, h=3, create_using=nx.DiGraph)
  1451. lines = []
  1452. write = lines.append
  1453. write("--- Original ---")
  1454. nx.write_network_text(graph, path=write, end="")
  1455. graph.nodes[1]["collapse"] = True
  1456. write("--- Collapse Node 1 ---")
  1457. nx.write_network_text(graph, path=write, end="")
  1458. write("--- Add alternate path (5, 3) to collapsed zone")
  1459. graph.add_edge(5, 3)
  1460. nx.write_network_text(graph, path=write, end="")
  1461. write("--- Collapse Node 0 ---")
  1462. graph.nodes[0]["collapse"] = True
  1463. nx.write_network_text(graph, path=write, end="")
  1464. text = "\n".join(lines)
  1465. target = dedent(
  1466. """
  1467. --- Original ---
  1468. ╙── 0
  1469. ├─╼ 1
  1470. │ ├─╼ 3
  1471. │ │ ├─╼ 7
  1472. │ │ └─╼ 8
  1473. │ └─╼ 4
  1474. │ ├─╼ 9
  1475. │ └─╼ 10
  1476. └─╼ 2
  1477. ├─╼ 5
  1478. │ ├─╼ 11
  1479. │ └─╼ 12
  1480. └─╼ 6
  1481. ├─╼ 13
  1482. └─╼ 14
  1483. --- Collapse Node 1 ---
  1484. ╙── 0
  1485. ├─╼ 1
  1486. │ └─╼ ...
  1487. └─╼ 2
  1488. ├─╼ 5
  1489. │ ├─╼ 11
  1490. │ └─╼ 12
  1491. └─╼ 6
  1492. ├─╼ 13
  1493. └─╼ 14
  1494. --- Add alternate path (5, 3) to collapsed zone
  1495. ╙── 0
  1496. ├─╼ 1
  1497. │ └─╼ ...
  1498. └─╼ 2
  1499. ├─╼ 5
  1500. │ ├─╼ 11
  1501. │ ├─╼ 12
  1502. │ └─╼ 3 ╾ 1
  1503. │ ├─╼ 7
  1504. │ └─╼ 8
  1505. └─╼ 6
  1506. ├─╼ 13
  1507. └─╼ 14
  1508. --- Collapse Node 0 ---
  1509. ╙── 0
  1510. └─╼ ...
  1511. """
  1512. ).strip()
  1513. assert target == text
  1514. def test_collapse_undirected():
  1515. graph = nx.balanced_tree(r=2, h=3, create_using=nx.Graph)
  1516. lines = []
  1517. write = lines.append
  1518. write("--- Original ---")
  1519. nx.write_network_text(graph, path=write, end="", sources=[0])
  1520. graph.nodes[1]["collapse"] = True
  1521. write("--- Collapse Node 1 ---")
  1522. nx.write_network_text(graph, path=write, end="", sources=[0])
  1523. write("--- Add alternate path (5, 3) to collapsed zone")
  1524. graph.add_edge(5, 3)
  1525. nx.write_network_text(graph, path=write, end="", sources=[0])
  1526. write("--- Collapse Node 0 ---")
  1527. graph.nodes[0]["collapse"] = True
  1528. nx.write_network_text(graph, path=write, end="", sources=[0])
  1529. text = "\n".join(lines)
  1530. target = dedent(
  1531. """
  1532. --- Original ---
  1533. ╙── 0
  1534. ├── 1
  1535. │ ├── 3
  1536. │ │ ├── 7
  1537. │ │ └── 8
  1538. │ └── 4
  1539. │ ├── 9
  1540. │ └── 10
  1541. └── 2
  1542. ├── 5
  1543. │ ├── 11
  1544. │ └── 12
  1545. └── 6
  1546. ├── 13
  1547. └── 14
  1548. --- Collapse Node 1 ---
  1549. ╙── 0
  1550. ├── 1 ─ 3, 4
  1551. │ └── ...
  1552. └── 2
  1553. ├── 5
  1554. │ ├── 11
  1555. │ └── 12
  1556. └── 6
  1557. ├── 13
  1558. └── 14
  1559. --- Add alternate path (5, 3) to collapsed zone
  1560. ╙── 0
  1561. ├── 1 ─ 3, 4
  1562. │ └── ...
  1563. └── 2
  1564. ├── 5
  1565. │ ├── 11
  1566. │ ├── 12
  1567. │ └── 3 ─ 1
  1568. │ ├── 7
  1569. │ └── 8
  1570. └── 6
  1571. ├── 13
  1572. └── 14
  1573. --- Collapse Node 0 ---
  1574. ╙── 0 ─ 1, 2
  1575. └── ...
  1576. """
  1577. ).strip()
  1578. assert target == text
  1579. def generate_test_graphs():
  1580. """
  1581. Generate a gauntlet of different test graphs with different properties
  1582. """
  1583. import random
  1584. rng = random.Random(976689776)
  1585. num_randomized = 3
  1586. for directed in [0, 1]:
  1587. cls = nx.DiGraph if directed else nx.Graph
  1588. for num_nodes in range(17):
  1589. # Disconnected graph
  1590. graph = cls()
  1591. graph.add_nodes_from(range(num_nodes))
  1592. yield graph
  1593. # Randomize graphs
  1594. if num_nodes > 0:
  1595. for p in [0.1, 0.3, 0.5, 0.7, 0.9]:
  1596. for seed in range(num_randomized):
  1597. graph = nx.erdos_renyi_graph(
  1598. num_nodes, p, directed=directed, seed=rng
  1599. )
  1600. yield graph
  1601. yield nx.complete_graph(num_nodes, cls)
  1602. yield nx.path_graph(3, create_using=cls)
  1603. yield nx.balanced_tree(r=1, h=3, create_using=cls)
  1604. if not directed:
  1605. yield nx.circular_ladder_graph(4, create_using=cls)
  1606. yield nx.star_graph(5, create_using=cls)
  1607. yield nx.lollipop_graph(4, 2, create_using=cls)
  1608. yield nx.wheel_graph(7, create_using=cls)
  1609. yield nx.dorogovtsev_goltsev_mendes_graph(4, create_using=cls)
  1610. @pytest.mark.parametrize(
  1611. ("vertical_chains", "ascii_only"),
  1612. tuple(
  1613. [
  1614. (vertical_chains, ascii_only)
  1615. for vertical_chains in [0, 1]
  1616. for ascii_only in [0, 1]
  1617. ]
  1618. ),
  1619. )
  1620. def test_network_text_round_trip(vertical_chains, ascii_only):
  1621. """
  1622. Write the graph to network text format, then parse it back in, assert it is
  1623. the same as the original graph. Passing this test is strong validation of
  1624. both the format generator and parser.
  1625. """
  1626. from networkx.readwrite.text import _parse_network_text
  1627. for graph in generate_test_graphs():
  1628. graph = nx.relabel_nodes(graph, {n: str(n) for n in graph.nodes})
  1629. lines = list(
  1630. nx.generate_network_text(
  1631. graph, vertical_chains=vertical_chains, ascii_only=ascii_only
  1632. )
  1633. )
  1634. new = _parse_network_text(lines)
  1635. try:
  1636. assert new.nodes == graph.nodes
  1637. assert new.edges == graph.edges
  1638. except Exception:
  1639. nx.write_network_text(graph)
  1640. raise