okstra 0.107.2 → 0.109.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (649) hide show
  1. package/package.json +1 -1
  2. package/runtime/BUILD.json +2 -2
  3. package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
  4. package/runtime/prompts/profiles/_common-contract.md +3 -2
  5. package/runtime/python/okstra_ctl/graphify_cmd.py +225 -0
  6. package/runtime/python/okstra_ctl/resolve_task_key.py +15 -9
  7. package/runtime/python/okstra_ctl/session.py +44 -0
  8. package/runtime/python/okstra_project/__init__.py +2 -0
  9. package/runtime/python/okstra_project/state.py +36 -0
  10. package/runtime/python/okstra_token_usage/claude.py +15 -9
  11. package/runtime/python/okstra_token_usage/cli.py +23 -0
  12. package/runtime/python/okstra_token_usage/collect.py +163 -4
  13. package/runtime/python/okstra_vendor/__init__.py +41 -0
  14. package/runtime/python/okstra_vendor/graphify/.vendored-version +1 -0
  15. package/runtime/python/okstra_vendor/graphify/__init__.py +28 -0
  16. package/runtime/python/okstra_vendor/graphify/__main__.py +1371 -0
  17. package/runtime/python/okstra_vendor/graphify/analyze.py +540 -0
  18. package/runtime/python/okstra_vendor/graphify/benchmark.py +129 -0
  19. package/runtime/python/okstra_vendor/graphify/build.py +107 -0
  20. package/runtime/python/okstra_vendor/graphify/cache.py +169 -0
  21. package/runtime/python/okstra_vendor/graphify/cluster.py +137 -0
  22. package/runtime/python/okstra_vendor/graphify/detect.py +510 -0
  23. package/runtime/python/okstra_vendor/graphify/export.py +1014 -0
  24. package/runtime/python/okstra_vendor/graphify/extract.py +3277 -0
  25. package/runtime/python/okstra_vendor/graphify/hooks.py +220 -0
  26. package/runtime/python/okstra_vendor/graphify/ingest.py +297 -0
  27. package/runtime/python/okstra_vendor/graphify/manifest.py +4 -0
  28. package/runtime/python/okstra_vendor/graphify/report.py +175 -0
  29. package/runtime/python/okstra_vendor/graphify/security.py +203 -0
  30. package/runtime/python/okstra_vendor/graphify/serve.py +373 -0
  31. package/runtime/python/okstra_vendor/graphify/skill-aider.md +1184 -0
  32. package/runtime/python/okstra_vendor/graphify/skill-claw.md +1184 -0
  33. package/runtime/python/okstra_vendor/graphify/skill-codex.md +1242 -0
  34. package/runtime/python/okstra_vendor/graphify/skill-copilot.md +1268 -0
  35. package/runtime/python/okstra_vendor/graphify/skill-droid.md +1239 -0
  36. package/runtime/python/okstra_vendor/graphify/skill-kiro.md +1183 -0
  37. package/runtime/python/okstra_vendor/graphify/skill-opencode.md +1238 -0
  38. package/runtime/python/okstra_vendor/graphify/skill-trae.md +1208 -0
  39. package/runtime/python/okstra_vendor/graphify/skill-vscode.md +253 -0
  40. package/runtime/python/okstra_vendor/graphify/skill-windows.md +1245 -0
  41. package/runtime/python/okstra_vendor/graphify/skill.md +1319 -0
  42. package/runtime/python/okstra_vendor/graphify/transcribe.py +182 -0
  43. package/runtime/python/okstra_vendor/graphify/validate.py +72 -0
  44. package/runtime/python/okstra_vendor/graphify/watch.py +188 -0
  45. package/runtime/python/okstra_vendor/graphify/wiki.py +214 -0
  46. package/runtime/python/okstra_vendor/networkx/__init__.py +62 -0
  47. package/runtime/python/okstra_vendor/networkx/algorithms/__init__.py +134 -0
  48. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/__init__.py +26 -0
  49. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clique.py +259 -0
  50. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clustering_coefficient.py +71 -0
  51. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/connectivity.py +412 -0
  52. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/density.py +396 -0
  53. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/distance_measures.py +150 -0
  54. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/dominating_set.py +149 -0
  55. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/kcomponents.py +369 -0
  56. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/matching.py +44 -0
  57. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/maxcut.py +143 -0
  58. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/ramsey.py +53 -0
  59. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/steinertree.py +265 -0
  60. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/__init__.py +0 -0
  61. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_approx_clust_coeff.py +41 -0
  62. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_clique.py +112 -0
  63. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_connectivity.py +199 -0
  64. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_density.py +146 -0
  65. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_distance_measures.py +59 -0
  66. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_dominating_set.py +78 -0
  67. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_kcomponents.py +303 -0
  68. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_matching.py +8 -0
  69. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_maxcut.py +94 -0
  70. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_ramsey.py +31 -0
  71. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_steinertree.py +306 -0
  72. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_traveling_salesman.py +1014 -0
  73. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_treewidth.py +274 -0
  74. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_vertex_cover.py +68 -0
  75. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/traveling_salesman.py +1508 -0
  76. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/treewidth.py +255 -0
  77. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/vertex_cover.py +83 -0
  78. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/__init__.py +5 -0
  79. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/connectivity.py +122 -0
  80. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/correlation.py +302 -0
  81. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/mixing.py +255 -0
  82. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/neighbor_degree.py +160 -0
  83. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/pairs.py +127 -0
  84. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/__init__.py +0 -0
  85. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/base_test.py +81 -0
  86. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_connectivity.py +143 -0
  87. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_correlation.py +122 -0
  88. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_mixing.py +174 -0
  89. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_neighbor_degree.py +107 -0
  90. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_pairs.py +87 -0
  91. package/runtime/python/okstra_vendor/networkx/algorithms/asteroidal.py +164 -0
  92. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/__init__.py +88 -0
  93. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/basic.py +322 -0
  94. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/centrality.py +290 -0
  95. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/cluster.py +289 -0
  96. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/covering.py +57 -0
  97. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/edgelist.py +360 -0
  98. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/extendability.py +105 -0
  99. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/generators.py +603 -0
  100. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/link_analysis.py +316 -0
  101. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matching.py +590 -0
  102. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matrix.py +232 -0
  103. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/projection.py +526 -0
  104. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/redundancy.py +112 -0
  105. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/spectral.py +69 -0
  106. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/__init__.py +0 -0
  107. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_basic.py +125 -0
  108. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_centrality.py +192 -0
  109. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_cluster.py +84 -0
  110. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_covering.py +33 -0
  111. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_edgelist.py +240 -0
  112. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_extendability.py +334 -0
  113. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_generators.py +407 -0
  114. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_link_analysis.py +218 -0
  115. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matching.py +327 -0
  116. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matrix.py +138 -0
  117. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_project.py +409 -0
  118. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_redundancy.py +35 -0
  119. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py +80 -0
  120. package/runtime/python/okstra_vendor/networkx/algorithms/boundary.py +168 -0
  121. package/runtime/python/okstra_vendor/networkx/algorithms/bridges.py +205 -0
  122. package/runtime/python/okstra_vendor/networkx/algorithms/broadcasting.py +164 -0
  123. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/__init__.py +20 -0
  124. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness.py +591 -0
  125. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness_subset.py +236 -0
  126. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/closeness.py +282 -0
  127. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness.py +364 -0
  128. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness_subset.py +227 -0
  129. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_closeness.py +96 -0
  130. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/degree_alg.py +150 -0
  131. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/dispersion.py +107 -0
  132. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/eigenvector.py +357 -0
  133. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/flow_matrix.py +130 -0
  134. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/group.py +787 -0
  135. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/harmonic.py +88 -0
  136. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/katz.py +331 -0
  137. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/laplacian.py +150 -0
  138. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/load.py +200 -0
  139. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/percolation.py +128 -0
  140. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/reaching.py +209 -0
  141. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/second_order.py +141 -0
  142. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/subgraph_alg.py +361 -0
  143. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/__init__.py +0 -0
  144. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality.py +923 -0
  145. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py +354 -0
  146. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_closeness_centrality.py +274 -0
  147. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py +259 -0
  148. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py +147 -0
  149. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_closeness.py +43 -0
  150. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_degree_centrality.py +144 -0
  151. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_dispersion.py +73 -0
  152. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py +186 -0
  153. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_group.py +277 -0
  154. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_harmonic_centrality.py +122 -0
  155. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_katz_centrality.py +345 -0
  156. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_laplacian_centrality.py +220 -0
  157. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_load_centrality.py +344 -0
  158. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_percolation_centrality.py +87 -0
  159. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_reaching.py +140 -0
  160. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_second_order_centrality.py +82 -0
  161. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_subgraph.py +110 -0
  162. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_trophic.py +302 -0
  163. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_voterank.py +64 -0
  164. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/trophic.py +181 -0
  165. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/voterank_alg.py +95 -0
  166. package/runtime/python/okstra_vendor/networkx/algorithms/chains.py +172 -0
  167. package/runtime/python/okstra_vendor/networkx/algorithms/chordal.py +443 -0
  168. package/runtime/python/okstra_vendor/networkx/algorithms/clique.py +818 -0
  169. package/runtime/python/okstra_vendor/networkx/algorithms/cluster.py +732 -0
  170. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/__init__.py +4 -0
  171. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/equitable_coloring.py +505 -0
  172. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/greedy_coloring.py +565 -0
  173. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/__init__.py +0 -0
  174. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/test_coloring.py +863 -0
  175. package/runtime/python/okstra_vendor/networkx/algorithms/communicability_alg.py +163 -0
  176. package/runtime/python/okstra_vendor/networkx/algorithms/community/__init__.py +28 -0
  177. package/runtime/python/okstra_vendor/networkx/algorithms/community/asyn_fluid.py +153 -0
  178. package/runtime/python/okstra_vendor/networkx/algorithms/community/bipartitions.py +354 -0
  179. package/runtime/python/okstra_vendor/networkx/algorithms/community/centrality.py +171 -0
  180. package/runtime/python/okstra_vendor/networkx/algorithms/community/community_utils.py +30 -0
  181. package/runtime/python/okstra_vendor/networkx/algorithms/community/divisive.py +216 -0
  182. package/runtime/python/okstra_vendor/networkx/algorithms/community/kclique.py +79 -0
  183. package/runtime/python/okstra_vendor/networkx/algorithms/community/label_propagation.py +338 -0
  184. package/runtime/python/okstra_vendor/networkx/algorithms/community/leiden.py +162 -0
  185. package/runtime/python/okstra_vendor/networkx/algorithms/community/local.py +220 -0
  186. package/runtime/python/okstra_vendor/networkx/algorithms/community/louvain.py +384 -0
  187. package/runtime/python/okstra_vendor/networkx/algorithms/community/lukes.py +227 -0
  188. package/runtime/python/okstra_vendor/networkx/algorithms/community/modularity_max.py +452 -0
  189. package/runtime/python/okstra_vendor/networkx/algorithms/community/quality.py +347 -0
  190. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/__init__.py +0 -0
  191. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_asyn_fluid.py +147 -0
  192. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_bipartitions.py +157 -0
  193. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_centrality.py +85 -0
  194. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_divisive.py +106 -0
  195. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_kclique.py +91 -0
  196. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_label_propagation.py +241 -0
  197. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_leiden.py +138 -0
  198. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_local.py +76 -0
  199. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_louvain.py +264 -0
  200. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_lukes.py +152 -0
  201. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_modularity_max.py +340 -0
  202. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_quality.py +139 -0
  203. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_utils.py +26 -0
  204. package/runtime/python/okstra_vendor/networkx/algorithms/components/__init__.py +6 -0
  205. package/runtime/python/okstra_vendor/networkx/algorithms/components/attracting.py +115 -0
  206. package/runtime/python/okstra_vendor/networkx/algorithms/components/biconnected.py +394 -0
  207. package/runtime/python/okstra_vendor/networkx/algorithms/components/connected.py +282 -0
  208. package/runtime/python/okstra_vendor/networkx/algorithms/components/semiconnected.py +71 -0
  209. package/runtime/python/okstra_vendor/networkx/algorithms/components/strongly_connected.py +359 -0
  210. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/__init__.py +0 -0
  211. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_attracting.py +70 -0
  212. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_biconnected.py +248 -0
  213. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_connected.py +138 -0
  214. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_semiconnected.py +55 -0
  215. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_strongly_connected.py +193 -0
  216. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_weakly_connected.py +96 -0
  217. package/runtime/python/okstra_vendor/networkx/algorithms/components/weakly_connected.py +196 -0
  218. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/__init__.py +11 -0
  219. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/connectivity.py +811 -0
  220. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/cuts.py +616 -0
  221. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/disjoint_paths.py +408 -0
  222. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_augmentation.py +1270 -0
  223. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_kcomponents.py +592 -0
  224. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcomponents.py +220 -0
  225. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcutsets.py +235 -0
  226. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/stoerwagner.py +152 -0
  227. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/__init__.py +0 -0
  228. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_connectivity.py +421 -0
  229. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_cuts.py +309 -0
  230. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_disjoint_paths.py +249 -0
  231. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_augmentation.py +502 -0
  232. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_kcomponents.py +488 -0
  233. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcomponents.py +323 -0
  234. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcutsets.py +280 -0
  235. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_stoer_wagner.py +102 -0
  236. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/utils.py +88 -0
  237. package/runtime/python/okstra_vendor/networkx/algorithms/core.py +588 -0
  238. package/runtime/python/okstra_vendor/networkx/algorithms/covering.py +142 -0
  239. package/runtime/python/okstra_vendor/networkx/algorithms/cuts.py +416 -0
  240. package/runtime/python/okstra_vendor/networkx/algorithms/cycles.py +1234 -0
  241. package/runtime/python/okstra_vendor/networkx/algorithms/d_separation.py +677 -0
  242. package/runtime/python/okstra_vendor/networkx/algorithms/dag.py +1392 -0
  243. package/runtime/python/okstra_vendor/networkx/algorithms/distance_measures.py +1095 -0
  244. package/runtime/python/okstra_vendor/networkx/algorithms/distance_regular.py +272 -0
  245. package/runtime/python/okstra_vendor/networkx/algorithms/dominance.py +142 -0
  246. package/runtime/python/okstra_vendor/networkx/algorithms/dominating.py +268 -0
  247. package/runtime/python/okstra_vendor/networkx/algorithms/efficiency_measures.py +167 -0
  248. package/runtime/python/okstra_vendor/networkx/algorithms/euler.py +470 -0
  249. package/runtime/python/okstra_vendor/networkx/algorithms/flow/__init__.py +11 -0
  250. package/runtime/python/okstra_vendor/networkx/algorithms/flow/boykovkolmogorov.py +370 -0
  251. package/runtime/python/okstra_vendor/networkx/algorithms/flow/capacityscaling.py +407 -0
  252. package/runtime/python/okstra_vendor/networkx/algorithms/flow/dinitz_alg.py +238 -0
  253. package/runtime/python/okstra_vendor/networkx/algorithms/flow/edmondskarp.py +241 -0
  254. package/runtime/python/okstra_vendor/networkx/algorithms/flow/gomory_hu.py +178 -0
  255. package/runtime/python/okstra_vendor/networkx/algorithms/flow/maxflow.py +611 -0
  256. package/runtime/python/okstra_vendor/networkx/algorithms/flow/mincost.py +356 -0
  257. package/runtime/python/okstra_vendor/networkx/algorithms/flow/networksimplex.py +662 -0
  258. package/runtime/python/okstra_vendor/networkx/algorithms/flow/preflowpush.py +425 -0
  259. package/runtime/python/okstra_vendor/networkx/algorithms/flow/shortestaugmentingpath.py +300 -0
  260. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/__init__.py +0 -0
  261. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gl1.gpickle.bz2 +0 -0
  262. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gw1.gpickle.bz2 +0 -0
  263. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/netgen-2.gpickle.bz2 +0 -0
  264. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_gomory_hu.py +128 -0
  265. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow.py +573 -0
  266. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow_large_graph.py +155 -0
  267. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_mincost.py +475 -0
  268. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_networksimplex.py +481 -0
  269. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/wlm3.gpickle.bz2 +0 -0
  270. package/runtime/python/okstra_vendor/networkx/algorithms/flow/utils.py +194 -0
  271. package/runtime/python/okstra_vendor/networkx/algorithms/graph_hashing.py +435 -0
  272. package/runtime/python/okstra_vendor/networkx/algorithms/graphical.py +483 -0
  273. package/runtime/python/okstra_vendor/networkx/algorithms/hierarchy.py +57 -0
  274. package/runtime/python/okstra_vendor/networkx/algorithms/hybrid.py +196 -0
  275. package/runtime/python/okstra_vendor/networkx/algorithms/isolate.py +107 -0
  276. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/__init__.py +7 -0
  277. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/ismags.py +1306 -0
  278. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorph.py +336 -0
  279. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorphvf2.py +1262 -0
  280. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/matchhelpers.py +352 -0
  281. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/temporalisomorphvf2.py +308 -0
  282. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/__init__.py +0 -0
  283. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 +0 -0
  284. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 +0 -0
  285. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.A99 +0 -0
  286. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 +0 -0
  287. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_ismags.py +719 -0
  288. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphism.py +103 -0
  289. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphvf2.py +490 -0
  290. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_match_helpers.py +64 -0
  291. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_temporalisomorphvf2.py +212 -0
  292. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_tree_isomorphism.py +202 -0
  293. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp.py +1655 -0
  294. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp_helpers.py +3118 -0
  295. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2userfunc.py +196 -0
  296. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tree_isomorphism.py +264 -0
  297. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2pp.py +1102 -0
  298. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2userfunc.py +192 -0
  299. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/__init__.py +2 -0
  300. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/hits_alg.py +337 -0
  301. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/pagerank_alg.py +498 -0
  302. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/__init__.py +0 -0
  303. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_hits.py +77 -0
  304. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_pagerank.py +213 -0
  305. package/runtime/python/okstra_vendor/networkx/algorithms/link_prediction.py +687 -0
  306. package/runtime/python/okstra_vendor/networkx/algorithms/lowest_common_ancestors.py +280 -0
  307. package/runtime/python/okstra_vendor/networkx/algorithms/matching.py +1148 -0
  308. package/runtime/python/okstra_vendor/networkx/algorithms/minors/__init__.py +27 -0
  309. package/runtime/python/okstra_vendor/networkx/algorithms/minors/contraction.py +738 -0
  310. package/runtime/python/okstra_vendor/networkx/algorithms/minors/tests/test_contraction.py +544 -0
  311. package/runtime/python/okstra_vendor/networkx/algorithms/mis.py +78 -0
  312. package/runtime/python/okstra_vendor/networkx/algorithms/moral.py +59 -0
  313. package/runtime/python/okstra_vendor/networkx/algorithms/node_classification.py +219 -0
  314. package/runtime/python/okstra_vendor/networkx/algorithms/non_randomness.py +155 -0
  315. package/runtime/python/okstra_vendor/networkx/algorithms/operators/__init__.py +4 -0
  316. package/runtime/python/okstra_vendor/networkx/algorithms/operators/all.py +324 -0
  317. package/runtime/python/okstra_vendor/networkx/algorithms/operators/binary.py +468 -0
  318. package/runtime/python/okstra_vendor/networkx/algorithms/operators/product.py +633 -0
  319. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/__init__.py +0 -0
  320. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_all.py +328 -0
  321. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_binary.py +451 -0
  322. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_product.py +491 -0
  323. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_unary.py +55 -0
  324. package/runtime/python/okstra_vendor/networkx/algorithms/operators/unary.py +77 -0
  325. package/runtime/python/okstra_vendor/networkx/algorithms/perfect_graph.py +73 -0
  326. package/runtime/python/okstra_vendor/networkx/algorithms/planar_drawing.py +464 -0
  327. package/runtime/python/okstra_vendor/networkx/algorithms/planarity.py +1463 -0
  328. package/runtime/python/okstra_vendor/networkx/algorithms/polynomials.py +306 -0
  329. package/runtime/python/okstra_vendor/networkx/algorithms/reciprocity.py +98 -0
  330. package/runtime/python/okstra_vendor/networkx/algorithms/regular.py +167 -0
  331. package/runtime/python/okstra_vendor/networkx/algorithms/richclub.py +138 -0
  332. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/__init__.py +5 -0
  333. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/astar.py +239 -0
  334. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/dense.py +264 -0
  335. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/generic.py +716 -0
  336. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/__init__.py +0 -0
  337. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_astar.py +254 -0
  338. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense.py +212 -0
  339. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py +88 -0
  340. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_generic.py +511 -0
  341. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_unweighted.py +149 -0
  342. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_weighted.py +983 -0
  343. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/unweighted.py +625 -0
  344. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/weighted.py +2542 -0
  345. package/runtime/python/okstra_vendor/networkx/algorithms/similarity.py +2107 -0
  346. package/runtime/python/okstra_vendor/networkx/algorithms/simple_paths.py +966 -0
  347. package/runtime/python/okstra_vendor/networkx/algorithms/smallworld.py +404 -0
  348. package/runtime/python/okstra_vendor/networkx/algorithms/smetric.py +30 -0
  349. package/runtime/python/okstra_vendor/networkx/algorithms/sparsifiers.py +296 -0
  350. package/runtime/python/okstra_vendor/networkx/algorithms/structuralholes.py +374 -0
  351. package/runtime/python/okstra_vendor/networkx/algorithms/summarization.py +564 -0
  352. package/runtime/python/okstra_vendor/networkx/algorithms/swap.py +406 -0
  353. package/runtime/python/okstra_vendor/networkx/algorithms/tests/__init__.py +0 -0
  354. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_asteroidal.py +23 -0
  355. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_boundary.py +154 -0
  356. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_bridges.py +144 -0
  357. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_broadcasting.py +109 -0
  358. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chains.py +136 -0
  359. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chordal.py +129 -0
  360. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_clique.py +300 -0
  361. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cluster.py +678 -0
  362. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_communicability.py +80 -0
  363. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_core.py +266 -0
  364. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_covering.py +85 -0
  365. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cuts.py +171 -0
  366. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cycles.py +984 -0
  367. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_d_separation.py +340 -0
  368. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dag.py +835 -0
  369. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_measures.py +831 -0
  370. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_regular.py +85 -0
  371. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominance.py +299 -0
  372. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominating.py +115 -0
  373. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_efficiency.py +58 -0
  374. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_euler.py +314 -0
  375. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graph_hashing.py +872 -0
  376. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graphical.py +163 -0
  377. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hierarchy.py +46 -0
  378. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hybrid.py +24 -0
  379. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_isolate.py +26 -0
  380. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_link_prediction.py +615 -0
  381. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_lowest_common_ancestors.py +459 -0
  382. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_matching.py +556 -0
  383. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_max_weight_clique.py +179 -0
  384. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_mis.py +62 -0
  385. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_moral.py +15 -0
  386. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_node_classification.py +140 -0
  387. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_non_randomness.py +60 -0
  388. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_perfect_graph.py +27 -0
  389. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planar_drawing.py +274 -0
  390. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planarity.py +556 -0
  391. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_polynomials.py +57 -0
  392. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_reciprocity.py +37 -0
  393. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_regular.py +88 -0
  394. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_richclub.py +149 -0
  395. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_similarity.py +1158 -0
  396. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_simple_paths.py +803 -0
  397. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smallworld.py +76 -0
  398. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smetric.py +8 -0
  399. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_sparsifiers.py +138 -0
  400. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_structuralholes.py +191 -0
  401. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_summarization.py +642 -0
  402. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_swap.py +179 -0
  403. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_threshold.py +270 -0
  404. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_time_dependent.py +431 -0
  405. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_tournament.py +161 -0
  406. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_triads.py +248 -0
  407. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_vitality.py +41 -0
  408. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_voronoi.py +103 -0
  409. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_walks.py +54 -0
  410. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_wiener.py +157 -0
  411. package/runtime/python/okstra_vendor/networkx/algorithms/threshold.py +981 -0
  412. package/runtime/python/okstra_vendor/networkx/algorithms/time_dependent.py +142 -0
  413. package/runtime/python/okstra_vendor/networkx/algorithms/tournament.py +406 -0
  414. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/__init__.py +5 -0
  415. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/beamsearch.py +90 -0
  416. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/breadth_first_search.py +576 -0
  417. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/depth_first_search.py +529 -0
  418. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgebfs.py +185 -0
  419. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgedfs.py +182 -0
  420. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/__init__.py +0 -0
  421. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_beamsearch.py +25 -0
  422. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_bfs.py +203 -0
  423. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_dfs.py +307 -0
  424. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgebfs.py +147 -0
  425. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgedfs.py +131 -0
  426. package/runtime/python/okstra_vendor/networkx/algorithms/tree/__init__.py +7 -0
  427. package/runtime/python/okstra_vendor/networkx/algorithms/tree/branchings.py +1042 -0
  428. package/runtime/python/okstra_vendor/networkx/algorithms/tree/coding.py +413 -0
  429. package/runtime/python/okstra_vendor/networkx/algorithms/tree/decomposition.py +88 -0
  430. package/runtime/python/okstra_vendor/networkx/algorithms/tree/distance_measures.py +219 -0
  431. package/runtime/python/okstra_vendor/networkx/algorithms/tree/mst.py +1281 -0
  432. package/runtime/python/okstra_vendor/networkx/algorithms/tree/operations.py +106 -0
  433. package/runtime/python/okstra_vendor/networkx/algorithms/tree/recognition.py +273 -0
  434. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/__init__.py +0 -0
  435. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_branchings.py +624 -0
  436. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_coding.py +114 -0
  437. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_decomposition.py +79 -0
  438. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_distance_measures.py +99 -0
  439. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_mst.py +934 -0
  440. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_operations.py +53 -0
  441. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_recognition.py +174 -0
  442. package/runtime/python/okstra_vendor/networkx/algorithms/triads.py +500 -0
  443. package/runtime/python/okstra_vendor/networkx/algorithms/vitality.py +76 -0
  444. package/runtime/python/okstra_vendor/networkx/algorithms/voronoi.py +86 -0
  445. package/runtime/python/okstra_vendor/networkx/algorithms/walks.py +77 -0
  446. package/runtime/python/okstra_vendor/networkx/algorithms/wiener.py +278 -0
  447. package/runtime/python/okstra_vendor/networkx/classes/__init__.py +13 -0
  448. package/runtime/python/okstra_vendor/networkx/classes/coreviews.py +435 -0
  449. package/runtime/python/okstra_vendor/networkx/classes/digraph.py +1363 -0
  450. package/runtime/python/okstra_vendor/networkx/classes/filters.py +95 -0
  451. package/runtime/python/okstra_vendor/networkx/classes/function.py +1549 -0
  452. package/runtime/python/okstra_vendor/networkx/classes/graph.py +2082 -0
  453. package/runtime/python/okstra_vendor/networkx/classes/graphviews.py +269 -0
  454. package/runtime/python/okstra_vendor/networkx/classes/multidigraph.py +977 -0
  455. package/runtime/python/okstra_vendor/networkx/classes/multigraph.py +1294 -0
  456. package/runtime/python/okstra_vendor/networkx/classes/reportviews.py +1447 -0
  457. package/runtime/python/okstra_vendor/networkx/classes/tests/__init__.py +0 -0
  458. package/runtime/python/okstra_vendor/networkx/classes/tests/dispatch_interface.py +192 -0
  459. package/runtime/python/okstra_vendor/networkx/classes/tests/historical_tests.py +476 -0
  460. package/runtime/python/okstra_vendor/networkx/classes/tests/test_coreviews.py +362 -0
  461. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph.py +331 -0
  462. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph_historical.py +110 -0
  463. package/runtime/python/okstra_vendor/networkx/classes/tests/test_filters.py +177 -0
  464. package/runtime/python/okstra_vendor/networkx/classes/tests/test_function.py +1045 -0
  465. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph.py +950 -0
  466. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph_historical.py +12 -0
  467. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graphviews.py +349 -0
  468. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multidigraph.py +459 -0
  469. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multigraph.py +528 -0
  470. package/runtime/python/okstra_vendor/networkx/classes/tests/test_reportviews.py +1421 -0
  471. package/runtime/python/okstra_vendor/networkx/classes/tests/test_special.py +131 -0
  472. package/runtime/python/okstra_vendor/networkx/classes/tests/test_subgraphviews.py +371 -0
  473. package/runtime/python/okstra_vendor/networkx/conftest.py +261 -0
  474. package/runtime/python/okstra_vendor/networkx/convert.py +502 -0
  475. package/runtime/python/okstra_vendor/networkx/convert_matrix.py +1314 -0
  476. package/runtime/python/okstra_vendor/networkx/drawing/__init__.py +7 -0
  477. package/runtime/python/okstra_vendor/networkx/drawing/layout.py +2036 -0
  478. package/runtime/python/okstra_vendor/networkx/drawing/nx_agraph.py +470 -0
  479. package/runtime/python/okstra_vendor/networkx/drawing/nx_latex.py +570 -0
  480. package/runtime/python/okstra_vendor/networkx/drawing/nx_pydot.py +361 -0
  481. package/runtime/python/okstra_vendor/networkx/drawing/nx_pylab.py +2978 -0
  482. package/runtime/python/okstra_vendor/networkx/drawing/tests/__init__.py +0 -0
  483. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_complex.png +0 -0
  484. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_empty_graph.png +0 -0
  485. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_house_with_colors.png +0 -0
  486. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_labels_and_colors.png +0 -0
  487. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_shortest_path.png +0 -0
  488. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_house_with_colors.png +0 -0
  489. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_agraph.py +237 -0
  490. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_image_comparison_pylab_mpl.py +229 -0
  491. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_latex.py +285 -0
  492. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_layout.py +631 -0
  493. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pydot.py +146 -0
  494. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pylab.py +1582 -0
  495. package/runtime/python/okstra_vendor/networkx/exception.py +131 -0
  496. package/runtime/python/okstra_vendor/networkx/generators/__init__.py +34 -0
  497. package/runtime/python/okstra_vendor/networkx/generators/atlas.dat.gz +0 -0
  498. package/runtime/python/okstra_vendor/networkx/generators/atlas.py +227 -0
  499. package/runtime/python/okstra_vendor/networkx/generators/classic.py +1091 -0
  500. package/runtime/python/okstra_vendor/networkx/generators/cographs.py +68 -0
  501. package/runtime/python/okstra_vendor/networkx/generators/community.py +1070 -0
  502. package/runtime/python/okstra_vendor/networkx/generators/degree_seq.py +886 -0
  503. package/runtime/python/okstra_vendor/networkx/generators/directed.py +572 -0
  504. package/runtime/python/okstra_vendor/networkx/generators/duplication.py +174 -0
  505. package/runtime/python/okstra_vendor/networkx/generators/ego.py +66 -0
  506. package/runtime/python/okstra_vendor/networkx/generators/expanders.py +499 -0
  507. package/runtime/python/okstra_vendor/networkx/generators/geometric.py +1037 -0
  508. package/runtime/python/okstra_vendor/networkx/generators/harary_graph.py +163 -0
  509. package/runtime/python/okstra_vendor/networkx/generators/internet_as_graphs.py +443 -0
  510. package/runtime/python/okstra_vendor/networkx/generators/intersection.py +125 -0
  511. package/runtime/python/okstra_vendor/networkx/generators/interval_graph.py +70 -0
  512. package/runtime/python/okstra_vendor/networkx/generators/joint_degree_seq.py +664 -0
  513. package/runtime/python/okstra_vendor/networkx/generators/lattice.py +405 -0
  514. package/runtime/python/okstra_vendor/networkx/generators/line.py +501 -0
  515. package/runtime/python/okstra_vendor/networkx/generators/mycielski.py +110 -0
  516. package/runtime/python/okstra_vendor/networkx/generators/nonisomorphic_trees.py +259 -0
  517. package/runtime/python/okstra_vendor/networkx/generators/random_clustered.py +117 -0
  518. package/runtime/python/okstra_vendor/networkx/generators/random_graphs.py +1416 -0
  519. package/runtime/python/okstra_vendor/networkx/generators/small.py +1070 -0
  520. package/runtime/python/okstra_vendor/networkx/generators/social.py +554 -0
  521. package/runtime/python/okstra_vendor/networkx/generators/spectral_graph_forge.py +120 -0
  522. package/runtime/python/okstra_vendor/networkx/generators/stochastic.py +54 -0
  523. package/runtime/python/okstra_vendor/networkx/generators/sudoku.py +131 -0
  524. package/runtime/python/okstra_vendor/networkx/generators/tests/__init__.py +0 -0
  525. package/runtime/python/okstra_vendor/networkx/generators/tests/test_atlas.py +75 -0
  526. package/runtime/python/okstra_vendor/networkx/generators/tests/test_classic.py +642 -0
  527. package/runtime/python/okstra_vendor/networkx/generators/tests/test_cographs.py +20 -0
  528. package/runtime/python/okstra_vendor/networkx/generators/tests/test_community.py +362 -0
  529. package/runtime/python/okstra_vendor/networkx/generators/tests/test_degree_seq.py +224 -0
  530. package/runtime/python/okstra_vendor/networkx/generators/tests/test_directed.py +189 -0
  531. package/runtime/python/okstra_vendor/networkx/generators/tests/test_duplication.py +103 -0
  532. package/runtime/python/okstra_vendor/networkx/generators/tests/test_ego.py +39 -0
  533. package/runtime/python/okstra_vendor/networkx/generators/tests/test_expanders.py +182 -0
  534. package/runtime/python/okstra_vendor/networkx/generators/tests/test_geometric.py +488 -0
  535. package/runtime/python/okstra_vendor/networkx/generators/tests/test_harary_graph.py +133 -0
  536. package/runtime/python/okstra_vendor/networkx/generators/tests/test_internet_as_graphs.py +221 -0
  537. package/runtime/python/okstra_vendor/networkx/generators/tests/test_intersection.py +28 -0
  538. package/runtime/python/okstra_vendor/networkx/generators/tests/test_interval_graph.py +144 -0
  539. package/runtime/python/okstra_vendor/networkx/generators/tests/test_joint_degree_seq.py +125 -0
  540. package/runtime/python/okstra_vendor/networkx/generators/tests/test_lattice.py +264 -0
  541. package/runtime/python/okstra_vendor/networkx/generators/tests/test_line.py +316 -0
  542. package/runtime/python/okstra_vendor/networkx/generators/tests/test_mycielski.py +30 -0
  543. package/runtime/python/okstra_vendor/networkx/generators/tests/test_nonisomorphic_trees.py +82 -0
  544. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_clustered.py +33 -0
  545. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_graphs.py +495 -0
  546. package/runtime/python/okstra_vendor/networkx/generators/tests/test_small.py +220 -0
  547. package/runtime/python/okstra_vendor/networkx/generators/tests/test_spectral_graph_forge.py +49 -0
  548. package/runtime/python/okstra_vendor/networkx/generators/tests/test_stochastic.py +72 -0
  549. package/runtime/python/okstra_vendor/networkx/generators/tests/test_sudoku.py +92 -0
  550. package/runtime/python/okstra_vendor/networkx/generators/tests/test_time_series.py +64 -0
  551. package/runtime/python/okstra_vendor/networkx/generators/tests/test_trees.py +195 -0
  552. package/runtime/python/okstra_vendor/networkx/generators/tests/test_triads.py +15 -0
  553. package/runtime/python/okstra_vendor/networkx/generators/time_series.py +74 -0
  554. package/runtime/python/okstra_vendor/networkx/generators/trees.py +1070 -0
  555. package/runtime/python/okstra_vendor/networkx/generators/triads.py +94 -0
  556. package/runtime/python/okstra_vendor/networkx/lazy_imports.py +188 -0
  557. package/runtime/python/okstra_vendor/networkx/linalg/__init__.py +13 -0
  558. package/runtime/python/okstra_vendor/networkx/linalg/algebraicconnectivity.py +650 -0
  559. package/runtime/python/okstra_vendor/networkx/linalg/attrmatrix.py +466 -0
  560. package/runtime/python/okstra_vendor/networkx/linalg/bethehessianmatrix.py +77 -0
  561. package/runtime/python/okstra_vendor/networkx/linalg/graphmatrix.py +168 -0
  562. package/runtime/python/okstra_vendor/networkx/linalg/laplacianmatrix.py +512 -0
  563. package/runtime/python/okstra_vendor/networkx/linalg/modularitymatrix.py +166 -0
  564. package/runtime/python/okstra_vendor/networkx/linalg/spectrum.py +186 -0
  565. package/runtime/python/okstra_vendor/networkx/linalg/tests/__init__.py +0 -0
  566. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_algebraic_connectivity.py +400 -0
  567. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_attrmatrix.py +108 -0
  568. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_bethehessian.py +40 -0
  569. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_graphmatrix.py +275 -0
  570. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_laplacian.py +334 -0
  571. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_modularity.py +86 -0
  572. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_spectrum.py +70 -0
  573. package/runtime/python/okstra_vendor/networkx/readwrite/__init__.py +17 -0
  574. package/runtime/python/okstra_vendor/networkx/readwrite/adjlist.py +330 -0
  575. package/runtime/python/okstra_vendor/networkx/readwrite/edgelist.py +489 -0
  576. package/runtime/python/okstra_vendor/networkx/readwrite/gexf.py +1084 -0
  577. package/runtime/python/okstra_vendor/networkx/readwrite/gml.py +879 -0
  578. package/runtime/python/okstra_vendor/networkx/readwrite/graph6.py +427 -0
  579. package/runtime/python/okstra_vendor/networkx/readwrite/graphml.py +1053 -0
  580. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/__init__.py +19 -0
  581. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/adjacency.py +156 -0
  582. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/cytoscape.py +190 -0
  583. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/node_link.py +261 -0
  584. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/__init__.py +0 -0
  585. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_adjacency.py +78 -0
  586. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_cytoscape.py +78 -0
  587. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_node_link.py +109 -0
  588. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_tree.py +48 -0
  589. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tree.py +137 -0
  590. package/runtime/python/okstra_vendor/networkx/readwrite/leda.py +108 -0
  591. package/runtime/python/okstra_vendor/networkx/readwrite/multiline_adjlist.py +393 -0
  592. package/runtime/python/okstra_vendor/networkx/readwrite/p2g.py +113 -0
  593. package/runtime/python/okstra_vendor/networkx/readwrite/pajek.py +286 -0
  594. package/runtime/python/okstra_vendor/networkx/readwrite/sparse6.py +379 -0
  595. package/runtime/python/okstra_vendor/networkx/readwrite/tests/__init__.py +0 -0
  596. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_adjlist.py +354 -0
  597. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_edgelist.py +318 -0
  598. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gexf.py +612 -0
  599. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gml.py +744 -0
  600. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graph6.py +181 -0
  601. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graphml.py +1531 -0
  602. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_leda.py +30 -0
  603. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_p2g.py +63 -0
  604. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_pajek.py +128 -0
  605. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_sparse6.py +166 -0
  606. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_text.py +1742 -0
  607. package/runtime/python/okstra_vendor/networkx/readwrite/text.py +851 -0
  608. package/runtime/python/okstra_vendor/networkx/relabel.py +285 -0
  609. package/runtime/python/okstra_vendor/networkx/tests/__init__.py +0 -0
  610. package/runtime/python/okstra_vendor/networkx/tests/test_all_random_functions.py +248 -0
  611. package/runtime/python/okstra_vendor/networkx/tests/test_convert.py +321 -0
  612. package/runtime/python/okstra_vendor/networkx/tests/test_convert_numpy.py +531 -0
  613. package/runtime/python/okstra_vendor/networkx/tests/test_convert_pandas.py +349 -0
  614. package/runtime/python/okstra_vendor/networkx/tests/test_convert_scipy.py +281 -0
  615. package/runtime/python/okstra_vendor/networkx/tests/test_exceptions.py +40 -0
  616. package/runtime/python/okstra_vendor/networkx/tests/test_import.py +11 -0
  617. package/runtime/python/okstra_vendor/networkx/tests/test_lazy_imports.py +96 -0
  618. package/runtime/python/okstra_vendor/networkx/tests/test_relabel.py +349 -0
  619. package/runtime/python/okstra_vendor/networkx/tests/test_removed_functions_exception_messages.py +8 -0
  620. package/runtime/python/okstra_vendor/networkx/utils/__init__.py +8 -0
  621. package/runtime/python/okstra_vendor/networkx/utils/backends.py +2171 -0
  622. package/runtime/python/okstra_vendor/networkx/utils/configs.py +396 -0
  623. package/runtime/python/okstra_vendor/networkx/utils/decorators.py +1233 -0
  624. package/runtime/python/okstra_vendor/networkx/utils/heaps.py +338 -0
  625. package/runtime/python/okstra_vendor/networkx/utils/mapped_queue.py +297 -0
  626. package/runtime/python/okstra_vendor/networkx/utils/misc.py +703 -0
  627. package/runtime/python/okstra_vendor/networkx/utils/random_sequence.py +198 -0
  628. package/runtime/python/okstra_vendor/networkx/utils/rcm.py +159 -0
  629. package/runtime/python/okstra_vendor/networkx/utils/tests/__init__.py +0 -0
  630. package/runtime/python/okstra_vendor/networkx/utils/tests/test__init.py +11 -0
  631. package/runtime/python/okstra_vendor/networkx/utils/tests/test_backends.py +225 -0
  632. package/runtime/python/okstra_vendor/networkx/utils/tests/test_config.py +263 -0
  633. package/runtime/python/okstra_vendor/networkx/utils/tests/test_decorators.py +510 -0
  634. package/runtime/python/okstra_vendor/networkx/utils/tests/test_heaps.py +131 -0
  635. package/runtime/python/okstra_vendor/networkx/utils/tests/test_mapped_queue.py +268 -0
  636. package/runtime/python/okstra_vendor/networkx/utils/tests/test_misc.py +393 -0
  637. package/runtime/python/okstra_vendor/networkx/utils/tests/test_random_sequence.py +53 -0
  638. package/runtime/python/okstra_vendor/networkx/utils/tests/test_rcm.py +63 -0
  639. package/runtime/python/okstra_vendor/networkx/utils/tests/test_unionfind.py +55 -0
  640. package/runtime/python/okstra_vendor/networkx/utils/union_find.py +106 -0
  641. package/runtime/skills/okstra-graphify/SKILL.md +161 -0
  642. package/runtime/skills/okstra-inspect/SKILL.md +17 -9
  643. package/runtime/templates/reports/settings.template.json +4 -0
  644. package/runtime/validators/forbidden_actions.py +8 -2
  645. package/runtime/validators/validate_session_conformance.py +26 -5
  646. package/src/cli-registry.mjs +7 -0
  647. package/src/commands/graphify.mjs +32 -0
  648. package/src/commands/lifecycle/doctor.mjs +9 -0
  649. package/src/lib/skill-catalog.mjs +1 -0
@@ -0,0 +1,2036 @@
1
+ """
2
+ ******
3
+ Layout
4
+ ******
5
+
6
+ Node positioning algorithms for graph drawing.
7
+
8
+ For `random_layout()` the possible resulting shape
9
+ is a square of side [0, scale] (default: [0, 1])
10
+ Changing `center` shifts the layout by that amount.
11
+
12
+ For the other layout routines, the extent is
13
+ [center - scale, center + scale] (default: [-1, 1]).
14
+
15
+ Warning: Most layout routines have only been tested in 2-dimensions.
16
+
17
+ """
18
+
19
+ import networkx as nx
20
+ from networkx.utils import np_random_state
21
+
22
+ __all__ = [
23
+ "bipartite_layout",
24
+ "circular_layout",
25
+ "forceatlas2_layout",
26
+ "kamada_kawai_layout",
27
+ "random_layout",
28
+ "rescale_layout",
29
+ "rescale_layout_dict",
30
+ "shell_layout",
31
+ "spring_layout",
32
+ "spectral_layout",
33
+ "planar_layout",
34
+ "fruchterman_reingold_layout",
35
+ "spiral_layout",
36
+ "multipartite_layout",
37
+ "bfs_layout",
38
+ "arf_layout",
39
+ ]
40
+
41
+
42
+ def _process_params(G, center, dim):
43
+ # Some boilerplate code.
44
+ import numpy as np
45
+
46
+ if not isinstance(G, nx.Graph):
47
+ empty_graph = nx.Graph()
48
+ empty_graph.add_nodes_from(G)
49
+ G = empty_graph
50
+
51
+ if center is None:
52
+ center = np.zeros(dim)
53
+ else:
54
+ center = np.asarray(center)
55
+
56
+ if len(center) != dim:
57
+ msg = "length of center coordinates must match dimension of layout"
58
+ raise ValueError(msg)
59
+
60
+ return G, center
61
+
62
+
63
+ @np_random_state(3)
64
+ def random_layout(G, center=None, dim=2, seed=None, store_pos_as=None):
65
+ """Position nodes uniformly at random in the unit square.
66
+
67
+ For every node, a position is generated by choosing each of dim
68
+ coordinates uniformly at random on the interval [0.0, 1.0).
69
+
70
+ NumPy (http://scipy.org) is required for this function.
71
+
72
+ Parameters
73
+ ----------
74
+ G : NetworkX graph or list of nodes
75
+ A position will be assigned to every node in G.
76
+
77
+ center : array-like or None
78
+ Coordinate pair around which to center the layout.
79
+
80
+ dim : int
81
+ Dimension of layout.
82
+
83
+ seed : int, RandomState instance or None optional (default=None)
84
+ Set the random state for deterministic node layouts.
85
+ If int, `seed` is the seed used by the random number generator,
86
+ if numpy.random.RandomState instance, `seed` is the random
87
+ number generator,
88
+ if None, the random number generator is the RandomState instance used
89
+ by numpy.random.
90
+
91
+ store_pos_as : str, default None
92
+ If non-None, the position of each node will be stored on the graph as
93
+ an attribute with this string as its name, which can be accessed with
94
+ ``G.nodes[...][store_pos_as]``. The function still returns the dictionary.
95
+
96
+ Returns
97
+ -------
98
+ pos : dict
99
+ A dictionary of positions keyed by node
100
+
101
+ Examples
102
+ --------
103
+ >>> from pprint import pprint
104
+ >>> G = nx.lollipop_graph(4, 3)
105
+ >>> pos = nx.random_layout(G)
106
+ >>> # suppress the returned dict and store on the graph directly
107
+ >>> _ = nx.random_layout(G, seed=42, store_pos_as="pos")
108
+ >>> pprint(nx.get_node_attributes(G, "pos"))
109
+ {0: array([0.37454012, 0.9507143 ], dtype=float32),
110
+ 1: array([0.7319939, 0.5986585], dtype=float32),
111
+ 2: array([0.15601864, 0.15599452], dtype=float32),
112
+ 3: array([0.05808361, 0.8661761 ], dtype=float32),
113
+ 4: array([0.601115 , 0.7080726], dtype=float32),
114
+ 5: array([0.02058449, 0.96990985], dtype=float32),
115
+ 6: array([0.83244264, 0.21233912], dtype=float32)}
116
+ """
117
+ import numpy as np
118
+
119
+ G, center = _process_params(G, center, dim)
120
+ pos = seed.rand(len(G), dim) + center
121
+ pos = pos.astype(np.float32)
122
+ pos = dict(zip(G, pos))
123
+
124
+ if store_pos_as is not None:
125
+ nx.set_node_attributes(G, pos, store_pos_as)
126
+ return pos
127
+
128
+
129
+ def circular_layout(G, scale=1, center=None, dim=2, store_pos_as=None):
130
+ # dim=2 only
131
+ """Position nodes on a circle.
132
+
133
+ Parameters
134
+ ----------
135
+ G : NetworkX graph or list of nodes
136
+ A position will be assigned to every node in G.
137
+
138
+ scale : number (default: 1)
139
+ Scale factor for positions.
140
+
141
+ center : array-like or None
142
+ Coordinate pair around which to center the layout.
143
+
144
+ dim : int
145
+ Dimension of layout.
146
+ If dim>2, the remaining dimensions are set to zero
147
+ in the returned positions.
148
+ If dim<2, a ValueError is raised.
149
+
150
+ store_pos_as : str, default None
151
+ If non-None, the position of each node will be stored on the graph as
152
+ an attribute with this string as its name, which can be accessed with
153
+ ``G.nodes[...][store_pos_as]``. The function still returns the dictionary.
154
+
155
+ Returns
156
+ -------
157
+ pos : dict
158
+ A dictionary of positions keyed by node
159
+
160
+ Raises
161
+ ------
162
+ ValueError
163
+ If dim < 2
164
+
165
+ Examples
166
+ --------
167
+ >>> from pprint import pprint
168
+ >>> G = nx.path_graph(4)
169
+ >>> pos = nx.circular_layout(G)
170
+ >>> # suppress the returned dict and store on the graph directly
171
+ >>> _ = nx.circular_layout(G, store_pos_as="pos")
172
+ >>> pprint(nx.get_node_attributes(G, "pos"))
173
+ {0: array([9.99999986e-01, 2.18556937e-08]),
174
+ 1: array([-3.57647606e-08, 1.00000000e+00]),
175
+ 2: array([-9.9999997e-01, -6.5567081e-08]),
176
+ 3: array([ 1.98715071e-08, -9.99999956e-01])}
177
+
178
+
179
+ Notes
180
+ -----
181
+ This algorithm currently only works in two dimensions and does not
182
+ try to minimize edge crossings.
183
+
184
+ """
185
+ import numpy as np
186
+
187
+ if dim < 2:
188
+ raise ValueError("cannot handle dimensions < 2")
189
+
190
+ G, center = _process_params(G, center, dim)
191
+
192
+ paddims = max(0, (dim - 2))
193
+
194
+ if len(G) == 0:
195
+ pos = {}
196
+ elif len(G) == 1:
197
+ pos = {nx.utils.arbitrary_element(G): center}
198
+ else:
199
+ # Discard the extra angle since it matches 0 radians.
200
+ theta = np.linspace(0, 1, len(G) + 1)[:-1] * 2 * np.pi
201
+ theta = theta.astype(np.float32)
202
+ pos = np.column_stack(
203
+ [np.cos(theta), np.sin(theta), np.zeros((len(G), paddims))]
204
+ )
205
+ pos = rescale_layout(pos, scale=scale) + center
206
+ pos = dict(zip(G, pos))
207
+
208
+ if store_pos_as is not None:
209
+ nx.set_node_attributes(G, pos, store_pos_as)
210
+
211
+ return pos
212
+
213
+
214
+ def shell_layout(
215
+ G, nlist=None, rotate=None, scale=1, center=None, dim=2, store_pos_as=None
216
+ ):
217
+ """Position nodes in concentric circles.
218
+
219
+ Parameters
220
+ ----------
221
+ G : NetworkX graph or list of nodes
222
+ A position will be assigned to every node in G.
223
+
224
+ nlist : list of lists
225
+ List of node lists for each shell.
226
+
227
+ rotate : angle in radians (default=pi/len(nlist))
228
+ Angle by which to rotate the starting position of each shell
229
+ relative to the starting position of the previous shell.
230
+ To recreate behavior before v2.5 use rotate=0.
231
+
232
+ scale : number (default: 1)
233
+ Scale factor for positions.
234
+
235
+ center : array-like or None
236
+ Coordinate pair around which to center the layout.
237
+
238
+ dim : int
239
+ Dimension of layout, currently only dim=2 is supported.
240
+ Other dimension values result in a ValueError.
241
+
242
+ store_pos_as : str, default None
243
+ If non-None, the position of each node will be stored on the graph as
244
+ an attribute with this string as its name, which can be accessed with
245
+ ``G.nodes[...][store_pos_as]``. The function still returns the dictionary.
246
+
247
+ Returns
248
+ -------
249
+ pos : dict
250
+ A dictionary of positions keyed by node
251
+
252
+ Raises
253
+ ------
254
+ ValueError
255
+ If dim != 2
256
+
257
+ Examples
258
+ --------
259
+ >>> from pprint import pprint
260
+ >>> G = nx.path_graph(4)
261
+ >>> shells = [[0], [1, 2, 3]]
262
+ >>> pos = nx.shell_layout(G, shells)
263
+ >>> # suppress the returned dict and store on the graph directly
264
+ >>> _ = nx.shell_layout(G, shells, store_pos_as="pos")
265
+ >>> pprint(nx.get_node_attributes(G, "pos"))
266
+ {0: array([0., 0.]),
267
+ 1: array([-5.00000000e-01, -4.37113883e-08]),
268
+ 2: array([ 0.24999996, -0.43301272]),
269
+ 3: array([0.24999981, 0.43301281])}
270
+
271
+ Notes
272
+ -----
273
+ This algorithm currently only works in two dimensions and does not
274
+ try to minimize edge crossings.
275
+
276
+ """
277
+ import numpy as np
278
+
279
+ if dim != 2:
280
+ raise ValueError("can only handle 2 dimensions")
281
+
282
+ G, center = _process_params(G, center, dim)
283
+
284
+ if len(G) == 0:
285
+ return {}
286
+ if len(G) == 1:
287
+ return {nx.utils.arbitrary_element(G): center}
288
+
289
+ if nlist is None:
290
+ # draw the whole graph in one shell
291
+ nlist = [list(G)]
292
+
293
+ radius_bump = scale / len(nlist)
294
+
295
+ if len(nlist[0]) == 1:
296
+ # single node at center
297
+ radius = 0.0
298
+ else:
299
+ # else start at r=1
300
+ radius = radius_bump
301
+
302
+ if rotate is None:
303
+ rotate = np.pi / len(nlist)
304
+ first_theta = rotate
305
+ npos = {}
306
+ for nodes in nlist:
307
+ # Discard the last angle (endpoint=False) since 2*pi matches 0 radians
308
+ theta = (
309
+ np.linspace(0, 2 * np.pi, len(nodes), endpoint=False, dtype=np.float32)
310
+ + first_theta
311
+ )
312
+ pos = radius * np.column_stack([np.cos(theta), np.sin(theta)]) + center
313
+ npos.update(zip(nodes, pos))
314
+ radius += radius_bump
315
+ first_theta += rotate
316
+
317
+ if store_pos_as is not None:
318
+ nx.set_node_attributes(G, npos, store_pos_as)
319
+ return npos
320
+
321
+
322
+ def bipartite_layout(
323
+ G,
324
+ nodes=None,
325
+ align="vertical",
326
+ scale=1,
327
+ center=None,
328
+ aspect_ratio=4 / 3,
329
+ store_pos_as=None,
330
+ ):
331
+ """Position nodes in two straight lines.
332
+
333
+ Parameters
334
+ ----------
335
+ G : NetworkX graph or list of nodes
336
+ A position will be assigned to every node in G.
337
+
338
+ nodes : collection of nodes
339
+ Nodes in one node set of the graph. This set will be placed on
340
+ left or top. If `None` (the default), a node set is chosen arbitrarily
341
+ if the graph if bipartite.
342
+
343
+ align : string (default='vertical')
344
+ The alignment of nodes. Vertical or horizontal.
345
+
346
+ scale : number (default: 1)
347
+ Scale factor for positions.
348
+
349
+ center : array-like or None
350
+ Coordinate pair around which to center the layout.
351
+
352
+ aspect_ratio : number (default=4/3):
353
+ The ratio of the width to the height of the layout.
354
+
355
+ store_pos_as : str, default None
356
+ If non-None, the position of each node will be stored on the graph as
357
+ an attribute with this string as its name, which can be accessed with
358
+ ``G.nodes[...][store_pos_as]``. The function still returns the dictionary.
359
+
360
+ Returns
361
+ -------
362
+ pos : dict
363
+ A dictionary of positions keyed by node.
364
+
365
+ Raises
366
+ ------
367
+ NetworkXError
368
+ If ``nodes=None`` and `G` is not bipartite.
369
+
370
+ Examples
371
+ --------
372
+ >>> G = nx.complete_bipartite_graph(3, 3)
373
+ >>> pos = nx.bipartite_layout(G)
374
+
375
+ The ordering of the layout (i.e. which nodes appear on the left/top) can
376
+ be specified with the `nodes` parameter:
377
+
378
+ >>> top, bottom = nx.bipartite.sets(G)
379
+ >>> pos = nx.bipartite_layout(G, nodes=bottom) # "bottom" set appears on the left
380
+
381
+ `store_pos_as` can be used to store the node positions for the computed layout
382
+ directly on the nodes:
383
+
384
+ >>> _ = nx.bipartite_layout(G, nodes=bottom, store_pos_as="pos")
385
+ >>> from pprint import pprint
386
+ >>> pprint(nx.get_node_attributes(G, "pos"))
387
+ {0: array([ 1. , -0.75]),
388
+ 1: array([1., 0.]),
389
+ 2: array([1. , 0.75]),
390
+ 3: array([-1. , -0.75]),
391
+ 4: array([-1., 0.]),
392
+ 5: array([-1. , 0.75])}
393
+
394
+
395
+ The ``bipartite_layout`` function can be used with non-bipartite graphs
396
+ by explicitly specifying how the layout should be partitioned with `nodes`:
397
+
398
+ >>> G = nx.complete_graph(5) # Non-bipartite
399
+ >>> pos = nx.bipartite_layout(G, nodes={0, 1, 2})
400
+
401
+ Notes
402
+ -----
403
+ This algorithm currently only works in two dimensions and does not
404
+ try to minimize edge crossings.
405
+
406
+ """
407
+
408
+ import numpy as np
409
+
410
+ if align not in ("vertical", "horizontal"):
411
+ msg = "align must be either vertical or horizontal."
412
+ raise ValueError(msg)
413
+
414
+ G, center = _process_params(G, center=center, dim=2)
415
+ if len(G) == 0:
416
+ return {}
417
+
418
+ height = 1
419
+ width = aspect_ratio * height
420
+ offset = (width / 2, height / 2)
421
+
422
+ if nodes is None:
423
+ top, bottom = nx.bipartite.sets(G)
424
+ nodes = list(G)
425
+ else:
426
+ top = set(nodes)
427
+ bottom = set(G) - top
428
+ # Preserves backward-compatible node ordering in returned pos dict
429
+ nodes = list(top) + list(bottom)
430
+
431
+ left_xs = np.repeat(0, len(top))
432
+ right_xs = np.repeat(width, len(bottom))
433
+ left_ys = np.linspace(0, height, len(top))
434
+ right_ys = np.linspace(0, height, len(bottom))
435
+
436
+ top_pos = np.column_stack([left_xs, left_ys]) - offset
437
+ bottom_pos = np.column_stack([right_xs, right_ys]) - offset
438
+
439
+ pos = np.concatenate([top_pos, bottom_pos])
440
+ pos = rescale_layout(pos, scale=scale) + center
441
+ if align == "horizontal":
442
+ pos = pos[:, ::-1] # swap x and y coords
443
+ pos = dict(zip(nodes, pos))
444
+
445
+ if store_pos_as is not None:
446
+ nx.set_node_attributes(G, pos, store_pos_as)
447
+
448
+ return pos
449
+
450
+
451
+ @np_random_state("seed")
452
+ def spring_layout(
453
+ G,
454
+ k=None,
455
+ pos=None,
456
+ fixed=None,
457
+ iterations=50,
458
+ threshold=1e-4,
459
+ weight="weight",
460
+ scale=1,
461
+ center=None,
462
+ dim=2,
463
+ seed=None,
464
+ store_pos_as=None,
465
+ *,
466
+ method="auto",
467
+ gravity=1.0,
468
+ ):
469
+ """Position nodes using Fruchterman-Reingold force-directed algorithm.
470
+
471
+ The algorithm simulates a force-directed representation of the network
472
+ treating edges as springs holding nodes close, while treating nodes
473
+ as repelling objects, sometimes called an anti-gravity force.
474
+ Simulation continues until the positions are close to an equilibrium.
475
+
476
+ There are some hard-coded values: minimal distance between
477
+ nodes (0.01) and "temperature" of 0.1 to ensure nodes don't fly away.
478
+ During the simulation, `k` helps determine the distance between nodes,
479
+ though `scale` and `center` determine the size and place after
480
+ rescaling occurs at the end of the simulation.
481
+
482
+ Fixing some nodes doesn't allow them to move in the simulation.
483
+ It also turns off the rescaling feature at the simulation's end.
484
+ In addition, setting `scale` to `None` turns off rescaling.
485
+
486
+ Parameters
487
+ ----------
488
+ G : NetworkX graph or list of nodes
489
+ A position will be assigned to every node in G.
490
+
491
+ k : float (default=None)
492
+ Optimal distance between nodes. If None the distance is set to
493
+ 1/sqrt(n) where n is the number of nodes. Increase this value
494
+ to move nodes farther apart.
495
+
496
+ pos : dict or None optional (default=None)
497
+ Initial positions for nodes as a dictionary with node as keys
498
+ and values as a coordinate list or tuple. If None, then use
499
+ random initial positions.
500
+
501
+ fixed : list or None optional (default=None)
502
+ Nodes to keep fixed at initial position.
503
+ Nodes not in ``G.nodes`` are ignored.
504
+ ValueError raised if `fixed` specified and `pos` not.
505
+
506
+ iterations : int optional (default=50)
507
+ Maximum number of iterations taken
508
+
509
+ threshold: float optional (default = 1e-4)
510
+ Threshold for relative error in node position changes.
511
+ The iteration stops if the error is below this threshold.
512
+
513
+ weight : string or None optional (default='weight')
514
+ The edge attribute that holds the numerical value used for
515
+ the edge weight. Larger means a stronger attractive force.
516
+ If None, then all edge weights are 1.
517
+
518
+ scale : number or None (default: 1)
519
+ Scale factor for positions. Not used unless `fixed is None`.
520
+ If scale is None, no rescaling is performed.
521
+
522
+ center : array-like or None
523
+ Coordinate pair around which to center the layout.
524
+ Not used unless `fixed is None`.
525
+
526
+ dim : int
527
+ Dimension of layout.
528
+
529
+ seed : int, RandomState instance or None optional (default=None)
530
+ Used only for the initial positions in the algorithm.
531
+ Set the random state for deterministic node layouts.
532
+ If int, `seed` is the seed used by the random number generator,
533
+ if numpy.random.RandomState instance, `seed` is the random
534
+ number generator,
535
+ if None, the random number generator is the RandomState instance used
536
+ by numpy.random.
537
+
538
+ store_pos_as : str, default None
539
+ If non-None, the position of each node will be stored on the graph as
540
+ an attribute with this string as its name, which can be accessed with
541
+ ``G.nodes[...][store_pos_as]``. The function still returns the dictionary.
542
+
543
+ method : str optional (default='auto')
544
+ The method to compute the layout.
545
+ If 'force', the force-directed Fruchterman-Reingold algorithm [1]_ is used.
546
+ If 'energy', the energy-based optimization algorithm [2]_ is used with absolute
547
+ values of edge weights and gravitational forces acting on each connected component.
548
+ If 'auto', we use 'force' if ``len(G) < 500`` and 'energy' otherwise.
549
+
550
+ gravity: float optional (default=1.0)
551
+ Used only for the method='energy'.
552
+ The positive coefficient of gravitational forces per connected component.
553
+
554
+ Returns
555
+ -------
556
+ pos : dict
557
+ A dictionary of positions keyed by node
558
+
559
+ Examples
560
+ --------
561
+ >>> from pprint import pprint
562
+ >>> G = nx.path_graph(4)
563
+ >>> pos = nx.spring_layout(G)
564
+ >>> # suppress the returned dict and store on the graph directly
565
+ >>> _ = nx.spring_layout(G, seed=123, store_pos_as="pos")
566
+ >>> pprint(nx.get_node_attributes(G, "pos"))
567
+ {0: array([-0.61495802, -1. ]),
568
+ 1: array([-0.21789544, -0.35432583]),
569
+ 2: array([0.21847843, 0.35527369]),
570
+ 3: array([0.61437502, 0.99905215])}
571
+
572
+
573
+ # The same using longer but equivalent function name
574
+ >>> pos = nx.fruchterman_reingold_layout(G)
575
+
576
+ References
577
+ ----------
578
+ .. [1] Fruchterman, Thomas MJ, and Edward M. Reingold.
579
+ "Graph drawing by force-directed placement."
580
+ Software: Practice and experience 21, no. 11 (1991): 1129-1164.
581
+ http://dx.doi.org/10.1002/spe.4380211102
582
+ .. [2] Hamaguchi, Hiroki, Naoki Marumo, and Akiko Takeda.
583
+ "Initial Placement for Fruchterman--Reingold Force Model With Coordinate Newton Direction."
584
+ arXiv preprint arXiv:2412.20317 (2024).
585
+ https://arxiv.org/abs/2412.20317
586
+ """
587
+ import numpy as np
588
+
589
+ if method not in ("auto", "force", "energy"):
590
+ raise ValueError("the method must be either auto, force, or energy.")
591
+ if method == "auto":
592
+ method = "force" if len(G) < 500 else "energy"
593
+
594
+ G, center = _process_params(G, center, dim)
595
+
596
+ if fixed is not None:
597
+ if pos is None:
598
+ raise ValueError("nodes are fixed without positions given")
599
+ for node in fixed:
600
+ if node not in pos:
601
+ raise ValueError("nodes are fixed without positions given")
602
+ nfixed = {node: i for i, node in enumerate(G)}
603
+ fixed = np.asarray([nfixed[node] for node in fixed if node in nfixed])
604
+
605
+ if pos is not None:
606
+ # Determine size of existing domain to adjust initial positions
607
+ dom_size = max(coord for pos_tup in pos.values() for coord in pos_tup)
608
+ if dom_size == 0:
609
+ dom_size = 1
610
+ pos_arr = seed.rand(len(G), dim) * dom_size + center
611
+
612
+ for i, n in enumerate(G):
613
+ if n in pos:
614
+ pos_arr[i] = np.asarray(pos[n])
615
+ else:
616
+ pos_arr = None
617
+ dom_size = 1
618
+
619
+ if len(G) == 0:
620
+ return {}
621
+ if len(G) == 1:
622
+ pos = {nx.utils.arbitrary_element(G.nodes()): center}
623
+ if store_pos_as is not None:
624
+ nx.set_node_attributes(G, pos, store_pos_as)
625
+ return pos
626
+
627
+ # Sparse matrix
628
+ if len(G) >= 500 or method == "energy":
629
+ A = nx.to_scipy_sparse_array(G, weight=weight, dtype="f")
630
+ if k is None and fixed is not None:
631
+ # We must adjust k by domain size for layouts not near 1x1
632
+ nnodes, _ = A.shape
633
+ k = dom_size / np.sqrt(nnodes)
634
+ pos = _sparse_fruchterman_reingold(
635
+ A, k, pos_arr, fixed, iterations, threshold, dim, seed, method, gravity
636
+ )
637
+ else:
638
+ A = nx.to_numpy_array(G, weight=weight)
639
+ if k is None and fixed is not None:
640
+ # We must adjust k by domain size for layouts not near 1x1
641
+ nnodes, _ = A.shape
642
+ k = dom_size / np.sqrt(nnodes)
643
+ pos = _fruchterman_reingold(
644
+ A, k, pos_arr, fixed, iterations, threshold, dim, seed
645
+ )
646
+ if fixed is None and scale is not None:
647
+ pos = rescale_layout(pos, scale=scale) + center
648
+ pos = dict(zip(G, pos))
649
+
650
+ if store_pos_as is not None:
651
+ nx.set_node_attributes(G, pos, store_pos_as)
652
+
653
+ return pos
654
+
655
+
656
+ fruchterman_reingold_layout = spring_layout
657
+
658
+
659
+ @np_random_state(7)
660
+ def _fruchterman_reingold(
661
+ A, k=None, pos=None, fixed=None, iterations=50, threshold=1e-4, dim=2, seed=None
662
+ ):
663
+ # Position nodes in adjacency matrix A using Fruchterman-Reingold
664
+ # Entry point for NetworkX graph is fruchterman_reingold_layout()
665
+ import numpy as np
666
+
667
+ try:
668
+ nnodes, _ = A.shape
669
+ except AttributeError as err:
670
+ msg = "fruchterman_reingold() takes an adjacency matrix as input"
671
+ raise nx.NetworkXError(msg) from err
672
+
673
+ if pos is None:
674
+ # random initial positions
675
+ pos = np.asarray(seed.rand(nnodes, dim), dtype=A.dtype)
676
+ else:
677
+ # make sure positions are of same type as matrix
678
+ pos = pos.astype(A.dtype)
679
+
680
+ # optimal distance between nodes
681
+ if k is None:
682
+ k = np.sqrt(1.0 / nnodes)
683
+ # the initial "temperature" is about .1 of domain area (=1x1)
684
+ # this is the largest step allowed in the dynamics.
685
+ # We need to calculate this in case our fixed positions force our domain
686
+ # to be much bigger than 1x1
687
+ t = max(max(pos.T[0]) - min(pos.T[0]), max(pos.T[1]) - min(pos.T[1])) * 0.1
688
+ # simple cooling scheme.
689
+ # linearly step down by dt on each iteration so last iteration is size dt.
690
+ dt = t / (iterations + 1)
691
+ delta = np.zeros((pos.shape[0], pos.shape[0], pos.shape[1]), dtype=A.dtype)
692
+ # the inscrutable (but fast) version
693
+ # this is still O(V^2)
694
+ # could use multilevel methods to speed this up significantly
695
+ for iteration in range(iterations):
696
+ # matrix of difference between points
697
+ delta = pos[:, np.newaxis, :] - pos[np.newaxis, :, :]
698
+ # distance between points
699
+ distance = np.linalg.norm(delta, axis=-1)
700
+ # enforce minimum distance of 0.01
701
+ np.clip(distance, 0.01, None, out=distance)
702
+ # displacement "force"
703
+ displacement = np.einsum(
704
+ "ijk,ij->ik", delta, (k * k / distance**2 - A * distance / k)
705
+ )
706
+ # update positions
707
+ length = np.linalg.norm(displacement, axis=-1)
708
+ # Threshold the minimum length prior to position scaling
709
+ # See gh-8113 for detailed discussion of the threshold
710
+ length = np.clip(length, a_min=0.01, a_max=None)
711
+ delta_pos = np.einsum("ij,i->ij", displacement, t / length)
712
+ if fixed is not None:
713
+ # don't change positions of fixed nodes
714
+ delta_pos[fixed] = 0.0
715
+ pos += delta_pos
716
+ # cool temperature
717
+ t -= dt
718
+ if (np.linalg.norm(delta_pos) / nnodes) < threshold:
719
+ break
720
+ return pos
721
+
722
+
723
+ @np_random_state(7)
724
+ def _sparse_fruchterman_reingold(
725
+ A,
726
+ k=None,
727
+ pos=None,
728
+ fixed=None,
729
+ iterations=50,
730
+ threshold=1e-4,
731
+ dim=2,
732
+ seed=None,
733
+ method="energy",
734
+ gravity=1.0,
735
+ ):
736
+ # Position nodes in adjacency matrix A using Fruchterman-Reingold
737
+ # Entry point for NetworkX graph is fruchterman_reingold_layout()
738
+ # Sparse version
739
+ import numpy as np
740
+ import scipy as sp
741
+
742
+ try:
743
+ nnodes, _ = A.shape
744
+ except AttributeError as err:
745
+ msg = "fruchterman_reingold() takes an adjacency matrix as input"
746
+ raise nx.NetworkXError(msg) from err
747
+
748
+ if pos is None:
749
+ # random initial positions
750
+ pos = np.asarray(seed.rand(nnodes, dim), dtype=A.dtype)
751
+ else:
752
+ # make sure positions are of same type as matrix
753
+ pos = pos.astype(A.dtype)
754
+
755
+ # no fixed nodes
756
+ if fixed is None:
757
+ fixed = []
758
+
759
+ # optimal distance between nodes
760
+ if k is None:
761
+ k = np.sqrt(1.0 / nnodes)
762
+
763
+ if method == "energy":
764
+ return _energy_fruchterman_reingold(
765
+ A, nnodes, k, pos, fixed, iterations, threshold, dim, gravity
766
+ )
767
+
768
+ # make sure we have a LIst of Lists representation
769
+ try:
770
+ A = A.tolil()
771
+ except AttributeError:
772
+ A = (sp.sparse.coo_array(A)).tolil()
773
+
774
+ # the initial "temperature" is about .1 of domain area (=1x1)
775
+ # this is the largest step allowed in the dynamics.
776
+ t = max(max(pos.T[0]) - min(pos.T[0]), max(pos.T[1]) - min(pos.T[1])) * 0.1
777
+ # simple cooling scheme.
778
+ # linearly step down by dt on each iteration so last iteration is size dt.
779
+ dt = t / (iterations + 1)
780
+
781
+ displacement = np.zeros((dim, nnodes))
782
+ for iteration in range(iterations):
783
+ displacement *= 0
784
+ # loop over rows
785
+ for i in range(A.shape[0]):
786
+ if i in fixed:
787
+ continue
788
+ # difference between this row's node position and all others
789
+ delta = (pos[i] - pos).T
790
+ # distance between points
791
+ distance = np.sqrt((delta**2).sum(axis=0))
792
+ # enforce minimum distance of 0.01
793
+ distance = np.clip(distance, a_min=0.01, a_max=None)
794
+ # the adjacency matrix row
795
+ Ai = A.getrowview(i).toarray() # TODO: revisit w/ sparse 1D container
796
+ # displacement "force"
797
+ displacement[:, i] += (
798
+ delta * (k * k / distance**2 - Ai * distance / k)
799
+ ).sum(axis=1)
800
+ # update positions
801
+ length = np.sqrt((displacement**2).sum(axis=0))
802
+ # Threshold the minimum length prior to position scaling
803
+ # See gh-8113 for detailed discussion of the threshold
804
+ length = np.clip(length, a_min=0.01, a_max=None)
805
+ delta_pos = (displacement * t / length).T
806
+ pos += delta_pos
807
+ # cool temperature
808
+ t -= dt
809
+ if (np.linalg.norm(delta_pos) / nnodes) < threshold:
810
+ break
811
+ return pos
812
+
813
+
814
+ def _energy_fruchterman_reingold(
815
+ A, nnodes, k, pos, fixed, iterations, threshold, dim, gravity
816
+ ):
817
+ # Entry point for NetworkX graph is fruchterman_reingold_layout()
818
+ # energy-based version
819
+ import numpy as np
820
+ import scipy as sp
821
+
822
+ if gravity <= 0:
823
+ raise ValueError(f"the gravity must be positive.")
824
+
825
+ # make sure we have a Compressed Sparse Row format
826
+ try:
827
+ A = A.tocsr()
828
+ except AttributeError:
829
+ A = sp.sparse.csr_array(A)
830
+
831
+ # Take absolute values of edge weights and symmetrize it
832
+ A = np.abs(A)
833
+ A = (A + A.T) / 2
834
+
835
+ n_components, labels = sp.sparse.csgraph.connected_components(A, directed=False)
836
+ bincount = np.bincount(labels)
837
+ batchsize = 500
838
+
839
+ def _cost_FR(x):
840
+ pos = x.reshape((nnodes, dim))
841
+ grad = np.zeros((nnodes, dim))
842
+ cost = 0.0
843
+ for l in range(0, nnodes, batchsize):
844
+ r = min(l + batchsize, nnodes)
845
+ # difference between selected node positions and all others
846
+ delta = pos[l:r, np.newaxis, :] - pos[np.newaxis, :, :]
847
+ # distance between points with a minimum distance of 1e-5
848
+ distance2 = np.sum(delta * delta, axis=2)
849
+ distance2 = np.maximum(distance2, 1e-10)
850
+ distance = np.sqrt(distance2)
851
+ # temporary variable for calculation
852
+ Ad = A[l:r] * distance
853
+ # attractive forces and repulsive forces
854
+ grad[l:r] = 2 * np.einsum("ij,ijk->ik", Ad / k - k**2 / distance2, delta)
855
+ # integrated attractive forces
856
+ cost += np.sum(Ad * distance2) / (3 * k)
857
+ # integrated repulsive forces
858
+ cost -= k**2 * np.sum(np.log(distance))
859
+ # gravitational force from the centroids of connected components to (0.5, ..., 0.5)^T
860
+ centers = np.zeros((n_components, dim))
861
+ np.add.at(centers, labels, pos)
862
+ delta0 = centers / bincount[:, np.newaxis] - 0.5
863
+ grad += gravity * delta0[labels]
864
+ cost += gravity * 0.5 * np.sum(bincount * np.linalg.norm(delta0, axis=1) ** 2)
865
+ # fix positions of fixed nodes
866
+ grad[fixed] = 0.0
867
+ return cost, grad.ravel()
868
+
869
+ # Optimization of the energy function by L-BFGS algorithm
870
+ options = {"maxiter": iterations, "gtol": threshold}
871
+ return sp.optimize.minimize(
872
+ _cost_FR, pos.ravel(), method="L-BFGS-B", jac=True, options=options
873
+ ).x.reshape((nnodes, dim))
874
+
875
+
876
+ def kamada_kawai_layout(
877
+ G,
878
+ dist=None,
879
+ pos=None,
880
+ weight="weight",
881
+ scale=1,
882
+ center=None,
883
+ dim=2,
884
+ store_pos_as=None,
885
+ ):
886
+ """Position nodes using Kamada-Kawai path-length cost-function.
887
+
888
+ Parameters
889
+ ----------
890
+ G : NetworkX graph or list of nodes
891
+ A position will be assigned to every node in G.
892
+
893
+ dist : dict (default=None)
894
+ A two-level dictionary of optimal distances between nodes,
895
+ indexed by source and destination node.
896
+ If None, the distance is computed using shortest_path_length().
897
+
898
+ pos : dict or None optional (default=None)
899
+ Initial positions for nodes as a dictionary with node as keys
900
+ and values as a coordinate list or tuple. If None, then use
901
+ circular_layout() for dim >= 2 and a linear layout for dim == 1.
902
+
903
+ weight : string or None optional (default='weight')
904
+ The edge attribute that holds the numerical value used for
905
+ the edge weight. If None, then all edge weights are 1.
906
+
907
+ scale : number (default: 1)
908
+ Scale factor for positions.
909
+
910
+ center : array-like or None
911
+ Coordinate pair around which to center the layout.
912
+
913
+ dim : int
914
+ Dimension of layout.
915
+
916
+ store_pos_as : str, default None
917
+ If non-None, the position of each node will be stored on the graph as
918
+ an attribute with this string as its name, which can be accessed with
919
+ ``G.nodes[...][store_pos_as]``. The function still returns the dictionary.
920
+
921
+ Returns
922
+ -------
923
+ pos : dict
924
+ A dictionary of positions keyed by node
925
+
926
+ Examples
927
+ --------
928
+ >>> from pprint import pprint
929
+ >>> G = nx.path_graph(4)
930
+ >>> pos = nx.kamada_kawai_layout(G)
931
+ >>> # suppress the returned dict and store on the graph directly
932
+ >>> _ = nx.kamada_kawai_layout(G, store_pos_as="pos")
933
+ >>> pprint(nx.get_node_attributes(G, "pos"))
934
+ {0: array([0.99996577, 0.99366857]),
935
+ 1: array([0.32913544, 0.33543827]),
936
+ 2: array([-0.33544334, -0.32910684]),
937
+ 3: array([-0.99365787, -1. ])}
938
+ """
939
+ import numpy as np
940
+
941
+ G, center = _process_params(G, center, dim)
942
+ nNodes = len(G)
943
+ if nNodes == 0:
944
+ return {}
945
+
946
+ if dist is None:
947
+ dist = dict(nx.shortest_path_length(G, weight=weight))
948
+ dist_mtx = 1e6 * np.ones((nNodes, nNodes))
949
+ for row, nr in enumerate(G):
950
+ if nr not in dist:
951
+ continue
952
+ rdist = dist[nr]
953
+ for col, nc in enumerate(G):
954
+ if nc not in rdist:
955
+ continue
956
+ dist_mtx[row][col] = rdist[nc]
957
+
958
+ if pos is None:
959
+ if dim >= 3:
960
+ pos = random_layout(G, dim=dim)
961
+ elif dim == 2:
962
+ pos = circular_layout(G, dim=dim)
963
+ else:
964
+ pos = dict(zip(G, np.linspace(0, 1, len(G))))
965
+ pos_arr = np.array([pos[n] for n in G])
966
+
967
+ pos = _kamada_kawai_solve(dist_mtx, pos_arr, dim)
968
+
969
+ pos = rescale_layout(pos, scale=scale) + center
970
+ pos = dict(zip(G, pos))
971
+
972
+ if store_pos_as is not None:
973
+ nx.set_node_attributes(G, pos, store_pos_as)
974
+
975
+ return pos
976
+
977
+
978
+ def _kamada_kawai_solve(dist_mtx, pos_arr, dim):
979
+ # Anneal node locations based on the Kamada-Kawai cost-function,
980
+ # using the supplied matrix of preferred inter-node distances,
981
+ # and starting locations.
982
+
983
+ import numpy as np
984
+ import scipy as sp
985
+
986
+ meanwt = 1e-3
987
+ costargs = (np, 1 / (dist_mtx + np.eye(dist_mtx.shape[0]) * 1e-3), meanwt, dim)
988
+
989
+ optresult = sp.optimize.minimize(
990
+ _kamada_kawai_costfn,
991
+ pos_arr.ravel(),
992
+ method="L-BFGS-B",
993
+ args=costargs,
994
+ jac=True,
995
+ )
996
+
997
+ return optresult.x.reshape((-1, dim))
998
+
999
+
1000
+ def _kamada_kawai_costfn(pos_vec, np, invdist, meanweight, dim):
1001
+ # Cost-function and gradient for Kamada-Kawai layout algorithm
1002
+ nNodes = invdist.shape[0]
1003
+ pos_arr = pos_vec.reshape((nNodes, dim))
1004
+
1005
+ delta = pos_arr[:, np.newaxis, :] - pos_arr[np.newaxis, :, :]
1006
+ nodesep = np.linalg.norm(delta, axis=-1)
1007
+ direction = np.einsum("ijk,ij->ijk", delta, 1 / (nodesep + np.eye(nNodes) * 1e-3))
1008
+
1009
+ offset = nodesep * invdist - 1.0
1010
+ offset[np.diag_indices(nNodes)] = 0
1011
+
1012
+ cost = 0.5 * np.sum(offset**2)
1013
+ grad = np.einsum("ij,ij,ijk->ik", invdist, offset, direction) - np.einsum(
1014
+ "ij,ij,ijk->jk", invdist, offset, direction
1015
+ )
1016
+
1017
+ # Additional parabolic term to encourage mean position to be near origin:
1018
+ sumpos = np.sum(pos_arr, axis=0)
1019
+ cost += 0.5 * meanweight * np.sum(sumpos**2)
1020
+ grad += meanweight * sumpos
1021
+
1022
+ return (cost, grad.ravel())
1023
+
1024
+
1025
+ def spectral_layout(G, weight="weight", scale=1, center=None, dim=2, store_pos_as=None):
1026
+ """Position nodes using the eigenvectors of the graph Laplacian.
1027
+
1028
+ Using the unnormalized Laplacian, the layout shows possible clusters of
1029
+ nodes which are an approximation of the ratio cut. If dim is the number of
1030
+ dimensions then the positions are the entries of the dim eigenvectors
1031
+ corresponding to the ascending eigenvalues starting from the second one.
1032
+
1033
+ Parameters
1034
+ ----------
1035
+ G : NetworkX graph or list of nodes
1036
+ A position will be assigned to every node in G.
1037
+
1038
+ weight : string or None optional (default='weight')
1039
+ The edge attribute that holds the numerical value used for
1040
+ the edge weight. If None, then all edge weights are 1.
1041
+
1042
+ scale : number (default: 1)
1043
+ Scale factor for positions.
1044
+
1045
+ center : array-like or None
1046
+ Coordinate pair around which to center the layout.
1047
+
1048
+ dim : int
1049
+ Dimension of layout.
1050
+
1051
+ store_pos_as : str, default None
1052
+ If non-None, the position of each node will be stored on the graph as
1053
+ an attribute with this string as its name, which can be accessed with
1054
+ ``G.nodes[...][store_pos_as]``. The function still returns the dictionary.
1055
+
1056
+ Returns
1057
+ -------
1058
+ pos : dict
1059
+ A dictionary of positions keyed by node
1060
+
1061
+ Examples
1062
+ --------
1063
+ >>> from pprint import pprint
1064
+ >>> G = nx.path_graph(4)
1065
+ >>> pos = nx.spectral_layout(G)
1066
+ >>> # suppress the returned dict and store on the graph directly
1067
+ >>> _ = nx.spectral_layout(G, store_pos_as="pos")
1068
+ >>> pprint(nx.get_node_attributes(G, "pos"))
1069
+ {0: array([-1. , 0.76536686]),
1070
+ 1: array([-0.41421356, -0.76536686]),
1071
+ 2: array([ 0.41421356, -0.76536686]),
1072
+ 3: array([1. , 0.76536686])}
1073
+
1074
+
1075
+ Notes
1076
+ -----
1077
+ Directed graphs will be considered as undirected graphs when
1078
+ positioning the nodes.
1079
+
1080
+ For larger graphs (>500 nodes) this will use the SciPy sparse
1081
+ eigenvalue solver (ARPACK).
1082
+ """
1083
+ # handle some special cases that break the eigensolvers
1084
+ import numpy as np
1085
+
1086
+ G, center = _process_params(G, center, dim)
1087
+
1088
+ if len(G) <= 2:
1089
+ if len(G) == 0:
1090
+ pos = np.array([])
1091
+ elif len(G) == 1:
1092
+ pos = np.array([center])
1093
+ else:
1094
+ pos = np.array([np.zeros(dim), np.array(center) * 2.0])
1095
+ return dict(zip(G, pos))
1096
+ try:
1097
+ # Sparse matrix
1098
+ if len(G) < 500: # dense solver is faster for small graphs
1099
+ raise ValueError
1100
+ A = nx.to_scipy_sparse_array(G, weight=weight, dtype="d")
1101
+ # Symmetrize directed graphs
1102
+ if G.is_directed():
1103
+ A = A + np.transpose(A)
1104
+ pos = _sparse_spectral(A, dim)
1105
+ except (ImportError, ValueError):
1106
+ # Dense matrix
1107
+ A = nx.to_numpy_array(G, weight=weight)
1108
+ # Symmetrize directed graphs
1109
+ if G.is_directed():
1110
+ A += A.T
1111
+ pos = _spectral(A, dim)
1112
+
1113
+ pos = rescale_layout(pos, scale=scale) + center
1114
+ pos = dict(zip(G, pos))
1115
+
1116
+ if store_pos_as is not None:
1117
+ nx.set_node_attributes(G, pos, store_pos_as)
1118
+
1119
+ return pos
1120
+
1121
+
1122
+ def _spectral(A, dim=2):
1123
+ # Input adjacency matrix A
1124
+ # Uses dense eigenvalue solver from numpy
1125
+ import numpy as np
1126
+
1127
+ try:
1128
+ nnodes, _ = A.shape
1129
+ except AttributeError as err:
1130
+ msg = "spectral() takes an adjacency matrix as input"
1131
+ raise nx.NetworkXError(msg) from err
1132
+
1133
+ # form Laplacian matrix where D is diagonal of degrees
1134
+ D = np.identity(nnodes, dtype=A.dtype) * np.sum(A, axis=1)
1135
+ L = D - A
1136
+
1137
+ eigenvalues, eigenvectors = np.linalg.eig(L)
1138
+ # sort and keep smallest nonzero
1139
+ index = np.argsort(eigenvalues)[1 : dim + 1] # 0 index is zero eigenvalue
1140
+ return np.real(eigenvectors[:, index])
1141
+
1142
+
1143
+ def _sparse_spectral(A, dim=2):
1144
+ # Input adjacency matrix A
1145
+ # Uses sparse eigenvalue solver from scipy
1146
+ # Could use multilevel methods here, see Koren "On spectral graph drawing"
1147
+ import numpy as np
1148
+ import scipy as sp
1149
+
1150
+ try:
1151
+ nnodes, _ = A.shape
1152
+ except AttributeError as err:
1153
+ msg = "sparse_spectral() takes an adjacency matrix as input"
1154
+ raise nx.NetworkXError(msg) from err
1155
+
1156
+ # form Laplacian matrix
1157
+ D = sp.sparse.dia_array((A.sum(axis=1), 0), shape=(nnodes, nnodes)).tocsr()
1158
+ L = D - A
1159
+
1160
+ k = dim + 1
1161
+ # number of Lanczos vectors for ARPACK solver.What is the right scaling?
1162
+ ncv = max(2 * k + 1, int(np.sqrt(nnodes)))
1163
+ # return smallest k eigenvalues and eigenvectors
1164
+ eigenvalues, eigenvectors = sp.sparse.linalg.eigsh(L, k, which="SM", ncv=ncv)
1165
+ index = np.argsort(eigenvalues)[1:k] # 0 index is zero eigenvalue
1166
+ return np.real(eigenvectors[:, index])
1167
+
1168
+
1169
+ def planar_layout(G, scale=1, center=None, dim=2, store_pos_as=None):
1170
+ """Position nodes without edge intersections.
1171
+
1172
+ Parameters
1173
+ ----------
1174
+ G : NetworkX graph or list of nodes
1175
+ A position will be assigned to every node in G. If G is of type
1176
+ nx.PlanarEmbedding, the positions are selected accordingly.
1177
+
1178
+ scale : number (default: 1)
1179
+ Scale factor for positions.
1180
+
1181
+ center : array-like or None
1182
+ Coordinate pair around which to center the layout.
1183
+
1184
+ dim : int
1185
+ Dimension of layout.
1186
+
1187
+ store_pos_as : str, default None
1188
+ If non-None, the position of each node will be stored on the graph as
1189
+ an attribute with this string as its name, which can be accessed with
1190
+ ``G.nodes[...][store_pos_as]``. The function still returns the dictionary.
1191
+
1192
+ Returns
1193
+ -------
1194
+ pos : dict
1195
+ A dictionary of positions keyed by node
1196
+
1197
+ Raises
1198
+ ------
1199
+ NetworkXException
1200
+ If G is not planar
1201
+
1202
+ Examples
1203
+ --------
1204
+ >>> from pprint import pprint
1205
+ >>> G = nx.path_graph(4)
1206
+ >>> pos = nx.planar_layout(G)
1207
+ >>> # suppress the returned dict and store on the graph directly
1208
+ >>> _ = nx.planar_layout(G, store_pos_as="pos")
1209
+ >>> pprint(nx.get_node_attributes(G, "pos"))
1210
+ {0: array([-0.77777778, -0.33333333]),
1211
+ 1: array([ 1. , -0.33333333]),
1212
+ 2: array([0.11111111, 0.55555556]),
1213
+ 3: array([-0.33333333, 0.11111111])}
1214
+ """
1215
+ import numpy as np
1216
+
1217
+ if dim != 2:
1218
+ raise ValueError("can only handle 2 dimensions")
1219
+
1220
+ G, center = _process_params(G, center, dim)
1221
+
1222
+ if len(G) == 0:
1223
+ return {}
1224
+
1225
+ if isinstance(G, nx.PlanarEmbedding):
1226
+ embedding = G
1227
+ else:
1228
+ is_planar, embedding = nx.check_planarity(G)
1229
+ if not is_planar:
1230
+ raise nx.NetworkXException("G is not planar.")
1231
+ pos = nx.combinatorial_embedding_to_pos(embedding)
1232
+ node_list = list(embedding)
1233
+ pos = np.vstack([pos[x] for x in node_list])
1234
+ pos = pos.astype(np.float64)
1235
+ pos = rescale_layout(pos, scale=scale) + center
1236
+ pos = dict(zip(node_list, pos))
1237
+ if store_pos_as is not None:
1238
+ nx.set_node_attributes(G, pos, store_pos_as)
1239
+ return pos
1240
+
1241
+
1242
+ def spiral_layout(
1243
+ G,
1244
+ scale=1,
1245
+ center=None,
1246
+ dim=2,
1247
+ resolution=0.35,
1248
+ equidistant=False,
1249
+ store_pos_as=None,
1250
+ ):
1251
+ """Position nodes in a spiral layout.
1252
+
1253
+ Parameters
1254
+ ----------
1255
+ G : NetworkX graph or list of nodes
1256
+ A position will be assigned to every node in G.
1257
+
1258
+ scale : number (default: 1)
1259
+ Scale factor for positions.
1260
+
1261
+ center : array-like or None
1262
+ Coordinate pair around which to center the layout.
1263
+
1264
+ dim : int, default=2
1265
+ Dimension of layout, currently only dim=2 is supported.
1266
+ Other dimension values result in a ValueError.
1267
+
1268
+ resolution : float, default=0.35
1269
+ The compactness of the spiral layout returned.
1270
+ Lower values result in more compressed spiral layouts.
1271
+
1272
+ equidistant : bool, default=False
1273
+ If True, nodes will be positioned equidistant from each other
1274
+ by decreasing angle further from center.
1275
+ If False, nodes will be positioned at equal angles
1276
+ from each other by increasing separation further from center.
1277
+
1278
+ store_pos_as : str, default None
1279
+ If non-None, the position of each node will be stored on the graph as
1280
+ an attribute with this string as its name, which can be accessed with
1281
+ ``G.nodes[...][store_pos_as]``. The function still returns the dictionary.
1282
+
1283
+ Returns
1284
+ -------
1285
+ pos : dict
1286
+ A dictionary of positions keyed by node
1287
+
1288
+ Raises
1289
+ ------
1290
+ ValueError
1291
+ If dim != 2
1292
+
1293
+ Examples
1294
+ --------
1295
+ >>> from pprint import pprint
1296
+ >>> G = nx.path_graph(4)
1297
+ >>> pos = nx.spiral_layout(G)
1298
+ >>> nx.draw(G, pos=pos)
1299
+ >>> # suppress the returned dict and store on the graph directly
1300
+ >>> _ = nx.spiral_layout(G, store_pos_as="pos")
1301
+ >>> pprint(nx.get_node_attributes(G, "pos"))
1302
+ {0: array([-0.64153279, -0.68555087]),
1303
+ 1: array([-0.03307913, -0.46344795]),
1304
+ 2: array([0.34927952, 0.14899882]),
1305
+ 3: array([0.32533239, 1. ])}
1306
+
1307
+ Notes
1308
+ -----
1309
+ This algorithm currently only works in two dimensions.
1310
+
1311
+ """
1312
+ import numpy as np
1313
+
1314
+ if dim != 2:
1315
+ raise ValueError("can only handle 2 dimensions")
1316
+
1317
+ G, center = _process_params(G, center, dim)
1318
+
1319
+ if len(G) == 0:
1320
+ return {}
1321
+ if len(G) == 1:
1322
+ pos = {nx.utils.arbitrary_element(G): center}
1323
+ if store_pos_as is not None:
1324
+ nx.set_node_attributes(G, pos, store_pos_as)
1325
+ return pos
1326
+
1327
+ pos = []
1328
+ if equidistant:
1329
+ chord = 1
1330
+ step = 0.5
1331
+ theta = resolution
1332
+ theta += chord / (step * theta)
1333
+ for _ in range(len(G)):
1334
+ r = step * theta
1335
+ theta += chord / r
1336
+ pos.append([np.cos(theta) * r, np.sin(theta) * r])
1337
+
1338
+ else:
1339
+ dist = np.arange(len(G), dtype=float)
1340
+ angle = resolution * dist
1341
+ pos = np.transpose(dist * np.array([np.cos(angle), np.sin(angle)]))
1342
+
1343
+ pos = rescale_layout(np.array(pos), scale=scale) + center
1344
+
1345
+ pos = dict(zip(G, pos))
1346
+
1347
+ if store_pos_as is not None:
1348
+ nx.set_node_attributes(G, pos, store_pos_as)
1349
+
1350
+ return pos
1351
+
1352
+
1353
+ def multipartite_layout(
1354
+ G, subset_key="subset", align="vertical", scale=1, center=None, store_pos_as=None
1355
+ ):
1356
+ """Position nodes in layers of straight lines.
1357
+
1358
+ Parameters
1359
+ ----------
1360
+ G : NetworkX graph or list of nodes
1361
+ A position will be assigned to every node in G.
1362
+
1363
+ subset_key : string or dict (default='subset')
1364
+ If a string, the key of node data in G that holds the node subset.
1365
+ If a dict, keyed by layer number to the nodes in that layer/subset.
1366
+
1367
+ align : string (default='vertical')
1368
+ The alignment of nodes. Vertical or horizontal.
1369
+
1370
+ scale : number (default: 1)
1371
+ Scale factor for positions.
1372
+
1373
+ center : array-like or None
1374
+ Coordinate pair around which to center the layout.
1375
+
1376
+ store_pos_as : str, default None
1377
+ If non-None, the position of each node will be stored on the graph as
1378
+ an attribute with this string as its name, which can be accessed with
1379
+ ``G.nodes[...][store_pos_as]``. The function still returns the dictionary.
1380
+
1381
+ Returns
1382
+ -------
1383
+ pos : dict
1384
+ A dictionary of positions keyed by node.
1385
+
1386
+ Examples
1387
+ --------
1388
+ >>> G = nx.complete_multipartite_graph(28, 16, 10)
1389
+ >>> pos = nx.multipartite_layout(G)
1390
+ >>> # suppress the returned dict and store on the graph directly
1391
+ >>> G = nx.complete_multipartite_graph(28, 16, 10)
1392
+ >>> _ = nx.multipartite_layout(G, store_pos_as="pos")
1393
+
1394
+ or use a dict to provide the layers of the layout
1395
+
1396
+ >>> G = nx.Graph([(0, 1), (1, 2), (1, 3), (3, 4)])
1397
+ >>> layers = {"a": [0], "b": [1], "c": [2, 3], "d": [4]}
1398
+ >>> pos = nx.multipartite_layout(G, subset_key=layers)
1399
+
1400
+ Notes
1401
+ -----
1402
+ This algorithm currently only works in two dimensions and does not
1403
+ try to minimize edge crossings.
1404
+
1405
+ Network does not need to be a complete multipartite graph. As long as nodes
1406
+ have subset_key data, they will be placed in the corresponding layers.
1407
+
1408
+ """
1409
+ import numpy as np
1410
+
1411
+ if align not in ("vertical", "horizontal"):
1412
+ msg = "align must be either vertical or horizontal."
1413
+ raise ValueError(msg)
1414
+
1415
+ G, center = _process_params(G, center=center, dim=2)
1416
+ if len(G) == 0:
1417
+ return {}
1418
+
1419
+ try:
1420
+ # check if subset_key is dict-like
1421
+ if len(G) != sum(len(nodes) for nodes in subset_key.values()):
1422
+ raise nx.NetworkXError(
1423
+ "all nodes must be in one subset of `subset_key` dict"
1424
+ )
1425
+ except AttributeError:
1426
+ # subset_key is not a dict, hence a string
1427
+ node_to_subset = nx.get_node_attributes(G, subset_key)
1428
+ if len(node_to_subset) != len(G):
1429
+ raise nx.NetworkXError(
1430
+ f"all nodes need a subset_key attribute: {subset_key}"
1431
+ )
1432
+ subset_key = nx.utils.groups(node_to_subset)
1433
+
1434
+ # Sort by layer, if possible
1435
+ try:
1436
+ layers = dict(sorted(subset_key.items()))
1437
+ except TypeError:
1438
+ layers = subset_key
1439
+
1440
+ pos = None
1441
+ nodes = []
1442
+ width = len(layers)
1443
+ for i, layer in enumerate(layers.values()):
1444
+ height = len(layer)
1445
+ xs = np.repeat(i, height)
1446
+ ys = np.arange(0, height, dtype=float)
1447
+ offset = ((width - 1) / 2, (height - 1) / 2)
1448
+ layer_pos = np.column_stack([xs, ys]) - offset
1449
+ if pos is None:
1450
+ pos = layer_pos
1451
+ else:
1452
+ pos = np.concatenate([pos, layer_pos])
1453
+ nodes.extend(layer)
1454
+ pos = rescale_layout(pos, scale=scale) + center
1455
+ if align == "horizontal":
1456
+ pos = pos[:, ::-1] # swap x and y coords
1457
+ pos = dict(zip(nodes, pos))
1458
+
1459
+ if store_pos_as is not None:
1460
+ nx.set_node_attributes(G, pos, store_pos_as)
1461
+
1462
+ return pos
1463
+
1464
+
1465
+ @np_random_state("seed")
1466
+ def arf_layout(
1467
+ G,
1468
+ pos=None,
1469
+ scaling=1,
1470
+ a=1.1,
1471
+ etol=1e-6,
1472
+ dt=1e-3,
1473
+ max_iter=1000,
1474
+ *,
1475
+ seed=None,
1476
+ store_pos_as=None,
1477
+ ):
1478
+ """Arf layout for networkx
1479
+
1480
+ The attractive and repulsive forces (arf) layout [1] improves the spring
1481
+ layout in three ways. First, it prevents congestion of highly connected nodes
1482
+ due to strong forcing between nodes. Second, it utilizes the layout space
1483
+ more effectively by preventing large gaps that spring layout tends to create.
1484
+ Lastly, the arf layout represents symmetries in the layout better than the
1485
+ default spring layout.
1486
+
1487
+ Parameters
1488
+ ----------
1489
+ G : nx.Graph or nx.DiGraph
1490
+ Networkx graph.
1491
+ pos : dict
1492
+ Initial position of the nodes. If set to None a
1493
+ random layout will be used.
1494
+ scaling : float
1495
+ Scales the radius of the circular layout space.
1496
+ a : float
1497
+ Strength of springs between connected nodes. Should be larger than 1.
1498
+ The greater a, the clearer the separation of unconnected sub clusters.
1499
+ etol : float
1500
+ Gradient sum of spring forces must be larger than `etol` before successful
1501
+ termination.
1502
+ dt : float
1503
+ Time step for force differential equation simulations.
1504
+ max_iter : int
1505
+ Max iterations before termination of the algorithm.
1506
+ seed : int, RandomState instance or None optional (default=None)
1507
+ Set the random state for deterministic node layouts.
1508
+ If int, `seed` is the seed used by the random number generator,
1509
+ if numpy.random.RandomState instance, `seed` is the random
1510
+ number generator,
1511
+ if None, the random number generator is the RandomState instance used
1512
+ by numpy.random.
1513
+ store_pos_as : str, default None
1514
+ If non-None, the position of each node will be stored on the graph as
1515
+ an attribute with this string as its name, which can be accessed with
1516
+ ``G.nodes[...][store_pos_as]``. The function still returns the dictionary.
1517
+
1518
+ Returns
1519
+ -------
1520
+ pos : dict
1521
+ A dictionary of positions keyed by node.
1522
+
1523
+ Examples
1524
+ --------
1525
+ >>> G = nx.grid_graph((5, 5))
1526
+ >>> pos = nx.arf_layout(G)
1527
+ >>> # suppress the returned dict and store on the graph directly
1528
+ >>> G = nx.grid_graph((5, 5))
1529
+ >>> _ = nx.arf_layout(G, store_pos_as="pos")
1530
+
1531
+ References
1532
+ ----------
1533
+ .. [1] "Self-Organization Applied to Dynamic Network Layout", M. Geipel,
1534
+ International Journal of Modern Physics C, 2007, Vol 18, No 10,
1535
+ pp. 1537-1549.
1536
+ https://doi.org/10.1142/S0129183107011558 https://arxiv.org/abs/0704.1748
1537
+ """
1538
+ import warnings
1539
+
1540
+ import numpy as np
1541
+
1542
+ if a <= 1:
1543
+ msg = "The parameter a should be larger than 1"
1544
+ raise ValueError(msg)
1545
+
1546
+ pos_tmp = nx.random_layout(G, seed=seed)
1547
+ if pos is None:
1548
+ pos = pos_tmp
1549
+ else:
1550
+ for node in G.nodes():
1551
+ if node not in pos:
1552
+ pos[node] = pos_tmp[node].copy()
1553
+
1554
+ # Initialize spring constant matrix
1555
+ N = len(G)
1556
+ # No nodes no computation
1557
+ if N == 0:
1558
+ return pos
1559
+
1560
+ # init force of springs
1561
+ K = np.ones((N, N)) - np.eye(N)
1562
+ node_order = {node: i for i, node in enumerate(G)}
1563
+ for x, y in G.edges():
1564
+ if x != y:
1565
+ idx, jdx = (node_order[i] for i in (x, y))
1566
+ K[idx, jdx] = a
1567
+
1568
+ # vectorize values
1569
+ p = np.asarray(list(pos.values()))
1570
+
1571
+ # equation 10 in [1]
1572
+ rho = scaling * np.sqrt(N)
1573
+
1574
+ # looping variables
1575
+ error = etol + 1
1576
+ n_iter = 0
1577
+ while error > etol:
1578
+ diff = p[:, np.newaxis] - p[np.newaxis]
1579
+ A = np.linalg.norm(diff, axis=-1)[..., np.newaxis]
1580
+ # attraction_force - repulsions force
1581
+ # suppress nans due to division; caused by diagonal set to zero.
1582
+ # Does not affect the computation due to nansum
1583
+ with warnings.catch_warnings():
1584
+ warnings.simplefilter("ignore")
1585
+ change = K[..., np.newaxis] * diff - rho / A * diff
1586
+ change = np.nansum(change, axis=0)
1587
+ p += change * dt
1588
+
1589
+ error = np.linalg.norm(change, axis=-1).sum()
1590
+ if n_iter > max_iter:
1591
+ break
1592
+ n_iter += 1
1593
+
1594
+ pos = dict(zip(G.nodes(), p))
1595
+
1596
+ if store_pos_as is not None:
1597
+ nx.set_node_attributes(G, pos, store_pos_as)
1598
+
1599
+ return pos
1600
+
1601
+
1602
+ @np_random_state("seed")
1603
+ @nx._dispatchable(edge_attrs="weight", mutates_input={"store_pos_as": 15})
1604
+ def forceatlas2_layout(
1605
+ G,
1606
+ pos=None,
1607
+ *,
1608
+ max_iter=100,
1609
+ jitter_tolerance=1.0,
1610
+ scaling_ratio=2.0,
1611
+ gravity=1.0,
1612
+ distributed_action=False,
1613
+ strong_gravity=False,
1614
+ node_mass=None,
1615
+ node_size=None,
1616
+ weight=None,
1617
+ linlog=False,
1618
+ seed=None,
1619
+ dim=2,
1620
+ store_pos_as=None,
1621
+ ):
1622
+ """Position nodes using the ForceAtlas2 force-directed layout algorithm.
1623
+
1624
+ This function applies the ForceAtlas2 layout algorithm [1]_ to a NetworkX graph,
1625
+ positioning the nodes in a way that visually represents the structure of the graph.
1626
+ The algorithm uses physical simulation to minimize the energy of the system,
1627
+ resulting in a more readable layout.
1628
+
1629
+ Parameters
1630
+ ----------
1631
+ G : nx.Graph
1632
+ A NetworkX graph to be laid out.
1633
+ pos : dict or None, optional
1634
+ Initial positions of the nodes. If None, random initial positions are used.
1635
+ max_iter : int (default: 100)
1636
+ Number of iterations for the layout optimization.
1637
+ jitter_tolerance : float (default: 1.0)
1638
+ Controls the tolerance for adjusting the speed of layout generation.
1639
+ scaling_ratio : float (default: 2.0)
1640
+ Determines the scaling of attraction and repulsion forces.
1641
+ gravity : float (default: 1.0)
1642
+ Determines the amount of attraction on nodes to the center. Prevents islands
1643
+ (i.e. weakly connected or disconnected parts of the graph)
1644
+ from drifting away.
1645
+ distributed_action : bool (default: False)
1646
+ Distributes the attraction force evenly among nodes.
1647
+ strong_gravity : bool (default: False)
1648
+ Applies a strong gravitational pull towards the center.
1649
+ node_mass : dict or None, optional
1650
+ Maps nodes to their masses, influencing the attraction to other nodes.
1651
+ node_size : dict or None, optional
1652
+ Maps nodes to their sizes, preventing crowding by creating a halo effect.
1653
+ weight : string or None, optional (default: None)
1654
+ The edge attribute that holds the numerical value used for
1655
+ the edge weight. If None, then all edge weights are 1.
1656
+ linlog : bool (default: False)
1657
+ Uses logarithmic attraction instead of linear.
1658
+ seed : int, RandomState instance or None optional (default=None)
1659
+ Used only for the initial positions in the algorithm.
1660
+ Set the random state for deterministic node layouts.
1661
+ If int, `seed` is the seed used by the random number generator,
1662
+ if numpy.random.RandomState instance, `seed` is the random
1663
+ number generator,
1664
+ if None, the random number generator is the RandomState instance used
1665
+ by numpy.random.
1666
+ dim : int (default: 2)
1667
+ Sets the dimensions for the layout. Ignored if `pos` is provided.
1668
+ store_pos_as : str, default None
1669
+ If non-None, the position of each node will be stored on the graph as
1670
+ an attribute with this string as its name, which can be accessed with
1671
+ ``G.nodes[...][store_pos_as]``. The function still returns the dictionary.
1672
+
1673
+ Examples
1674
+ --------
1675
+ >>> import networkx as nx
1676
+ >>> G = nx.florentine_families_graph()
1677
+ >>> pos = nx.forceatlas2_layout(G)
1678
+ >>> nx.draw(G, pos=pos)
1679
+ >>> # suppress the returned dict and store on the graph directly
1680
+ >>> pos = nx.forceatlas2_layout(G, store_pos_as="pos")
1681
+ >>> _ = nx.forceatlas2_layout(G, store_pos_as="pos")
1682
+
1683
+ References
1684
+ ----------
1685
+ .. [1] Jacomy, M., Venturini, T., Heymann, S., & Bastian, M. (2014).
1686
+ ForceAtlas2, a continuous graph layout algorithm for handy network
1687
+ visualization designed for the Gephi software. PloS one, 9(6), e98679.
1688
+ https://doi.org/10.1371/journal.pone.0098679
1689
+ """
1690
+ import numpy as np
1691
+
1692
+ if len(G) == 0:
1693
+ return {}
1694
+ # parse optional pos positions
1695
+ if pos is None:
1696
+ pos = nx.random_layout(G, dim=dim, seed=seed)
1697
+ pos_arr = np.array(list(pos.values()))
1698
+ elif len(pos) == len(G):
1699
+ pos_arr = np.array([pos[node].copy() for node in G])
1700
+ else:
1701
+ # set random node pos within the initial pos values
1702
+ pos_init = np.array(list(pos.values()))
1703
+ max_pos = pos_init.max(axis=0)
1704
+ min_pos = pos_init.min(axis=0)
1705
+ dim = max_pos.size
1706
+ pos_arr = min_pos + seed.rand(len(G), dim) * (max_pos - min_pos)
1707
+ for idx, node in enumerate(G):
1708
+ if node in pos:
1709
+ pos_arr[idx] = pos[node].copy()
1710
+
1711
+ mass = np.zeros(len(G))
1712
+ size = np.zeros(len(G))
1713
+
1714
+ # Only adjust for size when the users specifies size other than default (1)
1715
+ adjust_sizes = False
1716
+ if node_size is None:
1717
+ node_size = {}
1718
+ else:
1719
+ adjust_sizes = True
1720
+
1721
+ if node_mass is None:
1722
+ node_mass = {}
1723
+
1724
+ for idx, node in enumerate(G):
1725
+ mass[idx] = node_mass.get(node, G.degree(node) + 1)
1726
+ size[idx] = node_size.get(node, 1)
1727
+
1728
+ n = len(G)
1729
+ gravities = np.zeros((n, dim))
1730
+ attraction = np.zeros((n, dim))
1731
+ repulsion = np.zeros((n, dim))
1732
+ A = nx.to_numpy_array(G, weight=weight)
1733
+
1734
+ def estimate_factor(n, swing, traction, speed, speed_efficiency, jitter_tolerance):
1735
+ """Computes the scaling factor for the force in the ForceAtlas2 layout algorithm.
1736
+
1737
+ This helper function adjusts the speed and
1738
+ efficiency of the layout generation based on the
1739
+ current state of the system, such as the number of
1740
+ nodes, current swing, and traction forces.
1741
+
1742
+ Parameters
1743
+ ----------
1744
+ n : int
1745
+ Number of nodes in the graph.
1746
+ swing : float
1747
+ The current swing, representing the oscillation of the nodes.
1748
+ traction : float
1749
+ The current traction force, representing the attraction between nodes.
1750
+ speed : float
1751
+ The current speed of the layout generation.
1752
+ speed_efficiency : float
1753
+ The efficiency of the current speed, influencing how fast the layout converges.
1754
+ jitter_tolerance : float
1755
+ The tolerance for jitter, affecting how much speed adjustment is allowed.
1756
+
1757
+ Returns
1758
+ -------
1759
+ tuple
1760
+ A tuple containing the updated speed and speed efficiency.
1761
+
1762
+ Notes
1763
+ -----
1764
+ This function is a part of the ForceAtlas2 layout algorithm and is used to dynamically adjust the
1765
+ layout parameters to achieve an optimal and stable visualization.
1766
+
1767
+ """
1768
+ import numpy as np
1769
+
1770
+ # estimate jitter
1771
+ opt_jitter = 0.05 * np.sqrt(n)
1772
+ min_jitter = np.sqrt(opt_jitter)
1773
+ max_jitter = 10
1774
+ min_speed_efficiency = 0.05
1775
+
1776
+ other = min(max_jitter, opt_jitter * traction / n**2)
1777
+ jitter = jitter_tolerance * max(min_jitter, other)
1778
+
1779
+ if swing / traction > 2.0:
1780
+ if speed_efficiency > min_speed_efficiency:
1781
+ speed_efficiency *= 0.5
1782
+ jitter = max(jitter, jitter_tolerance)
1783
+ if swing == 0:
1784
+ target_speed = np.inf
1785
+ else:
1786
+ target_speed = jitter * speed_efficiency * traction / swing
1787
+
1788
+ if swing > jitter * traction:
1789
+ if speed_efficiency > min_speed_efficiency:
1790
+ speed_efficiency *= 0.7
1791
+ elif speed < 1000:
1792
+ speed_efficiency *= 1.3
1793
+
1794
+ max_rise = 0.5
1795
+ speed = speed + min(target_speed - speed, max_rise * speed)
1796
+ return speed, speed_efficiency
1797
+
1798
+ speed = 1
1799
+ speed_efficiency = 1
1800
+ swing = 1
1801
+ traction = 1
1802
+ for _ in range(max_iter):
1803
+ # compute pairwise difference
1804
+ diff = pos_arr[:, None] - pos_arr[None]
1805
+ # compute pairwise distance
1806
+ distance = np.linalg.norm(diff, axis=-1)
1807
+
1808
+ # linear attraction
1809
+ if linlog:
1810
+ attraction = -np.log(1 + distance) / distance
1811
+ np.fill_diagonal(attraction, 0)
1812
+ attraction = np.einsum("ij, ij -> ij", attraction, A)
1813
+ attraction = np.einsum("ijk, ij -> ik", diff, attraction)
1814
+
1815
+ else:
1816
+ attraction = -np.einsum("ijk, ij -> ik", diff, A)
1817
+
1818
+ if distributed_action:
1819
+ attraction /= mass[:, None]
1820
+
1821
+ # repulsion
1822
+ tmp = mass[:, None] @ mass[None]
1823
+ if adjust_sizes:
1824
+ distance += -size[:, None] - size[None]
1825
+
1826
+ d2 = distance**2
1827
+ # remove self-interaction
1828
+ np.fill_diagonal(tmp, 0)
1829
+ np.fill_diagonal(d2, 1)
1830
+ factor = (tmp / d2) * scaling_ratio
1831
+ repulsion = np.einsum("ijk, ij -> ik", diff, factor)
1832
+
1833
+ # gravity
1834
+ pos_centered = pos_arr - np.mean(pos_arr, axis=0)
1835
+ if strong_gravity:
1836
+ gravities = -gravity * mass[:, None] * pos_centered
1837
+ else:
1838
+ # hide warnings for divide by zero. Then change nan to 0
1839
+ with np.errstate(divide="ignore", invalid="ignore"):
1840
+ unit_vec = pos_centered / np.linalg.norm(pos_centered, axis=-1)[:, None]
1841
+ unit_vec = np.nan_to_num(unit_vec, nan=0)
1842
+ gravities = -gravity * mass[:, None] * unit_vec
1843
+
1844
+ # total forces
1845
+ update = attraction + repulsion + gravities
1846
+
1847
+ # compute total swing and traction
1848
+ swing += (mass * np.linalg.norm(pos_arr - update, axis=-1)).sum()
1849
+ traction += (0.5 * mass * np.linalg.norm(pos_arr + update, axis=-1)).sum()
1850
+
1851
+ speed, speed_efficiency = estimate_factor(
1852
+ n,
1853
+ swing,
1854
+ traction,
1855
+ speed,
1856
+ speed_efficiency,
1857
+ jitter_tolerance,
1858
+ )
1859
+
1860
+ # update pos
1861
+ if adjust_sizes:
1862
+ df = np.linalg.norm(update, axis=-1)
1863
+ swinging = mass * df
1864
+ factor = 0.1 * speed / (1 + np.sqrt(speed * swinging))
1865
+ factor = np.minimum(factor * df, 10.0 * np.ones(df.shape)) / df
1866
+ else:
1867
+ swinging = mass * np.linalg.norm(update, axis=-1)
1868
+ factor = speed / (1 + np.sqrt(speed * swinging))
1869
+
1870
+ factored_update = update * factor[:, None]
1871
+ pos_arr += factored_update
1872
+ if abs(factored_update).sum() < 1e-10:
1873
+ break
1874
+
1875
+ pos = dict(zip(G, pos_arr))
1876
+ if store_pos_as is not None:
1877
+ nx.set_node_attributes(G, pos, store_pos_as)
1878
+
1879
+ return pos
1880
+
1881
+
1882
+ def rescale_layout(pos, scale=1):
1883
+ """Returns scaled position array to (-scale, scale) in all axes.
1884
+
1885
+ The function acts on NumPy arrays which hold position information.
1886
+ Each position is one row of the array. The dimension of the space
1887
+ equals the number of columns. Each coordinate in one column.
1888
+
1889
+ To rescale, the mean (center) is subtracted from each axis separately.
1890
+ Then all values are scaled so that the largest magnitude value
1891
+ from all axes equals `scale` (thus, the aspect ratio is preserved).
1892
+ The resulting NumPy Array is returned (order of rows unchanged).
1893
+
1894
+ Parameters
1895
+ ----------
1896
+ pos : numpy array
1897
+ positions to be scaled. Each row is a position.
1898
+
1899
+ scale : number (default: 1)
1900
+ The size of the resulting extent in all directions.
1901
+
1902
+ attribute : str, default None
1903
+ If non-None, the position of each node will be stored on the graph as
1904
+ an attribute named `attribute` which can be accessed with
1905
+ `G.nodes[...][attribute]`. The function still returns the dictionary.
1906
+
1907
+ Returns
1908
+ -------
1909
+ pos : numpy array
1910
+ scaled positions. Each row is a position.
1911
+
1912
+ See Also
1913
+ --------
1914
+ rescale_layout_dict
1915
+ """
1916
+ import numpy as np
1917
+
1918
+ # Find max length over all dimensions
1919
+ pos -= pos.mean(axis=0)
1920
+ lim = np.abs(pos).max() # max coordinate for all axes
1921
+ # rescale to (-scale, scale) in all directions, preserves aspect
1922
+ if lim > 0:
1923
+ pos *= scale / lim
1924
+ return pos
1925
+
1926
+
1927
+ def rescale_layout_dict(pos, scale=1):
1928
+ """Return a dictionary of scaled positions keyed by node
1929
+
1930
+ Parameters
1931
+ ----------
1932
+ pos : A dictionary of positions keyed by node
1933
+
1934
+ scale : number (default: 1)
1935
+ The size of the resulting extent in all directions.
1936
+
1937
+ Returns
1938
+ -------
1939
+ pos : A dictionary of positions keyed by node
1940
+
1941
+ Examples
1942
+ --------
1943
+ >>> import numpy as np
1944
+ >>> pos = {0: np.array((0, 0)), 1: np.array((1, 1)), 2: np.array((0.5, 0.5))}
1945
+ >>> nx.rescale_layout_dict(pos)
1946
+ {0: array([-1., -1.]), 1: array([1., 1.]), 2: array([0., 0.])}
1947
+
1948
+ >>> pos = {0: np.array((0, 0)), 1: np.array((-1, 1)), 2: np.array((-0.5, 0.5))}
1949
+ >>> nx.rescale_layout_dict(pos, scale=2)
1950
+ {0: array([ 2., -2.]), 1: array([-2., 2.]), 2: array([0., 0.])}
1951
+
1952
+ See Also
1953
+ --------
1954
+ rescale_layout
1955
+ """
1956
+ import numpy as np
1957
+
1958
+ if not pos: # empty_graph
1959
+ return {}
1960
+ pos_v = np.array(list(pos.values()))
1961
+ pos_v = rescale_layout(pos_v, scale=scale)
1962
+ return dict(zip(pos, pos_v))
1963
+
1964
+
1965
+ def bfs_layout(G, start, *, align="vertical", scale=1, center=None, store_pos_as=None):
1966
+ """Position nodes according to breadth-first search algorithm.
1967
+
1968
+ Parameters
1969
+ ----------
1970
+ G : NetworkX graph
1971
+ A position will be assigned to every node in G.
1972
+
1973
+ start : node in `G`
1974
+ Starting node for bfs
1975
+
1976
+ align : string (default='vertical')
1977
+ The alignment of nodes within a layer, either `"vertical"` or
1978
+ `"horizontal"`.
1979
+
1980
+ scale : number (default: 1)
1981
+ Scale factor for positions.
1982
+
1983
+ center : array-like or None
1984
+ Coordinate pair around which to center the layout.
1985
+
1986
+ store_pos_as : str, default None
1987
+ If non-None, the position of each node will be stored on the graph as
1988
+ an attribute with this string as its name, which can be accessed with
1989
+ ``G.nodes[...][store_pos_as]``. The function still returns the dictionary.
1990
+
1991
+ Returns
1992
+ -------
1993
+ pos : dict
1994
+ A dictionary of positions keyed by node.
1995
+
1996
+ Examples
1997
+ --------
1998
+ >>> from pprint import pprint
1999
+ >>> G = nx.path_graph(4)
2000
+ >>> pos = nx.bfs_layout(G, 0)
2001
+ >>> # suppress the returned dict and store on the graph directly
2002
+ >>> _ = nx.bfs_layout(G, 0, store_pos_as="pos")
2003
+ >>> pprint(nx.get_node_attributes(G, "pos"))
2004
+ {0: array([-1., 0.]),
2005
+ 1: array([-0.33333333, 0. ]),
2006
+ 2: array([0.33333333, 0. ]),
2007
+ 3: array([1., 0.])}
2008
+
2009
+
2010
+
2011
+ Notes
2012
+ -----
2013
+ This algorithm currently only works in two dimensions and does not
2014
+ try to minimize edge crossings.
2015
+
2016
+ """
2017
+ G, center = _process_params(G, center, 2)
2018
+
2019
+ # Compute layers with BFS
2020
+ layers = dict(enumerate(nx.bfs_layers(G, start)))
2021
+
2022
+ if len(G) != sum(len(nodes) for nodes in layers.values()):
2023
+ raise nx.NetworkXError(
2024
+ "bfs_layout didn't include all nodes. Perhaps use input graph:\n"
2025
+ " G.subgraph(nx.node_connected_component(G, start))"
2026
+ )
2027
+
2028
+ # Compute node positions with multipartite_layout
2029
+ pos = multipartite_layout(
2030
+ G, subset_key=layers, align=align, scale=scale, center=center
2031
+ )
2032
+
2033
+ if store_pos_as is not None:
2034
+ nx.set_node_attributes(G, pos, store_pos_as)
2035
+
2036
+ return pos